chore(history): import initial web product snapshot

This commit is contained in:
wmq
2026-07-25 00:30:00 +08:00
commit cf0eed46c3
131 changed files with 15693 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
import { useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Settings, ShieldCheck, KeyRound, Bell, Globe, Database, ServerCog, Languages, UserCog, Palette, FileUp, FileDown, Clock } from 'lucide-react'
import { toast, errorMessage } from '@/app/store/toast'
import { PageHeader } from '@/components/common/PageHeader'
import { Button } from '@/components/ui/button'
import { Card, CardHeader } from '@/components/ui/card'
import { Input, Field } from '@/components/ui/input'
import { Select } from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
import { Tabs } from '@/components/ui/tabs'
import { PermissionGate } from '@/components/common/PermissionGate'
import { getSettings, updateSettings } from '@/services/api/org'
import { useI18n } from '@/i18n'
const SECTIONS = [
{ key: 'company', label: '企业资料', icon: UserCog },
{ key: 'brand', label: '品牌设置', icon: Palette },
{ key: 'lang', label: '语言与时区', icon: Globe },
{ key: 'notifications', label: '消息通知', icon: Bell },
{ key: 'ai', label: 'AI回复设置', icon: Languages },
{ key: 'translate', label: '翻译设置', icon: Languages },
{ key: 'safety', label: '安全设置', icon: ShieldCheck },
{ key: 'api', label: 'API与Webhook', icon: KeyRound },
{ key: 'import-export', label: '数据导入导出', icon: Database },
{ key: 'deploy', label: '私有化部署', icon: ServerCog },
]
export function SettingsPage() {
const queryClient = useQueryClient()
const [section, setSection] = useState('company')
const { data: settings, isLoading } = useQuery({ queryKey: ['settings'], queryFn: getSettings })
const { locale, setLocale, t } = useI18n()
const updateMut = useMutation({
mutationFn: (payload: any) => updateSettings(payload),
onSuccess: () => { toast.success('设置已保存'); queryClient.invalidateQueries({ queryKey: ['settings'] }) },
onError: (e) => toast.error(errorMessage(e)),
})
return (
<div className="flex h-[calc(100vh-56px)]">
<div className="w-48 shrink-0 border-e border-border p-3 space-y-1 overflow-y-auto scrollbar-thin hidden md:block">
{SECTIONS.map((s) => (
<button key={s.key} onClick={() => setSection(s.key)} className={`flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm ${section === s.key ? 'bg-primary-50 font-medium text-primary-700' : 'text-muted-foreground hover:text-foreground'}`}>
<s.icon className="h-4 w-4" />{s.label}
</button>
))}
</div>
<div className="flex-1 overflow-y-auto p-4 md:p-6 scrollbar-thin">
{section === 'company' && settings && (
<Card>
<CardHeader title="企业资料" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4">
<Field label="企业名称"><Input defaultValue={settings.company.name} onChange={(e) => updateMut.mutate({ company: { ...settings.company, name: e.target.value } })} /></Field>
<Field label="所属行业"><Input defaultValue={settings.company.industry} /></Field>
<Field label="联系方式"><Input defaultValue={settings.company.contact} /></Field>
<Field label="邮箱"><Input defaultValue={settings.company.email} /></Field>
<Field label="企业简介" className="md:col-span-2"><Input defaultValue={settings.company.intro} /></Field>
<div className="md:col-span-2"><Button onClick={() => toast.success('企业资料已保存')}>保存修改</Button></div>
</div>
</Card>
)}
{section === 'ai' && settings && (
<Card>
<CardHeader title="AI 回复设置" />
<div className="space-y-4 p-4">
{[
{ key: 'autoGenerate', label: '自动生成回复建议' },
{ key: 'requireConfirm', label: '发送前必须人工确认' },
{ key: 'sentimentAnalysis', label: '启用情感分析' },
{ key: 'riskAlert', label: '启用风险预警' },
{ key: 'cultureCheck', label: '启用文化敏感检测' },
{ key: 'allowHistoryRef', label: '允许AI引用历史成功回复' },
].map((item) => (
<div key={item.key} className="flex items-center justify-between">
<span className="text-sm">{item.label}</span>
<Switch
checked={(settings.ai as any)[item.key]}
onCheckedChange={(v) => updateMut.mutate({ ai: { [item.key]: v } })}
/>
</div>
))}
</div>
</Card>
)}
{section === 'notifications' && settings && (
<Card>
<CardHeader title="消息通知设置" />
<div className="space-y-4 p-4">
{[
{ key: 'newInquiry', label: '新询盘提醒' },
{ key: 'highIntent', label: '高意向客户提醒' },
{ key: 'riskAlert', label: '风险预警提醒' },
{ key: 'taskDue', label: '跟进到期提醒' },
{ key: 'taskOverdue', label: '跟进逾期提醒' },
{ key: 'channelOffline', label: '渠道断开提醒' },
{ key: 'planExpire', label: '套餐到期提醒' },
].map((item) => (
<div key={item.key} className="flex items-center justify-between">
<span className="text-sm">{item.label}</span>
<Switch
checked={(settings.notification as any)[item.key]}
onCheckedChange={(v) => updateMut.mutate({ notification: { [item.key]: v } })}
/>
</div>
))}
</div>
</Card>
)}
{section === 'safety' && (
<Card>
<CardHeader title="安全设置" />
<div className="space-y-4 p-4">
<Field label="修改密码"><Input type="password" placeholder="输入新密码…" /></Field>
<div className="flex items-center justify-between"><span className="text-sm">两步验证</span><Switch checked={false} onCheckedChange={() => toast.info('两步验证设置(演示)')} /></div>
<div className="flex items-center justify-between"><span className="text-sm">数据导出权限</span><Switch checked={true} /></div>
<Field label="会话保存周期"><Select options={[{value:'6',label:'6个月'},{value:'12',label:'12个月'},{value:'24',label:'24个月'},{value:'forever',label:'永久'}]} /></Field>
<div className="rounded bg-emerald-50 p-3 text-sm text-emerald-700"><ShieldCheck className="inline h-4 w-4 mr-1" />全链路加密已启用(TLS 1.3 + AES-256)</div>
<Button onClick={() => toast.success('安全设置已保存')}>保存</Button>
</div>
</Card>
)}
{section === 'api' && (
<Card>
<CardHeader title="API 与 Webhook" />
<div className="space-y-3 p-4">
<p className="text-sm text-muted-foreground">通过 API 或 Webhook 将灵犀跨境的客户数据、消息与风险预警接入您的自有系统。</p>
<div className="rounded bg-muted p-3 font-mono text-sm break-all">lx_live_8f2k••••••••9d21</div>
<Button variant="outline" onClick={() => toast.success('新 API Key 已生成')}>生成新 Key</Button>
</div>
</Card>
)}
{section === 'lang' && (
<Card>
<CardHeader title="语言与时区" />
<div className="space-y-4 p-4">
<Field label="界面语言">
<Select value={locale} options={[{value:'zh',label:'简体中文'},{value:'en',label:'English'},{value:'ar',label:'العربية (RTL)'}]} onChange={(e) => setLocale(e.target.value as any)} />
</Field>
<Field label="时区"><Select options={[{value:'Asia/Shanghai',label:'东八区 (UTC+8)'},{value:'Asia/Dubai',label:'迪拜 (UTC+4)'},{value:'America/New_York',label:'纽约 (UTC-5)'}]} /></Field>
<Button onClick={() => toast.success('语言与时区已保存')}>保存</Button>
</div>
</Card>
)}
{section === 'deploy' && (
<Card>
<CardHeader title="私有化部署咨询" />
<div className="p-4 space-y-3">
<p className="text-sm text-muted-foreground">增长版/定制版支持本地或私有云部署,数据不出企业、全链路审计。</p>
<p className="text-sm">📞 咨询热线:0516-8888-6621(演示)</p>
<p className="text-sm">📧 邮箱:sales@ouhui.tech</p>
</div>
</Card>
)}
{![ 'company', 'ai', 'notifications', 'safety', 'api', 'lang', 'deploy'].includes(section) && (
<Card className="p-8 text-center">
<p className="text-sm text-muted-foreground">{section === 'brand' ? '品牌设置' : section === 'translate' ? '翻译设置' : section === 'import-export' ? '数据导入导出' : '此设置模块'}在演示环境中为简化版,接入后端后可完整配置。</p>
</Card>
)}
</div>
</div>
)
}