import { useEffect, useMemo, useState } from 'react' import { Button, Form, message } from 'antd' import { PlusOutlined } from '@ant-design/icons' import { createChannel, createStaff, deleteStaff, getChannels, getCustomerTags, getStaff, getTenantSettings, updateChannel, updateStaff, updateTenantSettings, type Channel, type CustomerTag, type StaffUser, type TenantSettings, type WelcomeSegment, } from '@/services/api' import { useAuth, usePermission } from '@/stores/auth' import { tabGroups, tabItems } from './settings/tabs' import { typeMeta } from './settings/channelMeta' import { normalizeWelcomeFromSettings } from './settings/welcome' import { parseWorkSlot, weekDays } from './settings/worktime' import BasicSection from './settings/BasicSection' import ChannelsSection from './settings/ChannelsSection' import AssignmentSection from './settings/AssignmentSection' import StaffSection from './settings/StaffSection' import StaffModal from './settings/StaffModal' import AutoReplySection from './settings/AutoReplySection' import WorktimeSection from './settings/WorktimeSection' import NotifySection from './settings/NotifySection' import TagsSection from './settings/TagsSection' import TagModal from './settings/TagModal' const Settings = () => { const { user, permissions } = useAuth() const canViewStaff = usePermission('settings.staff') const canEditAssignment = usePermission('settings.assign_rule') const visibleTabGroups = useMemo( () => tabGroups .map(group => ({ ...group, items: group.items.filter(item => permissions.has(item.permission)) })) .filter(group => group.items.length > 0), [permissions], ) const visibleTabItems = useMemo(() => visibleTabGroups.flatMap(group => group.items), [visibleTabGroups]) const [activeTab, setActiveTab] = useState(() => visibleTabItems[0]?.key || '') const [basicForm] = Form.useForm() const [autoReplyForm] = Form.useForm() const [workForm] = Form.useForm() const [notifyForm] = Form.useForm() const [assignForm] = Form.useForm() const [staffForm] = Form.useForm() const [channels, setChannels] = useState([]) const [settings, setSettings] = useState(null) const [loadingChannels, setLoadingChannels] = useState(false) const [loadingSettings, setLoadingSettings] = useState(false) const [saving, setSaving] = useState(false) const [togglingId, setTogglingId] = useState(null) const [staffList, setStaffList] = useState([]) const [seatLimit, setSeatLimit] = useState(0) const [seatUsed, setSeatUsed] = useState(0) const [loadingStaff, setLoadingStaff] = useState(false) const [staffModalOpen, setStaffModalOpen] = useState(false) const [editingStaff, setEditingStaff] = useState(null) const [savingStaff, setSavingStaff] = useState(false) const [welcomeSegments, setWelcomeSegments] = useState([ { type: 'text', content: '您好!欢迎咨询,请问有什么可以帮您?' }, ]) const [uploadingWelcomeIdx, setUploadingWelcomeIdx] = useState(null) const [customerTags, setCustomerTags] = useState([]) const [loadingTags, setLoadingTags] = useState(false) const [tagModalOpen, setTagModalOpen] = useState(false) const [editingTag, setEditingTag] = useState(null) const [savingTag, setSavingTag] = useState(false) const [tagForm] = Form.useForm() const currentTab = visibleTabItems.find(t => t.key === activeTab) || visibleTabItems[0] || tabItems[0] const loadCustomerTags = async () => { setLoadingTags(true) try { const res = await getCustomerTags() setCustomerTags(Array.isArray(res.data) ? res.data : []) } catch (e) { setCustomerTags([]) message.error(e instanceof Error ? e.message : '加载标签失败') } finally { setLoadingTags(false) } } const loadStaff = async () => { setLoadingStaff(true) try { const res = await getStaff() setStaffList(Array.isArray(res.data?.list) ? res.data.list : []) setSeatLimit(res.data?.seat_limit ?? 0) setSeatUsed(res.data?.seat_used ?? 0) } catch (e) { setStaffList([]) message.error(e instanceof Error ? e.message : '加载坐席失败') } finally { setLoadingStaff(false) } } const loadChannels = async () => { setLoadingChannels(true) try { const res = await getChannels() setChannels(Array.isArray(res.data) ? res.data : []) } catch { setChannels([]) message.error('加载渠道失败') } finally { setLoadingChannels(false) } } const loadSettings = async () => { setLoadingSettings(true) try { const res = await getTenantSettings() const data = res.data setSettings(data) basicForm.setFieldsValue({ display_name: data.display_name, agent_nickname: data.agent_nickname, timezone: data.timezone || 'Asia/Shanghai', language: 'zh-CN', }) autoReplyForm.setFieldsValue({ offline_prompt: data.offline_prompt, }) setWelcomeSegments(normalizeWelcomeFromSettings(data)) { const workFields: Record = { worktime_prompt: data.worktime_prompt, } weekDays.forEach(d => { const parsed = parseWorkSlot(data.work_hours?.[d.key]) workFields[`${d.key}_mode`] = parsed.mode workFields[`${d.key}_range`] = parsed.range }) workForm.setFieldsValue(workFields) } notifyForm.setFieldsValue({ notify_new_session: data.notify_new_session, notify_offline_leave: data.notify_offline_leave, notify_daily_report: data.notify_daily_report, }) assignForm.setFieldsValue({ assign_strategy: data.assign_strategy || 'least_load', max_active_per_agent: data.max_active_per_agent ?? 0, }) } catch (e) { message.error(e instanceof Error ? e.message : '加载设置失败') } finally { setLoadingSettings(false) } } useEffect(() => { if (visibleTabItems.length > 0 && !visibleTabItems.some(item => item.key === activeTab)) { setActiveTab(visibleTabItems[0].key) } }, [activeTab, visibleTabItems]) useEffect(() => { if (activeTab === 'channels') loadChannels() if (['basic', 'autoreply', 'worktime', 'notify', 'assignment'].includes(activeTab)) loadSettings() if (activeTab === 'staff' && canViewStaff) loadStaff() if (activeTab === 'tags') void loadCustomerTags() }, [activeTab]) const openCreateStaff = () => { setEditingStaff(null) staffForm.resetFields() staffForm.setFieldsValue({ role: 'agent' }) setStaffModalOpen(true) } const openEditStaff = (s: StaffUser) => { setEditingStaff(s) staffForm.resetFields() staffForm.setFieldsValue({ nickname: s.nickname, role: s.role === 'admin' ? 'admin' : s.role, status: s.status, password: undefined, }) setStaffModalOpen(true) } const handleSaveStaff = async (values: { username?: string; password?: string; nickname?: string; role?: string; status?: string }) => { setSavingStaff(true) try { if (editingStaff) { await updateStaff(editingStaff.id, { nickname: values.nickname, role: values.role === 'admin' ? 'admin' : values.role, status: values.status, password: values.password || undefined, }) message.success('坐席已更新') } else { await createStaff({ username: values.username!.trim(), password: values.password!, nickname: values.nickname?.trim(), role: values.role || 'agent', }) message.success('坐席已创建') } setStaffModalOpen(false) await loadStaff() } catch (e) { message.error(e instanceof Error ? e.message : '保存失败') } finally { setSavingStaff(false) } } const handleDisableStaff = async (s: StaffUser) => { try { await deleteStaff(s.id) message.success('已禁用') await loadStaff() } catch (e) { message.error(e instanceof Error ? e.message : '操作失败') } } const handleEnableStaff = async (s: StaffUser) => { try { await updateStaff(s.id, { status: 'offline' }) message.success('已启用') await loadStaff() } catch (e) { message.error(e instanceof Error ? e.message : '启用失败') } } const savePartial = async (payload: Parameters[0], okText = '已保存') => { setSaving(true) try { const res = await updateTenantSettings(payload) setSettings(res.data) if (payload.welcome_messages || res.data?.welcome_messages) { setWelcomeSegments(normalizeWelcomeFromSettings(res.data)) } message.success(okText) } catch (e) { message.error(e instanceof Error ? e.message : '保存失败') } finally { setSaving(false) } } const toggleChannel = async (ch: Channel, enabled: boolean) => { setTogglingId(ch.id) try { const res = await updateChannel(ch.id, { status: enabled ? 'enabled' : 'disabled' }) setChannels(prev => prev.map(c => (c.id === ch.id ? res.data : c))) message.success(enabled ? '渠道已启用' : '渠道已停用') } catch (e) { message.error(e instanceof Error ? e.message : '更新失败') } finally { setTogglingId(null) } } const ensureWebChannel = async () => { try { await createChannel({ type: 'web', name: '网页聊天' }) message.success('已创建网页渠道') await loadChannels() } catch (e) { message.error(e instanceof Error ? e.message : '创建失败') } } const addChannelType = async (t: string) => { try { await createChannel({ type: t, name: typeMeta[t].label }) message.success('已添加') await loadChannels() } catch (e) { message.error(e instanceof Error ? e.message : '添加失败') } } const missingTypes = Object.keys(typeMeta).filter(t => !channels.some(c => c.type === t)) const panelHeaderAction = activeTab === 'channels' && missingTypes.includes('web') ? ( ) : activeTab === 'channels' && missingTypes.length > 0 ? ( ) : null const currentGroup = visibleTabGroups.find(g => g.items.some(i => i.key === activeTab)) return (
{/* 左侧设置导航 — 顶栏 56px */} {/* 右侧内容 */}
系统设置 / {currentGroup?.groupName || '配置'} / {currentTab.label}

{currentTab.desc}

{panelHeaderAction} {activeTab === 'staff' && canViewStaff && ( )}
{activeTab === 'basic' && ( )} {activeTab === 'channels' && ( )} {activeTab === 'assignment' && ( )} {activeTab === 'staff' && ( )} {activeTab === 'autoreply' && ( )} {activeTab === 'worktime' && ( )} {activeTab === 'notify' && ( )} {activeTab === 'tags' && ( )}
) } export default Settings