From 777d5cb6c378512017c6b8b06a24f11fb42f68ba Mon Sep 17 00:00:00 2001 From: mamamiyear Date: Tue, 7 Jul 2026 01:09:22 +0800 Subject: [PATCH] refactor: change dark theme color --- frontend/src/App.vue | 87 +++++++++++++-- frontend/src/views/SettingsView.vue | 157 +++++++++++++++++++++++----- frontend/wailsjs/go/models.ts | 2 + internal/domain/config.go | 14 +++ 4 files changed, 220 insertions(+), 40 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 7794ea8..535d593 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -11,7 +11,7 @@ * emits a `capture:overlay` event with the screenshot payload so the * frontend knows when (and what) to render. */ -import { onMounted, onUnmounted, ref } from 'vue' +import { computed, onMounted, onUnmounted, ref } from 'vue' import SettingsView from './views/SettingsView.vue' import Toast from './components/Toast.vue' import CaptureOverlay from './views/CaptureOverlay.vue' @@ -24,6 +24,7 @@ import { SaveRegionToRemote, SummarizeRegion, CancelRegion, + GetConfig, } from '../wailsjs/go/main/App' type HotkeyStatus = @@ -35,6 +36,25 @@ const hotkeyStatus = ref({ state: 'unknown' }) type Mode = 'settings' | 'overlay' const mode = ref('settings') +type ThemeMode = 'auto' | 'light' | 'dark' +const themeMode = ref('auto') +const appThemeClass = computed(() => `theme-${themeMode.value}`) + +function normalizeTheme(theme: unknown): ThemeMode { + return theme === 'light' || theme === 'dark' || theme === 'auto' + ? theme + : 'auto' +} + +async function loadThemePreference() { + try { + const cfg = await GetConfig() + themeMode.value = normalizeTheme((cfg as any)?.theme) + } catch { + themeMode.value = 'auto' + } +} + // `SettingsTab` is hoisted to the App shell so the sidebar (which lives // here) and the inner SettingsView (which renders the matching card) can // share a single source of truth without an event bus. @@ -224,6 +244,7 @@ async function onOverlayCancel() { } onMounted(() => { + void loadThemePreference() EventsOn('capture:start', () => { capturing.value = true }) @@ -286,7 +307,7 @@ onUnmounted(() => { /> -
+
Global hotkey is not active. @@ -312,7 +333,11 @@ onUnmounted(() => {
- +
@@ -344,6 +369,15 @@ onUnmounted(() => { color: #111827; font-size: 13px; } +.app-root.theme-light { + color-scheme: light; +} +.app-root.theme-dark { + color-scheme: dark; +} +.app-root.theme-auto { + color-scheme: light dark; +} .status-pill { font-size: 11px; @@ -498,33 +532,64 @@ onUnmounted(() => { } } +.app-root.theme-dark { + background: #1c1d22; + color: #e5e7eb; +} +.app-root.theme-dark .titlebar { + background: rgba(28, 29, 34, 0.7); + border-bottom-color: #2c2f36; +} +.app-root.theme-dark .titlebar-title { + color: #f3f4f6; +} +.app-root.theme-dark .sidebar { + background: rgba(0, 0, 0, 0.15); + border-right-color: #2c2f36; +} +.app-root.theme-dark .sidebar-item { + color: #d1d5db; +} +.app-root.theme-dark .sidebar-item:hover:not(.active) { + background: rgba(255, 255, 255, 0.05); +} +.app-root.theme-dark .sidebar-item.active { + background: rgba(59, 130, 246, 0.18); + color: #93c5fd; +} +.app-root.theme-dark .permission-banner { + background: rgba(120, 53, 15, 0.3); + border-color: rgba(253, 186, 116, 0.4); + color: #fed7aa; +} + @media (prefers-color-scheme: dark) { - .app-root { + .app-root.theme-auto { background: #1c1d22; color: #e5e7eb; } - .titlebar { + .app-root.theme-auto .titlebar { background: rgba(28, 29, 34, 0.7); border-bottom-color: #2c2f36; } - .titlebar-title { + .app-root.theme-auto .titlebar-title { color: #f3f4f6; } - .sidebar { + .app-root.theme-auto .sidebar { background: rgba(0, 0, 0, 0.15); border-right-color: #2c2f36; } - .sidebar-item { + .app-root.theme-auto .sidebar-item { color: #d1d5db; } - .sidebar-item:hover:not(.active) { + .app-root.theme-auto .sidebar-item:hover:not(.active) { background: rgba(255, 255, 255, 0.05); } - .sidebar-item.active { + .app-root.theme-auto .sidebar-item.active { background: rgba(59, 130, 246, 0.18); color: #93c5fd; } - .permission-banner { + .app-root.theme-auto .permission-banner { background: rgba(120, 53, 15, 0.3); border-color: rgba(253, 186, 116, 0.4); color: #fed7aa; diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 733f896..3047fb1 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -12,7 +12,7 @@ * * State flow: load() pulls config from Go on mount, save() pushes back. */ -import { computed, onMounted, ref } from 'vue' +import { computed, onMounted, ref, watch } from 'vue' import { GetConfig, SaveConfig, @@ -24,9 +24,15 @@ import { // Tab discriminator shared with the App shell. Kept as a string union so // the parent can pass the value without importing a type from this view. type TabId = 'general' | 's3' | 'ssh' | 'llm' +type ThemeMode = 'auto' | 'light' | 'dark' const props = defineProps<{ tab: TabId + theme: ThemeMode +}>() + +const emit = defineEmits<{ + (event: 'theme-change', theme: ThemeMode): void }>() interface S3Config { @@ -70,6 +76,7 @@ interface LLMConfig { interface AppConfig { hotkey: string + theme: ThemeMode s3: S3Config ssh: SSHConfig llm: LLMConfig @@ -81,6 +88,12 @@ const llmProviderOptions = [ { id: 'openai', label: 'ChatGPT / OpenAI-compatible' }, ] +const themeOptions: Array<{ id: ThemeMode; label: string }> = [ + { id: 'light', label: '浅色' }, + { id: 'dark', label: '深色' }, + { id: 'auto', label: '自动' }, +] + const DEFAULT_SUMMARY_PROMPT = `你是一个截图内容总结助手。请阅读截图,并用中文输出一段适合直接粘贴到聊天、工单、Issue 或 PR 中的说明。 要求: @@ -97,6 +110,7 @@ const DEFAULT_SUMMARY_PROMPT = `你是一个截图内容总结助手。请阅读 function defaultConfig(): AppConfig { return { hotkey: 'cmd+shift+a', + theme: 'auto', s3: { endpoint: '', region: 'us-east-1', @@ -184,6 +198,12 @@ const activeLLMProvider = computed(() => { return config.value.llm.providers[id] }) +function normalizeTheme(theme: unknown): ThemeMode { + return theme === 'light' || theme === 'dark' || theme === 'auto' + ? theme + : 'auto' +} + function flash(kind: 'ok' | 'err', text: string) { message.value = { kind, text } window.setTimeout(() => { @@ -205,7 +225,9 @@ async function load() { ...defaultConfig().llm.providers, ...((cfg as any).llm?.providers ?? {}), } + merged.theme = normalizeTheme((cfg as any).theme) config.value = merged + emit('theme-change', config.value.theme) } } @@ -249,11 +271,16 @@ async function manualCapture() { await CaptureNow() } +watch( + () => config.value.theme, + (theme) => emit('theme-change', normalizeTheme(theme)) +) + onMounted(load)