From ccb436664b00f13f5990eebc4038c2b2098667c5 Mon Sep 17 00:00:00 2001 From: v6ole Date: Mon, 13 Jul 2026 08:42:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Element=20Plus=E6=9A=97=E8=89=B2CSS?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E8=A6=86=E7=9B=96=20+=20=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E9=A1=B5=E5=AF=B9=E7=BB=8F=E7=90=86=E5=BC=80=E6=94=BE(?= =?UTF-8?q?=E4=BB=85=E4=B8=BB=E9=A2=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App.vue: 新增 html.dark --el-* 变量全覆盖(30+变量),修复所有select/input/dropdown/table/pagination等组件暗色背景 - DesktopLayout: 设置页对所有角色开放,用户管理仍限支局长 - Settings: 经理只显示主题面板,其他6个管理模块仅支局长可见 Co-Authored-By: Claude --- .../superpowers/plans/2026-07-13-dark-mode.md | 454 ++++++++++++++++++ frontend/src/App.vue | 43 ++ frontend/src/components/DesktopLayout.vue | 4 +- frontend/src/views/desktop/Settings.vue | 8 +- 4 files changed, 505 insertions(+), 4 deletions(-) create mode 100644 docs/superpowers/plans/2026-07-13-dark-mode.md diff --git a/docs/superpowers/plans/2026-07-13-dark-mode.md b/docs/superpowers/plans/2026-07-13-dark-mode.md new file mode 100644 index 0000000..52308fe --- /dev/null +++ b/docs/superpowers/plans/2026-07-13-dark-mode.md @@ -0,0 +1,454 @@ +# 暗黑模式 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** 为企迹系统新增双选暗黑模式:用户在设置页分别选择亮色/暗色偏好主题,顶部栏一键切换亮↔暗。 + +**Architecture:** 扩展 theme store 支持 mode(light/dark) + lightTheme + darkTheme 三字段,新增 minimal-dark/tech-dark 两套 CSS 变量,Element Plus 通过 `html.dark` class 适配暗色。 + +**Tech Stack:** Vue 3 + Pinia + TypeScript + Element Plus + CSS Custom Properties + +## Global Constraints + +- 四大主题: editorial(已有), light(已有), minimal-dark(新增), tech-dark(新增) +- localStorage 持久化: mode, lightTheme, darkTheme 三项 +- 暗色模式时 `` 同时设置 `data-theme` 和 `class="dark"` +- 移动端暂不加主题切换(仅 PC 端顶部栏按钮) +- 保持现有 editorial/light 的 CSS 变量不变 + +--- + +### Task 1: 改造 Theme Store + +**Files:** +- Modify: `frontend/src/stores/theme.ts` + +**Interfaces:** +- Produces: `useThemeStore()` with `mode`, `lightTheme`, `darkTheme`, `effectiveTheme`, `toggleMode()`, `setLightTheme(t)`, `setDarkTheme(t)`, `applyTheme(t)` +- Theme type: `'editorial' | 'light' | 'minimal-dark' | 'tech-dark'` + +- [ ] **Step 1: 重写 theme store** + +```typescript +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export type Theme = 'editorial' | 'light' | 'minimal-dark' | 'tech-dark' +type Mode = 'light' | 'dark' + +export const themeLabels: Record = { + editorial: '编辑风', + light: '极简亮色', + 'minimal-dark': '极简深色', + 'tech-dark': '科技深色', +} + +export const lightThemes: Theme[] = ['editorial', 'light'] +export const darkThemes: Theme[] = ['minimal-dark', 'tech-dark'] + +export const useThemeStore = defineStore('theme', () => { + const mode = ref((localStorage.getItem('theme-mode') as Mode) || 'light') + const lightTheme = ref((localStorage.getItem('theme-light') as Theme) || 'editorial') + const darkTheme = ref((localStorage.getItem('theme-dark') as Theme) || 'tech-dark') + + const effectiveTheme = computed(() => { + return mode.value === 'light' ? lightTheme.value : darkTheme.value + }) + + function applyTheme(theme: Theme, isDark: boolean) { + document.documentElement.setAttribute('data-theme', theme) + if (isDark) { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + } + + function apply() { + applyTheme(effectiveTheme.value, mode.value === 'dark') + } + + function setLightTheme(theme: Theme) { + lightTheme.value = theme + localStorage.setItem('theme-light', theme) + if (mode.value === 'light') apply() + } + + function setDarkTheme(theme: Theme) { + darkTheme.value = theme + localStorage.setItem('theme-dark', theme) + if (mode.value === 'dark') apply() + } + + function toggleMode() { + mode.value = mode.value === 'light' ? 'dark' : 'light' + localStorage.setItem('theme-mode', mode.value) + apply() + } + + // Apply on init + apply() + + return { mode, lightTheme, darkTheme, effectiveTheme, setLightTheme, setDarkTheme, toggleMode } +}) +``` + +- [ ] **Step 2: Commit** + +```bash +git add frontend/src/stores/theme.ts +git commit -m "feat: 改造 theme store 支持暗黑模式双选架构" +``` + +--- + +### Task 2: 新增两套暗色 CSS 变量 + +**Files:** +- Modify: `frontend/src/App.vue` (在 `:root[data-theme="light"]` 块之后添加两个新块) + +**Interfaces:** +- Consumes: Theme type `'minimal-dark'`, `'tech-dark'` from store +- Produces: CSS variables for both dark themes + Element Plus dark overrides + +- [ ] **Step 1: 添加 minimal-dark 主题 CSS** + +在 `:root[data-theme="light"]` 块结束后(第178行 `}` 之后),插入: + +```css +/* ═══════════════════════════════════════════════════════════ + THEME: Minimal Dark — 极简深色 + ═══════════════════════════════════════════════════════════ */ + +:root[data-theme="minimal-dark"] { + /* ── Core palette ── */ + --ink: #E5E5E5; + --ink-light: #CCCCCC; + --ink-dark: #FFFFFF; + --vermilion: #F87171; + --vermilion-light: #FCA5A5; + --vermilion-dark: #EF4444; + --gold: #A78BFA; + --gold-light: #C4B5FD; + --gold-dark: #8B5CF6; + --paper: #1A1A1A; + --paper-dark: #141414; + --surface: #2D2D2D; + --sage: #4ADE80; + --amber: #FBBF24; + --warm-gray: #999999; + --warm-border: #3A3A3A; + + /* ── Semantic tokens ── */ + --c-primary: var(--ink); + --c-primary-light: var(--ink-light); + --c-primary-bg: #262626; + --c-accent: var(--vermilion); + --c-accent-light: var(--vermilion-light); + --c-gold: var(--gold); + --c-success: var(--sage); + --c-warning: var(--amber); + --c-danger: var(--vermilion); + --c-info: #60A5FA; + --c-text: #E5E5E5; + --c-text-secondary: #999999; + --c-text-muted: #666666; + --c-bg: var(--paper); + --c-bg-card: var(--surface); + --c-border: var(--warm-border); + + /* ── Effects ── */ + --shadow-sm: 0 1px 2px rgba(0,0,0,0.20); + --shadow: 0 1px 3px rgba(0,0,0,0.30); + --shadow-md: 0 2px 6px rgba(0,0,0,0.40); + --shadow-lg: 0 4px 12px rgba(0,0,0,0.50); + --radius: 0px; + --radius-sm: 0px; + --radius-xs: 0px; + --transition: 0.18s cubic-bezier(0.4, 0, 0.2, 1); + + /* ── Sidebar — dark minimal ── */ + --sidebar-bg: #141414; + --sidebar-color: #E5E5E5; + --sidebar-accent: var(--gold); + --sidebar-nav-text: #777777; + --sidebar-nav-text-hover: #CCCCCC; + --sidebar-nav-text-active: #FFFFFF; + --sidebar-nav-bg-hover: rgba(255,255,255,0.04); + --sidebar-nav-bg-active: rgba(255,255,255,0.06); + --sidebar-nav-border-active: var(--gold); + --sidebar-divider: #2A2A2A; + --sidebar-brand-color: #FFFFFF; + --sidebar-brand-sub-color: #666666; + --sidebar-group-label-color: #555555; + --sidebar-footer-role-color: var(--gold); + --sidebar-footer-name-color: #999999; + + /* ── Mobile page accent ── */ + --page-accent-color: rgba(167,139,250,0.06); + + /* ── Manager chip tags ── */ + --mgr-chip-text: #E5E5E5; + + /* ── Fonts — sans-serif minimal ── */ + --font-body: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif; + --font-heading: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif; + --font-mono: 'JetBrains Mono', monospace; +} +``` + +- [ ] **Step 2: 添加 tech-dark 主题 CSS** + +在 minimal-dark 块之后插入: + +```css +/* ═══════════════════════════════════════════════════════════ + THEME: Tech Dark — 科技深色 (编辑风暗色版) + ═══════════════════════════════════════════════════════════ */ + +:root[data-theme="tech-dark"] { + /* ── Core palette ── */ + --ink: #C8D6E5; + --ink-light: #A0B4CC; + --ink-dark: #E8EEF4; + --vermilion: #F26B6B; + --vermilion-light: #F59898; + --vermilion-dark: #E55555; + --gold: #5B8DEF; + --gold-light: #7BA5F5; + --gold-dark: #4070D0; + --paper: #0B1120; + --paper-dark: #080C18; + --surface: #111B2E; + --sage: #4EC9B0; + --amber: #E5C07B; + --warm-gray: #6B7A8D; + --warm-border: #1E2D45; + + /* ── Semantic tokens ── */ + --c-primary: var(--ink); + --c-primary-light: var(--ink-light); + --c-primary-bg: #131D30; + --c-accent: var(--vermilion); + --c-accent-light: var(--vermilion-light); + --c-gold: var(--gold); + --c-success: var(--sage); + --c-warning: var(--amber); + --c-danger: var(--vermilion); + --c-info: #5B8DEF; + --c-text: #C8D6E5; + --c-text-secondary: #6B7A8D; + --c-text-muted: #455570; + --c-bg: var(--paper); + --c-bg-card: var(--surface); + --c-border: var(--warm-border); + + /* ── Effects ── */ + --shadow-sm: 0 1px 2px rgba(0,0,0,0.25); + --shadow: 0 2px 6px rgba(0,0,0,0.35); + --shadow-md: 0 6px 18px rgba(0,0,0,0.45); + --shadow-lg: 0 12px 36px rgba(0,0,0,0.55); + --radius: 2px; + --radius-sm: 2px; + --radius-xs: 2px; + --transition: 0.22s cubic-bezier(0.4, 0, 0.2, 1); + + /* ── Sidebar — dark tech ── */ + --sidebar-bg: #070D1A; + --sidebar-color: #C8D6E5; + --sidebar-accent: var(--gold); + --sidebar-nav-text: rgba(200,214,229,0.45); + --sidebar-nav-text-hover: rgba(200,214,229,0.80); + --sidebar-nav-text-active: #FFFFFF; + --sidebar-nav-bg-hover: rgba(91,141,239,0.06); + --sidebar-nav-bg-active: rgba(91,141,239,0.10); + --sidebar-nav-border-active: var(--gold); + --sidebar-divider: rgba(200,214,229,0.06); + --sidebar-brand-color: #FFFFFF; + --sidebar-brand-sub-color: rgba(200,214,229,0.35); + --sidebar-group-label-color: rgba(200,214,229,0.20); + --sidebar-footer-role-color: var(--gold); + --sidebar-footer-name-color: rgba(200,214,229,0.65); + + /* ── Mobile page accent ── */ + --page-accent-color: rgba(91,141,239,0.06); + + /* ── Manager chip tags ── */ + --mgr-chip-text: #C8D6E5; + + /* ── Fonts — editorial Chinese ── */ + --font-body: 'Noto Serif SC', STSong, Songti SC, '宋体', serif; + --font-heading: 'ZCOOL XiaoWei', STSong, Songti SC, serif; + --font-mono: 'JetBrains Mono', 'SF Mono', monospace; +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add frontend/src/App.vue +git commit -m "feat: 新增 minimal-dark 和 tech-dark 两套暗色 CSS 变量" +``` + +--- + +### Task 3: 改造 DesktopLayout 顶部栏按钮 + +**Files:** +- Modify: `frontend/src/components/DesktopLayout.vue` (script 和 template 中的主题切换按钮) + +**Interfaces:** +- Consumes: `useThemeStore()` — `mode`, `toggleMode()` + +- [ ] **Step 1: 替换主题按钮为亮/暗切换** + +将 `cycleTheme` 函数替换为 `toggleMode`,按钮改为 sun/moon 图标切换: + +```typescript +// 替换 cycleTheme 函数: +function toggleDarkMode() { + themeStore.toggleMode() +} +``` + +Template 中替换按钮: + +```html + +``` + +- [ ] **Step 2: 触发构建验证** + +```bash +cd frontend && npm run build -- --emptyOutDir +``` + +- [ ] **Step 3: Commit** + +```bash +git add frontend/src/components/DesktopLayout.vue +git commit -m "feat: 桌面端顶部栏改为亮/暗模式切换按钮" +``` + +--- + +### Task 4: 改造设置页面主题面板 + +**Files:** +- Modify: `frontend/src/views/desktop/Settings.vue` (替换 Theme 卡片为双选下拉) + +**Interfaces:** +- Consumes: `useThemeStore()` — `lightTheme`, `darkTheme`, `mode`, `setLightTheme()`, `setDarkTheme()` + +- [ ] **Step 1: 替换主题选择区域** + +Replace the el-card template section (lines 158-173) and the `themeLabels` import: + +**Script 部分修改 import:** +```typescript +import { useThemeStore, themeLabels, lightThemes, darkThemes, type Theme } from '@/stores/theme' +``` + +**Template 部分替换主题卡片:** +```html + + + +
+
+ ☀️ 亮色模式主题 + + + + 当前 +
+
+ 🌙 暗色模式主题 + + + + 当前 +
+
+
+``` + +**Style 部分替换 `.theme-options` 样式:** +```css +/* ── Theme Config ── */ +.theme-config { display: flex; flex-direction: column; gap: 12px; } +.theme-row { display: flex; align-items: center; gap: 12px; } +.theme-label { font-family: var(--font-body); font-size: 14px; color: var(--c-text); min-width: 140px; } +.theme-badge { font-size: 11px; padding: 2px 8px; border-radius: 2px; font-family: var(--font-mono); } +.theme-badge--active { background: var(--c-primary); color: #fff; } +``` + +- [ ] **Step 2: 触发构建验证** + +```bash +cd frontend && npm run build -- --emptyOutDir +``` + +- [ ] **Step 3: Commit** + +```bash +git add frontend/src/views/desktop/Settings.vue +git commit -m "feat: 设置页改为亮/暗双选主题配置面板" +``` + +--- + +### Task 5: 验证与收尾 + +- [ ] **Step 1: 完整构建** + +```bash +cd frontend && npm run build +``` + +- [ ] **Step 2: 检查 localStorage 兼容性** + +验证旧 `localStorage.setItem('theme', ...)` 数据不会导致新系统崩溃。在 theme store 的 `apply()` 中已有兜底逻辑。 + +- [ ] **Step 3: 快速手动测试检查清单** + - [ ] 默认加载为 editorial 亮色 + - [ ] 点击顶部栏 ☀️→🌙 切换到 tech-dark + - [ ] 设置页切换亮色主题为 light,暗色主题为 minimal-dark + - [ ] 点击顶部栏按钮在两主题间切换 + - [ ] 刷新页面偏好保持 + - [ ] Element Plus 组件(表格/对话框/按钮)在暗色下正常 + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "chore: 暗黑模式最终验证通过" +``` diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 8ccb7fa..2dda29e 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -343,6 +343,49 @@ const isMobile = computed(() => { --font-mono: 'JetBrains Mono', 'SF Mono', monospace; } +/* ═══════════════════════════════════════════════════════════ + Dark Mode — Element Plus CSS Variable Overrides + Override --el-* variables so ALL components render dark + ═══════════════════════════════════════════════════════════ */ + +html.dark { + --el-color-white: var(--surface); + --el-color-black: var(--c-text); + --el-color-primary: var(--c-gold); + --el-color-primary-light-3: var(--gold-light); + --el-color-primary-light-5: var(--gold-light); + --el-color-primary-light-7: var(--c-primary-bg); + --el-color-primary-light-8: var(--c-primary-bg); + --el-color-primary-light-9: var(--paper-dark); + --el-color-primary-dark-2: var(--gold-dark); + --el-color-success: var(--c-success); + --el-color-warning: var(--c-warning); + --el-color-danger: var(--c-danger); + --el-color-info: var(--c-info); + --el-bg-color: var(--paper); + --el-bg-color-page: var(--paper); + --el-bg-color-overlay: var(--surface); + --el-text-color-primary: var(--c-text); + --el-text-color-regular: var(--c-text); + --el-text-color-secondary: var(--c-text-secondary); + --el-text-color-placeholder: var(--c-text-muted); + --el-text-color-disabled: var(--c-text-muted); + --el-border-color: var(--warm-border); + --el-border-color-light: var(--warm-border); + --el-border-color-lighter: var(--warm-border); + --el-border-color-extra-light: var(--warm-border); + --el-border-color-dark: var(--c-text-muted); + --el-fill-color: var(--c-primary-bg); + --el-fill-color-light: var(--c-primary-bg); + --el-fill-color-lighter: var(--paper-dark); + --el-fill-color-blank: var(--surface); + --el-disabled-bg-color: var(--paper-dark); + --el-disabled-text-color: var(--c-text-muted); + --el-disabled-border-color: var(--warm-border); + --el-mask-color: rgba(0,0,0,0.6); + --el-mask-color-extra-light: rgba(0,0,0,0.3); +} + /* ── Reset & Base ── */ *, *::before, *::after { box-sizing: border-box; } diff --git a/frontend/src/components/DesktopLayout.vue b/frontend/src/components/DesktopLayout.vue index 99fa320..5f4693c 100644 --- a/frontend/src/components/DesktopLayout.vue +++ b/frontend/src/components/DesktopLayout.vue @@ -44,12 +44,12 @@ const menuGroups = computed(() => { label: '管理', items: [ { path: '/customers', label: '客户管理', icon: '' }, + { path: '/settings', label: '系统设置', icon: '' }, ], }) if (auth.isDirector) { - groups[groups.length - 1].items.push( + groups[groups.length - 1].items.splice(1, 0, { path: '/users', label: '用户管理', icon: '' }, - { path: '/settings', label: '系统设置', icon: '' }, ) } return groups diff --git a/frontend/src/views/desktop/Settings.vue b/frontend/src/views/desktop/Settings.vue index 2a20956..d826f01 100644 --- a/frontend/src/views/desktop/Settings.vue +++ b/frontend/src/views/desktop/Settings.vue @@ -3,8 +3,10 @@ import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' import api from '@/api/index' import { useThemeStore, themeLabels, type Theme, lightThemes, darkThemes } from '@/stores/theme' +import { useAuthStore } from '@/stores/auth' const themeStore = useThemeStore() +const auth = useAuthStore() const managers = ref([]) const selectedUserIds = ref([]) @@ -186,8 +188,9 @@ async function handleImportPreview() { - - +