chore: Optimize the code of the theme switching function; :star2:
| | |
| | | position: relative; |
| | | width: 100%; |
| | | .wrap { |
| | | background-color: #f5f5f5; |
| | | background-color: @bg-color; |
| | | position: relative; |
| | | } |
| | | .progress { |
| | |
| | | } |
| | | span.active { |
| | | background-color: #314659 !important; |
| | | color: #fff !important; |
| | | color: @text-color-inverse !important; |
| | | } |
| | | span:last-child { |
| | | float: right; |
| | |
| | | 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: { |
| | |
| | | // 主题模式 |
| | | const mode = { |
| | | LIGHT: 'light', |
| | | DARK: 'dark', |
| | | NIGHT: 'night', |
| | | } |
| | | // 亮色模式 |
| | | const light = { |
| | | 'layout-body-background': '#f0f2f5', |
| | |
| | | 'btn-primary-color': '#141414', |
| | | } |
| | | |
| | | module.exports = {light, dark, night} |
| | | module.exports = {light, dark, night, mode} |
New file |
| | |
| | | /** |
| | | * 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} |
New file |
| | |
| | | /** |
| | | * 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 |
| | |
| | | |
| | | @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; |
| | |
| | | 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) { |
| | |
| | | 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 |
| | | } |
New file |
| | |
| | | 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} |
| | |
| | | // 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] |
| | | } |
| | | } |
| | |
| | | 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: { |
| | |
| | | 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 |
| | | } |
| | | } |