vue-cli 初始化的项目动态换肤实践
换肤最终达到的目标如下:
- 只需要简单的配置,即可开发一套新主题
- 内置多套皮肤
- 可以随意设置颜色,生成新主题 CSS
使用 vue-cli 初始化一个项目 vue-switch-theme。
这里选的 CSS 预处理语言为 less。
一般实现多套主题都是预先定义好各种主题相关的变量,譬如主题色,文字颜色,导航背景色等等,不同的主题调整变量即可。为了可以迅速开发一套新的主题,那么理想情况下配置的变量越少越好。于是就有了以下主题开发的流程。
主题目录结构如下:
theme/blue/style.less | 蓝色主题入口样式 |
theme/dark/style.less | 暗黑主题入口样式 |
theme/default/style.less | 默认主题入口样式 |
theme/template/style.less | 模板主题,用于实时配置新的主题 |
theme/style.less | 汇总系统所有样式 |
theme/var.less | 全局主题 less 变量 |
theme/style.less 一般用来汇总项目中所有的样式。例如:
@import '../css/components/select-city.less';
@import '../css/components/calendar.less';
@import '../css/views/home.less';
@import '../css/views/about.less';
全局主题 less 变量文件包含了默认主题的变量,当然还包括和主题无关的一些变量。例如:
@calendar-bg-color: #f1f1f1;
@main-font-color: #444;
@tip-font-color: #999;
@nav-width: 200px;
每个主题 less 文件内容结构如下,里面的顺序不能随意更改:
// 引入全局主题变量
@import '../var.less';
// 主题相关配置
// 覆盖主题的变量配置
@primary-color: #008891;
// 引入系统样式合集
@import '../style.less';
这里要重点讲下 template 下的 style.less 文件如何去做, 这个 template 顾名思义是一个模板主题,最终会生成一个模板主题 CSS 文件,用户随意设置颜色,通过字符串的方式生成一个新的主题 CSS 文件。
template/style.less 要将所有主题相关的变量定义为一个占位符,这样可以方便做字符串替换。例如:
// 引入全局主题变量
@import '../var.less';
// 覆盖主题的变量配置
@primary-color: '__primary-color__';
@main-font-color: '__main-font-color__';
@tip-font-color: '__tip-font-color__';
// 引入系统样式合集
@import '../style.less';
主题开发相关的介绍完了。接下来介绍如何做打包的配置,从上面的主题开发的目录结构可以看到,每个主题最终都会生成一个 CSS 文件,这里有多种方式:
- 样式单独去打包,和项目(也就是 vue-cli-service serve/build)的打包分开。比如用 lessc 去打包,但是 less 中用到的图片,字体等资源就需要自己处理
- 跟着项目一起打包,但是 Webpack 也不支持直接配置类似多种主题的方案,这里我们就只能通过配置多入口项目的方式来解决这个问题。
在 vue.config.js 中配置各个主题入口。
module.exports = {
filenameHashing: false,
chainWebpack: (config) => {
// 主题入口不需要生成 html
config.plugins.store.delete('html-theme_blue')
config.plugins.store.delete('html-theme_dark')
config.plugins.store.delete('html-theme_template')
},
pages: {
index: {
entry: 'src/main.js',
},
theme_blue: {
entry: 'src/assets/theme/blue/style.less',
},
theme_dark: {
entry: 'src/assets/theme/dark/style.less',
},
theme_template: {
entry: 'src/assets/theme/template/style.less',
},
},
}
默认主题在 main.js 中引入了,因此不需要再去单独配置入口。最终打包后的文件如下图所示。
打开 theme_template.css,主题相关的变量已经替换为占位符,当然替换的时候需要特别注意,不仅要替换占位符,也要将引号一并给替换了。
切换皮肤,其实就是加载相应皮肤的 CSS 文件。
比如在 php 后端通过 url 参数的方式加载不同的皮肤。
<?php if(\Yii::$app->request->get('theme') =='dark'){?>
<link rel="stylesheet" href="/css/theme_dark.css">
<?php }else{?>
<link rel="stylesheet" href="/css/index.css">
<?php }?>
或者直接用 JavaScript 加载不同的皮肤。
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = '/css/theme_dark.css'
document.head.appendChild(link)
上面主要介绍了如何去加载内置皮肤,那么设置主题相关的变量,生成下新的主题 CSS 文件如何做?
最简单的方式:
- 将主题相关的变量提炼出来,可以做成配置页面,让用户配置
- 配置完成后,提交给后端生成 CSS 文件
URL参数的方式:
- 将主题配置 JSON 转成 base64,放到 URL 参数中,譬如 theme=xxx
- 后端将 base64 生成 md5 值,解析成主题配置,生成新的 CSS 文件
- 文件名用第二步生成的 md5 值,这样的好处是相同主题配置不用重复生成,实际生产中,还要考虑模板主题 CSS 文件的更新时间,如果生产更新是删除后更新没问题。
前面为了保障内置主题文件名固定,因此将 filenameHashing 禁用了。那么实际生产中这样做肯定不合适,因此我们需要将 hash 改到 url 参数中去。在 vue.config.js 中调整 filename 和 chunkfileName。
chainWebpack: (config) => {
if (process.env.NODE_ENV === 'production') {
config.output.filename('js/[name].js?[contenthash:8]')
config.output.chunkFilename('js/[name].js?[contenthash:8]')
config.plugin('extract-css').tap((args) => {
args[0].filename = 'css/[name].css?[contenthash:8]'
args[0].chunkFilename = 'css/[name].css?[contenthash:8]'
return args
})
}
}