From f17c3c24a74853d19aa6feaaa6b4b6c37d0d8b38 Mon Sep 17 00:00:00 2001 From: cfy666 <3087823110@qq.com> Date: Sat, 13 Jun 2026 16:54:57 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20=E8=AE=BE=E7=BD=AE=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=E6=94=B9=E4=B8=BA=E5=8F=B3=E4=BE=A7=E6=8A=BD=E5=B1=89?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=B8=83=E5=B1=80=E8=B7=B3=E5=8A=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ConfigPanel 重写为 Drawer:右侧 320px 滑入面板 + 半透明遮罩 - 设置按钮始终可见,不依赖连接状态 - 抽屉不推挤主布局,点击遮罩或✕关闭 - 设置项分组显示,带功能描述文案 Co-Authored-By: Claude --- frontend/src/App.css | 114 +++++++++++++++--- frontend/src/App.tsx | 16 ++- frontend/src/components/ConfigPanel/index.tsx | 91 ++++++++------ 3 files changed, 158 insertions(+), 63 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index a9f8ac0..d215a75 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -140,7 +140,7 @@ body { /* ---- 左侧视频面板 ---- */ .video-panel { - width: 340px; + width: 400px; flex-shrink: 0; display: flex; flex-direction: column; @@ -392,44 +392,107 @@ body { border: 1px solid rgba(245, 158, 11, 0.2); } -/* ---- Config Panel ---- */ +/* ---- Drawer (Config Panel) ---- */ -.config-panel { +.drawer-overlay { + position: fixed; + inset: 0; + z-index: 100; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(2px); + animation: fadeIn 0.2s ease; +} + +.drawer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 320px; background: var(--color-surface); - border-bottom: 1px solid var(--color-border); - padding: 16px 24px; + border-left: 1px solid var(--color-border); + display: flex; + flex-direction: column; + animation: slideInRight 0.25s ease; + box-shadow: -8px 0 24px rgba(0, 0, 0, 0.3); +} + +.drawer__header { display: flex; align-items: center; - gap: 24px; + justify-content: space-between; + padding: 14px 16px; + border-bottom: 1px solid var(--color-border); flex-shrink: 0; } -.config-panel__header { - display: flex; - align-items: center; - gap: 8px; +.drawer__title { font-weight: 600; - font-size: 0.85rem; + font-size: 0.9rem; } -.config-panel__close { +.drawer__close { background: none; border: none; color: var(--color-text-muted); cursor: pointer; - font-size: 0.9rem; - padding: 2px 6px; + font-size: 1rem; + padding: 4px 8px; + border-radius: var(--radius-sm); + transition: all 0.15s; } -.config-panel__close:hover { +.drawer__close:hover { + background: var(--color-surface-2); color: var(--color-text); } +.drawer__body { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +/* ---- Config Group ---- */ + +.config-group { + margin-bottom: 20px; +} + +.config-group__title { + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--color-text-muted); + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid var(--color-border); +} + .config-row { display: flex; align-items: center; - gap: 8px; + justify-content: space-between; + padding: 10px 0; font-size: 0.82rem; + color: var(--color-text); + cursor: pointer; +} + +.config-row__info { + display: flex; + flex-direction: column; + gap: 2px; +} + +.config-row__label { + font-weight: 500; + color: var(--color-text); +} + +.config-row__desc { + font-size: 0.72rem; color: var(--color-text-muted); } @@ -439,8 +502,15 @@ body { color: var(--color-text); border: 1px solid var(--color-border); border-radius: var(--radius-sm); - padding: 4px 8px; + padding: 5px 10px; font-size: 0.8rem; + cursor: pointer; +} + +.config-row input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; } /* ---- Toast ---- */ @@ -559,6 +629,16 @@ body { to { opacity: 1; transform: translateX(0); } } +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideInRight { + from { transform: translateX(100%); } + to { transform: translateX(0); } +} + /* ---- Scrollbar ---- */ ::-webkit-scrollbar { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7c5918d..e5d6bae 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -54,15 +54,13 @@ function App() { {isConnected ? "已连接" : connectionStatus === "connecting" ? "连接中..." : "未连接"} - {isConnected && ( - - )} + diff --git a/frontend/src/components/ConfigPanel/index.tsx b/frontend/src/components/ConfigPanel/index.tsx index a56306f..192cd0b 100644 --- a/frontend/src/components/ConfigPanel/index.tsx +++ b/frontend/src/components/ConfigPanel/index.tsx @@ -1,5 +1,5 @@ // ============================================================ -// ConfigPanel — 会话配置面板 +// ConfigPanel — 右侧抽屉式配置面板 // 职责:TTS 开关、detail level 切换、语言选择 // ============================================================ @@ -13,43 +13,60 @@ interface ConfigPanelProps { export function ConfigPanel({ config, onUpdate, onClose }: ConfigPanelProps) { return ( -
-
- 设置 - +
+
e.stopPropagation()}> +
+ 设置 + +
+ +
+
+
会话
+ + + + + + +
+
- - - - - -
); } -- 2.49.1 From 022782212076d90318c92b2f1eef390da28a47b6 Mon Sep 17 00:00:00 2001 From: cfy666 <3087823110@qq.com> Date: Sat, 13 Jun 2026 17:08:56 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=B7=B1?= =?UTF-8?q?=E8=89=B2/=E6=B5=85=E8=89=B2=E4=B8=BB=E9=A2=98=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - types 新增 Theme 类型(dark/light) - storage 新增 loadTheme/saveTheme 持久化主题偏好 - ConfigPanel 新增外观分组,主题下拉选择 - App.tsx 通过 data-theme 属性挂载到 html 元素 - App.css 新增 [data-theme=light] 亮色变量覆盖 Co-Authored-By: Claude --- frontend/src/App.css | 18 +++++++++++-- frontend/src/App.tsx | 17 +++++++++++- frontend/src/components/ConfigPanel/index.tsx | 26 ++++++++++++++++--- frontend/src/lib/storage.ts | 19 +++++++++++++- frontend/src/types/index.ts | 2 ++ 5 files changed, 75 insertions(+), 7 deletions(-) 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; -- 2.49.1