Merge pull request '添加计时器,麦克风,摄像头开关' (#45) from develop-frontend8 into develop 33分钟前 #46

Merged
huanghaosheng merged 60 commits from develop into main 2026-06-13 20:43:07 +08:00
5 changed files with 75 additions and 7 deletions
Showing only changes of commit 0227822120 - Show all commits

View File

@@ -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 {

View File

@@ -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<Theme>(loadTheme);
// 切换主题时更新 <html> 的 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 && (
<ConfigPanel
config={config}
theme={theme}
onUpdate={updateConfig}
onThemeChange={handleThemeChange}
onClose={() => setShowConfig(false)}
/>
)}

View File

@@ -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<SessionConfig>) => void;
onThemeChange: (theme: Theme) => void;
onClose: () => void;
}
export function ConfigPanel({ config, onUpdate, onClose }: ConfigPanelProps) {
export function ConfigPanel({ config, theme, onUpdate, onThemeChange, onClose }: ConfigPanelProps) {
return (
<div className="drawer-overlay" onClick={onClose}>
<div className="drawer" onClick={(e) => e.stopPropagation()}>
@@ -21,6 +23,24 @@ export function ConfigPanel({ config, onUpdate, onClose }: ConfigPanelProps) {
</div>
<div className="drawer__body">
<div className="config-group">
<div className="config-group__title"></div>
<label className="config-row">
<div className="config-row__info">
<span className="config-row__label"></span>
<span className="config-row__desc"></span>
</div>
<select
value={theme}
onChange={(e) => onThemeChange(e.target.value as Theme)}
>
<option value="dark"></option>
<option value="light"></option>
</select>
</label>
</div>
<div className="config-group">
<div className="config-group__title"></div>

View File

@@ -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 */ }
}

View File

@@ -11,6 +11,8 @@ export interface SessionConfig {
language: string;
}
export type Theme = "dark" | "light";
export interface Session {
sessionId: string;
createdAt: string;