From 01c97615cd8d065a8fd01bee6cb2155e813e61fc Mon Sep 17 00:00:00 2001 From: Nixevol Date: Fri, 15 May 2026 16:23:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=96=B0=E7=89=88?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E5=A4=9C=E9=97=B4=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/project_context.md | 8 +++++++ frontend/src/App.vue | 11 ++++++--- frontend/src/AppShell.vue | 8 ++----- frontend/src/composables/theme.ts | 40 +++++++++++++++++++++++++++++++ frontend/src/styles.css | 16 +++++++++++++ 5 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 frontend/src/composables/theme.ts diff --git a/docs/project_context.md b/docs/project_context.md index 8ed5dce..c269215 100644 --- a/docs/project_context.md +++ b/docs/project_context.md @@ -1,5 +1,13 @@ # 项目上下文记录 +## 2026-05-15:修复新版前端夜间模式组件颜色 + +- 新增 `frontend/src/composables/theme.ts` 作为前端共享主题状态,统一读写 `localStorage.theme` 并同步 `document.documentElement[data-theme]`。 +- `App.vue` 的 `n-config-provider` 现在会在夜间模式下使用 Naive UI `darkTheme`,修复 `n-card`、`n-input`、`n-form`、`n-menu`、`n-button` 等组件仍按亮色主题渲染的问题。 +- `AppShell.vue` 的主题切换改为调用共享主题逻辑,避免 AppShell 和 Naive Provider 各自维护主题状态。 +- `styles.css` 补充工作卡片、卡片标题、表单标签、菜单图标和菜单文字颜色兜底,防止局部组件样式覆盖暗色文本。 +- 已执行 `npm run build`,构建通过,仅有 Monaco/Vite 大 chunk 体积警告;已用 headless Chrome 打开新版 `/settings` 并预置 `theme=dark` 验证:页面、卡片标题、表单标签、输入框文字、字段映射标题和顶部按钮均为暗色主题可读颜色。 + ## 2026-05-15:对齐新版前端主体布局并修复全局拖拽默认行为 - 新版 Vue 前端主体内容继续按旧版 `frontend_old/` 的工作区布局对齐:上传页保留旧版上传区尺寸和文件列表结构,数据库页恢复左侧表列表 + 右侧数据区,设置页恢复左右列,脚本页恢复编辑器容器和底部状态栏。 diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 0dec5ba..552dd99 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,5 +1,5 @@ +import { computed } from 'vue'; +import { darkTheme } from 'naive-ui'; +import AppShell from './AppShell.vue'; +import { themeName } from './composables/theme'; + +const naiveTheme = computed(() => (themeName.value === 'dark' ? darkTheme : undefined)); + diff --git a/frontend/src/AppShell.vue b/frontend/src/AppShell.vue index 4f1bd53..7583381 100644 --- a/frontend/src/AppShell.vue +++ b/frontend/src/AppShell.vue @@ -98,6 +98,7 @@ import { apiPost, clearToken, getToken, setToken, setUnauthorizedHandler } from import type { LoginResponse } from './types'; import LoginView from './components/LoginView.vue'; import { pageHeader, resetPageHeader, resolveHeaderValue, type PageHeaderAction } from './composables/pageHeader'; +import { toggleAppTheme } from './composables/theme'; const message = useMessage(); const route = useRoute(); @@ -105,7 +106,6 @@ const router = useRouter(); const token = ref(getToken()); const loginLoading = ref(false); const sidebarCollapsed = ref(localStorage.getItem('sidebarCollapsed') === 'true'); -const theme = ref(localStorage.getItem('theme') || 'light'); const menuKeys = ['workflow', 'history', 'database', 'script', 'settings'] as const; type MenuKey = (typeof menuKeys)[number]; @@ -126,8 +126,6 @@ const currentTitle = computed(() => { }); const pageSubtitle = computed(() => resolveHeaderValue(pageHeader.subtitle || '', '')); -document.documentElement.setAttribute('data-theme', theme.value); - onMounted(() => { window.addEventListener('dragover', preventWindowFileDrop, { capture: true }); window.addEventListener('drop', preventWindowFileDrop, { capture: true }); @@ -173,9 +171,7 @@ function toggleSidebar() { } function toggleTheme() { - theme.value = theme.value === 'dark' ? 'light' : 'dark'; - localStorage.setItem('theme', theme.value); - document.documentElement.setAttribute('data-theme', theme.value); + toggleAppTheme(); } async function restartService() { diff --git a/frontend/src/composables/theme.ts b/frontend/src/composables/theme.ts new file mode 100644 index 0000000..6b8dfab --- /dev/null +++ b/frontend/src/composables/theme.ts @@ -0,0 +1,40 @@ +import { ref } from 'vue'; + +export type AppTheme = 'light' | 'dark'; + +const THEME_KEY = 'theme'; + +export const themeName = ref(readStoredTheme()); + +applyTheme(themeName.value); + +export function setAppTheme(nextTheme: AppTheme) { + themeName.value = nextTheme; + applyTheme(nextTheme); +} + +export function toggleAppTheme() { + setAppTheme(themeName.value === 'dark' ? 'light' : 'dark'); +} + +function readStoredTheme(): AppTheme { + try { + const stored = typeof localStorage === 'undefined' ? null : localStorage.getItem(THEME_KEY); + return stored === 'dark' ? 'dark' : 'light'; + } catch { + return 'light'; + } +} + +function applyTheme(nextTheme: AppTheme) { + if (typeof document !== 'undefined') { + document.documentElement.setAttribute('data-theme', nextTheme); + } + try { + if (typeof localStorage !== 'undefined') { + localStorage.setItem(THEME_KEY, nextTheme); + } + } catch { + // Ignore storage failures so theme switching never blocks the UI. + } +} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index b97391d..00c70b0 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -210,6 +210,11 @@ select { transition: all var(--td-transition); } +.app-menu .n-menu-item-content-header, +.app-menu .n-menu-item-content__icon { + color: inherit; +} + .app-menu .n-menu-item-content:hover { color: var(--td-text-color-primary); background: var(--td-bg-color-container-hover); @@ -371,8 +376,14 @@ select { border-bottom: 1px solid var(--td-border-color-light); } +.work-card > .n-card-header .n-card-header__main, +.work-card > .n-card-header .n-card-header__extra { + color: var(--td-text-color-primary); +} + .work-card > .n-card__content { padding: 16px 20px; + color: var(--td-text-color-primary); } .work-card > .n-card__footer { @@ -381,6 +392,11 @@ select { border-top: 1px solid var(--td-border-color-light); } +.work-card .n-form-item-label, +.work-card .n-form-item-label__text { + color: var(--td-text-color-secondary); +} + .login-page { display: grid; min-height: 100vh;