系列链接

一、安装element-plus

npm i element-plus --save

安装后,如果npm有类似:“element-plus需要vue3.0.x”,这样的提示,可选择升级vue的版本

二、按需引入

1. 为什么要按需引入?

按需引入是为了减少项目打包后的体积,因为一整个UI库,总会有你用不到的东西,所以那些用不到的东西也没有必要打包进去。毕竟过大的文件体积会影响前端的加载速度不是?

2. 如何按需引入?

element-plus官方文档中提供了使用babel-plugin-import的方式去按需引入组件,详见 => 传送门

而我这里介绍另外一种按需引入的方式:es6 module,我们只需要将我们用到的组件,用解构赋值的方式给它取出来,然后注册到vue实例上去,就可以实现按需引入了。但是由于vue3.0不提倡获取vue实例,如果将ElMessage这种服务挂载到vue实例上,我们是不能在.vue文件中直接使用this.$message()的,所以这里我采用将ElMessage封装,并挂载到window对象上的方式,全局使用message。

在src目录下新建src/typings/window.d.ts,给window对象扩充属性:

/* eslint-disable no-unused-vars */
import { MessageType } from 'element-plus/lib/el-message/src/types'

declare global {
  interface Window {
    $toast: (type: MessageType, message: string) => Promise<void>
  }
}

然后向tsconfig.json中添加代码:

{
  "compilerOptions": {
    "typeRoots": ["./src/typings", "./node_modules/@types"]
  }
}

在element-plus目录下新建index.ts,然后输入以下代码:

import { App } from 'vue'
import { locale, ElButton, ElMessage } from 'element-plus'
import { MessageType } from 'element-plus/lib/el-message/src/types'
import lang from 'element-plus/lib/locale/lang/zh-cn'
import 'dayjs/locale/zh-cn'
import 'element-plus/lib/theme-chalk/index.css'

const components = [ElButton]

const Element = {
  install: (app: App<Element>) => {
    // 语言包
    locale(lang)
    // 注册组件
    components.forEach((component) => app.component(component.name, component))
    // 封装ElMessage
    window.$toast = (type: MessageType, message: string) => {
      return new Promise((resolve) => {
        ElMessage({
          type,
          message,
          offset: 50,
          onClose: () => resolve()
        })
      })
    }
  }
}

export default Element

入口文件写好后,我们需要去main.ts中引入,因为我们已经在入口文件中暴露出了带有install方法的Element对象,所以,我们只需在main.ts中使用app.use(),即可完成引入:

import Element from './plugins/element-plus'
app.use(Element)

3. 验证是否引入成功

这里我在App.vue中随便写点东西测试一下:

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
  <el-button type="primary" @click="onClick">显示消息</el-button>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default defineComponent({
  name: 'App',
  components: {
    HelloWorld
  },
  setup() {
    const onClick = () => window.$toast('success', '我是一条消息')

    return {
      onClick
    }
  }
})
</script>

然后在终端输入npm run serve,回车,完成后终端会显示这样的界面:
在这里插入图片描述
打开浏览器,输入http://localhost:8080,回车:
在这里插入图片描述
点一下【显示消息】按钮,就会出现消息框啦:
在这里插入图片描述
至此,element-plus按需引入就成功了。但是,因为我们一会儿还需要实现动态换肤的功能,所以这里的css文件是一次性全部引入的,即这里的按需引入是不完全的。如果项目中不需要实现动态换肤的功能,可以这样按需引入:

import { App } from 'vue'
import { locale, ElButton, ElMessage } from 'element-plus'
import { MessageType } from 'element-plus/lib/el-message/src/types'
import lang from 'element-plus/lib/locale/lang/zh-cn'
import 'dayjs/locale/zh-cn'
import 'element-plus/lib/theme-chalk/base.css'

const components = [ElButton]

const Element = {
  install: (app: App<Element>) => {
    locale(lang)
    components.forEach((component) => {
      app.component(component.name, component)
      const cssName = component.name
        .replace(/[A-Z]/g, (item) => '-' + item.toLowerCase())
        .replace('-', '')
      require(`element-plus/lib/theme-chalk/${cssName}.css`)
    })
    // 封装ElMessage
    require('element-plus/lib/theme-chalk/el-message.css')
    window.$toast = async (type: MessageType, message: string) => {
      return new Promise((resolve) => {
        ElMessage({
          type,
          message,
          offset: 50,
          onClose: () => resolve()
        })
      })
    }
  }
}

export default Element

但是这样做的话,也还有一些需要注意的地方:

  • 类似ElMessage,ElMessageBox,ElNotification等这样以服务形式编写的组件,需要自己require对应的css文件
  • 有一些组件会引用到另外一个组件。例如ElMenu,它就会用到Elpoper,所以在引入ElMenu组件的时候,也要记得引入ElPoper

三、动态换肤

这个世界上,每个人的审美多多少少都有些不同,我喜欢蓝色,你可能喜欢绿色;我喜欢浅色模式,你可能喜欢深色模式。所以,给网站添加一个动态换肤的功能其实是很有必要的。那话不多说,我们开始吧。

1. 制作自定义主题

动态换肤的基础,就是我们必须已经拥有了相应的主题文件(网上也有一些可以随意换色的方案,但是只能更换主题色。如果想细致的换肤,还得是先准备好css文件)。我在实现的时候,参考了vue-element-admin作者的博客文章(手摸手,带你用vue撸后台 系列三(实战篇)),以及element-plus文档给出的解决方案。但是我在实际使用时,却出现了一个问题:官方推荐的在线主题定制工具生成的主题在引入之后,一些组件会出现样式上的bug、命令行工具element-theme则会报一些莫名其妙的错误而导致无法正常使用,而node_modules/element-plus中的默认主题却能正常运行。

所以,我顺着问题去想,所谓的主题不就是sass编译后的css文件吗?我为什么不自己去写一个编译过程,去编译node_modules/element-plus里本就已经存在的sass呢?这样不就不会出现样式的bug了吗?所以,我请来了非常好用的gulp,写了一个编译器:element-theme-maker (github地址:https://github.com/wyb199877/element-theme-maker)。

好的,让我们开始制作主题吧:

  1. 将仓库克隆到本地,如果电脑访问github的网速太慢,可以先将仓库导入gitee(如果导入为了公开仓库,记得标明出处),然后再克隆。
    git clone https://github.com/wyb199877/element-theme-maker.git
    
  2. 打开package.json文件,修改依赖中"element-plus": "^1.0.2-beta.39"的版本号与自己项目中的版本号相同
  3. 安装依赖,使用终端软件打开当前文件夹,输入npm i,回车运行
  4. 打开theme/element-variables.scss,修改你想更改的sass变量。如果改乱了想恢复默认值,可以拷贝同目录下element-variables-default.scss中的内容,覆盖即可
  5. 回到终端,按如下格式输入命令:
    npm run build -- --theme-default
    
    或者
    gulp --theme-default
    
    参数注释:
    --theme-default:这个参数是整个主题的命名空间,也就是每个样式类共同具有的一个父类 .theme-default,可将其添加至body标签,以达到切换主题的目的
  6. 运行成功之后,会在根目录出现一个result文件夹,其中的内容就是生成的主题的css文件与font文件,可直接拷贝到项目中使用

好的,现在我就当你已经成功做出了一个default主题,下面我们开始引入主题吧。

2. 引入自定义主题

回到多页签项目,新建public/theme/default,将生成器result目录下的所有文件全部拷贝过去。为什么我们不将主题放进src中呢?原因很简单,因为主题文件很大,一两个还好,如果很多个主题的话,就会使打包后的文件变得过大,导致系统首次加载的时候非常非常慢。所以,我选择将主题放在public中,这里的文件不会被webpack打包,而是会被当做静态资源单独安放。

然后,我们需要定义一些ts类型,新建src/store/typings/index.d.ts

// 这个Theme是这样的:type Theme = 'default' | 'green' | 'pink' | 'purple' | 'brown',大家自己去定义一下
import { Theme } from '@/typings'
import { RouteRecordName } from 'vue-router'

export interface RootStateProps {
  settingModule: SettingStateProps
}

export interface SettingStateProps {
  theme: Theme
}

然后需要使用vuex去记录一下我们当前使用的是哪个主题,新建src/store/modules/setting.ts

import { Module } from 'vuex'
import { SettingStateProps, RootStateProps } from '../typings'
import { Theme } from '@/typings'

const settingModule: Module<SettingStateProps, RootStateProps> = {
  namespaced: true,
  state: {
    theme: 'default'
  },
  mutations: {
    SET_THEME(state, theme: Theme) {
      state.theme = theme
      let classNames = document.body.className.split(' ')
      classNames = classNames.filter((className) => !className.includes('theme-'))
      classNames.push('theme-' + theme)
      document.body.className = classNames.join(' ')
    }
  }
}

export default settingModule

然后,新建一个常量模块,存放一下mutationKey,新建src/consts/index.ts

class ModuleName {
  public static SETTING = 'settingModule'
}

class Consts {
  public static MutationKey = class {
    public static SET_THEME = ModuleName.SETTING + '/SET_THEME'
  }
}

export default Consts

不要问我为什么要用面向对象的方式去写这个文件,问就是写js对象写腻了 (doge)
之后,我们需要设置一下主题列表以及启动时应该加载的主题:

新建src/setting.ts

import { SettingProps } from './typings'
/**
 * 这里的SettingProps是这样的:
 * export interface SettingProps {
 *   THEME_LIST: ThemeListItem[]
 * }
 * 
 * ThemeListItem是这样的:
 * export interface ThemeListItem {
 *   name: Theme
 *   color: string
 * }
 * 大家自己添加一下
 */

const Setting: SettingProps = {
  // 主题列表(主题名不需要写theme-前缀)
  THEME_LIST: [
    { name: 'default', color: '#409EFF' }
  ]
}

export default Setting

修改src/plugins/element-plus/index.ts,将下面的内容添加进install方法中:

// 添加全部主题包
Setting.THEME_LIST.forEach((theme) => {
  const link = document.createElement('link')
  link.href = `/theme/${theme.name}/index.css`
  link.rel = 'stylesheet'
  link.type = 'text/css'
  document.head.appendChild(link)
})
// 设置激活的主题
const theme = store.state.settingModule.theme
require(`/public/theme/${theme}/index.css`)

之后,删掉默认主题的引入,删除import 'element-plus/lib/theme-chalk/index.css这句。保存后,我们的default主题就成功引入了。后面如需添加新主题,可重复以上步骤。

3. 修改非element组件元素的主题色

在我们的项目中,有很多其他元素也会用到我们的主题色,但是这些主题色是不受element主题文件所控制的。那么怎么办呢?其实很简单,我们只需要利用sass变量+css变量,即可解决这个问题。那么,我们先来安装一下sass吧:

npm i node-sass@4.14.1 sass-loader@7.3.1 --save-dev

注意,这里我安装的时候是指定版本安装的,至于为什么,其实是因为如果这两个东西全都默认安装最新版的话,会出现不兼容的问题,导致项目无法运行。所以在安装的时候需要注意二者版本号的相容性。然后,这两个版本是我测试过可以使用的,所以就指定版本号了。

安装好之后,我们就来写一下sass变量,新建src/assets/styles/variables.scss

/*** theme ***/
$color--primary: var(--theme-color, #409eff) !default;

:export {
  PRIMARY: $color--primary;
}

好了,我们就这样定义好了一个sass+css变量的变量,同时导出了颜色值。不过,这时你如果在ts中像这样import Variables from '@/assets/styles/variables.scss'直接导入这个模块的话,是会报错的。为什么呢?首先第一个是因为我们没有将.scss.sass这两种后缀名的文件声明为一个模块,ts就无法识别;其次是因为我们根本就没开启css模块化,当然不能导出变量了。那么我们现在就来解决这两个问题:

新建src/typings/module.d.ts

declare module '*.scss'
declare module '*.sass'

修改vue.config.js,将下面的内容添加至options中:

css: {
    // 开启css模块化
    requireModuleExtension: true,
    // 全局引入sass变量
    loaderOptions: {
      sass: {
        // 因为我这里用的sass-loader的版本是低于8的,所以用的是data属性,如果版本高于8,那用的就是prependData
        data: '@import "@/assets/styles/variables.scss";',
        sourceMap: false
      }
    }
  }

好的,现在只差最后一步了,我们只需要在vuex中setting模块的SET_THEME中,添加修改--theme-color变量的语句就可以了,修改src/store/modules/setting.ts

mutations: {
  SET_THEME(state, theme: Theme) {
    // 存储新主题
    state.theme = theme
    // 变更主题命名空间
    let classNames = document.body.className.split(' ')
    classNames = classNames.filter((className) => !className.includes('theme-'))
    classNames.push('theme-' + theme)
    document.body.className = classNames.join(' ')
    // 变更其他非element组件元素的主题色
    document.body.style.setProperty(
      '--theme-color',
      Setting.THEME_LIST.find((item) => item.name === theme)!.color
    )
  }
}

好的,保存并重新编译之后就大功告成了,现在非element组件元素的主题色也可以动态变更了。不过,当你刷新浏览器之后,你会发现,主题又回到了最开始的模样。这就不行了,谁家的主题换完还能一刷新自己就变回去的?所以,我们需要对vuex的全局状态做一个持久化。

4. 安装vuex-persist实现数据持久化

vuex-persist是一个专门为vuex数据持久化而生的插件,它可以通过 cookies、sessionStorage和localStorage三种方式来自动存储vuex中的全局状态。我们的主题是一种需要长久保存的状态,所以我们需要将其存储至localStorage,修改src/store/index.ts

import VuexPersist from 'vuex-persist'

const vuexLocal = new VuexPersist({
  storage: window.localStorage,
  key: 'vue-local',
  modules: ['settingModule']
})

const store = createStore<RootStateProps>({
  modules,
  plugins: [vuexLocal.plugin]
})

简简单单的三步,我们的主题已经被保存下来了,现在再去刷新就不会有问题了。另外我补充一点,这个store的plugins属性中,并不是每个插件只能用一次,你完全可以通过插件的构造函数创建无数个变量,然后全塞进plugins中,也是没有问题的。就例如我这样写也是OK的:

import VuexPersist from 'vuex-persist'

// setting模块需要长久存储
const vuexLocal = new VuexPersist({
  storage: window.localStorage,
  key: 'vue-local',
  modules: ['settingModule']
})

// tag和app模块只需运行时存储
const vuexSession = new VuexPersist({
  storage: window.sessionStorage,
  key: 'vue-session',
  modules: ['tagModule', 'appModule']
})

const store = createStore<RootStateProps>({
  modules,
  plugins: [vuexLocal.plugin,vuexSession.plugin]
})

下一篇预告:vue3.0+ts+element-plus多页签应用模板:如何优雅地使用Svg图标

Logo

前往低代码交流专区

更多推荐