chore(history): import initial web product snapshot
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user