Blog
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)

HiuZing

🍑
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)
  • 前端面试题

  • JavaScript

  • Vue2

  • port

  • CSS

  • Node.js

  • JavaScript优化

  • uniapp

  • Mini Program

  • TypeScript

  • 面向对象编程

  • UI组件

  • Plugin

  • Vue3

    • 教程

    • Vue Router

    • API

    • Vuex

    • 实例处理方案

      • 图标处理方案
      • 本地缓存处理方案
      • 响应拦截器处理方案
      • 登录鉴权处理方案
      • 退出登录处理方案
      • 国际化处理方案
      • 动态换肤处理方案
        • 动态换肤实现方案分析
          • 实现方案
        • 方案落地:创建 ThemeSelect 组件
        • 创建 SelectColor 组件
          • 完成 SelectColor 弹窗展示的双向数据绑定
          • 把选中的色值进行本地缓存
        • 方案落地:处理 element-plus 主题变更原理与步骤分析
          • 实现原理:
          • 实现步骤:
        • 方案落地:处理 element-plus 主题变更
        • 方案落地:element-plus 新主题的立即生效
        • 方案落地:自定义主题变更
        • 自定义主题方案总结
      • Screenfull全屏处理方案
      • HeaderSearch 处理方案
      • TagsView处理方案
      • Guide 处理方案
      • Excel 导入处理方案
      • 打印详情处理方案
      • 权限受控处理方案
      • 动态表格处理方案
      • 富文本和markdown处理方案
      • 项目部署处理方案
      • 可视化处理方案
    • 文档

    • 用法

  • 性能优化

  • Axios

  • 状态管理

  • React

  • Mock

  • Icon

  • Template

  • 构建工具

  • 项目规范配置

  • Taro

  • SVG

  • React Native

  • 前端
  • Vue3
  • 实例处理方案
HiuZing
2023-03-15
目录

动态换肤处理方案

动态换肤 在实现 el-menu 的背景色时,不能直接写死,而需要通过一个动态的值进行指定。

前置条件就是:色值不可以写死!

 <el-menu
    :default-active="activeMenu"
    :collapse="!$store.getters.sidebarOpened"
    :background-color="$store.getters.cssVar.menuBg"
    :text-color="$store.getters.cssVar.menuText"
    :active-text-color="$store.getters.cssVar.menuActiveText"
    :unique-opened="true"
    router
  >
1
2
3
4
5
6
7
8
9

在 scss 中,我们可以通过 $变量名:变量值 的方式定义 css 变量 ,然后通过该 css 来去指定某一块 DOM 对应的颜色。

动态换肤实现思路:当大量的 DOM 都依赖这个 css 变量 设置颜色时,只需要改变这个 css 变量 ,那么所有 DOM 的颜色都会发生变化

需要同时处理两个方面的内容:

  1. element-plus 主题
  2. 非 element-plus 主题

# 动态换肤实现方案分析

从原理中我们可以得到以下两个关键信息:

  1. 动态换肤的关键是修改 css 变量 的值
  2. 换肤需要同时兼顾
    1. element-plus
    2. 非 element-plus

# 实现方案

  1. 创建一个组件 ThemeSelect 用来处理修改之后的 css 变量 的值

  2. 根据新值修改 element-plus 主题色

  3. 根据新值修改非 element-plus 主题色

# 方案落地:创建 ThemeSelect 组件

ThemeSelect 组件将由两部分组成:

  1. navbar 中的展示图标
  2. 选择颜色的弹出层

navbar 中的展示图标

创建 components/ThemeSelect/index 组件

<template>
  <!-- 主题图标  
  v-bind:https://v3.cn.vuejs.org/api/instance-properties.html#attrs -->
  <el-dropdown
    v-bind="$attrs"
    trigger="click"
    class="theme"
    @command="handleSetTheme"
  >
    <div>
      <el-tooltip :content="$t('msg.navBar.themeChange')">
        <svg-icon icon="change-theme" />
      </el-tooltip>
    </div>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item command="color">
          {{ $t('msg.theme.themeColorChange') }}
        </el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
  <!-- 展示弹出层 -->
  <div></div>
</template>

<script setup>
const handleSetTheme = command => {}
</script>

<style lang="scss" scoped></style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

在 layout/components/navbar 中进行引用

<div class="right-menu">
      <theme-picker class="right-menu-item hover-effect"></theme-picker>
      
import ThemePicker from '@/components/ThemeSelect/index'
1
2
3
4

# 创建 SelectColor 组件

在有了 ThemeSelect 之后,接下来我们来去处理颜色选择的组件 SelectColor,在这里我们会用到 element 中的 el-color-picker 组件

对于 SelectColor 的处理,我们需要分成两步进行:

  1. 完成 SelectColor 弹窗展示的双向数据绑定

  2. 把选中的色值进行本地缓存

# 完成 SelectColor 弹窗展示的双向数据绑定

创建 components/ThemePicker/components/SelectColor.vue

<template>
  <el-dialog title="提示" :model-value="modelValue" @close="closed" width="22%">
    <div class="center">
      <p class="title">{{ $t('msg.theme.themeColorChange') }}</p>
      <el-color-picker
        v-model="mColor"
        :predefine="predefineColors"
      ></el-color-picker>
    </div>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="closed">{{ $t('msg.universal.cancel') }}</el-button>
        <el-button type="primary" @click="comfirm">{{
          $t('msg.universal.confirm')
        }}</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script setup>
import { defineProps, defineEmits, ref } from 'vue'
defineProps({
  modelValue: {
    type: Boolean,
    required: true
  }
})
const emits = defineEmits(['update:modelValue'])

// 预定义色值
const predefineColors = [
  '#ff4500',
  '#ff8c00',
  '#ffd700',
  '#90ee90',
  '#00ced1',
  '#1e90ff',
  '#c71585',
  'rgba(255, 69, 0, 0.68)',
  'rgb(255, 120, 0)',
  'hsv(51, 100, 98)',
  'hsva(120, 40, 94, 0.5)',
  'hsl(181, 100%, 37%)',
  'hsla(209, 100%, 56%, 0.73)',
  '#c7158577'
]
// 默认色值
const mColor = ref('#00ff00')

/**
 * 关闭
 */
const closed = () => {
  emits('update:modelValue', false)
}
/**
 * 确定
 * 1. 修改主题色
 * 2. 保存最新的主题色
 * 3. 关闭 dialog
 */
const comfirm = async () => {
  // 3. 关闭 dialog
  closed()
}
</script>

<style lang="scss" scoped>
.center {
  text-align: center;
  .title {
    margin-bottom: 12px;
  }
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

在 ThemePicker/index 中使用该组件

<template>
  ...
  <!-- 展示弹出层 -->
  <div>
    <select-color v-model="selectColorVisible"></select-color>
  </div>
</template>

<script setup>
import SelectColor from './components/SelectColor.vue'
import { ref } from 'vue'

const selectColorVisible = ref(false)
const handleSetTheme = command => {
  selectColorVisible.value = true
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 把选中的色值进行本地缓存

  1. vuex
  2. 本地存储

在 constants/index 下新建常量值

// 主题色保存的 key
export const MAIN_COLOR = 'mainColor'
// 默认色值
export const DEFAULT_COLOR = '#409eff'
1
2
3
4

创建 store/modules/theme 模块,用来处理 主题色 相关内容

import { getItem, setItem } from '@/utils/storage'
import { MAIN_COLOR, DEFAULT_COLOR } from '@/constant'
export default {
  namespaced: true,
  state: () => ({
    mainColor: getItem(MAIN_COLOR) || DEFAULT_COLOR
  }),
  mutations: {
    /**
     * 设置主题色
     */
    setMainColor(state, newColor) {
      state.mainColor = newColor
      setItem(MAIN_COLOR, newColor)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

在 store/getters 下指定快捷访问

mainColor: state => state.theme.mainColor
1

在 store/index 中导入 theme

...
import theme from './modules/theme.js'

export default createStore({
  getters,
  modules: {
    ...
    theme
  }
})
1
2
3
4
5
6
7
8
9
10

在 selectColor 中,设置初始色值 和 缓存色值

...
<script setup>
import { defineProps, defineEmits, ref } from 'vue'
import { useStore } from 'vuex'
...
const store = useStore()
// 默认色值
const mColor = ref(store.getters.mainColor)
...
/**
 * 确定
 * 1. 修改主题色
 * 2. 保存最新的主题色
 * 3. 关闭 dialog
 */
const comfirm = async () => {
  // 2. 保存最新的主题色
  store.commit('theme/setMainColor', mColor.value)
  // 3. 关闭 dialog
  closed()
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 方案落地:处理 element-plus 主题变更原理与步骤分析

# 实现原理:

在之前我们分析主题变更的实现原理时,我们说过,核心的原理是:**通过修改 scss 变量 ** 的形式修改主题色完成主题变更

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

# 实现步骤:

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

# 方案落地:处理 element-plus 主题变更

创建 utils/theme 工具类,写入两个方法

/**
 * 写入新样式到 style
 * @param {*} elNewStyle  element-plus 的新样式
 * @param {*} isNewStyleTag 是否生成新的 style 标签
 */
export const writeNewStyle = elNewStyle => {
  
}

/**
 * 根据主色值,生成最新的样式表
 */
export const generateNewStyle =  primaryColor => {
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

那么接下来我们先实现第一个方法 generateNewStyle,在实现的过程中,我们需要安装两个工具类:

  1. rgb-hex (opens new window):转换RGB(A)颜色为十六进制
  2. css-color-function (opens new window):在CSS中提出的颜色函数的解析器和转换器

然后还需要写入一个 颜色转化计算器 formula.json

创建 constants/formula.json (https://gist.github.com/benfrain/7545629)

{
  "shade-1": "color(primary shade(10%))",
  "light-1": "color(primary tint(10%))",
  "light-2": "color(primary tint(20%))",
  "light-3": "color(primary tint(30%))",
  "light-4": "color(primary tint(40%))",
  "light-5": "color(primary tint(50%))",
  "light-6": "color(primary tint(60%))",
  "light-7": "color(primary tint(70%))",
  "light-8": "color(primary tint(80%))",
  "light-9": "color(primary tint(90%))",
  "subMenuHover": "color(primary tint(70%))",
  "subMenuBg": "color(primary tint(80%))",
  "menuHover": "color(primary tint(90%))",
  "menuBg": "color(primary)"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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

import color from 'css-color-function'
import rgbHex from 'rgb-hex'
import formula from '@/constant/formula.json'
import axios from 'axios'

/**
 * 根据主色值,生成最新的样式表
 */
export const generateNewStyle = async primaryColor => {
  const colors = generateColors(primaryColor)
  let cssText = await getOriginalStyle()

  // 遍历生成的样式表,在 CSS 的原样式中进行全局替换
  Object.keys(colors).forEach(key => {
    cssText = cssText.replace(
      new RegExp('(:|\\s+)' + key, 'g'),
      '$1' + colors[key]
    )
  })

  return cssText
}

/**
 * 根据主色生成色值表
 */
export const generateColors = primary => {
  if (!primary) return
  const colors = {
    primary
  }
  Object.keys(formula).forEach(key => {
    const value = formula[key].replace(/primary/g, primary)
    colors[key] = '#' + rgbHex(color.convert(value))
  })
  return colors
}

/**
 * 获取当前 element-plus 的默认样式表
 */
const getOriginalStyle = async () => {
  const version = require('element-plus/package.json').version
  const url = `https://unpkg.com/element-plus@${version}/dist/index.css`
  const { data } = await axios(url)
  // 把获取到的数据筛选为原样式模板
  return getStyleTemplate(data)
}

/**
 * 返回 style 的 template
 */
const getStyleTemplate = data => {
  // element-plus 默认色值
  const colorMap = {
    '#3a8ee6': 'shade-1',
    '#409eff': 'primary',
    '#53a8ff': 'light-1',
    '#66b1ff': 'light-2',
    '#79bbff': 'light-3',
    '#8cc5ff': 'light-4',
    '#a0cfff': 'light-5',
    '#b3d8ff': 'light-6',
    '#c6e2ff': 'light-7',
    '#d9ecff': 'light-8',
    '#ecf5ff': 'light-9'
  }
  // 根据默认色值为要替换的色值打上标记
  Object.keys(colorMap).forEach(key => {
    const value = colorMap[key]
    data = data.replace(new RegExp(key, 'ig'), value)
  })
  return data
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

接下来处理 writeNewStyle 方法:

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

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

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

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

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

# 方案落地:element-plus 新主题的立即生效

到目前我们已经完成了 element-plus 的主题变更,但是当前的主题变更还有一个小问题,那就是:在刷新页面后,新主题会失效

那么出现这个问题的原因,非常简单:因为没有写入新的 style

所以我们只需要在 应用加载后,写入 style 即可

那么写入的时机,我们可以放入到 app.vue 中

<script setup>
import { useStore } from 'vuex'
import { generateNewStyle, writeNewStyle } from '@/utils/theme'

const store = useStore()
generateNewStyle(store.getters.mainColor).then(newStyleText => {
  writeNewStyle(newStyleText)
})
</script>
1
2
3
4
5
6
7
8
9

# 方案落地:自定义主题变更

自定义主题变更相对来说比较简单,因为 自己的代码更加可控。

目前在我们的代码中,需要进行 自定义主题变更 为 menu 菜单背景色

而目前指定 menu 菜单背景色的位置在 layout/components/sidebar/SidebarMenu.vue 中

  <el-menu
    :default-active="activeMenu"
    :collapse="!$store.getters.sidebarOpened"
    :background-color="$store.getters.cssVar.menuBg"
    :text-color="$store.getters.cssVar.menuText"
    :active-text-color="$store.getters.cssVar.menuActiveText"
    :unique-opened="true"
    router
  >
1
2
3
4
5
6
7
8
9

此处的 背景色是通过 getters 进行指定的,该 cssVar 的 getters 为:

cssVar: state => variables,
1

根据当前保存的 mainColor 覆盖原有的默认色值

import variables from '@/styles/variables.scss'
import { MAIN_COLOR } from '@/constant'
import { getItem } from '@/utils/storage'
import { generateColors } from '@/utils/theme'

const getters = {
  ...
  cssVar: state => {
    return {
      ...variables,
      ...generateColors(getItem(MAIN_COLOR))
    }
  },
  ...
}
export default getters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

但是我们这样设定之后,整个自定义主题变更,还存在两个问题:

  1. menuBg 背景颜色没有变化 image-20210925203000626

这个问题是因为咱们的 sidebar 的背景色未被替换,所以我们可以在 layout/index 中设置 sidebar 的 backgroundColor

<sidebar
      id="guide-sidebar"
      class="sidebar-container"
      :style="{ backgroundColor: $store.getters.cssVar.menuBg }"
    />
1
2
3
4
5
  1. 主题色替换之后,需要刷新页面才可响应

这个是因为 getters 中没有监听到 依赖值的响应变化,所以我们希望修改依赖值

在 store/modules/theme 中

...
import variables from '@/styles/variables.scss'
export default {
  namespaced: true,
  state: () => ({
    ...
    variables
  }),
  mutations: {
    /**
     * 设置主题色
     */
    setMainColor(state, newColor) {
      ...
      state.variables.menuBg = newColor
      ...
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在 getters 中

....
const getters = {
 ...
  cssVar: state => {
    return {
      ...state.theme.variables,
      ...generateColors(getItem(MAIN_COLOR))
    }
  },
  ...
}
export default getters
1
2
3
4
5
6
7
8
9
10
11
12

# 自定义主题方案总结

核心原理:修改scss变量来进行实现主题色变化

实现步骤具体情况具体分析

  1. 对于 element-plus:因为 element-plus 是第三方的包,所以它 不是完全可控 的,那么对于这种最简单直白的方案,就是直接拿到它编译后的 css 进行色值替换,利用 style 内部样式表 优先级高于 外部样式表 的特性,来进行主题替换
  2. 对于自定义主题:因为自定义主题是 完全可控 的,所以我们实现起来就轻松很多,只需要修改对应的 scss变量即可
上次更新: 2025/06/23, 07:26:12
国际化处理方案
Screenfull全屏处理方案

← 国际化处理方案 Screenfull全屏处理方案→

最近更新
01
CodePush
06-22
02
打包发布
03-09
03
常用命令
03-09
更多文章>
Theme by Vdoing | Copyright © 2021-2025 WeiXiaojing | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式