qiankun + antdv系统换肤与实践

323 阅读5分钟
  1. 系统采用了qiankun 微服务框架,主应用换肤后需要传递给每一个子应用进行换肤的功能;
  2. 同时我们采用了组件库二次封装(hs-ui),针对组件库也需要做换肤功能

方案一:

暗黑模式,通过css filter滤镜,进行页面颜色反转处理,需要对反转的地方添加处理,图片则需要反转回来,但是针对自定义颜色难以控制

@media (prefers-color-scheme: dark) {
    filter: invert(1) hue-rotate(180deg);
 }

方案二:

使用 css3 的 Variables(需要考虑浏览器支持情况)重新整理源码中的 less变量,在线修改 css 变量达到切换效果,但是组件库中使用了很多的less的颜色函数还只是预处理能力不支持 css 变量编译的,需要做很多的组件样式覆盖处理,这是需要不少的工作量的; ant-desing-vue需要4.17以上版本

方案三:

预设多份 less 变量文件,使用 webpack构建能力提前将所有的样式(包括组件库的)编译出总的多份 css 文件,在线切换 css 文件到达目的,但是需要对项目的所有的 less 的引用模式作调整,对构建环境也需很大的调整,样式与 js 完全分离,如果有使用 css modules 是更大的麻烦,而且在开发模式下修改调试样式极其不友好,还不能友好地对组件库的的 less 按需编译;

方案四:

如果使用的是 ant-design,可以选择采用antd-theme-webpack-pluginantd-theme-generatorumi-plugin-antd-theme等, less.modifyVars实现换肤,针对于颜色的定制,这也仅限于 antd;

方案五:

使用webpack-theme-color-replacer(vite版本对应的是vite-plugin-theme),这个插件可以从所有输出的css文件(如element-ui theme colors)中提取主题颜色样式,并生成一个只包含颜色样式的“theme-colors.css”文件。在你的网页运行时,客户端部分将帮助你下载这个css文件,然后动态地将颜色替换为新的自定义颜色。

方案四的实践

示例官方文档

步骤一:安装

yarn add antd-theme-generator@1.2.11 --save

步骤二:增加主题文件

在config配置目录下创建dark.js,文件示例如下:

const dark = {
  "@primary-color": '#00AEFC',
  "@menu-dark-bg": '#212226',
  "@link-color": '#00AEFC',
  "@custom-bg": '#00AEFC',
  "@mainBgColor": '#363940',
  "@layout-body-background": '#212226',
  "@layout-header-background": '#212226',
  "@nav-bg-color": '#43464D',
  "@nav-tab-active-color": '#363940'
}

module.exports = {
  dark
}

步骤三:配置 color.js

安装完成在根目录下创建color.js文件, 文件配置如下:

const { generateTheme } = require("antd-theme-generator");
const { dark } = require('./src/config/dark') // 引入方式不支持import
const fs = require("fs");
const path = require("path");

//生成的theme.less文件的位置
const outputFilePath = path.join(__dirname, "./public/theme.less");
//自定义样式
const cusCssFilePath = path.join(__dirname, "./src/assets/css/index.less");
// 主题变量
const themeVariables = Object.keys(dark)

const options = {
  antDir: path.join(__dirname, "./node_modules/ant-design-vue"), //antd包位置
  stylesDir: path.join(__dirname, "./src/assets/css"), //主题文件所在文件夹
  varFile: path.join(__dirname, "./src/assets/css/variables.less"), // 自定义默认的主题色
  mainLessFile: cusCssFilePath, // 项目中其他自定义的样式(如果不需要动态修改其他样式,该文件可以为空)
  themeVariables: themeVariables, //要改变的主题变量
  outputFilePath: outputFilePath, // 是否只生成一次
  customColorRegexArray: [/^color\(.*\)$/],
};

generateTheme(options)
  .then((less) => {
    //自定义样式与ant主题样式合并
    //读取提取过的ant样式
    const themeCss = fs.readFileSync(outputFilePath).toString();
    //读取自定义的CSS
    const cusCss = fs.readFileSync(cusCssFilePath).toString();
    fs.writeFileSync(outputFilePath, themeCss + cusCss);
    //重新覆盖themeCss
    console.log(`🌈 主题覆盖成功. OutputFile: ${outputFilePath}`);
  })
  .catch((error) => {
    console.log("Error", error);
  });

步骤四:运行 color.js

可以在vue.config.js获取

 require('./color')

添加完配置,/src/assets/ 创建一个 theme 文件夹,文件夹下创建文件 variables.less 和 index.less文件

index.less文件内容可以为空,variables.less 文件内容:

// 这段样式引入必须添加
@import "~ant-design-vue/lib/style/themes/default.less";
@primary-color: #992777;
复制代码

文件创建完毕,执行启动开发环境命令 yarn run serve,打包成功在public文件夹会有一个,theme.less 文件,如图:

image.png

步骤五:index.html 写入

 <body>
     ...
     <link rel="stylesheet/less" type="text/css" href="./theme.less" /> 
     <div id="hsMainApp"></div>
     // 需要使用到window.less这个对象里的方法
     <script src="https://cdn.bootcss.com/less.js/2.7.3/less.min.js"></script>
     ...
 </body>

使用

      declare const window: Window & { less: any,  }; // 防止报没有less属性
      window.less.modifyVars({
          '@primary-color': 'red' // 可根据当前主题,切换对应变量即可
        })
        .then(() => {
          console.log('成功');
        })
        .catch((error: any) => {
          alert('失败');
          console.log(error);
        });

qiankun使用

  1. 当使用了样式隔离{ strictStyleIsolation: true },由子服务自己更新主题
  2. 未使用样式隔离,则不需要下面这步, 统一由主服务更新主题, 样式层级需求高点,避免被子服务覆盖。
      /* 主服务 */
      // 通知子应用换肤
      const { setGlobalState } = initGlobalState()
      setGlobalState({ theme: targetTheme })
      
      /* 子服务 */
      // 接收主服务传递过来的参数
       props.onGlobalStateChange &&
       props.onGlobalStateChange(
          (value: any, prev: any) => {
            console.log('子应用接收参数')
            setTheme(value)
            console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev)
          },
          true
        );

注意事项

1. antd-theme-generator版本问题

推荐使用: yarn add antd-theme-generator@1.2.11 --save

2. 样式覆盖,样式不出现的问题

  1. 注意theme.less的 link标签放的位置,要放在body的第一行里,因为到时候style是会生成在该ling标签下面的,如果你把link放在head里,到时候生成的主题样式会被覆盖掉。
  2. 我们需要在theme.less 的link 的下方引入cdn.bootcss.com/less.js/2.7… (less版本不可以超过3.0)文件,因为我们需要使用到window.less这个对象里的方法,但是我们不能引入less3.0以上的,不然浏览器控制台会报错,样式也不会出现。

总结

有更好、简单的方案,请在评论区艾特我,谢谢!

参考链接

ant-design-vue 运行时换肤方案

基于less和sass的在webpack或vite中的动态主题的实现方案

antd-theme-generator:github.com/mzohaibqc/a…

antd-theme-webpack-plugin: github.com/mzohaibqc/a…