一文搞懂 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>
        第七步、查看实现效果

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