研发图纸文件管理系统-前端项目
iczer
2020-07-08 6e5592fff72a9326dcc0ab70b8a97bed58ce8df4
chore: Optimize the code of the theme switching function; :star2:
3个文件已添加
8个文件已修改
322 ■■■■ 已修改文件
src/components/chart/MiniProgress.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chart/RankingList.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/setting/Setting.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/default/theme.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/replacer/index.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/replacer/resolve.config.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/theme/default/color.less 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/colors.js 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/theme-color-replacer-extend.js 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/themeUtil.js 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chart/MiniProgress.vue
@@ -25,7 +25,7 @@
    position: relative;
    width: 100%;
    .wrap {
      background-color: #f5f5f5;
      background-color: @bg-color;
      position: relative;
    }
    .progress {
src/components/chart/RankingList.vue
@@ -48,7 +48,7 @@
        }
        span.active {
          background-color: #314659 !important;
          color: #fff !important;
          color: @text-color-inverse !important;
        }
        span:last-child {
          float: right;
src/components/setting/Setting.vue
@@ -97,7 +97,7 @@
    return {
      animate: this.$store.state.setting.animate.name,
      direction: this.$store.state.setting.animate.direction,
      colors: ['#f5222d', '#fa541c', '#fadb14', '#49aa19', '#13c2c2', '#1890ff', '#722ed1', '#eb2f96'],
      colors: ['#f5222d', '#fa541c', '#fadb14', '#3eaf7c', '#13c2c2', '#1890ff', '#722ed1', '#eb2f96'],
    }
  },
  computed: {
src/config/default/theme.js
@@ -1,3 +1,9 @@
// 主题模式
const mode = {
  LIGHT: 'light',
  DARK: 'dark',
  NIGHT: 'night',
}
// 亮色模式
const light = {
  'layout-body-background': '#f0f2f5',
@@ -61,4 +67,4 @@
  'btn-primary-color': '#141414',
}
module.exports = {light, dark, night}
module.exports = {light, dark, night, mode}
src/config/replacer/index.js
New file
@@ -0,0 +1,10 @@
/**
 * webpack-theme-color-replacer 配置
 * webpack-theme-color-replacer 是一个高效的主题色替换插件,可以实现系统运行时动态切换主题功能。
 * 但有些情景下,我们需要为 webpack-theme-color-replacer 配置一些规则,以达到我们的个性化需求的目的
 *
 * @cssResolve: css处理规则,在 webpack-theme-color-replacer 提取 需要替换主题色的 css 后,应用此规则。一般在
 *              webpack-theme-color-replacer 默认规则无法达到我们的要求时使用。
 */
const cssResolve = require('./resolve.config')
module.exports = {cssResolve}
src/config/replacer/resolve.config.js
New file
@@ -0,0 +1,38 @@
/**
 * webpack-theme-color-replacer 插件的 resolve 配置
 * 为特定的 css 选择器(selector)配置 resolve 规则。
 *
 * key 为 css selector 值或合法的正则表达式字符串
 * 当 key 设置 css selector 值时,会匹配对应的 css
 * 当 key 设置为正则表达式时,会匹配所有满足此正则表达式的的 css
 *
 * value 可以设置为 boolean 值 false 或 一个对象
 * 当 value 为 false 时,则会忽略此 css,即此 css 不纳入 webpack-theme-color-replacer 管理
 * 当 value 为 对象时,会调用该对象的 resolve 函数,并传入 cssText(原始的 css文本) 和 cssObj(css对象)参数; resolve函数应该返
 * 回一个处理后的、合法的 css字符串(包含 selector)
 * 注意: value 不能设置为 true
 */
const cssResolve = {
  '.ant-checkbox-checked .ant-checkbox-inner::after': false,
  '.ant-menu-dark .ant-menu-inline.ant-menu-sub': {
    resolve(cssText, cssObj) {
      cssObj.rules = cssObj.rules.filter(rule => rule.indexOf('box-shadow') == -1)
      return cssObj.toText()
    }
  },
  '.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu:hover,.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-submenu-selected': {
    resolve(cssText, cssObj) {
      cssObj.selector = cssObj.selector.replace(/.ant-menu-horizontal/g, '.ant-menu-horizontal:not(.ant-menu-dark)')
      return cssObj.toText()
    }
  },
  '.ant-layout-sider': {
    resolve(cssText, cssObj) {
      cssObj.selector = '.ant-layout-sider-dark'
      return cssObj.toText()
    }
  },
  '/keyframes/': false
}
module.exports = cssResolve
src/theme/default/color.less
@@ -17,6 +17,7 @@
@title-color: @heading-color;
@text-color: @text-color;
@text-color-inverse: @text-color-inverse;
@text-color-second: @text-color-secondary;
@base-bg-color: @body-background;
@bg-color: @layout-body-background;
src/utils/colors.js
@@ -1,25 +1,47 @@
const varyColor = require('webpack-theme-color-replacer/client/varyColor')
const generate =  require('@ant-design/colors/lib/generate').default
const {theme} = require('../config/default')
const themeMode = theme.mode
// ant design vue 默认主题色
const antPrimaryColor = '#1890ff'
// ant design vue 默认dark主题色,若主题色为默认主题色则返回此 dark 主题色系
const antDarkColors = ['#000c17', '#001529', '#002140']
const nightColors = ['#151515', '#1f1f1f', '#1f1f1f']
const antdPrimary = '#1890ff'
function getDarkColors(color, theme) {
  if (theme == 'night') {
    return nightColors
  }
  if (color == antPrimaryColor) {
    return antDarkColors
  }
  const darkColors = []
  darkColors.push(varyColor.darken(color, 0.93), varyColor.darken(color, 0.83), varyColor.darken(color, 0.73))
  return darkColors
// 获取 ant design 色系
function getAntdColors(color, mode) {
  let options = mode && (mode == themeMode.NIGHT) ? {theme: 'dark'} : undefined
  return generate(color, options)
}
function getBgColors(theme) {
  return theme == 'light' ? ['#f0f2f5', '#ffffff'] : ['#000000', '#141414']
// 获取菜单色系
function getMenuColors(color, mode) {
  if (mode == themeMode.NIGHT) {
    return ['#151515', '#1f1f1f', '#1e1e1e']
  } else if (color == antdPrimary) {
    return ['#000c17', '#001529', '#002140']
  } else {
    return [varyColor.darken(color, 0.93), varyColor.darken(color, 0.83), varyColor.darken(color, 0.73)]
  }
}
// 获取主题模式切换色系
function getThemeToggleColors(color, mode) {
  //主色系
  const mainColors = getAntdColors(color, mode)
  const primary = mainColors[5]
  //辅助色系,因为 antd 目前没针对夜间模式设计,所以增加辅助色系以保证夜间模式的正常切换
  const subColors = getAntdColors(primary, themeMode.LIGHT)
  //菜单色系
  const menuColors = getMenuColors(color, mode)
  //内容色系(包含背景色、文字颜色等)
  const themeCfg = theme[mode]
  let contentColors = Object.keys(themeCfg)
    .map(key => themeCfg[key])
    .map(color => isHex(color) ? color : toNum3(color).join(','))
  // 内容色去重
  // contentColors = [...new Set(contentColors)]
  // rgb 格式的主题色
  let rgbColors = [toNum3(primary).join(',')]
  return {primary, mainColors, subColors, menuColors, contentColors, rgbColors}
}
function toNum3(color) {
@@ -51,4 +73,12 @@
  return color.length >= 13 && color.slice(0, 4) == 'rgba'
}
module.exports = {getDarkColors, getBgColors, isHex, isRgb, isRgba, toNum3}
module.exports = {
  isHex,
  isRgb,
  isRgba,
  toNum3,
  getAntdColors,
  getMenuColors,
  getThemeToggleColors
}
src/utils/theme-color-replacer-extend.js
New file
@@ -0,0 +1,89 @@
const {cssResolve} = require('../config/replacer')
// 修正 webpack-theme-color-replacer 插件提取的 css 结果
function resolveCss(output, srcArr) {
  let regExps = []
  // 提取 resolve 配置中所有的正则配置
  Object.keys(cssResolve).forEach(key => {
    let isRegExp = false
    let reg = {}
    try {
      reg = eval(key)
      isRegExp = reg instanceof RegExp
    } catch (e) {
      isRegExp = false
    }
    if (isRegExp) {
      regExps.push([reg, cssResolve[key]])
    }
  })
  // 去重
  srcArr = dropDuplicate(srcArr)
  // 处理 css
  let outArr = []
  srcArr.forEach(text => {
    // 转换为 css 对象
    let cssObj = parseCssObj(text)
    // 根据selector匹配配置,匹配成功,则按配置处理 css
    if (cssResolve[cssObj.selector]) {
      outArr.push(cssResolve[cssObj.selector].resolve(text, cssObj))
    } else {
      let cssText = ''
      // 匹配不成功,则测试是否有匹配的正则配置,有则按正则对应的配置处理
      for (let regExp of regExps) {
        if (regExp[0].test(cssObj.selector)) {
          let cssCfg = regExp[1]
          cssText = cssCfg ? cssCfg.resolve(text, cssObj) : ''
          break
        }
        // 未匹配到正则,则设置 cssText 为默认的 css(即不处理)
        cssText = text
      }
      if (cssText != '') {
        outArr.push(cssText)
      }
    }
  })
  output = outArr.join('\n')
  return output
}
// 数组去重
function dropDuplicate(arr) {
  let map = {}
  let r = []
  for (let s of arr) {
    if (!map[s]) {
      r.push(s)
      map[s] = 1
    }
  }
  return r
}
/**
 * 从字符串解析 css 对象
 * @param cssText
 * @returns {{
 *   name: String,
 *   rules: Array[String],
 *   toText: function
 * }}
 */
function parseCssObj(cssText) {
  let css = {}
  const ruleIndex = cssText.indexOf('{')
  css.selector = cssText.substring(0, ruleIndex)
  const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1)
  const rules = ruleBody.split(';')
  css.rules = rules
  css.toText = function () {
    let body = ''
    this.rules.forEach(item => {body += item + ';'})
    return `${this.selector}{${body}}`
  }
  return css
}
module.exports = {resolveCss}
src/utils/themeUtil.js
@@ -1,80 +1,40 @@
// const varyColor = require('webpack-theme-color-replacer/client/varyColor')
const client = require('webpack-theme-color-replacer/client')
const generate =  require('@ant-design/colors/lib/generate').default
const {theme, themeColor} = require('../config')
const {getDarkColors, isHex, toNum3} = require('../utils/colors')
const themeCfg = require('../config/default').theme
const {getMenuColors, getAntdColors, getThemeToggleColors} = require('../utils/colors')
const {theme: themeCfg} = require('../config/default')
module.exports = {
  primaryColor: themeColor,
  getThemeColors(color, $theme) {
    let _theme = $theme || theme
    let opts = (_theme == 'night') ? {theme: 'dark'} : undefined
    let palettes = generate(color, opts)
    const primary = palettes[5]
    palettes = palettes.concat(generate(primary))
    console.log(palettes)
    const darkBgColors = getDarkColors(color, _theme)
    const _themeCfg = themeCfg[_theme]
    const bgColors = Object.keys(_themeCfg)
      .map(key => _themeCfg[key])
      .map(color => isHex(color) ? color : toNum3(color).join(','))
    let rgb = toNum3(primary).join(',')
    return palettes.concat(darkBgColors).concat(bgColors).concat(rgb)
    const _color = color || themeColor
    const _theme = $theme || theme
    const replaceColors = getThemeToggleColors(_color, _theme)
    const themeColors = [
      ...replaceColors.mainColors,
      ...replaceColors.subColors,
      ...replaceColors.menuColors,
      ...replaceColors.contentColors,
      ...replaceColors.rgbColors
    ]
    return themeColors
  },
  changeThemeColor (newColor, $theme) {
    let options = {
      newColors: this.getThemeColors(newColor, $theme)
    }
    let promise = client.changer.changeColor(options)
    let promise = client.changer.changeColor({newColors: this.getThemeColors(newColor, $theme)})
    return promise
  },
  changeSelector (selector) {
    switch (selector) {
      case '.ant-layout-sider':
        return '.ant-layout-sider-dark'
      case '.ant-menu-dark .ant-menu-inline.ant-menu-sub':
        return '.ant-menu-dark .ant-menu-inline:not(.ant-menu-sub)'
      case '.ant-checkbox-checked .ant-checkbox-inner::after':
        return '.ant-checkbox-checked :not(.ant-checkbox-inner)::after'
      case '.side-menu .logo h1':
        return '.side-menu .logo :not(h1)'
      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
    }
  },
  modifyVars(color) {
    let opts = (theme == 'night') ? {theme: 'dark'} : undefined
    const darkColors = getDarkColors(color, theme)
    const palettes = generate(color, opts)
    let _color = color || themeColor
    const palettes = getAntdColors(_color, theme)
    const menuColors = getMenuColors(_color, theme)
    const primary = palettes[5]
    return {
      'primary-color': palettes[5],
      'primary-1': palettes[0],
      'primary-2': palettes[1],
      'primary-3': palettes[2],
      'primary-4': palettes[3],
      'primary-5': palettes[4],
      'primary-6': palettes[5],
      'primary-7': palettes[6],
      'primary-8': palettes[7],
      'primary-9': palettes[8],
      'primary-10': palettes[9],
      'info-color': palettes[5],
      'primary-color': primary,
      'info-color': primary,
      'alert-info-bg-color': palettes[0],
      'alert-info-border-color': palettes[3],
      'processing-color': palettes[5],
      'menu-dark-submenu-bg': darkColors[0],
      'layout-header-background': darkColors[1],
      'layout-trigger-background': darkColors[2],
      'processing-color': primary,
      'menu-dark-submenu-bg': menuColors[0],
      'layout-header-background': menuColors[1],
      'layout-trigger-background': menuColors[2],
      ...themeCfg[theme]
    }
  }
vue.config.js
@@ -1,7 +1,7 @@
let path = require('path')
const ThemeColorReplacer = require('webpack-theme-color-replacer')
const {getThemeColors, changeSelector, modifyVars} = require('./src/utils/themeUtil')
const themeColor = require('./src/config').themeColor
const {getThemeColors, modifyVars} = require('./src/utils/themeUtil')
const {resolveCss} = require('./src/utils/theme-color-replacer-extend')
module.exports = {
  pluginOptions: {
@@ -15,24 +15,26 @@
    config.plugins.push(
      new ThemeColorReplacer({
        fileName: 'css/theme-colors-[contenthash:8].css',
        matchColors: getThemeColors(themeColor),
        changeSelector
        matchColors: getThemeColors(),
        resolveCss
      })
    )
  },
  chainWebpack: config => {
    config
      .plugin('optimize-css')
    // 生产环境下关闭css压缩的 colormin 项,因为此项优化与主题色替换功能冲突
    if (process.env.NODE_ENV === 'production') {
      config.plugin('optimize-css')
      .tap(args => {
        args[0].cssnanoOptions.preset[1].colormin = false
        return args
      })
    }
  },
  css: {
    loaderOptions: {
      less: {
        lessOptions: {
          modifyVars: modifyVars(themeColor),
          modifyVars: modifyVars(),
          javascriptEnabled: true
        }
      }