根据后端返回的颜色值来适配项目的主题色的解决历程

1,142 阅读4分钟

方式一:

  • 实现方式

在全局入口文件main.js(或者全局可以访问到的文件,我这里是在permission.js)中,调用更换主题色的方法,核心代码如下:

store.dispatch('GetColor').then((res) => {
  if (res.success) {
    updateTheme(res.data.color) // 更新主题色的方法
  }
})

通过less.modifyVars修改变量@primary-color的值来更新主题色方法,核心代码如下:

const updateTheme = primaryColor => {
  if (!primaryColor) {
    return;
  }
  function buildIt() {
    // 正确的判定less是否已经加载less.modifyVars可用
    if (!window.less || !window.less.modifyVars) {
      return;
    }
    // less.modifyVars可用
    window.less.modifyVars({
        '@primary-color': primaryColor,
      })
      .then(() => {
        console.info(`正在编译主题!`)
      })
      .catch(() => {
        console.log('主题颜色编译失败');
      });
  }
  if (!lessNodesAppended) {
    // insert less.js and color.less
    const lessStyleNode = document.createElement('link');
    const lessConfigNode = document.createElement('script');
    const lessScriptNode = document.createElement('script');
    lessStyleNode.setAttribute('rel', 'stylesheet/less');
    lessStyleNode.setAttribute('href', '/color.less');
    lessConfigNode.innerHTML = `
      window.less = {
        async: true,
        env: 'production',
        javascriptEnabled: true
      };
    `;
    let url = '/less.min.js'
    lessScriptNode.src = url;
    lessScriptNode.async = true;
    lessScriptNode.onload = () => {
      buildIt();
      lessScriptNode.onload = null;
    };
    document.body.appendChild(lessStyleNode);
    document.body.appendChild(lessConfigNode);
    document.body.appendChild(lessScriptNode);
    lessNodesAppended = true;
  } else {
    buildIt();
  }
};

引用public文件夹下的color.less文件,使用方式如下

@primary-color: #1890ff;
.ant-btn-primary {
    background-color: @primary-color;
}

vue.config.js中配置默认主题色:

module.exports = {
    css: {
    loaderOptions: {
      less: {
        modifyVars: {
          /* less 变量覆盖,用于自定义 ant design 主题 */
          'primary-color': '#1890FF',
        },
        javascriptEnabled: true,
      }
    }
  },
}
  • 缺陷:

刷新页面会存在一个从默认主题色到替换的主题色变化的一个延迟过渡效果(根据接口请求时常而定,一般至少1-2秒),体验不好

  • 尝试过的解决缺陷失败了的方式(都失败了,不可行):
  1. 思路是:先替换主题色,再渲染render DOM,最后挂载$mount

想在new Vue()中添加created钩子函数执行更新主题色的方法updateTheme,代码如下:

new Vue({
  router,
  store,
  created() {
    getColor() {
      store.dispatch('GetColor').then((res) => {
        if (res.success) {
          updateTheme(res.data.color)
        }
      })
    }  },
  mounted () {
    store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
  },
  render: h => h(App),
}).$mount('#app')

2.思路是:使用接口返回的color赋值给vue.config.js中的@primary-color,参数location获取不到为undefinedgetColor()是异步执行的,'primary-color'获取到的是undefined;代码如下:

const axios = require('axios')

function getColor() {
    axios.post("http://10.1.10.11/get/color", {dns: location.hostname}).then(res => {
        if(res.success) {
            return res.data.color;
        }
    })}

module.exports = {
    css: {
        loaderOptions: {
            less: {
                modifyVars: {
                    'primary-color': getColor(),
                }
                javascriptEnabled: true,
            }
        }
    }
}

方式二:

  • 实现方式

利用插件 webpack-theme-color-replacer 来实现,参考segmentfault.com/a/119000001…,实现原理是:webpack构建时,在emit事件(准备写入dist结果文件时)中,将即将生成的所有css文件的内容中 带有指定颜色的css规则单独提取出来,再合并为一个theme-colors.css输出文件。然后在切换主题色时,下载这个文件,并替换为需要的颜色,应用到页面上

npm i -D webpack-theme-color-replacer

更新主题色方法,核心代码:

import themeColor from './themeColor.js'

const updateTheme = newPrimaryColor => {
  themeColor.changeColor(newPrimaryColor).finally(t => {
    setTimeout(() => {
      console.log('主题色替换')
    })
  })
}

themeColor.js代码:

import client from 'webpack-theme-color-replacer/client'
import generate from '@ant-design/colors/lib/generate'
export default {
  getAntdSerials (color) {
    // 淡化(即less的tint)
    const lightens = new Array(9).fill().map((t, i) => {
      return client.varyColor.lighten(color, i / 10)
    })
    // colorPalette变换得到颜色值
    const colorPalettes = generate(color)
    const rgb = client.varyColor.toNum3(color.replace('#', '')).join(',')
    return lightens.concat(colorPalettes).concat(rgb)
  },
  changeColor (newColor) {
    var options = {
      newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
      changeUrl (cssUrl) {
        return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
      }
    }
    return client.changer.changeColor(options, Promise)
  }
}

webpack相关的配置,vue.config.js代码:

const createThemeColorReplacerPlugin = require('./config/plugin.config')

module.exports = {
  configureWebpack: config => {
    if(process.env.NODE_ENV === 'development') {
      config.plugins.push(createThemeColorReplacerPlugin())
    }
  },
}

通过配置matchColors,匹配到所有文件中的该颜色值,统一放到theme-colors.css文件中然后再下载该文件;plugins.config.js代码:

const ThemeColorReplacer = require('webpack-theme-color-replacer')
const generate = require('@ant-design/colors/lib/generate').default

const getAntdSerials = (color) => {
  // 淡化(即less的tint)
  const lightens = new Array(9).fill().map((t, i) => {
    return ThemeColorReplacer.varyColor.lighten(color, i / 10)
  })
  const colorPalettes = generate(color)
  const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace('#', '')).join(',')
  return lightens.concat(colorPalettes).concat(rgb)
}

const themePluginOption = {
  fileName: 'css/theme-colors-[contenthash:8].css',
  matchColors: getAntdSerials('#1890ff'), // 主色系列
  // 改变样式选择器,解决样式覆盖问题
  changeSelector (selector) {
    switch (selector) {
      case '.ant-calendar-today .ant-calendar-date':
        return ':not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)' + selector
      case '.ant-btn:focus,.ant-btn:hover':
        return '.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)'
      case '.ant-btn.active,.ant-btn:active':
        return '.ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)'
      case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
      case '.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon':
        return ':not(.ant-steps-item-process)' + selector
      case '.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover':
      case '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover':
        return '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover'
      case '.ant-menu-horizontal > .ant-menu-item-selected > a':
      case '.ant-menu-horizontal>.ant-menu-item-selected>a':
        return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a'
      case '.ant-menu-horizontal > .ant-menu-item > a:hover':
      case '.ant-menu-horizontal>.ant-menu-item>a:hover':
        return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover'
      default :
        return selector
    }
  }
}

const createThemeColorReplacerPlugin = () => new ThemeColorReplacer(themePluginOption)

module.exports = createThemeColorReplacerPlugin

优点:该方式大大缩减了刷新页面的主题色延迟过渡时间,大概ms的过渡时间;

缺点:刷新页面还是能看到从默认颜色到替换主题色的过渡效果;目前还没找到更好的方法来实现刷新页面时消除主题色的过渡效果;