上周研究了 Vben Admin 的环境变量和权限的设计,现在项目已经在用了还在搭建页面结构阶段,在主题这需要修改遇到了疑惑所以就把这部分的实现也看看。
疑问点
本部分主要分析主题相关的配置、主题切换的逻辑已经怎么定义修改主题。
- 主题是怎么切换的逻辑是什么?
- 怎么定义主题需要修改哪些配置?
主题定义
点开设置里,可以设置 主题、系统主题、顶栏主题、菜单主题 先来看看配置订单在那。
可用的颜色列表的配置。
// src/settings/designSetting.ts
// app theme preset color
export const APP_PRESET_COLOR_LIST: string[] = [
/* ... */
];
// header preset color
export const HEADER_PRESET_BG_COLOR_LIST: string[] = [
/* ... */
];
// sider preset color
export const SIDE_BAR_BG_COLOR_LIST: string[] = [
/* ... */
];
和主题相关的项目配置,主题变化的时候会更新这些值。
// src/settings/projectSetting.ts
const setting = {
// 项目主题色
themeColor: primaryColor,
// 网站灰色模式
grayMode: false,
// 色弱模式
colorWeak: false,
// 头部配置
headerSetting: {
// 背景色
bgColor: '#ffffff',
// 主题
theme: MenuThemeEnum.LIGHT,
}
// 菜单配置
menuSetting: {
// 背景色
bgColor: '#273352',
// 菜单主题
theme: MenuThemeEnum.DARK
}
}
主题插件配置
在分析实现之前先看一下 vite-plugin-theme 插件,因为基于此实现的。
此插件主要做了两个事情:加载 ant-design-vue 的暗黑主题配置和把一个颜色值替换成另一个颜色值。
基于 ant-design-vue 的官网文档定制主题-使用暗黑主题 中提供的 getThemeVariables 实现暗黑主题再额外做一些定制。
// build/generate/generateModifyVars.ts
import { getThemeVariables } from 'ant-design-vue/dist/theme';
export function generateModifyVars(dark = false) {
const modifyVars = getThemeVariables({ dark });
return {
...modifyVars
};
}
// build/vite/plugin/theme
import { antdDarkThemePlugin } from 'vite-plugin-theme';
export function configThemePlugin() {
const plugin = [
antdDarkThemePlugin({
darkModifyVars: {
...generateModifyVars(true)
}
});
]
return plugin
}
还有一个配置是把一个颜色值替换成另一个颜色值,源码中处理的比较复杂用一个简化的例子来看。假设现有一个样式定义。
.test {
color: #e72528;
}
定义插件并设置匹配需要修改的颜色。
// build/vite/plugin/theme
import { viteThemePlugin } from 'vite-plugin-theme';
export function configThemePlugin() {
const plugin = [
viteThemePlugin({
// 匹配需要修改的颜色
colorVariables: ["#e72528"]
});
]
return plugin
}
然后实现一个函数用于替换样式表的颜色值。
import { replaceStyleVariables } from "vite-plugin-theme/es/client";
export async function changeThemeColor(color: string) {
return await replaceStyleVariables({
colorVariables: [color]
});
}
当调用 changeThemeColor("#1d1b1b") 最后实现的效果如下。
.test {
color: #e72528;
}
/* 多一个样式,覆盖之前的 */
.test {
color: #e72528;
}
主题切换处理
在主题切换的时候发现都是经过一个函数处理,那么重点看这个函数怎么处理的即可。
// src/layouts/default/setting/SettingDrawer.tsx
function renderHeaderTheme() {
// ...
baseHandler(event, value);
}
function renderSiderTheme() {
// ...
baseHandler(event, value);
}
function renderMainTheme() {
// ...
baseHandler(event, value);
}
在每个分支调用不同的函数进行单独的处理,下面看看每个函数的实现。
// src/layouts/default/setting/handler.ts
export function baseHandler(event: HandlerEnum, value: any) {
const appStore = useAppStore();
// 处理设置类型
const config = handler(event, value);
// 更新项目配置
appStore.setProjectConfig(config);
}
export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConfig> {
const appStore = useAppStore();
const { getThemeColor, getDarkMode } = useRootSetting();
switch (event) {
// 更新系统颜色
case HandlerEnum.CHANGE_THEME_COLOR:
if (getThemeColor.value === value) {
return {};
}
changeTheme(value);
return { themeColor: value };
// 更新系统主题
case HandlerEnum.CHANGE_THEME:
if (getDarkMode.value === value) {
return {};
}
updateDarkTheme(value);
return {};
// 更新菜单主题
case HandlerEnum.MENU_THEME:
updateSidebarBgColor(value);
return { menuSetting: { bgColor: value } };
// 更新顶栏主题
case HandlerEnum.HEADER_THEME:
updateHeaderBgColor(value);
return { headerSetting: { bgColor: value } };
}
}
系统主题
调用 changeTheme 更新系统主题就是利用上面替换样式表的颜色值方式实现的,配置方式同上。
// src/logics/theme/index.ts
export async function changeTheme(color: string) {
const colors = generateColors({
mixDarken,
mixLighten,
tinycolor,
color
});
return await replaceStyleVariables({
colorVariables: [...getThemeColors(color), ...colors]
});
}
默认主题和黑暗主题
调用 updateDarkTheme 更新暗黑主题,通过改变 html 标签的 data-theme 属性来进行黑暗主题切换。
// src/logics/theme/dark.ts
export async function updateDarkTheme(mode: string | null = "light") {
const htmlRoot = document.getElementById("htmlRoot");
if (mode === "dark") {
if (import.meta.env.PROD && !darkCssIsReady) {
await loadDarkThemeCss();
}
htmlRoot.setAttribute("data-theme", "dark");
} else {
htmlRoot.setAttribute("data-theme", "light");
}
}
使用示例列子。
[data-theme="dark"] {
/* 黑暗主题主题时的样式 */
}
[data-theme="light"] {
/* 亮色主题主题时的样式 */
}
顶栏主题和菜单主题
顶栏和菜单样式是使用 css 函数 var 获取样式变量,并更新 html 属性上的变量值实现的。
// src/logics/theme/updateBackground.ts
export function updateHeaderBgColor(color) {
setCssVar("--header-bg-color", color);
// ...
}
export function updateSidebarBgColor(color) {
setCssVar("--sider-dark-bg-color", color);
// ...
}
使用示例列子。
html {
--header-bg-color: #394664;
--sider-dark-bg-color: #273352;
}
.ant-layout-sider-dark {
background-color: var(--sider-dark-bg-color);
}
.vben-layout-header--dark {
background-color: var(--header-bg-color);
}
色弱模式和灰色模式
还有两个主题模式是整体改变项目颜色,使用滤镜实现。
// src/logics/theme/updateColorWeak.ts
export function updateColorWeak(colorWeak: boolean) {
toggleClass(colorWeak, "color-weak", document.documentElement);
}
// src/logics/theme/updateGrayMode.ts
export function updateGrayMode(gray: boolean) {
toggleClass(gray, "gray-mode", document.documentElement);
}
.color-weak {
filter: invert(80%);
}
.gray-mode {
filter: grayscale(100%);
filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
}
总结
没有总结了,现有的 Vben Admin 常用的情况已经处理好了新写组件的时候应尽量使用定义好的变量,善用现有的主题规则。