实现原理:

  1. 获取当前 element-plus 的所有样式
  2. 找到我们想要替换的样式部分,通过正则完成替换
  3. 把替换后的样式写入到 style 标签中,利用样式优先级的特性,替代固有样式

实现步骤:

  1. 获取当前 element-plus 的所有样式
  2. 定义我们要替换之后的样式
  3. 在原样式中,利用正则替换新样式
  4. 把替换后的样式写入到 style 标签中

    方案落地

    创建 utils/theme 工具类,写入两个方法 ```javascript /**
    • 写入新样式到 style
    • @param {*} elNewStyle element-plus 的新样式
    • @param {} isNewStyleTag 是否生成新的 style 标签 / export const writeNewStyle = elNewStyle => {

}

/**

  • 根据主色值,生成最新的样式表 */ export const generateNewStyle = primaryColor => {

}

  1. 那么接下来我们先实现第一个方法 `generateNewStyle`,在实现的过程中,我们需要安装两个工具类:
  2. 1. [rgb-hex](https://www.npmjs.com/package/rgb-hex):转换RGB(A)颜色为十六进制
  3. 1. [css-color-function](https://www.npmjs.com/package/css-color-function):在CSS中提出的颜色函数的解析器和转换器
  4. 然后还需要写入一个 **颜色转化计算器 **`**formula.json**`<br />创建 `constants/formula.json` https://gist.github.com/benfrain/7545629)
  5. ```json
  6. {
  7. "shade-1": "color(primary shade(10%))",
  8. "light-1": "color(primary tint(10%))",
  9. "light-2": "color(primary tint(20%))",
  10. "light-3": "color(primary tint(30%))",
  11. "light-4": "color(primary tint(40%))",
  12. "light-5": "color(primary tint(50%))",
  13. "light-6": "color(primary tint(60%))",
  14. "light-7": "color(primary tint(70%))",
  15. "light-8": "color(primary tint(80%))",
  16. "light-9": "color(primary tint(90%))",
  17. "subMenuHover": "color(primary tint(70%))",
  18. "subMenuBg": "color(primary tint(80%))",
  19. "menuHover": "color(primary tint(90%))",
  20. "menuBg": "color(primary)"
  21. }

准备就绪后,我们来实现 generateNewStyle 方法:

  1. import color from 'css-color-function'
  2. import rgbHex from 'rgb-hex'
  3. import formula from '@/constant/formula.json'
  4. import axios from 'axios'
  5. /**
  6. * 根据主色值,生成最新的样式表
  7. */
  8. export const generateNewStyle = async primaryColor => {
  9. const colors = generateColors(primaryColor)
  10. let cssText = await getOriginalStyle()
  11. // 遍历生成的样式表,在 CSS 的原样式中进行全局替换
  12. Object.keys(colors).forEach(key => {
  13. cssText = cssText.replace(
  14. new RegExp('(:|\\s+)' + key, 'g'),
  15. '$1' + colors[key]
  16. )
  17. })
  18. return cssText
  19. }
  20. /**
  21. * 根据主色生成色值表
  22. */
  23. export const generateColors = primary => {
  24. if (!primary) return
  25. const colors = {
  26. primary
  27. }
  28. Object.keys(formula).forEach(key => {
  29. const value = formula[key].replace(/primary/g, primary)
  30. colors[key] = '#' + rgbHex(color.convert(value))
  31. })
  32. return colors
  33. }
  34. /**
  35. * 获取当前 element-plus 的默认样式表
  36. */
  37. const getOriginalStyle = async () => {
  38. const version = require('element-plus/package.json').version
  39. const url = `https://unpkg.com/element-plus@${version}/dist/index.css`
  40. const { data } = await axios(url)
  41. // 把获取到的数据筛选为原样式模板
  42. return getStyleTemplate(data)
  43. }
  44. /**
  45. * 返回 style 的 template
  46. */
  47. const getStyleTemplate = data => {
  48. // element-plus 默认色值
  49. const colorMap = {
  50. '#3a8ee6': 'shade-1',
  51. '#409eff': 'primary',
  52. '#53a8ff': 'light-1',
  53. '#66b1ff': 'light-2',
  54. '#79bbff': 'light-3',
  55. '#8cc5ff': 'light-4',
  56. '#a0cfff': 'light-5',
  57. '#b3d8ff': 'light-6',
  58. '#c6e2ff': 'light-7',
  59. '#d9ecff': 'light-8',
  60. '#ecf5ff': 'light-9'
  61. }
  62. // 根据默认色值为要替换的色值打上标记
  63. Object.keys(colorMap).forEach(key => {
  64. const value = colorMap[key]
  65. data = data.replace(new RegExp(key, 'ig'), value)
  66. })
  67. return data
  68. }

接下来处理 writeNewStyle 方法:

  1. /**
  2. * 写入新样式到 style
  3. * @param {*} elNewStyle element-plus 的新样式
  4. * @param {*} isNewStyleTag 是否生成新的 style 标签
  5. */
  6. export const writeNewStyle = elNewStyle => {
  7. const style = document.createElement('style')
  8. style.innerText = elNewStyle
  9. document.head.appendChild(style)
  10. }

最后在 SelectColor.vue 中导入这两个方法:

  1. ...
  2. <script setup>
  3. ...
  4. import { generateNewStyle, writeNewStyle } from '@/utils/theme'
  5. ...
  6. /**
  7. * 确定
  8. * 1. 修改主题色
  9. * 2. 保存最新的主题色
  10. * 3. 关闭 dialog
  11. */
  12. const comfirm = async () => {
  13. // 1.1 获取主题色
  14. const newStyleText = await generateNewStyle(mColor.value)
  15. // 1.2 写入最新主题色
  16. writeNewStyle(newStyleText)
  17. // 2. 保存最新的主题色
  18. store.commit('theme/setMainColor', mColor.value)
  19. // 3. 关闭 dialog
  20. closed()
  21. }
  22. </script>

一些处理完成之后,我们可以在 profile 中通过一些代码进行测试:

  1. <el-row>
  2. <el-button>Default</el-button>
  3. <el-button type="primary">Primary</el-button>
  4. <el-button type="success">Success</el-button>
  5. <el-button type="info">Info</el-button>
  6. <el-button type="warning">Warning</el-button>
  7. <el-button type="danger">Danger</el-button>
  8. </el-row>

element-plus 新主题的立即生效

目前 element-plus 的主题变更还有一个小问题,那就是:在刷新页面后,新主题会失效
出现这个问题的原因:因为没有写入新的 **style**
所以我们只需要在 应用加载后,写入 **style** 即可
那么写入的时机,我们可以放入到 app.vue

  1. <script setup>
  2. import { useStore } from 'vuex'
  3. import { generateNewStyle, writeNewStyle } from '@/utils/theme'
  4. const store = useStore()
  5. generateNewStyle(store.getters.mainColor).then(newStyleText => {
  6. writeNewStyle(newStyleText)
  7. })
  8. </script>