diff --git a/frontend/src/App.css b/frontend/src/App.css index d215a75..b6260b9 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -19,6 +19,20 @@ --radius-sm: 6px; } +[data-theme="light"] { + --color-primary: #2563eb; + --color-primary-hover: #1d4ed8; + --color-bg: #f1f5f9; + --color-surface: #ffffff; + --color-surface-2: #f8fafc; + --color-text: #0f172a; + --color-text-muted: #64748b; + --color-border: #e2e8f0; + --color-success: #16a34a; + --color-warning: #d97706; + --color-error: #dc2626; +} + * { margin: 0; padding: 0; @@ -151,7 +165,7 @@ body { .video-container { position: relative; aspect-ratio: 4 / 3; - background: #0a0e17; + background: var(--color-surface-2); border-radius: var(--radius); overflow: hidden; border: 1px solid var(--color-border); @@ -167,7 +181,7 @@ body { width: 100%; height: 100%; object-fit: cover; - background: #000; + background: #1a1a2e; } .video-placeholder { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e5d6bae..bde81ef 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,16 +2,29 @@ // CamTalk — 主应用组件(Web 端双栏布局) // ============================================================ -import { useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useVisionSession } from "./hooks/useVisionSession"; import { VideoPreview } from "./components/VideoPreview"; import { ChatPanel } from "./components/ChatPanel"; import { ConfigPanel } from "./components/ConfigPanel"; import { ToastContainer } from "./components/Toast"; +import { loadTheme, saveTheme } from "./lib/storage"; +import type { Theme } from "./types"; import "./App.css"; function App() { const [showConfig, setShowConfig] = useState(false); + const [theme, setTheme] = useState(loadTheme); + + // 切换主题时更新 的 data-theme 属性 + useEffect(() => { + document.documentElement.setAttribute("data-theme", theme); + }, [theme]); + + const handleThemeChange = useCallback((t: Theme) => { + setTheme(t); + saveTheme(t); + }, []); const { messages, @@ -67,7 +80,9 @@ function App() { {showConfig && ( setShowConfig(false)} /> )} diff --git a/frontend/src/components/ConfigPanel/index.tsx b/frontend/src/components/ConfigPanel/index.tsx index 192cd0b..3844e6d 100644 --- a/frontend/src/components/ConfigPanel/index.tsx +++ b/frontend/src/components/ConfigPanel/index.tsx @@ -1,17 +1,19 @@ // ============================================================ // ConfigPanel — 右侧抽屉式配置面板 -// 职责:TTS 开关、detail level 切换、语言选择 +// 职责:主题切换、TTS 开关、detail level 切换、语言选择 // ============================================================ -import type { SessionConfig } from "../../types"; +import type { SessionConfig, Theme } from "../../types"; interface ConfigPanelProps { config: SessionConfig; + theme: Theme; onUpdate: (partial: Partial) => void; + onThemeChange: (theme: Theme) => void; onClose: () => void; } -export function ConfigPanel({ config, onUpdate, onClose }: ConfigPanelProps) { +export function ConfigPanel({ config, theme, onUpdate, onThemeChange, onClose }: ConfigPanelProps) { return (
e.stopPropagation()}> @@ -21,6 +23,24 @@ export function ConfigPanel({ config, onUpdate, onClose }: ConfigPanelProps) {
+
+
外观
+ + +
+
会话
diff --git a/frontend/src/lib/storage.ts b/frontend/src/lib/storage.ts index 34fc369..fb12324 100644 --- a/frontend/src/lib/storage.ts +++ b/frontend/src/lib/storage.ts @@ -3,9 +3,10 @@ // 职责:会话配置持久化 // ============================================================ -import type { SessionConfig } from "../types"; +import type { SessionConfig, Theme } from "../types"; const CONFIG_KEY = "camtalk:config"; +const THEME_KEY = "camtalk:theme"; const DEFAULT_CONFIG: SessionConfig = { ttsEnabled: true, @@ -33,3 +34,19 @@ export function saveConfig(config: SessionConfig): void { // localStorage 不可用时静默失败 } } + +/** 加载主题偏好 */ +export function loadTheme(): Theme { + try { + const raw = localStorage.getItem(THEME_KEY); + if (raw === "light" || raw === "dark") return raw; + } catch { /* ignore */ } + return "dark"; +} + +/** 保存主题偏好 */ +export function saveTheme(theme: Theme): void { + try { + localStorage.setItem(THEME_KEY, theme); + } catch { /* ignore */ } +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 4cad08a..0f29ec5 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -11,6 +11,8 @@ export interface SessionConfig { language: string; } +export type Theme = "dark" | "light"; + export interface Session { sessionId: string; createdAt: string;