一文搞懂 useDark:Vue 项目中实现深色模式的正确姿势
效果
话不多说,先上效果
高亮效果

暗黑效果

前言
在近期的项目开发中,遇到了一个常见的用户体验需求:实现界面的高亮(亮色)与暗黑主题切换功能。用户通过点击按钮即可自由切换主题,并且系统需记住用户的偏好------将当前主题状态持久化至本地缓存,确保页面刷新或下次访问时仍保持最后一次选择的主题样式。 当前项目基于Vue 3 + Vite
构建,并使用了 Arco Design
作为 UI
组件库。在调研了多种实现方案后,结合社区优秀实践与相关技术文档,我最终选择了使用 VueUse
提供的响应式 API useDark
,配合 SCSS
变量进行主题管理。该方案不仅简洁高效,而且具备良好的可维护性和扩展性,完美契合现代前端开发的需求。
思路
通过useDark
实现,useDark()
的核心机制就是通过在根元素(通常是<html>
或 <body>
)上添加或移除一个特定的 CSS
类名(默认是 .dark
) 来实现主题切换。
useDark() 的工作原理
- 状态管理:
useDark()
内部会创建一个响应式的布尔值isDark
,用于表示当前是否处于暗黑模式。 DOM
操作:当isDark
的值发生变化时(例如,用户点击了切换按钮),useDark()
会自动:
如果
isDark
为true
,则在你指定的目标元素(默认是document.documentElement
,即<html>
标签)上添加.dark
类。 如果isDark
为false
,则从该元素上移除.dark
类。
CSS
样式响应:你编写的CSS/SCSS
样式会利用这个.dark
类的存在与否来应用不同的主题样式。
暗黑主题样式应为class*="dark"

实现步骤
第一步、安装@vueuse/core
查看你们当前项目node_modeles
是否包含@vueuse
,如果没有可进行CDN
引入和npm
安装,我更倾向于使用npm安装方式安装。
-
npm
安装bashnpm i @vueuse/core
-
CDN引入
bash<script src="https://unpkg.com/@vueuse/shared"></script> <script src="https://unpkg.com/@vueuse/core"></script>

第三步、pinia状态管理及localStorage存储
-
在
stores
中创建dark.ts
文件,用于主题状态管理,后期可以添加其他主题。 -
编辑
@/stores/dark.ts
主题状态管理文件,因为你的页面不止有你自己的暗黑主题,还会有arcoDesign
的主题,arcoDesign
提供了切换主题方法,使用即可。typescriptimport { defineStore } from 'pinia' import { Local } from '@/utils/storage' import { nextTick } from 'vue' import type { layoutConfigState } from '@/types/pinid' export const useLayoutConfigStore = defineStore('layoutConfig', { state: (): layoutConfigState => { return { isDark: false, // 黑暗模式 } }, getters: { }, actions: { // 更新状态 updateState(state: layoutConfigState) { // 将传递的值更新到state状态中 this.$patch(state) console.log("我被执行了", state) } } }) nextTick(() => { const layoutConfig = useLayoutConfigStore() console.log("我被执行了", layoutConfig) // 监听状态变化,将状态持久化 layoutConfig.$subscribe((mutation, state) => { // 保存到浏览器的localStorage中 if (state.isDark) { document.body.setAttribute('arco-theme', 'dark') } else { document.body.removeAttribute('arco-theme') } Local.set('layoutConfig', state) }) })
-
编写
utils/storage.ts
缓存管理文件。typescript/** * window.localStorage 浏览器永久缓存 * @method set 设置永久缓存 * @method get 获取永久缓存 * @method remove 移除永久缓存 * @method clear 移除全部永久缓存 */ export const Local = { // 设置永久缓存 set(key: string, val: any) { window.localStorage.setItem(key, JSON.stringify(val || '')); }, // 获取永久缓存 get(key: string) { let json: any = window.localStorage.getItem(key); return JSON.parse(json); }, // 移除永久缓存 remove(key: string) { window.localStorage.removeItem(key); }, // 移除全部永久缓存 clear() { window.localStorage.clear(); }, }; /** * window.sessionStorage 浏览器临时缓存 * @method set 设置临时缓存 * @method get 获取临时缓存 * @method remove 移除临时缓存 * @method clear 移除全部临时缓存 */ export const Session = { // 设置临时缓存 set(key: string, val: any) { // val为undefined保存时,当get时parse转成json对象会失败,加上 || ''解决这个 window.sessionStorage.setItem(key, JSON.stringify(val || '')); }, // 获取临时缓存 get(key: string) { let json: any = window.sessionStorage.getItem(key); // parse 无法对undefined 和 ''进行解析的 return JSON.parse(json); }, // 移除临时缓存 remove(key: string) { window.sessionStorage.removeItem(key); }, // 移除全部临时缓存 clear() { window.sessionStorage.clear(); }, };
第二步、通过开关控制pinia及状态管理
-
引入
useDark
及状态管理文件typescriptimport { useDark } from "@vueuse/core"; import { useLayoutConfigStore } from "../../stores/dark"; const layoutConfig = useLayoutConfigStore();
-
添加主题开关
typescript<a-switch v-model="isDark" @change="changeDark" inline-prompt active-icon="ele-Moon" inactive-icon="ele-Sunny" > <template #checked-icon> <icon-moon-fill /> </template> <template #unchecked-icon> <icon-sun-fill /> </template ></a-switch>
-
监听
isDark
变化,设置对应主题typescript// 会自动监听isDark变化,来设置对应的主题模式 const isDark = useDark({ valueDark: "dark", // 暗黑模式,在 html元素的class属性值 valueLight: "", // 高亮模式,在 html元素的class属性值 initialValue: "dark", // 初始模式: dark|auto ( 高亮模式) });
-
切换主题逻辑实现
typescript// 切换暗黑模式,更新状态值 function changeDark(isDark) { console.log("isDark:", isDark); layoutConfig.isDark = isDark; }
第四步、创建高亮及暗黑.scss文件
-
在
style
中创建一个app.scss
与dark.scss
文件,app.scss
大部分样式用来高亮主题,dark.scss
主要用来暗黑样式。 -
创建
@styles/index.scss
文件,用于引用app.scss
与dark.scss
样式,注意dark.scss
的权重要比app.scss
权重要高,引用顺序相对于靠后。 -
main.ts
引入@/styles/index.scss
样式文件cssimport '@/styles/index.scss';
-
编写
app.scss
样式文件,大多为样式变量,以下是部分样式代码:
css
/* 初始化样式
------------------------------- */
:root {
/* 模态框背景颜色 */
--wyk-modal-bg-color: rgba(23, 23, 26, 0.6);
/*菜单样式开始*/
--wyk-menu-font-color: #000; // 菜单字体颜色
--wyk-menu-bg-color: #fff; // 菜单背景颜色
--wyk-menu-divider-color: #ededed;
/*菜单样式结束*/
/*白天黑夜字体颜色与背景颜色开始*/
--wyk-font-color: #000;
--wyk-bg-color: #fff;
--wyk-grey-font-color: #333;
--wyk-grey-border-color: #d5d5d5;
--wyk-btn-color: #fff;
--wyk-btn-boder-color: #325ab5;
/*白天黑夜字体颜色与背景颜色结束*/
/* home 页面黑夜白天主题添加开始*/
--wyk-home-item-content: #f5f8fe;
/* home 页面黑夜白天主题添加结束*/
/* dataset 数据集页面白天黑夜主题添加 */
--wyk-dataset-bg-colod: #f5f5f5;
--wyk-dataset-title-box: #fff;
/* task management 任务管理背景色*/
--wyk-task-box-bgcolor: #fff;
--wyk-task-bgcolor: #f5f5f5;
--wyk-task-title-color: #000;
--wyk-task-tab-hover-color: #f5f8fe;
/* 日志管理样式*/
--wyk-log-border-color: #dcdfe6;
/*模型管理样式*/
--wyk-model-box-bgcolor: #fff;
--wyk-model-border-color: #dcdfe6;
/*标签管理样式*/
--wyk-label-box-bgcolor: #fff;
--wyk-label-border-color: #dcdfe6;
/* 用户角色样式*/
--wyk-user-box-bgcolor: #fff;
/* 悬浮球弹窗样式 */
--wyk-float-ball-pop-bgcolor: #fff;
--wyk-float-ball-pop-box-shadow: #f2f6fd;
--wyk-float-ball-progress-bgcolor: #f2f6fd;
--wyk-color-theme: #325ab5;
--wyk-color-white: #fff;
// --wyk-color-black: #2b2f38;
--wyk-color-primary: #325ab5;
--wyk-border-color: #f1f2f3;
--wyk-color-hover: #3c3c3c;
--wyk-color-hover-rgba: rgba(0, 0, 0, 0.05);
// 左侧菜单
--wyk-bg-menuMainColor: var(--wyk-menu-bg-color) !important;
--wyk-bg-menuActiveColor: var(--wyk-color-black) !important;
--wyk-bg-menuHoverColor: var(--wyk-color-black) !important;
--wyk-text-menuMainColor: var(--wyk-color-black) !important;
--wyk-text-menuActiveColor: var(--wyk-color-white) !important;
--wyk-text-menuHoverColor: var(--wyk-color-white) !important;
// 头部导航
--wyk-bg-headerBarColor: var(--wyk-color-white) !important;
// 头部右侧图标光标浮动
--wyk-color-user-hover: var(--wyk-color-hover-rgba) !important;
// 边框
--wyk-border-color-light: var(--wyk-border-color) !important;
}
.
.
.
- 编写
dark.scss
样式文件,大多为样式变量,以下是部分暗黑主题样式代码:
css
/* 暗黑样式
--------------------------- */
html[class*="dark"] {
/* 模态框背景颜色 */
--wyk-modal-bg-color: rgba(23, 23, 26, 0.8);
/*菜单样式开始*/
--wyk-menu-font-color: #fff; // 菜单字体颜色
--wyk-menu-bg-color: #333333;
--wyk-menu-divider-color: #fff;
/*菜单样式结束*/
/*白天黑夜字体颜色与背景颜色开始*/
--wyk-font-color: #c0c0c0;
--wyk-bg-color: #333333;
--wyk-scrollbar-color: #333333;
--wyk-grey-font-color: #c0c0c0;
--wyk-grey-border-color: #c0c0c0;
--wyk-btn-color: #404040;
--wyk-btn-boder-color: #404040;
/*白天黑夜字体颜色与背景颜色结束*/
/* home 页面黑夜白天主题添加开始*/
--wyk-home-item-content: #333333;
/* home 页面黑夜白天主题添加结束*/
/* dataset 数据集页面白天黑夜主题添加 */
--wyk-dataset-bg-colod: #333333;
--wyk-dataset-title-box: #333333;
/* task management 任务管理背景色*/
--wyk-task-box-bgcolor: #333333;
--wyk-task-bgcolor: #333333;
--wyk-task-title-color: #c0c0c0;
--wyk-task-tab-hover-color: rgba(245, 248, 254, 0.1);
/* 日志 样式*/
--wyk-log-border-color: #333333;
/*模型管理样式*/
--wyk-model-box-bgcolor: #333;
--wyk-model-border-color: #333;
/*标签管理样式*/
--wyk-label-box-bgcolor: #333;
--wyk-label-border-color: #333;
/* 用户样式*/
--wyk-user-box-bgcolor: #333;
/* 悬浮球弹窗样式 */
--wyk-float-ball-pop-bgcolor: #333;
--wyk-float-ball-pop-box-shadow: #333;
--wyk-float-ball-progress-bgcolor: #423f3f;
--wyk-color-theme: #325ab5;
--wyk-color-white: #fff;
// --wyk-color-black: #2b2f38;
--wyk-color-primary: #325ab5;
--wyk-border-color: #f1f2f3;
--wyk-color-hover: #3c3c3c;
--wyk-color-hover-rgba: rgba(0, 0, 0, 0.05);
// 菜单
--wyk-bg-menuMainColor: var(--wyk-color-black) !important;
--wyk-bg-menuActiveColor: var(--wyk-color-primary) !important;
--wyk-bg-menuHoverColor: #2e436e !important;
--wyk-text-menuMainColor: var(--wyk-color-white) !important;
--wyk-text-menuActiveColor: var(--wyk-color-white) !important;
--wyk-text-menuHoverColor: var(--wyk-color-white) !important;
--wyk-bg-headerBarColor: var(--wyk-color-black) !important;
// 头部右侧图标光标浮
--wyk-color-user-hover: var(--wyk-color-hover-rgba) !important;
// 边框
--wyk-border-color-light: var(--wyk-border-color) !important;
/* wangeditor 富文本编辑器 - css vars
--------------------------- */
--w-e-toolbar-bg-color: var(--el-bg-color) !important;
--w-e-toolbar-color: var(--el-text-color-primary) !important;
// // 工具栏浮动显示
--w-e-toolbar-active-color: var(--el-text-color-primary) !important;
--w-e-toolbar-active-bg-color: var(--el-color-primary-light-9) !important;
--w-e-toolbar-border-color: var(--wyk-border-color) !important;
// // 内容区域
--w-e-textarea-bg-color: var(--el-bg-color) !important;
}
第五步、在页面中使用scss
的var()
方法使用样式变量
css
<style lang="scss" scoped></style>

第六步、app.vue
页面进行主题初始化
因为主题配置都放在了pinia
中和localStorage
中,需要在app.vue
的onMounted
中拿到本地缓存的layoutConfig
主题信息更新到pinia
中,而且还需要将arcoDesign
的主题设置为暗黑主题,arcoDesign
官网有主题切换代码,直接拷贝即可。
typescript
<script setup lang="ts"></script>
<template>
<!-- 不要少了`class="h100"` 高度100%,不然布局的高度无法占满 -->
<div class="h100">
<!-- 路由组件渲染出品 -->
<router-view></router-view>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { Local } from "../src/utils/storage";
import { useLayoutConfigStore } from "../src/stores/dark";
const layoutConfig = useLayoutConfigStore();
onMounted(() => {
// 获取localStorage中的布局配置数据,
const layoutConfigState = Local.get("layoutConfig");
console.log("layoutConfigState-----------", layoutConfigState);
if (layoutConfigState === null) {
layoutConfig.isDark = false;
document.body.removeAttribute("arco-theme");
} else {
if (layoutConfigState.isDark) {
document.body.setAttribute("arco-theme", "dark");
} else {
layoutConfig.isDark = false;
document.body.removeAttribute("arco-theme");
}
}
// 更新到pinia状态中
if (layoutConfigState) layoutConfig.updateState(layoutConfigState);
});
</script>
<style scoped></style>
第七步、查看实现效果

结语
高亮暗黑主题切换的功能到这里就结束了,如果对你有帮助别忘一键三联哦~