763 lines
26 KiB
TypeScript
763 lines
26 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
import { useState, useRef, useEffect } from "react";
|
|
import { useUser } from "../user-context";
|
|
import { useRouter } from "next/navigation";
|
|
import { cn } from "@/lib/utils";
|
|
import {
|
|
Settings,
|
|
User,
|
|
Bell,
|
|
Shield,
|
|
Database,
|
|
Mail,
|
|
Globe,
|
|
Palette,
|
|
Monitor,
|
|
Moon,
|
|
Sun,
|
|
Check,
|
|
ChevronDown,
|
|
Search,
|
|
X,
|
|
Save,
|
|
RotateCcw
|
|
} from "lucide-react";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
|
|
interface Tag {
|
|
id: string;
|
|
label: string;
|
|
color?: string;
|
|
}
|
|
|
|
interface TagInputProps {
|
|
onChange?: (tags: Array<Tag>) => void;
|
|
defaultTags?: Array<Tag>;
|
|
suggestions?: Array<Tag>;
|
|
maxTags?: number;
|
|
label?: string;
|
|
placeholder?: string;
|
|
error?: string;
|
|
}
|
|
|
|
function useTags({
|
|
onChange,
|
|
defaultTags = [],
|
|
maxTags = 10,
|
|
}: {
|
|
onChange?: (tags: Tag[]) => void;
|
|
defaultTags?: Tag[];
|
|
maxTags?: number;
|
|
}) {
|
|
const [tags, setTags] = useState<Tag[]>(defaultTags);
|
|
|
|
function addTag(tag: Tag) {
|
|
if (tags.length >= maxTags) return;
|
|
const newTags = [...tags, tag];
|
|
setTags(newTags);
|
|
onChange?.(newTags);
|
|
return newTags;
|
|
}
|
|
|
|
function removeTag(tagId: string) {
|
|
const newTags = tags.filter((t) => t.id !== tagId);
|
|
setTags(newTags);
|
|
onChange?.(newTags);
|
|
return newTags;
|
|
}
|
|
|
|
function removeLastTag() {
|
|
if (tags.length === 0) return;
|
|
return removeTag(tags[tags.length - 1].id);
|
|
}
|
|
|
|
return {
|
|
tags,
|
|
setTags,
|
|
addTag,
|
|
removeTag,
|
|
removeLastTag,
|
|
hasReachedMax: tags.length >= maxTags,
|
|
};
|
|
}
|
|
|
|
function useClickOutside<T extends HTMLElement = HTMLElement>(
|
|
ref: React.RefObject<T>,
|
|
handler: (event: MouseEvent | TouchEvent) => void,
|
|
mouseEvent: 'mousedown' | 'mouseup' = 'mousedown'
|
|
): void {
|
|
useEffect(() => {
|
|
const listener = (event: MouseEvent | TouchEvent) => {
|
|
const el = ref?.current;
|
|
const target = event.target;
|
|
|
|
if (!el || !target || el.contains(target as Node)) {
|
|
return;
|
|
}
|
|
|
|
handler(event);
|
|
};
|
|
|
|
document.addEventListener(mouseEvent, listener);
|
|
document.addEventListener('touchstart', listener);
|
|
|
|
return () => {
|
|
document.removeEventListener(mouseEvent, listener);
|
|
document.removeEventListener('touchstart', listener);
|
|
};
|
|
}, [ref, handler, mouseEvent]);
|
|
}
|
|
|
|
const tagStyles = {
|
|
base: "inline-flex items-center gap-1.5 px-2 py-0.5 text-sm rounded-md transition-colors duration-150",
|
|
colors: {
|
|
blue: "bg-blue-50 text-blue-700 border border-blue-200 hover:border-blue-300 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-700/30 dark:hover:border-blue-600/50",
|
|
purple: "bg-purple-50 text-purple-700 border border-purple-200 hover:border-purple-300 dark:bg-purple-900/30 dark:text-purple-300 dark:border-purple-700/30 dark:hover:border-purple-600/50",
|
|
green: "bg-green-50 text-green-700 border border-green-200 hover:border-green-300 dark:bg-green-900/30 dark:text-green-300 dark:border-green-700/30 dark:hover:border-green-600/50",
|
|
},
|
|
};
|
|
|
|
function TagInput({
|
|
onChange,
|
|
defaultTags = [],
|
|
suggestions = [
|
|
{ id: "nextjs", label: "Next.js" },
|
|
{ id: "react", label: "React" },
|
|
{ id: "tailwind", label: "Tailwind" },
|
|
],
|
|
maxTags = 10,
|
|
label = "Tags",
|
|
placeholder = "Add tags...",
|
|
error,
|
|
}: TagInputProps) {
|
|
const { tags, addTag, removeTag, removeLastTag } = useTags({
|
|
onChange,
|
|
defaultTags,
|
|
maxTags,
|
|
});
|
|
const [input, setInput] = useState("");
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const filteredSuggestions = suggestions
|
|
.filter(
|
|
(suggestion: Tag) =>
|
|
typeof suggestion.label === "string" &&
|
|
typeof input === "string" &&
|
|
suggestion.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 &&
|
|
!tags.some((tag: Tag) => tag.id === suggestion.id)
|
|
)
|
|
.slice(0, 5);
|
|
|
|
const canAddNewTag =
|
|
input.length > 0 &&
|
|
!suggestions.some((s) => s.label.toLowerCase() === input.toLowerCase());
|
|
|
|
function handleKeyDown(e: React.KeyboardEvent) {
|
|
if (e.key === "Backspace" && input === "" && tags.length > 0) {
|
|
removeLastTag();
|
|
} else if (e.key === "Enter" && input) {
|
|
e.preventDefault();
|
|
if (isOpen && filteredSuggestions[selectedIndex]) {
|
|
addTag(filteredSuggestions[selectedIndex]);
|
|
setInput("");
|
|
setIsOpen(false);
|
|
} else if (canAddNewTag) {
|
|
addTag({ id: input, label: input });
|
|
setInput("");
|
|
setIsOpen(false);
|
|
}
|
|
} else if (e.key === "Escape") {
|
|
setIsOpen(false);
|
|
}
|
|
}
|
|
|
|
useClickOutside(containerRef as React.RefObject<HTMLElement>, () => setIsOpen(false));
|
|
|
|
return (
|
|
<div className="w-full space-y-2" ref={containerRef}>
|
|
{label && (
|
|
<Label className="text-sm font-medium text-foreground" htmlFor={label}>
|
|
{label}
|
|
</Label>
|
|
)}
|
|
|
|
<div
|
|
className={cn(
|
|
"min-h-[2.5rem] p-1.5 rounded-lg border border-border bg-background",
|
|
"focus-within:ring-2 focus-within:ring-ring/20",
|
|
"flex items-center flex-row flex-wrap gap-1.5 relative"
|
|
)}
|
|
>
|
|
{tags.map((tag) => (
|
|
<span
|
|
key={tag.id}
|
|
className={cn(
|
|
tagStyles.base,
|
|
tag.color || tagStyles.colors.blue
|
|
)}
|
|
>
|
|
{tag.label}
|
|
<button
|
|
type="button"
|
|
onClick={() => removeTag(tag.id)}
|
|
className="text-current/60 hover:text-current transition-colors"
|
|
>
|
|
<X className="w-3.5 h-3.5" />
|
|
</button>
|
|
</span>
|
|
))}
|
|
|
|
<input
|
|
ref={inputRef}
|
|
type="text"
|
|
value={input}
|
|
onChange={(e) => {
|
|
setInput(e.target.value);
|
|
setIsOpen(true);
|
|
setSelectedIndex(0);
|
|
}}
|
|
onFocus={() => setIsOpen(true)}
|
|
onKeyDown={handleKeyDown}
|
|
placeholder={tags.length === 0 ? placeholder : ""}
|
|
className="flex-1 min-w-[120px] bg-transparent h-7 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none"
|
|
/>
|
|
|
|
{isOpen && (input || filteredSuggestions.length > 0) && (
|
|
<div className="absolute left-0 right-0 top-full mt-1 z-50 max-h-[300px] overflow-y-auto bg-popover border border-border rounded-lg shadow-lg overflow-hidden">
|
|
<div className="px-2 py-1.5 border-b border-border">
|
|
<span className="text-xs font-medium text-muted-foreground">
|
|
选择标签或创建新标签
|
|
</span>
|
|
</div>
|
|
<div className="p-1.5 flex flex-wrap gap-1.5">
|
|
{filteredSuggestions.map((suggestion, index) => (
|
|
<button
|
|
type="button"
|
|
key={suggestion.id}
|
|
onClick={() => {
|
|
addTag(suggestion);
|
|
setInput("");
|
|
setIsOpen(false);
|
|
}}
|
|
className={cn(
|
|
tagStyles.base,
|
|
selectedIndex === index
|
|
? tagStyles.colors.blue
|
|
: "bg-muted text-muted-foreground border border-border hover:border-border/80"
|
|
)}
|
|
>
|
|
{suggestion.label}
|
|
{selectedIndex === index && <Check className="w-3.5 h-3.5" />}
|
|
</button>
|
|
))}
|
|
{canAddNewTag && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
const colorKeys = Object.keys(tagStyles.colors) as Array<keyof typeof tagStyles.colors>;
|
|
const randomColor = tagStyles.colors[colorKeys[Math.floor(Math.random() * colorKeys.length)]];
|
|
addTag({
|
|
id: input,
|
|
label: input,
|
|
color: randomColor,
|
|
});
|
|
setInput("");
|
|
setIsOpen(false);
|
|
}}
|
|
className={cn(
|
|
tagStyles.base,
|
|
"bg-muted text-muted-foreground border border-border hover:border-border/80"
|
|
)}
|
|
>
|
|
创建 "{input}"
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{error && (
|
|
<p className="text-sm text-destructive">{error}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface SettingsSectionProps {
|
|
icon: React.ReactNode;
|
|
title: string;
|
|
description: string;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
function SettingsSection({ icon, title, description, children }: SettingsSectionProps) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-3">
|
|
{icon}
|
|
{title}
|
|
</CardTitle>
|
|
<CardDescription>{description}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{children}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
interface SettingItemProps {
|
|
label: string;
|
|
description?: string;
|
|
children: React.ReactNode;
|
|
badge?: string;
|
|
}
|
|
|
|
function SettingItem({ label, description, children, badge }: SettingItemProps) {
|
|
return (
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<div className="flex items-center gap-2">
|
|
<Label className="text-sm font-medium">{label}</Label>
|
|
{badge && <Badge variant="secondary" className="text-xs">{badge}</Badge>}
|
|
</div>
|
|
{description && (
|
|
<p className="text-sm text-muted-foreground">{description}</p>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function MePage() {
|
|
const { isLoading, user } = useUser();
|
|
const router = useRouter();
|
|
|
|
const [settings, setSettings] = useState({
|
|
emailNotifications: true,
|
|
pushNotifications: false,
|
|
marketingEmails: true,
|
|
twoFactorAuth: false,
|
|
sessionTimeout: "30",
|
|
theme: "system",
|
|
language: "zh",
|
|
timezone: "Asia/Shanghai",
|
|
autoSave: true,
|
|
dataRetention: "90",
|
|
apiAccess: false,
|
|
debugMode: false,
|
|
});
|
|
|
|
const [profile, setProfile] = useState({
|
|
name: user?.name || "",
|
|
email: user?.email || "",
|
|
role: "用户",
|
|
department: "IT",
|
|
bio: "热爱技术的开发者,专注于创造优秀的用户体验。",
|
|
});
|
|
|
|
const [tags, setTags] = useState<Tag[]>([
|
|
{ id: "frontend", label: "前端开发", color: tagStyles.colors.blue },
|
|
{ id: "react", label: "React", color: tagStyles.colors.green },
|
|
]);
|
|
|
|
// 认证检查已在 layout.tsx 中处理,无需在此重复检查
|
|
|
|
useEffect(() => {
|
|
if (user) {
|
|
setProfile(prev => ({
|
|
...prev,
|
|
name: user.name || "",
|
|
email: user.email || "",
|
|
}));
|
|
}
|
|
}, [user]);
|
|
|
|
const handleSettingChange = (key: string, value: any) => {
|
|
setSettings(prev => ({ ...prev, [key]: value }));
|
|
};
|
|
|
|
const handleProfileChange = (key: string, value: string) => {
|
|
setProfile(prev => ({ ...prev, [key]: value }));
|
|
};
|
|
|
|
const handleSave = () => {
|
|
console.log("Settings saved:", settings);
|
|
console.log("Profile saved:", profile);
|
|
console.log("Tags saved:", tags);
|
|
// TODO: 实际保存到后端
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setSettings({
|
|
emailNotifications: true,
|
|
pushNotifications: false,
|
|
marketingEmails: true,
|
|
twoFactorAuth: false,
|
|
sessionTimeout: "30",
|
|
theme: "system",
|
|
language: "zh",
|
|
timezone: "Asia/Shanghai",
|
|
autoSave: true,
|
|
dataRetention: "90",
|
|
apiAccess: false,
|
|
debugMode: false,
|
|
});
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center h-screen">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<div className="container mx-auto p-6 max-w-6xl">
|
|
{/* Header */}
|
|
<div className="mb-8">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-foreground">个人设置</h1>
|
|
<p className="text-muted-foreground mt-2">
|
|
管理您的个人资料、偏好设置和账户安全
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<Button variant="outline" onClick={handleReset}>
|
|
<RotateCcw className="w-4 h-4 mr-2" />
|
|
重置
|
|
</Button>
|
|
<Button onClick={handleSave}>
|
|
<Save className="w-4 h-4 mr-2" />
|
|
保存设置
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Profile Section */}
|
|
<div className="lg:col-span-1">
|
|
<SettingsSection
|
|
icon={<User className="w-5 h-5 text-blue-600" />}
|
|
title="个人资料"
|
|
description="管理您的个人信息和账户设置"
|
|
>
|
|
<div className="flex items-center space-x-4 mb-6">
|
|
<Avatar className="w-16 h-16">
|
|
<AvatarImage src={user?.avatar || "/placeholder-avatar.jpg"} />
|
|
<AvatarFallback className="text-lg font-semibold">
|
|
{profile.name.split(' ').map(n => n[0] || '用').join('').slice(0, 2)}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<h3 className="font-semibold text-lg">{profile.name || '用户'}</h3>
|
|
<p className="text-sm text-muted-foreground">{profile.role}</p>
|
|
<Badge variant="outline" className="mt-1">{profile.department}</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="name">姓名</Label>
|
|
<Input
|
|
id="name"
|
|
value={profile.name}
|
|
onChange={(e) => handleProfileChange('name', e.target.value)}
|
|
className="mt-1"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="email">邮箱</Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
value={profile.email}
|
|
onChange={(e) => handleProfileChange('email', e.target.value)}
|
|
className="mt-1"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="department">部门</Label>
|
|
<Select value={profile.department} onValueChange={(value) => handleProfileChange('department', value)}>
|
|
<SelectTrigger className="mt-1">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="IT">信息技术部</SelectItem>
|
|
<SelectItem value="HR">人力资源部</SelectItem>
|
|
<SelectItem value="Finance">财务部</SelectItem>
|
|
<SelectItem value="Marketing">市场部</SelectItem>
|
|
<SelectItem value="Design">设计部</SelectItem>
|
|
<SelectItem value="Product">产品部</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="bio">个人简介</Label>
|
|
<Textarea
|
|
id="bio"
|
|
value={profile.bio}
|
|
onChange={(e) => handleProfileChange('bio', e.target.value)}
|
|
className="mt-1"
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<TagInput
|
|
label="技能标签"
|
|
placeholder="添加技能标签..."
|
|
defaultTags={tags}
|
|
suggestions={[
|
|
{ id: "javascript", label: "JavaScript" },
|
|
{ id: "typescript", label: "TypeScript" },
|
|
{ id: "python", label: "Python" },
|
|
{ id: "react", label: "React" },
|
|
{ id: "nextjs", label: "Next.js" },
|
|
{ id: "nodejs", label: "Node.js" },
|
|
{ id: "docker", label: "Docker" },
|
|
{ id: "kubernetes", label: "Kubernetes" },
|
|
{ id: "aws", label: "AWS" },
|
|
{ id: "ui-design", label: "UI设计" },
|
|
]}
|
|
onChange={setTags}
|
|
maxTags={8}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</SettingsSection>
|
|
</div>
|
|
|
|
{/* Settings Sections */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
{/* Notification Settings */}
|
|
<SettingsSection
|
|
icon={<Bell className="w-5 h-5 text-green-600" />}
|
|
title="通知设置"
|
|
description="配置您希望接收的通知类型"
|
|
>
|
|
<SettingItem
|
|
label="邮件通知"
|
|
description="接收重要系统更新的邮件通知"
|
|
>
|
|
<Switch
|
|
checked={settings.emailNotifications}
|
|
onCheckedChange={(checked) => handleSettingChange('emailNotifications', checked)}
|
|
/>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label="推送通知"
|
|
description="在浏览器中接收实时推送通知"
|
|
>
|
|
<Switch
|
|
checked={settings.pushNotifications}
|
|
onCheckedChange={(checked) => handleSettingChange('pushNotifications', checked)}
|
|
/>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label="营销邮件"
|
|
description="接收产品更新和营销信息"
|
|
>
|
|
<Switch
|
|
checked={settings.marketingEmails}
|
|
onCheckedChange={(checked) => handleSettingChange('marketingEmails', checked)}
|
|
/>
|
|
</SettingItem>
|
|
</SettingsSection>
|
|
|
|
{/* Security Settings */}
|
|
<SettingsSection
|
|
icon={<Shield className="w-5 h-5 text-red-600" />}
|
|
title="安全设置"
|
|
description="管理您的账户安全和访问控制"
|
|
>
|
|
<SettingItem
|
|
label="双因素认证"
|
|
description="为您的账户添加额外的安全层"
|
|
badge="推荐"
|
|
>
|
|
<Switch
|
|
checked={settings.twoFactorAuth}
|
|
onCheckedChange={(checked) => handleSettingChange('twoFactorAuth', checked)}
|
|
/>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label="会话超时"
|
|
description="设置自动登出的时间间隔"
|
|
>
|
|
<Select value={settings.sessionTimeout} onValueChange={(value) => handleSettingChange('sessionTimeout', value)}>
|
|
<SelectTrigger className="w-32">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="15">15分钟</SelectItem>
|
|
<SelectItem value="30">30分钟</SelectItem>
|
|
<SelectItem value="60">1小时</SelectItem>
|
|
<SelectItem value="120">2小时</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label="API访问"
|
|
description="允许第三方应用访问您的数据"
|
|
>
|
|
<Switch
|
|
checked={settings.apiAccess}
|
|
onCheckedChange={(checked) => handleSettingChange('apiAccess', checked)}
|
|
/>
|
|
</SettingItem>
|
|
</SettingsSection>
|
|
|
|
{/* System Preferences */}
|
|
<SettingsSection
|
|
icon={<Settings className="w-5 h-5 text-purple-600" />}
|
|
title="系统偏好"
|
|
description="自定义您的系统界面和行为"
|
|
>
|
|
<SettingItem
|
|
label="主题"
|
|
description="选择您偏好的界面主题"
|
|
>
|
|
<Select value={settings.theme} onValueChange={(value) => handleSettingChange('theme', value)}>
|
|
<SelectTrigger className="w-32">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="light">
|
|
<div className="flex items-center gap-2">
|
|
<Sun className="w-4 h-4" />
|
|
浅色
|
|
</div>
|
|
</SelectItem>
|
|
<SelectItem value="dark">
|
|
<div className="flex items-center gap-2">
|
|
<Moon className="w-4 h-4" />
|
|
深色
|
|
</div>
|
|
</SelectItem>
|
|
<SelectItem value="system">
|
|
<div className="flex items-center gap-2">
|
|
<Monitor className="w-4 h-4" />
|
|
系统
|
|
</div>
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label="语言"
|
|
description="选择界面显示语言"
|
|
>
|
|
<Select value={settings.language} onValueChange={(value) => handleSettingChange('language', value)}>
|
|
<SelectTrigger className="w-32">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="zh">中文</SelectItem>
|
|
<SelectItem value="en">English</SelectItem>
|
|
<SelectItem value="ja">日本語</SelectItem>
|
|
<SelectItem value="ko">한국어</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label="时区"
|
|
description="设置您的本地时区"
|
|
>
|
|
<Select value={settings.timezone} onValueChange={(value) => handleSettingChange('timezone', value)}>
|
|
<SelectTrigger className="w-40">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="UTC">UTC</SelectItem>
|
|
<SelectItem value="Asia/Shanghai">Asia/Shanghai</SelectItem>
|
|
<SelectItem value="Asia/Tokyo">Asia/Tokyo</SelectItem>
|
|
<SelectItem value="America/New_York">America/New_York</SelectItem>
|
|
<SelectItem value="Europe/London">Europe/London</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label="自动保存"
|
|
description="自动保存您的工作进度"
|
|
>
|
|
<Switch
|
|
checked={settings.autoSave}
|
|
onCheckedChange={(checked) => handleSettingChange('autoSave', checked)}
|
|
/>
|
|
</SettingItem>
|
|
</SettingsSection>
|
|
|
|
{/* Data Management */}
|
|
<SettingsSection
|
|
icon={<Database className="w-5 h-5 text-orange-600" />}
|
|
title="数据管理"
|
|
description="管理您的数据存储和隐私设置"
|
|
>
|
|
<SettingItem
|
|
label="数据保留期"
|
|
description="设置数据自动删除的时间"
|
|
>
|
|
<Select value={settings.dataRetention} onValueChange={(value) => handleSettingChange('dataRetention', value)}>
|
|
<SelectTrigger className="w-32">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="30">30天</SelectItem>
|
|
<SelectItem value="90">90天</SelectItem>
|
|
<SelectItem value="180">180天</SelectItem>
|
|
<SelectItem value="365">1年</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label="调试模式"
|
|
description="启用详细的系统日志记录"
|
|
badge="开发"
|
|
>
|
|
<Switch
|
|
checked={settings.debugMode}
|
|
onCheckedChange={(checked) => handleSettingChange('debugMode', checked)}
|
|
/>
|
|
</SettingItem>
|
|
</SettingsSection>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |