From 00b8ca25e27a4256044fe8e0d1b0207615bf365e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9A=AE=E6=98=9F?= <1009592348@qq.com>
Date: Mon, 10 Aug 2026 03:40:06 +0800
Subject: [PATCH] =?UTF-8?q?fix(theme):=20improve=20dark=20mode=20contrast?=
=?UTF-8?q?=20=E6=94=B9=E5=96=84=E6=9A=97=E8=89=B2=E4=B8=BB=E9=A2=98?=
=?UTF-8?q?=E5=AF=B9=E6=AF=94=E5=BA=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ui/index.html | 70 +++
ui/src/App.vue | 75 ++-
ui/src/assets/base.css | 18 +-
ui/src/assets/main.css | 9 +-
ui/src/components/changes/GitChangesPanel.vue | 43 +-
.../common/SplitDropdownControl.vue | 16 +-
ui/src/components/common/ThemeSwitcher.vue | 13 +-
ui/src/components/files/FileManagerPanel.vue | 88 +--
.../components/files/FilePreviewSurface.vue | 34 +-
ui/src/components/kanban/KanbanBoard.vue | 7 +-
ui/src/components/kanban/KanbanColumn.vue | 7 +-
ui/src/components/kanban/TaskCard.vue | 17 +-
ui/src/components/kanban/TaskDetailDrawer.vue | 14 +-
ui/src/components/notepad/NotePad.vue | 50 +-
ui/src/components/project/ProjectBrowser.vue | 21 +-
ui/src/components/project/RecentProjects.vue | 85 +--
ui/src/components/terminal/TerminalPanel.vue | 109 ++--
.../terminal/TerminalSidebarRow.vue | 50 +-
.../web-session/WebSessionComposerEditor.vue | 38 +-
.../web-session/WebSessionImportDialog.vue | 28 +-
.../web-session/WebSessionPanel.vue | 529 +++++++++++-------
.../web-session/WebSessionSidebarRow.vue | 60 +-
.../WebSessionSkillCatalogPanel.vue | 26 +-
.../workspace/DockedNotificationSidebar.vue | 26 +-
.../components/workspace/WorkspaceTabView.vue | 72 +--
ui/src/constants/themes.ts | 23 +
ui/src/i18n/locales/en-US.ts | 4 +-
ui/src/i18n/locales/zh-CN.ts | 4 +-
ui/src/styles/chat-markdown.css | 132 +++--
ui/src/styles/project-badges.css | 19 +
ui/src/styles/variables.css | 97 +++-
ui/src/utils/themeMaintenanceWarning.ts | 12 +-
ui/src/utils/themeOverrides.ts | 69 +--
ui/src/utils/themeSemanticPalette.ts | 86 +++
ui/src/views/GeneralSettings.vue | 82 +--
ui/src/views/LoginView.vue | 67 ++-
ui/src/views/ProjectWorkspace.vue | 27 +-
37 files changed, 1371 insertions(+), 756 deletions(-)
create mode 100644 ui/src/utils/themeSemanticPalette.ts
diff --git a/ui/index.html b/ui/index.html
index 4ee99a08..feeebf02 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -7,6 +7,76 @@
Code Kanban
+
diff --git a/ui/src/App.vue b/ui/src/App.vue
index 9bcfd3e3..f3466988 100644
--- a/ui/src/App.vue
+++ b/ui/src/App.vue
@@ -11,9 +11,10 @@ import { useSettingsStore } from '@/stores/settings';
import { useProjectStore } from '@/stores/project';
import { useResponsive } from '@/composables/useResponsive';
import { useAiStatusSummary } from '@/composables/useAiStatusSummary';
-import { darkenColor, lightenColor, isDarkHex } from '@/utils/color';
+import { isDarkHex } from '@/utils/color';
import { formatBrowserTabTitle } from '@/utils/browserTitle';
import { createThemeOverrides } from '@/utils/themeOverrides';
+import { createThemeSemanticPalette } from '@/utils/themeSemanticPalette';
import { getPresetById } from '@/constants/themes';
import { APP_NAME } from '@/constants/app';
@@ -89,15 +90,14 @@ const resolvedTextColor = computed(() => {
if (textColor && textColor.trim().length > 0) {
return textColor;
}
- return isDarkTheme.value ? '#FFFFFFD9' : '#000000E0';
+ return isDarkTheme.value ? '#D9D9D9' : '#333333';
});
-const inputBorderColor = computed(() => (isDarkTheme.value ? '#4B4B4B' : '#D0D5DD'));
-const inputBorderHoverColor = computed(() =>
- isDarkTheme.value
- ? lightenColor(inputBorderColor.value, 0.12)
- : darkenColor(inputBorderColor.value, 0.12)
+const semanticPalette = computed(() =>
+ createThemeSemanticPalette(theme.value, resolvedTextColor.value)
);
+const inputBorderColor = computed(() => semanticPalette.value.controlBorder);
+const inputBorderHoverColor = computed(() => semanticPalette.value.controlBorderHover);
// 根据当前语言动态切换 Naive UI 的 locale
const naiveLocale = computed(() => (locale.value === 'zh-CN' ? zhCN : enUS));
@@ -169,6 +169,64 @@ const cssVarsToSet = computed(() => ({
'--app-text-color': resolvedTextColor.value,
'--app-input-border-color': inputBorderColor.value,
'--app-input-border-hover-color': inputBorderHoverColor.value,
+ '--app-canvas': semanticPalette.value.canvas,
+ '--app-surface': semanticPalette.value.surface,
+ '--app-surface-raised': semanticPalette.value.surfaceRaised,
+ '--app-surface-sunken': semanticPalette.value.surfaceSunken,
+ '--app-surface-hover': semanticPalette.value.surfaceHover,
+ '--app-surface-active': semanticPalette.value.surfaceActive,
+ '--app-text-primary': semanticPalette.value.textPrimary,
+ '--app-text-secondary': semanticPalette.value.textSecondary,
+ '--app-text-muted': semanticPalette.value.textMuted,
+ '--app-text-inverse': semanticPalette.value.textInverse,
+ '--app-border': semanticPalette.value.border,
+ '--app-border-strong': semanticPalette.value.borderStrong,
+ '--app-control-border': semanticPalette.value.controlBorder,
+ '--app-control-border-hover': semanticPalette.value.controlBorderHover,
+ '--app-focus-ring': semanticPalette.value.focusRing,
+ '--app-accent': semanticPalette.value.accent,
+ '--app-accent-hover': semanticPalette.value.accentHover,
+ '--app-accent-pressed': semanticPalette.value.accentPressed,
+ '--app-accent-soft': semanticPalette.value.accentSoft,
+ '--app-accent-contrast': semanticPalette.value.accentContrast,
+ '--app-link': semanticPalette.value.link,
+ '--app-link-hover': semanticPalette.value.linkHover,
+ '--app-success': semanticPalette.value.success,
+ '--app-success-soft': semanticPalette.value.successSoft,
+ '--app-warning': semanticPalette.value.warning,
+ '--app-warning-soft': semanticPalette.value.warningSoft,
+ '--app-error': semanticPalette.value.error,
+ '--app-error-soft': semanticPalette.value.errorSoft,
+ '--app-info': semanticPalette.value.info,
+ '--app-info-soft': semanticPalette.value.infoSoft,
+ '--app-overlay': semanticPalette.value.overlay,
+ '--app-shadow': semanticPalette.value.shadow,
+ '--n-primary-color': semanticPalette.value.accent,
+ '--n-color-primary': semanticPalette.value.accent,
+ '--n-primary-color-hover': semanticPalette.value.accentHover,
+ '--n-primary-color-pressed': semanticPalette.value.accentPressed,
+ '--n-primary-color-suppl': semanticPalette.value.accentHover,
+ '--n-text-color-base': semanticPalette.value.textPrimary,
+ '--n-text-color': semanticPalette.value.textPrimary,
+ '--n-text-color-1': semanticPalette.value.textPrimary,
+ '--n-text-color-2': semanticPalette.value.textSecondary,
+ '--n-text-color-3': semanticPalette.value.textMuted,
+ '--n-text-color-disabled': semanticPalette.value.textMuted,
+ '--n-body-color': semanticPalette.value.canvas,
+ '--n-color': semanticPalette.value.surface,
+ '--n-color-hover': semanticPalette.value.surfaceHover,
+ '--n-color-target': semanticPalette.value.accentSoft,
+ '--n-color-embedded': semanticPalette.value.surfaceSunken,
+ '--n-card-color': semanticPalette.value.surface,
+ '--n-modal-color': semanticPalette.value.surfaceRaised,
+ '--n-popover-color': semanticPalette.value.surfaceRaised,
+ '--n-border-color': semanticPalette.value.border,
+ '--n-divider-color': semanticPalette.value.border,
+ '--n-box-shadow-color': semanticPalette.value.shadow,
+ '--n-error-color': semanticPalette.value.error,
+ '--n-warning-color': semanticPalette.value.warning,
+ '--n-success-color': semanticPalette.value.success,
+ '--n-info-color': semanticPalette.value.info,
}));
watch(
@@ -176,6 +234,9 @@ watch(
vars => {
if (typeof document !== 'undefined') {
const root = document.documentElement;
+ root.dataset.colorScheme = semanticPalette.value.colorScheme;
+ root.style.colorScheme = semanticPalette.value.colorScheme;
+ root.style.backgroundColor = semanticPalette.value.canvas;
Object.entries(vars).forEach(([key, value]) => {
root.style.setProperty(key, value ?? '');
});
diff --git a/ui/src/assets/base.css b/ui/src/assets/base.css
index 8816868a..3c6e0dd4 100644
--- a/ui/src/assets/base.css
+++ b/ui/src/assets/base.css
@@ -36,20 +36,6 @@
--section-gap: 160px;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --color-background: var(--vt-c-black);
- --color-background-soft: var(--vt-c-black-soft);
- --color-background-mute: var(--vt-c-black-mute);
-
- --color-border: var(--vt-c-divider-dark-2);
- --color-border-hover: var(--vt-c-divider-dark-1);
-
- --color-heading: var(--vt-c-text-dark-1);
- --color-text: var(--vt-c-text-dark-2);
- }
-}
-
*,
*::before,
*::after {
@@ -60,8 +46,8 @@
body {
min-height: 100vh;
- color: var(--color-text);
- background: var(--color-background);
+ color: var(--app-text-primary, var(--color-text));
+ background: var(--app-canvas, var(--color-background));
transition:
color 0.5s,
background-color 0.5s;
diff --git a/ui/src/assets/main.css b/ui/src/assets/main.css
index 81f715d1..e4d6a0b1 100644
--- a/ui/src/assets/main.css
+++ b/ui/src/assets/main.css
@@ -5,6 +5,8 @@
min-height: 100vh;
margin: 0;
padding: 0;
+ background-color: var(--app-canvas, var(--app-body-color, #f7f8fa));
+ color: var(--app-text-primary, var(--app-text-color, #333333));
}
main {
@@ -125,18 +127,19 @@ main {
left: 0;
right: 0;
bottom: 0;
- background-color: var(--app-surface-color, #ffffff);
+ background-color: var(--app-surface-raised, var(--app-surface-color, #ffffff));
+ color: var(--app-text-primary, var(--app-text-color, #333333));
z-index: 100;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
- box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 -4px 20px var(--app-shadow, rgba(0, 0, 0, 0.15));
}
/* 拖动条 (用于移动端面板) */
.drag-handle {
width: 40px;
height: 4px;
- background-color: var(--n-border-color, #e0e0e0);
+ background-color: var(--app-border-strong, var(--n-border-color, #e0e0e0));
border-radius: 2px;
margin: 8px auto;
}
diff --git a/ui/src/components/changes/GitChangesPanel.vue b/ui/src/components/changes/GitChangesPanel.vue
index 4be6d219..441e7dfb 100644
--- a/ui/src/components/changes/GitChangesPanel.vue
+++ b/ui/src/components/changes/GitChangesPanel.vue
@@ -861,9 +861,8 @@ onBeforeUnmount(() => {
min-height: 0;
min-width: 0;
overflow: hidden;
- background:
- linear-gradient(180deg, rgba(246, 248, 252, 0.98), rgba(238, 242, 247, 0.96)),
- radial-gradient(circle at top right, rgba(53, 119, 186, 0.12), transparent 32%);
+ background: var(--app-canvas);
+ color: var(--app-text-primary);
}
.git-changes-toolbar {
@@ -872,7 +871,8 @@ onBeforeUnmount(() => {
gap: 12px;
flex-wrap: wrap;
padding: 12px 16px;
- border-bottom: 1px solid rgba(24, 35, 51, 0.08);
+ border-bottom: 1px solid var(--app-border);
+ background: var(--app-surface);
}
.git-changes-scope {
@@ -925,8 +925,8 @@ onBeforeUnmount(() => {
flex: 0 0 360px;
min-width: 280px;
max-width: 440px;
- border-right: 1px solid rgba(24, 35, 51, 0.08);
- background: rgba(255, 255, 255, 0.72);
+ border-right: 1px solid var(--app-border);
+ background: var(--app-surface-sunken);
display: flex;
flex-direction: column;
min-height: 0;
@@ -959,7 +959,7 @@ onBeforeUnmount(() => {
overflow: auto;
padding: 12px;
scrollbar-width: thin;
- scrollbar-color: rgba(37, 90, 143, 0.38) transparent;
+ scrollbar-color: var(--app-border-strong) transparent;
}
.git-changes-list::-webkit-scrollbar {
@@ -969,7 +969,7 @@ onBeforeUnmount(() => {
.git-changes-list::-webkit-scrollbar-thumb {
border-radius: 999px;
- background: rgba(37, 90, 143, 0.28);
+ background: var(--app-border-strong);
}
.git-changes-empty,
@@ -988,9 +988,10 @@ onBeforeUnmount(() => {
display: flex;
width: 100%;
padding: 12px 14px;
- border: 1px solid rgba(24, 35, 51, 0.08);
+ border: 1px solid var(--app-border);
border-radius: 12px;
- background: rgba(255, 255, 255, 0.92);
+ background: var(--app-surface-raised);
+ color: var(--app-text-primary);
text-align: left;
cursor: pointer;
transition:
@@ -1001,9 +1002,13 @@ onBeforeUnmount(() => {
.git-change-row:hover,
.git-change-row.is-active {
- border-color: rgba(53, 119, 186, 0.32);
- background: rgba(240, 247, 255, 0.9);
- box-shadow: 0 10px 24px rgba(40, 73, 118, 0.08);
+ border-color: var(--app-accent);
+ background: var(--app-surface-hover);
+ box-shadow: 0 10px 24px color-mix(in srgb, var(--app-accent) 8%, transparent);
+}
+
+.git-change-row.is-active {
+ background: var(--app-surface-active);
}
.git-change-main {
@@ -1022,7 +1027,7 @@ onBeforeUnmount(() => {
}
.git-change-icon {
- color: #255a8f;
+ color: var(--app-info);
flex: 0 0 auto;
}
@@ -1042,12 +1047,12 @@ onBeforeUnmount(() => {
}
.git-change-stat-tag {
- color: rgba(34, 46, 67, 0.78);
+ color: var(--app-text-secondary);
}
.git-change-path,
.git-change-previous {
- color: rgba(34, 46, 67, 0.7);
+ color: var(--app-text-muted);
font-size: 12px;
line-height: 1.45;
word-break: break-all;
@@ -1055,7 +1060,7 @@ onBeforeUnmount(() => {
.git-changes-preview {
flex: 1;
- background: rgba(255, 255, 255, 0.82);
+ background: var(--app-surface);
}
.git-changes-mobile-preview-modal {
@@ -1067,7 +1072,7 @@ onBeforeUnmount(() => {
.git-changes-mobile-preview-surface {
width: 100vw;
min-height: min(100vh, 100dvh);
- background: rgba(255, 255, 255, 0.98);
+ background: var(--app-surface);
border-radius: 0;
overflow: hidden;
}
@@ -1082,7 +1087,7 @@ onBeforeUnmount(() => {
width: 100%;
max-width: none;
border-right: none;
- border-bottom: 1px solid rgba(24, 35, 51, 0.08);
+ border-bottom: 1px solid var(--app-border);
}
.git-changes-panel:not(.is-mobile-preview) .git-changes-list {
diff --git a/ui/src/components/common/SplitDropdownControl.vue b/ui/src/components/common/SplitDropdownControl.vue
index f571fa28..5a004a92 100644
--- a/ui/src/components/common/SplitDropdownControl.vue
+++ b/ui/src/components/common/SplitDropdownControl.vue
@@ -75,8 +75,9 @@ function handleSelect(key: string | number) {
border-radius: 6px;
gap: 0;
padding: 0;
- border: 1px solid var(--kanban-notification-button-border, rgba(0, 0, 0, 0.2));
- background: var(--app-surface-color, var(--body-color, #ffffff));
+ border: 1px solid var(--kanban-notification-button-border, var(--app-border, rgba(0, 0, 0, 0.2)));
+ background: var(--app-surface, var(--app-surface-color, var(--body-color, #ffffff)));
+ color: var(--app-text-primary, inherit);
box-shadow: none;
}
@@ -103,12 +104,12 @@ function handleSelect(key: string | number) {
.split-dropdown-menu {
padding: 0 8px;
- border-left: 1px solid rgba(0, 0, 0, 0.08);
+ border-left: 1px solid var(--app-border, rgba(0, 0, 0, 0.08));
justify-content: center;
}
.split-dropdown-control:hover {
- box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15);
+ box-shadow: 0 4px 12px var(--app-shadow, rgba(15, 23, 42, 0.15));
}
.split-dropdown-control.is-flat:hover {
@@ -120,13 +121,14 @@ function handleSelect(key: string | number) {
}
.split-dropdown-control.is-active:hover {
- box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15);
+ box-shadow: 0 4px 12px var(--app-shadow, rgba(15, 23, 42, 0.15));
}
.split-dropdown-main:focus-visible,
.split-dropdown-menu:focus-visible {
- outline: none;
- background: color-mix(in srgb, var(--n-primary-color) 4%, transparent);
+ outline: 2px solid var(--app-focus-ring, var(--n-primary-color));
+ outline-offset: -2px;
+ background: var(--app-accent-soft, color-mix(in srgb, var(--n-primary-color) 4%, transparent));
}
.split-dropdown-icon {
diff --git a/ui/src/components/common/ThemeSwitcher.vue b/ui/src/components/common/ThemeSwitcher.vue
index ceef8318..170cbe6c 100644
--- a/ui/src/components/common/ThemeSwitcher.vue
+++ b/ui/src/components/common/ThemeSwitcher.vue
@@ -16,9 +16,10 @@ import { storeToRefs } from 'pinia';
import { NIcon, useDialog } from 'naive-ui';
import { MoonOutline, SunnyOutline, CheckmarkOutline } from '@vicons/ionicons5';
import { useSettingsStore } from '@/stores/settings';
-import { THEME_PRESETS, getPresetById } from '@/constants/themes';
+import { THEME_PRESETS, isSupportedThemePreset } from '@/constants/themes';
import { useLocale } from '@/composables/useLocale';
import { useThemeOptions } from '@/composables/useThemeOptions';
+import { isDarkHex } from '@/utils/color';
import {
createThemeMaintenanceWarningController,
createThemeSelectionController,
@@ -39,7 +40,7 @@ const props = withDefaults(
const { t } = useLocale();
const dialog = useDialog();
const settingsStore = useSettingsStore();
-const { currentPresetId, followSystemTheme } = storeToRefs(settingsStore);
+const { activeTheme, currentPresetId, followSystemTheme } = storeToRefs(settingsStore);
const themeWarningController = createThemeMaintenanceWarningController({
t,
warning: options => dialog.warning(options),
@@ -51,13 +52,11 @@ const themeSelectionController = createThemeSelectionController({
toggleFollowSystemTheme: enabled => settingsStore.toggleFollowSystemTheme(enabled),
confirmPresetThemeChange: themeWarningController.confirmPresetThemeChange,
confirmFollowSystemEnable: themeWarningController.confirmFollowSystemEnable,
+ shouldConfirmPresetThemeChange: presetId => !isSupportedThemePreset(presetId),
+ shouldConfirmFollowSystemEnable: () => false,
});
-// 判断当前是否为暗色主题
-const isDarkTheme = computed(() => {
- const preset = getPresetById(currentPresetId.value);
- return preset?.isDark ?? false;
-});
+const isDarkTheme = computed(() => isDarkHex(activeTheme.value.bodyColor));
// 渲染颜色圆点
const renderColorDot = (color: string) =>
diff --git a/ui/src/components/files/FileManagerPanel.vue b/ui/src/components/files/FileManagerPanel.vue
index 15eba094..d551f615 100644
--- a/ui/src/components/files/FileManagerPanel.vue
+++ b/ui/src/components/files/FileManagerPanel.vue
@@ -1583,9 +1583,12 @@ onBeforeUnmount(() => {
min-width: 0;
min-height: 0;
overflow: hidden;
- background:
- linear-gradient(180deg, rgba(246, 248, 252, 0.98), rgba(238, 242, 247, 0.96)),
- radial-gradient(circle at top right, rgba(53, 119, 186, 0.12), transparent 30%);
+ background: linear-gradient(
+ 180deg,
+ color-mix(in srgb, var(--app-canvas) 96%, var(--app-accent) 4%),
+ var(--app-canvas)
+ );
+ color: var(--app-text-primary);
}
.file-manager-toolbar {
@@ -1594,7 +1597,7 @@ onBeforeUnmount(() => {
gap: 12px;
align-items: center;
padding: 12px 16px;
- border-bottom: 1px solid rgba(24, 35, 51, 0.08);
+ border-bottom: 1px solid var(--app-border);
}
.scope-select {
@@ -1613,7 +1616,7 @@ onBeforeUnmount(() => {
.file-manager-breadcrumb-label {
flex: 0 0 auto;
- color: rgba(34, 46, 67, 0.62);
+ color: var(--app-text-muted);
font-size: 12px;
font-weight: 600;
}
@@ -1673,8 +1676,8 @@ onBeforeUnmount(() => {
gap: 12px;
flex-shrink: 0;
padding: 8px 16px;
- border-bottom: 1px solid rgba(24, 35, 51, 0.08);
- background: rgba(255, 255, 255, 0.72);
+ border-bottom: 1px solid var(--app-border);
+ background: color-mix(in srgb, var(--app-surface) 72%, var(--app-canvas) 28%);
}
.file-manager-action-controls {
@@ -1689,7 +1692,7 @@ onBeforeUnmount(() => {
min-width: 72px;
font-size: 13px;
font-weight: 600;
- color: rgba(34, 46, 67, 0.8);
+ color: var(--app-text-secondary);
}
.file-manager-body {
@@ -1738,10 +1741,10 @@ onBeforeUnmount(() => {
display: flex;
align-items: center;
justify-content: center;
- border: 2px dashed rgba(53, 119, 186, 0.45);
+ border: 2px dashed color-mix(in srgb, var(--app-accent) 55%, transparent);
border-radius: 18px;
- background: rgba(255, 255, 255, 0.85);
- color: #1f4c7f;
+ background: color-mix(in srgb, var(--app-surface-raised) 88%, var(--app-canvas) 12%);
+ color: var(--app-info);
font-weight: 600;
pointer-events: none;
}
@@ -1759,9 +1762,10 @@ onBeforeUnmount(() => {
gap: 10px;
min-height: 44px;
padding: 8px 10px;
- border: 1px solid rgba(24, 35, 51, 0.08);
+ border: 1px solid var(--app-border);
border-radius: 10px;
- background: rgba(255, 255, 255, 0.88);
+ background: color-mix(in srgb, var(--app-surface) 88%, var(--app-canvas) 12%);
+ color: var(--app-text-primary);
text-align: left;
cursor: pointer;
transition:
@@ -1772,15 +1776,20 @@ onBeforeUnmount(() => {
.file-tree-row:hover,
.file-tree-row.is-active {
- border-color: rgba(53, 119, 186, 0.32);
- background: rgba(240, 247, 255, 0.9);
- box-shadow: 0 10px 24px rgba(40, 73, 118, 0.08);
+ border-color: color-mix(in srgb, var(--app-accent) 40%, var(--app-border) 60%);
+ background: color-mix(in srgb, var(--app-surface) 94%, var(--app-accent) 6%);
+ box-shadow: 0 10px 24px color-mix(in srgb, var(--app-shadow) 45%, transparent);
+}
+
+.file-tree-row:focus-visible {
+ outline: 2px solid var(--app-focus-ring);
+ outline-offset: 1px;
}
.tree-expand-hit {
width: 16px;
flex: 0 0 16px;
- color: rgba(34, 46, 67, 0.72);
+ color: var(--app-text-secondary);
text-align: center;
}
@@ -1821,7 +1830,7 @@ onBeforeUnmount(() => {
.file-preview-content,
.file-transfer-items {
scrollbar-width: thin;
- scrollbar-color: rgba(37, 90, 143, 0.38) transparent;
+ scrollbar-color: color-mix(in srgb, var(--app-accent) 42%, transparent) transparent;
}
.file-list-scroll::-webkit-scrollbar,
@@ -1841,13 +1850,13 @@ onBeforeUnmount(() => {
.file-preview-content::-webkit-scrollbar-thumb,
.file-transfer-items::-webkit-scrollbar-thumb {
border-radius: 999px;
- background: rgba(37, 90, 143, 0.28);
+ background: color-mix(in srgb, var(--app-accent) 32%, transparent);
}
.file-list-scroll::-webkit-scrollbar-thumb:hover,
.file-preview-content::-webkit-scrollbar-thumb:hover,
.file-transfer-items::-webkit-scrollbar-thumb:hover {
- background: rgba(37, 90, 143, 0.42);
+ background: color-mix(in srgb, var(--app-accent) 48%, transparent);
}
.file-list-checkbox {
@@ -1862,7 +1871,7 @@ onBeforeUnmount(() => {
}
.file-list-checkbox:hover {
- background: rgba(37, 90, 143, 0.08);
+ background: var(--app-accent-soft);
}
.file-list-checkbox :deep(input[type='checkbox']) {
@@ -1890,7 +1899,7 @@ onBeforeUnmount(() => {
}
.file-kind-icon {
- color: #255a8f;
+ color: var(--app-info);
}
.file-name-text {
@@ -1906,7 +1915,7 @@ onBeforeUnmount(() => {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
- color: rgba(34, 46, 67, 0.78);
+ color: var(--app-text-secondary);
font-size: 12px;
text-align: right;
}
@@ -1927,20 +1936,25 @@ onBeforeUnmount(() => {
border: none;
border-radius: 8px;
background: transparent;
- color: rgba(34, 46, 67, 0.88);
+ color: var(--app-text-primary);
cursor: pointer;
text-align: left;
}
.file-display-option:hover,
.file-display-option.is-selected {
- background: rgba(37, 90, 143, 0.08);
+ background: var(--app-accent-soft);
+}
+
+.file-display-option:focus-visible {
+ outline: 2px solid var(--app-focus-ring);
+ outline-offset: -2px;
}
.file-display-option-check {
width: 14px;
flex: 0 0 14px;
- color: #255a8f;
+ color: var(--app-info);
font-size: 13px;
font-weight: 700;
text-align: center;
@@ -1958,8 +1972,8 @@ onBeforeUnmount(() => {
.file-preview-pane {
flex: 1;
min-width: 0;
- border-left: 1px solid rgba(24, 35, 51, 0.08);
- background: rgba(255, 255, 255, 0.78);
+ border-left: 1px solid var(--app-border);
+ background: color-mix(in srgb, var(--app-surface) 78%, var(--app-canvas) 22%);
display: flex;
flex-direction: column;
min-height: 0;
@@ -1983,7 +1997,7 @@ onBeforeUnmount(() => {
.file-mobile-preview-surface {
min-height: min(100vh, 100dvh);
- background: rgba(255, 255, 255, 0.98);
+ background: var(--app-surface-raised);
}
.file-image-modal-title {
@@ -2000,13 +2014,13 @@ onBeforeUnmount(() => {
max-height: 78vh;
object-fit: contain;
border-radius: 12px;
- background: #f4f7fb;
+ background: var(--app-surface-sunken);
}
.file-transfer-queue {
flex-shrink: 0;
- border-top: 1px solid rgba(24, 35, 51, 0.08);
- background: rgba(255, 255, 255, 0.88);
+ border-top: 1px solid var(--app-border);
+ background: color-mix(in srgb, var(--app-surface) 88%, var(--app-canvas) 12%);
padding: 12px 16px;
}
@@ -2033,9 +2047,9 @@ onBeforeUnmount(() => {
gap: 12px;
align-items: flex-start;
padding: 10px 12px;
- border: 1px solid rgba(24, 35, 51, 0.08);
+ border: 1px solid var(--app-border);
border-radius: 14px;
- background: rgba(248, 250, 253, 0.96);
+ background: color-mix(in srgb, var(--app-canvas) 86%, var(--app-surface) 14%);
}
.file-transfer-main {
@@ -2053,7 +2067,7 @@ onBeforeUnmount(() => {
.file-transfer-meta {
margin: 4px 0 8px;
- color: rgba(34, 46, 67, 0.66);
+ color: var(--app-text-muted);
font-size: 12px;
}
@@ -2065,7 +2079,7 @@ onBeforeUnmount(() => {
.file-transfer-error {
margin-top: 6px;
- color: #b42318;
+ color: var(--app-error);
font-size: 12px;
}
@@ -2089,7 +2103,7 @@ onBeforeUnmount(() => {
min-width: 0;
min-height: 220px;
border-left: none;
- border-top: 1px solid rgba(24, 35, 51, 0.08);
+ border-top: 1px solid var(--app-border);
}
}
diff --git a/ui/src/components/files/FilePreviewSurface.vue b/ui/src/components/files/FilePreviewSurface.vue
index 043e5bf4..9a1cb80a 100644
--- a/ui/src/components/files/FilePreviewSurface.vue
+++ b/ui/src/components/files/FilePreviewSurface.vue
@@ -185,6 +185,8 @@ const headerMeta = computed(() =>
flex: 1;
flex-direction: column;
min-height: 0;
+ background: var(--app-surface);
+ color: var(--app-text-primary);
}
.file-preview-header {
@@ -193,8 +195,8 @@ const headerMeta = computed(() =>
justify-content: space-between;
gap: 12px;
padding: 16px;
- border-bottom: 1px solid rgba(24, 35, 51, 0.08);
- background: rgba(255, 255, 255, 0.86);
+ border-bottom: 1px solid var(--app-border);
+ background: color-mix(in srgb, var(--app-surface) 86%, var(--app-canvas) 14%);
}
.file-preview-shell.is-mobile .file-preview-header {
@@ -228,7 +230,7 @@ const headerMeta = computed(() =>
align-items: center;
padding: 3px;
border-radius: 999px;
- background: rgba(37, 90, 143, 0.08);
+ background: var(--app-accent-soft);
gap: 4px;
}
@@ -236,7 +238,7 @@ const headerMeta = computed(() =>
border: none;
border-radius: 999px;
background: transparent;
- color: rgba(34, 46, 67, 0.7);
+ color: var(--app-text-secondary);
font-size: 12px;
font-weight: 600;
padding: 6px 10px;
@@ -247,9 +249,14 @@ const headerMeta = computed(() =>
}
.file-preview-mode-button.is-active {
- background: #ffffff;
- color: #1f4c7f;
- box-shadow: 0 6px 16px rgba(31, 76, 127, 0.16);
+ background: var(--app-surface-raised);
+ color: var(--app-info);
+ box-shadow: 0 6px 16px color-mix(in srgb, var(--app-shadow) 65%, transparent);
+}
+
+.file-preview-mode-button:focus-visible {
+ outline: 2px solid var(--app-focus-ring);
+ outline-offset: 1px;
}
.file-preview-back {
@@ -266,7 +273,7 @@ const headerMeta = computed(() =>
.file-preview-meta {
margin-top: 4px;
- color: rgba(34, 46, 67, 0.62);
+ color: var(--app-text-muted);
font-size: 12px;
}
@@ -278,7 +285,7 @@ const headerMeta = computed(() =>
-webkit-overflow-scrolling: touch;
padding: 16px;
scrollbar-width: thin;
- scrollbar-color: rgba(37, 90, 143, 0.38) transparent;
+ scrollbar-color: color-mix(in srgb, var(--app-accent) 42%, transparent) transparent;
}
.file-preview-content::-webkit-scrollbar {
@@ -292,11 +299,11 @@ const headerMeta = computed(() =>
.file-preview-content::-webkit-scrollbar-thumb {
border-radius: 999px;
- background: rgba(37, 90, 143, 0.28);
+ background: color-mix(in srgb, var(--app-accent) 32%, transparent);
}
.file-preview-content::-webkit-scrollbar-thumb:hover {
- background: rgba(37, 90, 143, 0.42);
+ background: color-mix(in srgb, var(--app-accent) 48%, transparent);
}
.file-preview-image,
@@ -304,7 +311,7 @@ const headerMeta = computed(() =>
.file-preview-media {
width: 100%;
border-radius: 14px;
- background: #f4f7fb;
+ background: var(--app-surface-sunken);
}
.file-preview-image {
@@ -357,7 +364,7 @@ const headerMeta = computed(() =>
.file-preview-binary,
.file-preview-truncated {
- color: rgba(34, 46, 67, 0.7);
+ color: var(--app-text-secondary);
font-size: 13px;
}
@@ -372,6 +379,7 @@ const headerMeta = computed(() =>
height: 100%;
min-height: 220px;
padding: 16px;
+ color: var(--app-text-secondary);
text-align: center;
}
diff --git a/ui/src/components/kanban/KanbanBoard.vue b/ui/src/components/kanban/KanbanBoard.vue
index 732eba51..029f8010 100644
--- a/ui/src/components/kanban/KanbanBoard.vue
+++ b/ui/src/components/kanban/KanbanBoard.vue
@@ -708,12 +708,13 @@ function handleTaskViewEvent(event: { taskId?: string; projectId?: string }) {
display: flex;
flex-direction: column;
height: 100%;
- background-color: var(--app-surface-color, #ffffff);
+ background-color: var(--app-surface, var(--app-surface-color, #ffffff));
+ color: var(--app-text-primary, var(--app-text-color, #333333));
}
.board-header {
padding: 16px 24px;
- border-bottom: var(--kanban-border, 1px solid var(--n-border-color));
+ border-bottom: var(--kanban-border, 1px solid var(--app-border, #e0e0e0));
}
.board-header h2 {
@@ -760,7 +761,7 @@ function handleTaskViewEvent(event: { taskId?: string; projectId?: string }) {
position: sticky;
top: 0;
z-index: 10;
- background-color: var(--app-surface-color, #ffffff);
+ background-color: var(--app-surface, var(--app-surface-color, #ffffff));
}
.board-tabs-view :deep(.n-tabs-pane-wrapper) {
diff --git a/ui/src/components/kanban/KanbanColumn.vue b/ui/src/components/kanban/KanbanColumn.vue
index c199c6ba..45498ead 100644
--- a/ui/src/components/kanban/KanbanColumn.vue
+++ b/ui/src/components/kanban/KanbanColumn.vue
@@ -139,9 +139,10 @@ function handleChange(event: any) {
.kanban-column {
display: flex;
flex-direction: column;
- background-color: var(--kanban-board-bg, var(--app-body-color, #f5f5f5));
+ background-color: var(--kanban-board-bg, var(--app-canvas, var(--app-body-color, #f5f5f5)));
+ color: var(--app-text-primary, var(--app-text-color, #333333));
border-radius: 8px;
- border: 1px solid var(--n-border-color, #e0e0e0);
+ border: 1px solid var(--app-border, var(--n-border-color, #e0e0e0));
height: 100%;
overflow: hidden;
}
@@ -151,7 +152,7 @@ function handleChange(event: any) {
align-items: center;
justify-content: space-between;
padding: 12px 16px;
- border-bottom: var(--kanban-border, 1px solid var(--n-border-color));
+ border-bottom: var(--kanban-border, 1px solid var(--app-border, #e0e0e0));
flex-shrink: 0;
}
diff --git a/ui/src/components/kanban/TaskCard.vue b/ui/src/components/kanban/TaskCard.vue
index c4fad014..49e8413b 100644
--- a/ui/src/components/kanban/TaskCard.vue
+++ b/ui/src/components/kanban/TaskCard.vue
@@ -173,13 +173,14 @@ const handleStartWorkSelect = (key: string | number) => {
.task-card {
cursor: pointer;
transition: all 0.2s ease;
- --n-color: var(--kanban-card-bg, var(--app-surface-color, #ffffff));
+ --n-color: var(--kanban-card-bg, var(--app-surface-raised, var(--app-surface-color, #ffffff)));
+ color: var(--app-text-primary, var(--app-text-color, #333333));
position: relative;
}
.task-card:hover {
transform: translateY(-2px);
- box-shadow: 0 8px 16px var(--n-box-shadow-color, rgba(0, 0, 0, 0.12));
+ box-shadow: 0 8px 16px var(--app-shadow, var(--n-box-shadow-color, rgba(0, 0, 0, 0.12)));
}
.task-card__header {
@@ -238,15 +239,15 @@ const handleStartWorkSelect = (key: string | number) => {
padding: 4px 10px;
border-radius: 999px;
border: none;
- background-color: var(--kanban-terminal-pill-bg, #eef2ff);
- color: var(--kanban-terminal-pill-fg, #4c1d95);
+ background-color: var(--kanban-terminal-pill-bg, var(--app-accent-soft, #eef2ff));
+ color: var(--kanban-terminal-pill-fg, var(--app-accent, #4c1d95));
font-size: 12px;
line-height: 1;
cursor: pointer;
}
.task-card__terminal-status:focus-visible {
- outline: 2px solid var(--n-color-primary);
+ outline: 2px solid var(--app-focus-ring, var(--n-color-primary));
outline-offset: 2px;
}
@@ -258,15 +259,15 @@ const handleStartWorkSelect = (key: string | number) => {
}
.task-card__status-dot.status-linked {
- background-color: var(--n-primary-color, #4f8ff7);
+ background-color: var(--app-accent, var(--n-primary-color, #4f8ff7));
}
.task-card__status-dot.status-waiting_input {
- background-color: #94a3b8;
+ background-color: var(--app-text-muted, #94a3b8);
}
.task-card__status-dot.status-unknown {
- background-color: #94a3b8;
+ background-color: var(--app-text-muted, #94a3b8);
}
.task-card__terminal-text {
diff --git a/ui/src/components/kanban/TaskDetailDrawer.vue b/ui/src/components/kanban/TaskDetailDrawer.vue
index aea67ea0..9d0932d9 100644
--- a/ui/src/components/kanban/TaskDetailDrawer.vue
+++ b/ui/src/components/kanban/TaskDetailDrawer.vue
@@ -757,12 +757,12 @@ const formatDate = (value: string) => dayjs(value).format('YYYY-MM-DD HH:mm');
gap: 8px;
margin-top: 4px;
font-size: 12px;
- color: var(--n-text-color-3);
+ color: var(--app-text-muted, var(--n-text-color-3));
}
.linked-session-time,
.linked-session-count {
- color: var(--n-text-color-3);
+ color: var(--app-text-muted, var(--n-text-color-3));
}
/* 可用 AI Session 列表 */
@@ -781,7 +781,7 @@ const formatDate = (value: string) => dayjs(value).format('YYYY-MM-DD HH:mm');
gap: 12px;
padding: 12px;
margin-bottom: 8px;
- background: var(--n-color-embedded);
+ background: var(--app-surface-sunken, var(--n-color-embedded));
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
@@ -789,12 +789,12 @@ const formatDate = (value: string) => dayjs(value).format('YYYY-MM-DD HH:mm');
}
.available-session-item:hover {
- border-color: var(--n-border-color);
+ border-color: var(--app-border, var(--n-border-color));
}
.available-session-item.selected {
- border-color: var(--n-primary-color);
- background: var(--n-color-embedded);
+ border-color: var(--app-accent, var(--n-primary-color));
+ background: var(--app-accent-soft, var(--n-color-embedded));
}
.available-session-info {
@@ -816,7 +816,7 @@ const formatDate = (value: string) => dayjs(value).format('YYYY-MM-DD HH:mm');
align-items: center;
gap: 8px;
font-size: 12px;
- color: var(--n-text-color-3);
+ color: var(--app-text-muted, var(--n-text-color-3));
}
/* 移动端样式 */
diff --git a/ui/src/components/notepad/NotePad.vue b/ui/src/components/notepad/NotePad.vue
index d38b1e5b..d752c2ae 100644
--- a/ui/src/components/notepad/NotePad.vue
+++ b/ui/src/components/notepad/NotePad.vue
@@ -513,9 +513,9 @@ onMounted(() => {
top: 80px;
right: 0;
bottom: 40px;
- background: var(--app-body-color, #fafafa);
- border-left: 1px solid var(--n-border-color, #e0e0e0);
- box-shadow: -2px 0 8px var(--n-box-shadow-color, rgba(0, 0, 0, 0.08));
+ background: var(--app-canvas);
+ border-left: 1px solid var(--app-border);
+ box-shadow: -2px 0 8px var(--app-shadow);
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
@@ -537,7 +537,7 @@ onMounted(() => {
}
.resize-handle:hover {
- background: var(--n-color-target, var(--n-primary-color, #1890ff));
+ background: var(--app-accent);
}
.notepad-toggle {
@@ -546,8 +546,8 @@ onMounted(() => {
top: 20px;
width: 28px;
height: 56px;
- background: var(--app-surface-color, #fafafa);
- border: 1px solid var(--n-border-color, #e0e0e0);
+ background: var(--app-surface);
+ border: 1px solid var(--app-border);
border-right: none;
border-radius: 6px 0 0 6px;
display: flex;
@@ -557,7 +557,8 @@ onMounted(() => {
transition:
background-color 0.2s,
top 0.3s ease;
- box-shadow: -2px 0 4px var(--n-box-shadow-color, rgba(0, 0, 0, 0.05));
+ box-shadow: -2px 0 4px var(--app-shadow);
+ color: var(--app-text-secondary);
}
.notepad-container.collapsed .notepad-toggle {
@@ -566,7 +567,8 @@ onMounted(() => {
}
.notepad-toggle:hover {
- background: var(--app-body-color, #f0f0f0);
+ background: var(--app-surface-hover);
+ color: var(--app-text-primary);
}
.notepad-panel {
@@ -574,13 +576,13 @@ onMounted(() => {
display: flex;
flex-direction: column;
overflow: hidden;
- background: var(--app-surface-color, #ffffff);
+ background: var(--app-surface);
}
.notepad-header {
padding: 8px;
- border-bottom: 1px solid var(--n-border-color, #e0e0e0);
- background: var(--app-body-color, #fafafa);
+ border-bottom: 1px solid var(--app-border);
+ background: var(--app-surface-sunken);
}
.scope-switcher {
@@ -593,7 +595,7 @@ onMounted(() => {
flex: 1;
padding: 8px;
overflow-y: auto;
- background: var(--app-surface-color, #ffffff);
+ background: var(--app-surface);
}
.tab-bar {
@@ -615,11 +617,11 @@ onMounted(() => {
align-items: center;
gap: 4px;
padding: 2px 8px;
- border: 1px solid var(--n-border-color, #dedede);
+ border: 1px solid var(--app-border);
border-radius: 4px;
font-size: 12px;
- background: var(--app-surface-color, #ffffff);
- color: var(--n-text-color-2, #333333);
+ background: var(--app-surface);
+ color: var(--app-text-secondary);
cursor: grab;
user-select: none;
transition:
@@ -629,13 +631,9 @@ onMounted(() => {
}
.tab-item.active {
- border-color: var(--n-primary-color, #1890ff);
- color: var(--n-primary-color, #1890ff);
- background: color-mix(
- in srgb,
- var(--n-primary-color, #1890ff) 8%,
- var(--app-surface-color, #ffffff)
- );
+ border-color: var(--app-accent);
+ color: var(--app-accent);
+ background: var(--app-accent-soft);
}
.tab-item:active {
@@ -660,7 +658,7 @@ onMounted(() => {
}
.tab-close-btn:hover {
- color: var(--n-color-error, #ff4d4f);
+ color: var(--app-error);
}
.tab-ghost {
@@ -676,7 +674,7 @@ onMounted(() => {
align-items: center;
gap: 6px;
font-size: 12px;
- color: var(--n-text-color-3, #8c8c8c);
+ color: var(--app-text-muted);
margin-top: 6px;
}
@@ -684,6 +682,8 @@ onMounted(() => {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
font-size: 12px;
line-height: 1.6;
- background: var(--app-surface-color, #ffffff);
+ background: var(--app-surface);
+ color: var(--app-text-primary);
+ caret-color: var(--app-accent);
}
diff --git a/ui/src/components/project/ProjectBrowser.vue b/ui/src/components/project/ProjectBrowser.vue
index ca0e1987..a42007e3 100644
--- a/ui/src/components/project/ProjectBrowser.vue
+++ b/ui/src/components/project/ProjectBrowser.vue
@@ -191,7 +191,7 @@