refactor(settings): 重构设置页面布局和交互

- 优化页面布局,移除侧边栏采用独立页面设计
- 集成 ModelCardSelector 替代下拉选择
- 集成 FontSizePicker 支持字体大小调整
- 顶部导航栏添加主题切换按钮
- 移除冗余的偏好设置和危险区域板块
- 优化整体视觉效果和交互体验
This commit is contained in:
gaoziman 2025-12-19 13:58:22 +08:00
parent 6d1bf7275b
commit f81a1f0f2d

View File

@ -2,11 +2,11 @@
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { ArrowLeft, Download, Check, Loader2, Eye, EyeOff, RotateCcw } from 'lucide-react';
import { Sidebar, SidebarToggle } from '@/components/layout/Sidebar';
import { ArrowLeft, Download, Check, Loader2, Eye, EyeOff, RotateCcw, Moon, Sun, Sparkles, Trash2 } from 'lucide-react';
import { Toggle } from '@/components/ui/Toggle';
import { ModelCardSelector } from '@/components/ui/ModelCardSelector';
import { FontSizePicker } from '@/components/ui/FontSizePicker';
import { cn } from '@/lib/utils';
import { currentUser, chatHistories } from '@/data/mock';
import { useSettings, useModels, useTools } from '@/hooks/useSettings';
// 默认系统提示词
@ -44,7 +44,6 @@ const DEFAULT_SYSTEM_PROMPT = `你是一个专业、友好的 AI 助手。请遵
- `;
export default function SettingsPage() {
const [sidebarOpen, setSidebarOpen] = useState(true);
const { settings, loading, saving, updateSettings } = useSettings();
const { models, loading: modelsLoading } = useModels();
const { tools, loading: toolsLoading } = useTools();
@ -105,6 +104,17 @@ export default function SettingsPage() {
}
};
// 更新字体大小
const handleFontSizeChange = async (fontSize: number) => {
try {
await updateSettings({ fontSize });
// 实时应用到页面
document.documentElement.style.setProperty('--font-size-base', `${fontSize}px`);
} catch (error) {
console.error('Failed to update font size:', error);
}
};
// 切换工具
const handleToolToggle = async (toolId: string) => {
const currentTools = settings?.defaultTools || [];
@ -177,6 +187,17 @@ export default function SettingsPage() {
setTemperature(value);
};
// 切换主题
const handleToggleTheme = async () => {
const newTheme = settings?.theme === 'dark' ? 'light' : 'dark';
try {
await updateSettings({ theme: newTheme });
document.documentElement.setAttribute('data-theme', newTheme);
} catch (error) {
console.error('Failed to update theme:', error);
}
};
if (loading) {
return (
<div className="flex min-h-screen items-center justify-center">
@ -185,55 +206,46 @@ export default function SettingsPage() {
);
}
const isDarkMode = settings?.theme === 'dark';
return (
<div className="flex min-h-screen">
{/* 侧边栏 */}
<Sidebar
user={currentUser}
chatHistories={chatHistories}
isOpen={sidebarOpen}
onToggle={() => setSidebarOpen(!sidebarOpen)}
/>
{/* 主内容区 */}
<main
className={cn(
'flex-1 min-h-screen transition-all duration-300',
sidebarOpen ? 'ml-[var(--sidebar-width)]' : 'ml-0'
)}
>
{/* Header */}
<header className="h-[var(--header-height)] px-4 flex items-center">
<SidebarToggle onClick={() => setSidebarOpen(!sidebarOpen)} />
</header>
{/* Body */}
<div className="px-8 pb-8">
<div className="max-w-[800px] mx-auto">
{/* 返回链接 */}
<div className="min-h-screen bg-[var(--color-bg-secondary)]">
{/* 顶部导航栏 */}
<header className="sticky top-0 z-10 bg-[var(--color-bg-primary)] border-b border-[var(--color-border-light)]">
<div className="max-w-[900px] mx-auto px-6 h-16 flex items-center justify-between">
{/* 左侧:返回按钮和标题 */}
<div className="flex items-center gap-4">
<Link
href="/"
className="inline-flex items-center gap-2 text-[var(--color-text-secondary)] text-sm mb-4 hover:text-[var(--color-text-primary)] transition-colors"
className="p-2 -ml-2 rounded-lg text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-hover)] transition-colors"
title="返回聊天"
>
<ArrowLeft size={16} />
<span></span>
<ArrowLeft size={20} />
</Link>
{/* 页面标题 */}
<div className="mb-8">
<h1 className="text-2xl font-semibold text-[var(--color-text-primary)] mb-2">
</h1>
<p className="text-sm text-[var(--color-text-secondary)]">
</p>
<div className="flex items-center gap-2">
<Sparkles size={24} className="text-[var(--color-primary)]" />
<h1 className="text-xl font-semibold text-[var(--color-text-primary)]">Settings</h1>
</div>
</div>
{/* CCH 配置 */}
<SettingsSection
title="CCH 服务配置"
description="配置 Claude Code Hub 服务连接"
>
{/* 右侧:主题切换 */}
<button
onClick={handleToggleTheme}
className="p-2 rounded-lg text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-hover)] transition-colors"
title={isDarkMode ? '切换到浅色模式' : '切换到深色模式'}
>
{isDarkMode ? <Sun size={20} /> : <Moon size={20} />}
</button>
</div>
</header>
{/* 主内容区 */}
<main className="max-w-[900px] mx-auto px-6 py-8">
{/* CCH 配置 */}
<SettingsSection
title="CCH 服务配置"
description="配置 Claude Code Hub 服务连接"
>
<SettingsItem
label="CCH 服务地址"
description="Claude Code Hub 服务的 URL"
@ -315,23 +327,36 @@ export default function SettingsPage() {
title="AI 配置"
description="配置默认模型和工具"
>
<SettingsItem
label="默认模型"
description="为新对话选择默认 AI 模型"
>
<select
className="settings-select"
{/* 模型卡片选择 */}
<div className="px-5 py-4 border-b border-[var(--color-border-light)]">
<div className="text-sm font-medium text-[var(--color-text-primary)] mb-2">
</div>
<div className="text-xs text-[var(--color-text-tertiary)] mb-4">
AI
</div>
<ModelCardSelector
value={settings?.defaultModel || ''}
onChange={(e) => handleModelChange(e.target.value)}
onChange={handleModelChange}
disabled={modelsLoading || saving}
>
{models.map((model) => (
<option key={model.modelId} value={model.modelId}>
{model.displayName}
</option>
))}
</select>
</SettingsItem>
models={models}
/>
</div>
{/* 字体大小设置 */}
<div className="px-5 py-4 border-b border-[var(--color-border-light)]">
<div className="text-sm font-medium text-[var(--color-text-primary)] mb-2">
</div>
<div className="text-xs text-[var(--color-text-tertiary)] mb-4">
</div>
<FontSizePicker
value={settings?.fontSize || 15}
onChange={handleFontSizeChange}
disabled={saving}
/>
</div>
<SettingsItem
label="启用思考模式"
@ -361,7 +386,7 @@ export default function SettingsPage() {
onClick={() => handleToolToggle(tool.toolId)}
disabled={saving}
className={cn(
'inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-all',
'inline-flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all',
settings?.defaultTools?.includes(tool.toolId)
? 'bg-[var(--color-primary)] text-white'
: 'bg-[var(--color-bg-tertiary)] text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-secondary)]'
@ -465,45 +490,6 @@ export default function SettingsPage() {
</div>
</SettingsSection>
{/* 偏好设置 */}
<SettingsSection
title="偏好设置"
description="自定义您的体验"
>
<SettingsItem
label="主题"
description="选择您喜欢的主题"
>
<select
className="settings-select"
value={settings?.theme || 'light'}
onChange={(e) => handleThemeChange(e.target.value)}
disabled={saving}
>
<option value="system"></option>
<option value="light"></option>
<option value="dark"></option>
</select>
</SettingsItem>
<SettingsItem
label="语言"
description="选择您的首选语言"
>
<select
className="settings-select"
value={settings?.language || 'zh-CN'}
onChange={(e) => handleLanguageChange(e.target.value)}
disabled={saving}
>
<option value="en">English</option>
<option value="zh-CN"></option>
<option value="zh-TW"></option>
<option value="ja"></option>
</select>
</SettingsItem>
</SettingsSection>
{/* 数据与隐私 */}
<SettingsSection
title="数据与隐私"
@ -534,27 +520,12 @@ export default function SettingsPage() {
label="清除所有聊天"
description="删除所有对话历史"
>
<button className="btn-ghost text-red-600 hover:text-red-700">
<button className="btn-ghost text-red-600 hover:text-red-700 inline-flex items-center gap-2">
<Trash2 size={16} />
</button>
</SettingsItem>
</SettingsSection>
{/* 危险区域 */}
<SettingsSection
title="危险区域"
description="不可逆操作"
variant="danger"
>
<SettingsItem
label="删除账户"
description="永久删除您的账户和所有数据"
>
<button className="btn-danger"></button>
</SettingsItem>
</SettingsSection>
</div>
</div>
</main>
</div>
);
@ -572,7 +543,7 @@ function SettingsSection({ title, description, children, variant = 'default' }:
return (
<section
className={cn(
'bg-white border rounded-xl mb-6 overflow-hidden',
'bg-white border rounded-md mb-6 overflow-hidden',
variant === 'danger' ? 'border-red-200' : 'border-[var(--color-border)]'
)}
>