目录
- 一、安装element-plus
- 二、按需引入
- 1. 为什么要按需引入?
- 2. 如何按需引入?
- 3. 验证是否引入成功
- 三、动态换肤
- 1. 制作自定义主题
- 2. 引入自定义主题
- 3. 修改非element组件元素的主题色
- 4. 安装vuex-persist实现数据持久化
一、安装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)。
好的,让我们开始制作主题吧:
- 将仓库克隆到本地,如果电脑访问github的网速太慢,可以先将仓库导入gitee(如果导入为了公开仓库,记得标明出处),然后再克隆。
git clone https://github.com/wyb199877/element-theme-maker.git
- 打开
package.json
文件,修改依赖中"element-plus": "^1.0.2-beta.39"
的版本号与自己项目中的版本号相同 - 安装依赖,使用终端软件打开当前文件夹,输入
npm i
,回车运行 - 打开
theme/element-variables.scss
,修改你想更改的sass变量。如果改乱了想恢复默认值,可以拷贝同目录下element-variables-default.scss
中的内容,覆盖即可 - 回到终端,按如下格式输入命令:
npm run build -- --theme-default
- 或者
gulp --theme-default
- 参数注释:
--theme-default
:这个参数是整个主题的命名空间,也就是每个样式类共同具有的一个父类.theme-default
,可将其添加至body
标签,以达到切换主题的目的 - 运行成功之后,会在根目录出现一个
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图标