Vue 3 + Vite 2 + ElementPlus 1.1.0-beta(按需引入及自定义主题)


更新于:2021.11.16


关于 element-plus 自定义主题部分, 最新版的 element-plus 1.1.0-beta.1x 官网文档 ➡️ https://element-plus.gitee.io/zh-CN/guide/theming.html 又又又修改了!


不过这回貌似更简单了,按照以下步骤操作即可。

  1. 创建一个新的样式文件,例如 📃styles/element/index.scss,直接覆盖 Element Plus 样式变量:
// styles/element/index.scss

@forward "element-plus/theme-chalk/src/common/var.scss" with (
  $colors: (
    'primary': (
      'base': #4FC08D,
    ),
    'success': (
      'base': #8BC34A,
    ),
    'warning': (
      'base': #FFE787,
    ),
    'danger': (
      'base': #7C77B9,
    ),
    'error': (
      'base': #E65D6E,
    ),
    'info': (
      'base': #606266,
    ),
  ),
  $text-color: (
    'primary':  #3FB984,
    'regular': #606266,
    'secondary': #909399
  )
);

如果是完整导入 element-plus,则在入口📃 main.js/main.ts

import Vue from 'vue'

import './styles/element/index.scss'
import ElementPlus from 'element-plus'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus)
  1. 但我们需要在按需导入时自定义主题,并且使用 vite。就可以安装用于按需导入 element-plus 样式的 unplugin-element-plus 插件并进行配置。

📃vite.config.js

// vite.config.ts
import path from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// 自动按需导入 element-plus 样式
import ElementPlus from 'unplugin-element-plus/vite'

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/element/index.scss" as *;`,
      },
    },
  },
  plugins: [
    vue(),
    ElementPlus({
      useSource: true,
    }),
  ],
})

可选配置 useSource: boolean,默认是 false。

// useSource: false
import { ElButton } from 'element-plus'

      ↓ ↓ ↓ ↓ ↓ ↓

import { ElButton } from 'element-plus'
import 'element-plus/es/components/button/style/css'

// useSource: true
import { ElButton } from 'element-plus'

      ↓ ↓ ↓ ↓ ↓ ↓

import { ElButton } from 'element-plus'
import 'element-plus/es/components/button/style/index'。
  1. 接下来也不需要在使用时手动导入组件,而是配置一个自动导入插件 unplugin-vue-components

更新于:2021.09.23


element-plus 的官方文档更新了,组件的按需引入可以用 unplugin-vue-components 十分便捷地实现,步骤如下:

  1. 安装
npm install unplugin-vue-components -D
  1. 配置文件📃 vite.config.js
// vite.config.ts/vite.config.js
import { defineConfig } from 'vite'
// vue 按需自动导入组件插件,直接使用即可,公共组件无需再手动 import
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' //  ElementPlus 组件专用的内置解析器
import ElementPlus from 'unplugin-element-plus/vite' //  按需导入 element-plus 样式插件

export default defineConfig({
  plugins: [
    // ...
    Components({
      resolvers: [ElementPlusResolver()], // 启用 ElementPlus 专用的 UI 组件解析器
    }),
    ElementPlus({ useSource: true }), // import 组件后会自动引入组件对应的样式
  ],
  css: {
    preprocessorOptions: {
      scss: {
        // 自定义 element 主题样式
        additionalData: `@use "@styles/element/index.scss" as *;`,
      },
    },
  },
})

这样不仅 element-plus 组件,所有src/components下的公用自定义组件都不需要再做诸如以下这类导入和配置,而是直接在template中使用即可。

import DesignForm from '@/components/DesignForm.vue'

export default defineComponent({
  name: 'DesignIndex',
  components: { DesignForm },
  ...
}

以下是这个插件的默认配置,你可以在vite.config的插件配置中根据需要自定义

Components({
  // 用于查找自动处理的组件目录的相对路径
  dirs: ['src/components'],

  // 组件的有效文件扩展名
  extensions: ['vue'],
  // 是否查找子目录
  deep: true,
  // resolvers for custom components
  resolvers: [],
  // example of importing Vant,不是默认值
  /* resolvers: [
    (name) => {
      // where `name` is always CapitalCase
      if (name.startsWith('Van'))
        return { importName: name.slice(3), path: 'vant' }
    },
  ],*/
  // 是否生成 `components.d.ts` 全局声明,
  // also accepts a path for custom filename
  dts: false,

  // 不允许子目录作为组件的命名空间前缀
  directoryAsNamespace: false,
  // 忽略的命名空间前缀的子目录路径,当 `directoryAsNamespace: true` 时才起作用
  globalNamespaces: [],

  // 是否自动导入指令
  // 默认: `true` for Vue 3, `false` for Vue 2
  // Vue2 需要 Babel 来进行转换,出于性能考虑,默认禁用。
  // To install Babel, run: `npm install -D @babel/parser @babel/traverse`
  directives: true,

  // 转换目标的过滤器
  include: [/\.vue$/, /\.vue\?vue/],
  exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
})

有没有很省事!但是!当你需要用到自定义主题的时候,这个路子就不好使了。我们还是要像之前那样手动导入(然后全局注册)。 可以放心大胆滴使用~
跟着往下看:


以下(有部分)更新于2021.08.27


今天把 ElementPlus 更新到了最新,然后一跑项目马上就报错了。特记录下1.1.0版后使用的区别:
📃package.json

{
  "dependencies": {
    "element-plus": "^1.1.0-beta.9",
    "vue": "^3.2.6",
    "vue-router": "^4.0.11",
    "vuex": "^4.0.2"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.6.0",
    "@vitejs/plugin-vue-jsx": "^1.1.7",
    "@vue/compiler-sfc": "^3.0.5",
    "sass": "^1.38.1",
    "vite": "^2.5.1",
    "unplugin-element-plus": "0.1.0",
  }
}

unplugin-element-plus 插件来实现按需加载样式,别忘了先安装npm i unplugin-element-plus -D
但是官方是强烈建议全局引入样式,没必要为此特地用插件增加负担。此外像这样配置按需引入样式也无法使自定义主题生效。so 只是贴在这里记录方法📝。

  1. 按需引入 element-plus 后使用消息组件MessageMessageBox,就需要这样调用:
    ElMessage.error('xxx')
    ElMessageBox.confirm()

  2. 至于 v-loading 指令按需后也不好使了,我封装了个组合函数useLoading,参考下:

// src/composables/useLoading.js
import { ref, watch, nextTick } from 'vue'
import { ElLoading } from 'element-plus'

/**
@target:Loading 动画需要覆盖的 DOM 对象(ref 对象)
@isLoading:表示数据是否正在加载的 ref 对象
**/
export default function useLoading(target, isLoading) {
  const loadingInstance = ref(null)
  watch(
    [isLoading, target],
    vals => {
      if (vals[0] && vals[1]) {
        nextTick(() => {
          loadingInstance.value = ElLoading.service({
            target: vals[1].$el, // 需要获取DOM 节点
            fullscreen: false,
            text: ' 数据加载中',
          })
        })
      } else {
        nextTick(() => {
          if (loadingInstance.value) loadingInstance.value.close()
        })
      }
    },
    { immediate: true }
  )
}

使用简单示例:

<template>
  <el-table
    ref="loadingRef"
    :data="list">
  </el-table>
</template>
<script>
import { computed, defineComponent, watch, toRefs, reactive, ref } from 'vue'
import useList from '@composables/useList' // 封装好的分页列表查询函数
import useLoading from '@composables/useLoading'
import { getDesignList } from '@api/design' // 请求 api

export default defineComponent({
  name: 'Banner',
  components: {},
  setup() {
    const loadingRef = ref(null)
    const listQuery = reactive({
      query: '',
      page: 1,
      pageSize: 10,
    })

    const { list, total, listLoading, refreshList } = useList(
      listQuery, // 列表查询参数
      getDesignList // 列表请求 Api
    )

    useLoading(loadingRef, listLoading)

    return {
      isLoading
      loadingRef,
      list,
      total
    }
  }
})
</script>

📢:下面的部分都不用看了!!!

正式内容开始:

  1. 📃src/main.js
// import 'element-plus/dist/index.css' // 官方建议全局引入样式
import { createApp } from 'vue'
import App from './App.vue'
// 把按需引入 ElementPlus 组件 的代码单独拎到一个 js
import installElementPlus from './plugins/element' 
import router from './router'
import store from './store'
import './styles/index.scss' // 我们自己的样式

const app = createApp(App).use(store).use(router)
app.provide('apiUrl', import.meta.env.VITE_APP_BASE_API)

installElementPlus(app)

app.mount('#app')
  1. 创建一个用于覆盖 Element Plus 样式变量的文件
    📃src/styles/element-variables.scss
/* 改变主题色变量 */ 
$--colors: (
  'primary': (
    'base': #388E3C,
  ),
  'success': (
    'base': #67c23a,
  ),
  'warning': (
    'base': #C7D66D,
  ),
  'danger': (
    'base': #7C77B9,
  ),
  'error': (
    'base': #F6828C,
  ),
  'info': (
    'base': #909399,
  ),
);

/* 必须指明字体图标路径,不然会报错 */ 
$--font-path: 'element-plus/theme-chalk/fonts';
/* 在主题变量后再导入 element-plus的 scss,避免 sass 混合变量的问题 */ 
@import 'element-plus/packages/theme-chalk/src/index';

注意 ⚠️:ElementPlus 1.1.0 版之后有一些破坏性改动:
按需引入的情况下,那些必须嵌套使用的子组件,比如el-select组件内部的el-option组件、菜单组件的el-menu-item,都不用再导入了,因为内容已经被集成到父组件内了;

submenu不仅目录名改了,只剩下一个style子文件夹里面的内容也都空了(index.js等 4 个文件都没有内容)。 要记得把模版中原本的<el-submenu>替换成<el-sub-menu>

大致列一下避雷:
ElBreadcrumbItemElDropdownItemElDropdownItemElDropdownMenuElFormItemElMenuItemElOptionElRadioGroupElTableColumnElTabPane...

  1. 引入自定义主题scss并全局注册需要的组件
    📃src/plugins/element.js
import {
  ElBreadcrumb,
  ElButton,
  ...
  ElMessage,
  ElMessageBox,
}

const components = [
  ElBreadcrumb,
  ElButton,
  ...
  ElMessage,
  ElMessageBox,
]

const option = { size: 'small', zIndex: 3000 }

import '../styles/element-variables.scss'

export default app => {
  // 按需引入时做 element-plus 的全局配置
  app.config.globalProperties.$ELEMENT = option 
  // 注册需要的 element-plus 组件
  components.forEach(component => {
    app.use(component)
  })
}

新版的 ElementPlus 组件全都有install方法,而1.0.2的时候只有几个组件有install,其余要用app.component()注册全局组件。现在方便了,全部遍历然后直接app.use()即可。

而且用use()等于安装插件,那些消息组件就会自动像这样注册全局方法:app.config.globalProperties.$message = _Message
不用担心按需导入就无this.$message

  1. 以及国际化(设置默认语言为中文)的实现:
    📃src/App.vue
<template>
  <el-config-provider :locale="locale">
    <router-link to="/">Home</router-link> |
    <router-link to="/user">User</router-link> |
    <router-link to="/about">About</router-link>
    <router-view> </router-view>
  </el-config-provider>
</template>

<script>
import { defineComponent } from 'vue'
import { ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/lib/locale/lang/zh-cn'

export default defineComponent({
  components: {
    ElConfigProvider,
  },
  setup() {
    return {
      locale: zhCn, // 国际化,将组件设置为中文
    }
  },
})
</script>

📃src/router/index.js

import { createRouter, createWebHashHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@views/Home.vue'),
  },
  {
    path: '/user',
    name: 'User',
    component: () =>
      import('@views/User.vue'),
  },
  {
    path: '/404',
    component: () => import("page-404" */ '@views/404'),
    hidden: true,
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    redirect: '/404',
    hidden: true,
  },
]

const router = createRouter({
  scrollBehavior: () => ({
    top: 0,
  }),
  history: createWebHashHistory(),
  routes,
})

export default router

📃src/views/Home.vue

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <el-button type="primary" @click="this.$message('哈哈')"
        >调用全局 $message 方式一</el-button
      >
      <el-button type="success" @click="showMsg">调用全局 $message 方式二、三</el-button>
    <el-date-picker v-model="value1" type="date" placeholder="选择日期">
    </el-date-picker>
  </div>
</template>

<script>
import { ref, getCurrentInstance } from 'vue'
import { ElMessage } from 'element-plus'; // 方式三
export default {
name: 'Home',
  inject: ['apiUrl'], // 接收全局 provide 的变量
  setup() {
    const value1 = ref('')
    
    const internalInstance = getCurrentInstance() // 方式二,不推荐
    const showMsg = () => {
      internalInstance.appContext.config.globalProperties.$message('啊啊') // 方式二,不推荐
      ElMessage.success('好吧') // 方式三
    }
    return {
      value1,
      showMsg,
    }
  },
}
</script>
效果

比自己覆盖 UI 颜色样式轻松多了有木有~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容