
本文详细介绍如何在 Vue3 Element Plus 项目中实现完整的深色主题切换功能包括主题配置 Store、CSS 变量方案、组件样式深度适配等核心技术。默认 Dark 模式一键切换体验极佳## 效果预览先上最终效果本项目实现了完整的深色主题切换功能- **一键切换** - 点击顶部太阳/月亮图标即可切换主题- **默认 Dark** - 默认开启深色模式更符合开发者习惯- **持久化存储** - 主题选择自动保存刷新页面不丢失- ✨ **平滑过渡** - 0.3s 过渡动画切换流畅自然- **完整适配** - 50 Element Plus 组件深色样式完美适配## 技术选型### 为什么选择 CSS 变量方案在实现深色主题时主要有三种方案| 方案 | 优点 | 缺点 ||------|------|------|| **CSS 变量** | 性能好、易维护、支持动态切换 | 需要手动定义变量 || **SCSS 变量** | 编译时处理、代码简洁 | 不支持运行时切换 || **Element Plus 内置** | 开箱即用 | 自定义程度低 |**最终选择CSS 变量 手动适配**理由1. 完全控制每个组件的深色样式2. 支持运行时动态切换3. 性能最优无额外 JS 开销4. 易于扩展和维护## 核心实现### 一、主题配置 Store使用 Pinia 创建主题状态管理javascript// src/store/theme.jsimport { defineStore } from piniaimport { ref, watch } from vueexport const useThemeStore defineStore(theme, () {const isDark ref(true) // 默认 Dark 主题// 监听主题变化并应用到 HTMLwatch(isDark,(newVal) {if (newVal) {document.documentElement.classList.add(dark)document.documentElement.setAttribute(data-theme, dark)} else {document.documentElement.classList.remove(dark)document.documentElement.setAttribute(data-theme, light)}// 保存到 localStoragelocalStorage.setItem(theme, newVal ? dark : light)},{ immediate: true })// 从 localStorage 恢复主题const initTheme () {const savedTheme localStorage.getItem(theme)if (savedTheme) {isDark.value savedTheme dark}}const toggleTheme () {isDark.value !isDark.value}return {isDark,initTheme,toggleTheme}})**核心要点**- 使用 watch 监听主题变化自动应用到 HTML 元素- 使用 localStorage 持久化用户选择- immediate: true 确保初始化时立即应用主题### 二、初始化主题在 main.js 中初始化主题javascript// src/main.jsimport { createApp } from vueimport { createPinia } from piniaimport { useThemeStore } from ./store/themeconst app createApp(App)const pinia createPinia()// 初始化主题const themeStore useThemeStore(pinia)themeStore.initTheme()app.use(pinia)app.mount(#app)### 三、CSS 变量定义定义浅色和深色主题的 CSS 变量scss// src/assets/main.scss/* CSS 变量 - 浅色主题 */:root {--bg-color: #f0f2f5;--header-bg: #ffffff;--text-color: #303133;--text-color-secondary: #606266;--border-color: #e6e6e6;--card-bg: #ffffff;--sidebar-bg: #304156;}/* CSS 变量 - 深色主题 */html.dark {--bg-color: #141414;--header-bg: #1f1f1f;--text-color: #e5e5e5;--text-color-secondary: #a6a6a6;--border-color: #424242;--card-bg: #1f1f1f;--sidebar-bg: #0d0d0d;}**变量命名规范**- --bg-color - 页面背景色- --header-bg - 头部背景色- --text-color - 主要文字颜色- --text-color-secondary - 次要文字颜色- --border-color - 边框颜色- --card-bg - 卡片背景色### 四、主题切换按钮在布局组件中添加主题切换按钮vue!-- src/layouts/MainLayout.vue --templateel-header classheaderdiv classheader-right!-- 主题切换按钮 --el-buttonlinkclasstheme-toggleclicktoggleTheme:titlethemeStore.isDark ? 切换到浅色主题 : 切换到深色主题el-icon :size20component :isthemeStore.isDark ? Sunny : Moon //el-icon/el-button/div/el-header/templatescript setupimport { useThemeStore } from /store/themeconst themeStore useThemeStore()const toggleTheme () {themeStore.toggleTheme()}/script**图标选择**- 深色模式 → 显示太阳图标Sunny→ 点击切换到浅色- 浅色模式 → 显示月亮图标Moon→ 点击切换到深色### 五、组件样式深度适配#### 1. 基础组件适配scss/* 深色主题覆盖样式 */html.dark {background-color: var(--bg-color);color: var(--text-color);}html.dark .el-header {background-color: var(--header-bg);border-bottom-color: var(--border-color);color: var(--text-color);}html.dark .el-main {background-color: var(--bg-color);}html.dark .el-card {background-color: var(--card-bg);border-color: var(--border-color);}html.dark .el-card__header {border-bottom-color: var(--border-color);color: var(--text-color);}#### 2. 表格组件适配scsshtml.dark .el-table {--el-table-bg-color: var(--card-bg);--el-table-tr-bg-color: var(--card-bg);--el-table-header-bg-color: var(--card-bg);--el-table-text-color: var(--text-color);--el-table-header-text-color: var(--text-color);--el-table-row-hover-bg-color: rgba(255, 255, 255, 0.05);--el-table-border-color: var(--border-color);}html.dark .el-table--striped .el-table__body tr.el-table__row--striped td {background-color: rgba(255, 255, 255, 0.02);}#### 3. 表单组件适配scsshtml.dark .el-input__wrapper {background-color: rgba(255, 255, 255, 0.05);box-shadow: 0 0 0 1px var(--border-color) inset;}html.dark .el-input__inner {color: var(--text-color);}html.dark .el-textarea__inner {background-color: rgba(255, 255, 255, 0.05);color: var(--text-color);}html.dark .el-select-dropdown {background-color: var(--card-bg);border-color: var(--border-color);}html.dark .el-select-dropdown__item {color: var(--text-color);}html.dark .el-select-dropdown__item:hover {background-color: rgba(255, 255, 255, 0.05);}#### 4. Tabs 选项卡适配scsshtml.dark .el-tabs--border-card {background-color: var(--card-bg);border-color: var(--border-color);}html.dark .el-tabs__header {background-color: rgba(255, 255, 255, 0.05);border-bottom-color: var(--border-color);}html.dark .el-tabs__item {color: var(--text-color-secondary);border-color: var(--border-color);}html.dark .el-tabs__item.is-active {color: var(--el-color-primary);background-color: rgba(64, 158, 255, 0.1);}html.dark .el-tabs__item:hover {color: var(--text-color);}#### 5. Descriptions 描述列表适配scsshtml.dark .el-descriptions {background-color: var(--card-bg) !important;}html.dark .el-descriptions__title {color: #ffffff !important;font-weight: 600;font-size: 16px;}html.dark .el-descriptions__label {color: #a6a6a6 !important;font-weight: 500;background-color: rgba(255, 255, 255, 0.02) !important;}html.dark .el-descriptions__content {color: #e5e5e5 !important;background-color: rgba(255, 255, 255, 0.02) !important;}html.dark .el-descriptions__cell {border-color: var(--border-color) !important;}#### 6. 日期选择器适配scss/* 日期时间选择器深色主题 */html.dark .el-picker-panel {background-color: var(--card-bg) !important;border-color: var(--border-color) !important;color: var(--text-color) !important;}html.dark .el-picker-panel__header {color: var(--text-color) !important;border-bottom-color: var(--border-color) !important;}html.dark .el-picker-panel__header-label {color: var(--text-color) !important;}html.dark .el-date-table th {color: var(--text-color-secondary) !important;border-bottom-color: var(--border-color) !important;}html.dark .el-date-table td {color: var(--text-color) !important;}html.dark .el-date-table td.selected,html.dark .el-date-table td.current {background-color: var(--el-color-primary) !important;color: #ffffff !important;}html.dark .el-date-table td.today {color: var(--el-color-primary) !important;}html.dark .el-date-table td:hover:not(.disabled) {background-color: rgba(64, 158, 255, 0.2) !important;}html.dark .el-time-panel {background-color: var(--card-bg) !important;border-color: var(--border-color) !important;}html.dark .el-time-spinner__item {color: var(--text-color) !important;}html.dark .el-time-spinner__item.active {color: var(--el-color-primary) !important;font-weight: bold;}#### 7. 分页组件适配scss/* 分页组件深色主题 */html.dark .el-pagination {--el-pagination-text-color: var(--text-color) !important;--el-pagination-bg-color: var(--card-bg) !important;--el-pagination-button-disabled-bg-color: rgba(255, 255, 255, 0.05) !important;--el-pagination-button-disabled-color: var(--text-color-secondary) !important;}html.dark .el-pagination__total,html.dark .el-pagination__jump {color: var(--text-color-secondary) !important;}html.dark .el-pager li {background-color: rgba(255, 255, 255, 0.05) !important;color: var(--text-color) !important;border-color: var(--border-color) !important;}html.dark .el-pager li:hover {color: var(--el-color-primary) !important;}html.dark .el-pager li.is-active {background-color: var(--el-color-primary) !important;color: #ffffff !important;border-color: var(--el-color-primary) !important;}html.dark .el-pagination button {background-color: rgba(255, 255, 255, 0.05) !important;color: var(--text-color) !important;border-color: var(--border-color) !important;}html.dark .el-pagination button:hover {color: var(--el-color-primary) !important;border-color: var(--el-color-primary) !important;}html.dark .el-pagination button:disabled {background-color: rgba(255, 255, 255, 0.02) !important;color: var(--text-color-secondary) !important;cursor: not-allowed;}#### 8. 统计卡片文字调亮scss/* 深色主题下统计卡片数值文字调亮 */html.dark .stat-value {color: #ffffff !important;}html.dark .stat-label {color: #a6a6a6 !important;}### 六、完整组件适配列表已完整适配的 Element Plus 组件| 组件类型 | 组件名称 | 适配状态 ||---------|---------|---------|| **布局类** | Container | ✅ || **布局类** | Header | ✅ || **布局类** | Aside/Sidebar | ✅ || **布局类** | Main | ✅ || **卡片类** | Card | ✅ || **表格类** | Table | ✅ || **表格类** | Pagination | ✅ || **表单类** | Input | ✅ || **表单类** | Textarea | ✅ || **表单类** | Select | ✅ || **表单类** | DatePicker | ✅ || **表单类** | TimePicker | ✅ || **表单类** | Checkbox | ✅ || **表单类** | Form | ✅ || **导航类** | Tabs | ✅ || **导航类** | Breadcrumb | ✅ || **导航类** | Menu | ✅ || **数据展示** | Descriptions | ✅ || **数据展示** | Statistic | ✅ || **数据展示** | Badge | ✅ || **数据展示** | Tag | ✅ || **反馈类** | Dialog | ✅ || **反馈类** | Message | ✅ || **导航类** | Dropdown | ✅ || **导航类** | Divider | ✅ |## 技术亮点### 1. CSS 变量 !important 策略对于 Element Plus 组件的样式覆盖使用 !important 确保优先级scsshtml.dark .el-descriptions__title {color: #ffffff !important;font-weight: 600;}### 2. 组件级联选择器使用更具体的选择器确保样式准确应用scsshtml.dark .el-pagination__jump .el-input__wrapper {background-color: rgba(255, 255, 255, 0.05) !important;box-shadow: 0 0 0 1px var(--border-color) inset !important;}### 3. 状态伪类完整覆盖覆盖所有交互状态scsshtml.dark .el-date-table td:hover:not(.disabled) {background-color: rgba(64, 158, 255, 0.2) !important;}html.dark .el-date-table td.disabled {color: var(--text-color-secondary) !important;opacity: 0.5;}### 4. 平滑过渡动画在 HTML 元素上添加过渡效果scsshtml, body {transition: background-color 0.3s, color 0.3s;}## 避坑指南### 坑 1Element Plus 内置变量覆盖Element Plus 使用 CSS 变量需要同时覆盖组件变量scss// 错误写法html.dark .el-table {background-color: var(--card-bg);}// 正确写法html.dark .el-table {--el-table-bg-color: var(--card-bg);--el-table-text-color: var(--text-color);}### 坑 2样式优先级问题使用 !important 或更具体的选择器scss// 优先级不够html.dark .el-descriptions__title {color: #ffffff;}// 使用 !importanthtml.dark .el-descriptions__title {color: #ffffff !important;}### 坑 3弹出层样式丢失弹出层Picker、Select 等挂载在 body 下需要全局样式scss// 确保样式在全局 SCSS 文件中html.dark .el-picker-panel {background-color: var(--card-bg) !important;}### 坑 4图标切换逻辑图标切换要根据当前主题状态vueel-iconcomponent :isthemeStore.isDark ? Sunny : Moon //el-icon**逻辑**- 当前是 Dark → 显示 Sunny点击切换到 Light- 当前是 Light → 显示 Moon点击切换到 Dark## 性能优化### 1. 使用 CSS 变量CSS 变量在浏览器层面处理性能最优scss:root {--text-color: #303133;}html.dark {--text-color: #e5e5e5;}.element {color: var(--text-color); // 浏览器自动处理}### 2. 避免频繁 DOM 操作使用 class 切换而非内联样式javascript// 推荐document.documentElement.classList.add(dark)// 不推荐document.documentElement.style.backgroundColor #141414### 3. 使用 watch immediate初始化时立即应用主题避免闪烁javascriptwatch(isDark,(newVal) {// 应用主题},{ immediate: true } // 关键配置)## 扩展建议### 1. 添加更多主题可以扩展为多主题系统javascriptconst themes {dark: { ... },light: { ... },blue: { ... },green: { ... }}### 2. 系统主题跟随使用 prefers-color-scheme 媒体查询javascriptconst prefersDark window.matchMedia((prefers-color-scheme: dark))if (!localStorage.getItem(theme)) {isDark.value prefersDark.matches}### 3. 主题配置面板添加主题配置面板支持用户自定义颜色vueel-color-picker v-modelcustomColors.primary /**作者林宏权**公众号林宏权**觉得有用** 点赞 收藏 ⭐ 转发 三连支持一下