chore: Optimize the code of the theme switching function; 🌟

master
iczer 4 years ago
parent 8cbde7bff5
commit 6e5592fff7
  1. 2
      src/components/chart/MiniProgress.vue
  2. 2
      src/components/chart/RankingList.vue
  3. 2
      src/components/setting/Setting.vue
  4. 8
      src/config/default/theme.js
  5. 10
      src/config/replacer/index.js
  6. 38
      src/config/replacer/resolve.config.js
  7. 1
      src/theme/default/color.less
  8. 62
      src/utils/colors.js
  9. 89
      src/utils/theme-color-replacer-extend.js
  10. 88
      src/utils/themeUtil.js
  11. 24
      vue.config.js

@ -25,7 +25,7 @@ export default {
position: relative;
width: 100%;
.wrap {
background-color: #f5f5f5;
background-color: @bg-color;
position: relative;
}
.progress {

@ -48,7 +48,7 @@ export default {
}
span.active {
background-color: #314659 !important;
color: #fff !important;
color: @text-color-inverse !important;
}
span:last-child {
float: right;

@ -97,7 +97,7 @@ export default {
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: {

@ -1,3 +1,9 @@
// 主题模式
const mode = {
LIGHT: 'light',
DARK: 'dark',
NIGHT: 'night',
}
// 亮色模式
const light = {
'layout-body-background': '#f0f2f5',
@ -61,4 +67,4 @@ const night = {
'btn-primary-color': '#141414',
}
module.exports = {light, dark, night}
module.exports = {light, dark, night, mode}

@ -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}

@ -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文本 cssObjcss对象参数; 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

@ -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;

@ -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'
// 获取 ant design 色系
function getAntdColors(color, mode) {
let options = mode && (mode == themeMode.NIGHT) ? {theme: 'dark'} : undefined
return generate(color, options)
}
function getDarkColors(color, theme) {
if (theme == 'night') {
return nightColors
// 获取菜单色系
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)]
}
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
}
function getBgColors(theme) {
return theme == 'light' ? ['#f0f2f5', '#ffffff'] : ['#000000', '#141414']
// 获取主题模式切换色系
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 @@ function isRgba(color) {
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
}

@ -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}

@ -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]
}
}

@ -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 @@ module.exports = {
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')
.tap(args => {
args[0].cssnanoOptions.preset[1].colormin = false
return args
})
// 生产环境下关闭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
}
}

Loading…
Cancel
Save