likes
comments
collection
share

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

作者站长头像
站长
· 阅读数 66

前言

本系列文章是为了帮助没有直接上手(或上手比较困难)做项目能力的初级前端开发工程师采用 V3 Admin Vite 开源模板来编写业务代码。

本系列文章的视频教程版本地址:B 站(群友好心录制,可能会不同步)

文章目的

本文主要是写给想学习或二开本项目多主题模式的小伙伴。

Begin

效果图

系统目前内置了三种主题模式:默认、黑暗和深蓝,下面我们先欣赏一下部分截图

默认主题

也叫白天、亮色、浅色模式,在没有特殊需求时采用该配色比较通用

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

黑暗模式

也叫黑夜、夜间、暗色、深色、暗黑模式,这种模式现如今越来越流行,也是首要适配的模式

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

深蓝模式

也可以叫科技、大屏模式,这是我们 V3 Admin Vite 区别于其他系统,比较有特色的特有模式!

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

实现思路

基本原理

基本原理非常简单,是通过改变 <html>class 属性值来让预先写好的不同的 css 生效,从而实现主题的切换

举个例子,就好像是:

/** 黑暗模式 */
.dark {
  #app {
    color: #c0c4cc;
  }
  .login-container {
    color: #c0c4cc;
  }
}

/** 深蓝模式 */
.dark-blue {
  #app {
    color: rgba(255, 255, 255, 0.8);
  }
  .login-container {
    color: rgba(255, 255, 255, 0.8);
  }
}

这时候,我们只要挂载 <html class="dark"> 或者 <html class="dark-blue"> 就能实现 id 为 app类名为 login-container 的元素切换不同文字颜色!

但是,将这样一个原理直接落地到一个复杂的项目中时,会非常繁琐,因为:

  1. 真实且复杂的项目中,涉及到需要切换颜色的类名非常多
  2. 同一个主题模式下,颜色(比如文字颜色)很多都是一样的

所以:

  1. .dark.dark-blue 内部的结构非常相似,都有 #app.login-container,我们应该将其抽离出去统一处理
  2. #app.login-container 内部的 color 颜色分别都是一样的,我们应该将其抽离出去统一处理

所以在基本原理的基础上,我们还可以利用 scss 语法抽离出相同的结构和变量,告别大量重复的代码

到这里我们先不讲太深入,但是大家需要记住两个东西:

  1. 我们的基本原理是什么?
  2. 在基本原理的基础上,我们利用 scss 语法干了什么?

ThemeSwitch 组件

在页面顶部,我们能发现这个切换主题组件:

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

它所对应的代码非常简单,在 @/components/ThemeSwitch 目录下:

<script lang="ts" setup>
import { useTheme } from "@/hooks/useTheme"
import { MagicStick } from "@element-plus/icons-vue"

const { themeList, activeThemeName, setTheme } = useTheme()
</script>

<template>
  <el-dropdown trigger="click" @command="setTheme">
    <div>
      <el-tooltip effect="dark" content="主题模式" placement="bottom">
        <el-icon :size="20">
          <MagicStick />
        </el-icon>
      </el-tooltip>
    </div>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item
          v-for="(theme, index) in themeList"
          :key="index"
          :disabled="activeThemeName === theme.name"
          :command="theme.name"
        >
          <span>{{ theme.title }}</span>
        </el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

简单过一下代码,我们就能知道,下拉菜单的数据来对应 v-for="(theme, index) in themeList" 这条指令,指令中被遍历的 themeList 变量又来自于代码开头的这条解构赋值语句: const { themeList, activeThemeName, setTheme } = useTheme()

也就是说,如果我们要修改或新增一个主题,我们应该前往 useTheme 这个 hook 看一看

useTheme

这个处理主题的 hook @/hooks/useTheme.ts,可以仔细读一下:

import { ref, watchEffect } from "vue"
import { getActiveThemeName, setActiveThemeName } from "@/utils/cache/local-storage"

const DEFAULT_THEME_NAME = "normal"
type DefaultThemeName = typeof DEFAULT_THEME_NAME

/** 注册的主题名称, 其中 DefaultThemeName 是必填的 */
export type ThemeName = DefaultThemeName | "dark" | "dark-blue"

interface ThemeList {
  title: string
  name: ThemeName
}

/** 主题列表 */
const themeList: ThemeList[] = [
  {
    title: "默认",
    name: DEFAULT_THEME_NAME
  },
  {
    title: "黑暗",
    name: "dark"
  },
  {
    title: "深蓝",
    name: "dark-blue"
  }
]

/** 正在应用的主题名称 */
const activeThemeName = ref<ThemeName>(getActiveThemeName() || DEFAULT_THEME_NAME)

/** 设置主题 */
const setTheme = (value: ThemeName) => {
  activeThemeName.value = value
}

/** 在 html 根元素上挂载 class */
const setHtmlRootClassName = (value: ThemeName) => {
  document.documentElement.className = value
}

/** 初始化 */
const initTheme = () => {
  // watchEffect 来收集副作用
  watchEffect(() => {
    const value = activeThemeName.value
    setHtmlRootClassName(value)
    setActiveThemeName(value)
  })
}

/** 主题 hook */
export function useTheme() {
  return { themeList, activeThemeName, initTheme, setTheme }
}

简单解答一些特殊点:

  • DEFAULT_THEME_NAME 变量抽离出来是为了防止多处出现硬编码
  • 默认主题的 name 是可以随意更改的,默认取值为 normal
  • 其他主题的 name 虽然是自定义的,但不可以随意更改,比如内置的深蓝主题,就只能是 dark-blue,除非你去 @/styles/theme 目录下同步更改每一处叫 dark-blue 的地方
  • setHtmlRootClassName 方法是用来在 html 根元素上挂载 class 的,比如深蓝主题时就会变成 <html class="dark-blue" ...>
  • 如果你需要新增一个主题选项,那么需要给 ThemeNamethemeList 添加新增的主题名称

除了以上列举的几点外,其余的代码几乎是不用动的,下面我们先来解读一下内置的三种主题,然后大家基本上就可以按照这个规律来新增自己的主题了!

主题样式文件

多主题模式相关的样式文件都放在了 @/styles/theme 目录下:

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

  • register.scss 是用来注册的,或者说用来加载样式文件的,如果你在 @/hooks/useTheme.ts 中新增了主题选项后,还需要在这里注册一下,因为只有切换选项没有对应的样式文件是不行的
  • dark 目录下存放了黑暗主题样式
  • dark-blue 目录下存放了深蓝主题样式
  • 假如你需要新增一个主题,那么需要在 darkdark-blue 同级目录处新增一个你的主题文件夹(注意需要和 hook 里面的主题选项同名)

还记得文章开头我让大家记住两个东西吗:

  1. 我们的基本原理是什么?
  2. 在基本原理的基础上,我们利用 scss 语法干了什么?

是的,对应到我们现在的目录结构的话,darkdark-blue 实际上就是存放该主题下对应变量的,而 core 目录就是我们抽离出来的结构

默认主题

默认主题是最简单的,只需要在页面上像平常一样写样式即可,也就是说,默认主题的样式并没有放在 @/styles/theme 目录下,而是正常放在每个页面对应的 .vue 文件里

黑暗主题

因为 Element Plus 自带有黑暗主题,当我们将 <html class="dark" ...> 设置好时,Element Plus 所有组件的黑暗主题就会生效了,也就是说,我们只需要对没有用到 Element Plus 组件写的页面进行处理就行了!

打开 dark 目录下的 variables.scss 文件可以看见该主题下抽离出来的变量:

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

注意:$theme-name 这个变量需要和前面注册主题时提到的名称保持统一

/** dark 主题下的变量 */

// 主题名称
$theme-name: "dark";
// 主题背景颜色
$theme-bg-color: #151515;
// 主题色
$theme-color: #409eff;
// 默认文字颜色
$font-color: #c0c4cc;
// active 状态下文字颜色
$active-font-color: #fff;
// hover 状态下文字颜色
$hover-color: #fff;
// 边框颜色
$border-color: #303133;

现在我们来看看是如何让这些变量生效的

打开 core 目录中的 index.scss

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

代码如下:

.#{$theme-name} {
  @import "./layouts.scss";
  @import "./login.scss";
  @import "./error-page.scss";
  @import "./element-plus.scss";
  @import "./vxe-table.scss";
  @import "./other.scss";
}

因为 $theme-name: "dark"; 也就是说最终生效的就是这样子(伪代码):

.dark {
  @import "./layouts.scss";
  @import "./login.scss";
  @import "./error-page.scss";
  @import "./element-plus.scss";
  @import "./vxe-table.scss";
  @import "./other.scss";
}

所以,当设置 <html class="dark" ...> 时,上面的 ./layouts.scss./login.scss./error-page.scss./element-plus.scss./vxe-table.scss./other.scss 里面的样式都会生效

我们打开其中一个具体的样式文件看看,比如和登录页相关的 ./login.scss

/** Login 页面相关 */

.login-container {
  background-color: $theme-bg-color;
  color: $font-color;
  .login-card {
    background-color: lighten($theme-bg-color, 4%) !important;
  }
}

可以发现里面全是运用抽离出去的 scss 变量($theme-bg-color$font-color

其他文件也同理!我这里就不再截图了

深蓝主题

这个主题比黑暗主题复杂一点,因为 Element Plus 没有自带深蓝主题,这就需要我们去重写 Element Plus 它自己的样式变量,除此之外,其他内容和黑暗主题一模一样,所以请大家先阅读上一小节内容!

打开 dark-blue 目录:

【V3 Admin Vite】教程八:多主题(动态换肤)模式的实现

会发现 index.scssvariables.scss 两个文件黑暗主题也有,所以我们这小节就不提了,重点看一下 element-plus.csselement-plus.scss 这两个文件

这两个文件功能其实是一样的,都是重写 Element Plus 它自己的样式变量。之所以分别写 css 和 scss 两种,一是因为 Element Plus 支持这两种方式,二是因为社区有人喜欢 scss 方式并提交了 PR,原本我是只提供了 css 方式的

我们用 css 方式为例,看一下是如何重写 Element Plus 原本样式变量的:

/**
 * dark-blue 主题下的 Element Plus CSS 变量
 * 在此查阅所有可自定义的变量:https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss
 * 也可以打开浏览器控制台选择元素,查看要覆盖的变量名
 */

html.dark-blue {
  /** color */
  --el-color-primary: #01efb7bb;
  --el-color-primary-light-3: rgba(1, 147, 127, 0.8133333333);
  --el-color-primary-light-5: rgba(2, 102, 99, 0.8666666667);
  --el-color-primary-light-7: rgba(2, 65, 77, 0.92);
  --el-color-primary-light-8: rgba(2, 49, 68, 0.9466666667);
  --el-color-primary-light-9: rgba(2, 35, 59, 0.9733333333);
  --el-color-primary-dark-2: rgba(1, 167, 128, 0.7866666667);
  --el-color-success: #01efb7bb;
  /** text-color */
  --el-text-color-primary: rgba(255, 255, 255, 0.8);
  --el-text-color-regular: rgba(255, 255, 255, 0.8);
  --el-text-color-secondary: rgba(255, 255, 255, 0.8);
  --el-text-color-placeholder: rgba(255, 255, 255, 0.8);
  --el-text-color-disabled: rgba(255, 255, 255, 0.3);
  /** border-color */
  --el-border-color: #01efb755;
  --el-border-color-light: #01efb755;
  --el-border-color-lighter: #01efb755;
  /** fill-color */
  --el-fill-color: #01efb710;
  --el-fill-color-light: #01efb710;
  --el-fill-color-blank: #031e47;
  /** bg-color */
  --el-bg-color: #021633;
  --el-bg-color-overlay: #031e47;
  /** mask */
  --el-mask-color: rgba(0, 0, 0, 0.5);
}

/** button */
html.dark-blue .el-button {
  --el-button-disabled-text-color: rgba(255, 255, 255, 0.5);
}

注意命名:html.dark-blue

还是比较简单粗暴的,直接把 Element Plus 提供的 CSS 变量重新赋值即可。这样就能实现 Element Plus 组件的深蓝主题了!

新增主题

如果你阅读完前文,应该是能够新增主题的,这里我再简单总结一下步骤:

  1. 假如你要新增一个红色主题
  • 命名为 red
  1. 你需要前往以下地方注册新的主题
  • src/hooks/useTheme.ts
  • src/styles/theme/register.scss
  1. 创建你的主题样式文件
  • src/styles/theme/red/index.scss
  • src/styles/theme/red/variables.scss
  • src/styles/theme/red/element-plus.css

这三份文件可以直接复制深蓝主题下的文件,然后进行修改即可

End

本系列所有手摸手教程

V3 Admin Vite 相关链接

转载自:https://juejin.cn/post/7268123683222257701
评论
请登录