From f6f7fd478eb4b5f04f2830740983a5587c17db77 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Wed, 15 Jul 2026 13:31:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=B3=BB=E7=BB=9F=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E5=B8=83=E5=B1=80=E5=B9=B6=E5=AF=B9=E9=BD=90?= =?UTF-8?q?=E9=A1=B6=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 左右栏固定 56px 顶栏,设置分类导航与各配置面板视觉对齐效果图,保留渠道与租户设置能力。 --- web/src/pages/agent/Settings.tsx | 707 +++++++++++++++++-------------- 1 file changed, 388 insertions(+), 319 deletions(-) diff --git a/web/src/pages/agent/Settings.tsx b/web/src/pages/agent/Settings.tsx index 9997a2a..97f88ff 100644 --- a/web/src/pages/agent/Settings.tsx +++ b/web/src/pages/agent/Settings.tsx @@ -1,22 +1,24 @@ -import { useEffect, useState } from 'react' -import { Form, Input, Switch, Select, Button, Card, Tag, message, Spin, Empty } from 'antd' +import { useEffect, useState, type ReactNode } from 'react' +import { Form, Input, Switch, Select, Button, message, Spin, Empty } from 'antd' import { - LinkOutlined, WechatOutlined, PhoneOutlined, MailOutlined, MobileOutlined, CopyOutlined, PlusOutlined, + LinkOutlined, WechatOutlined, PhoneOutlined, MailOutlined, MobileOutlined, CopyOutlined, + PlusOutlined, SettingOutlined, ApiOutlined, TeamOutlined, SafetyCertificateOutlined, + MessageOutlined, ClockCircleOutlined, BellOutlined, GlobalOutlined, } from '@ant-design/icons' import { createChannel, getChannels, getTenantSettings, updateChannel, updateTenantSettings, type Channel, type TenantSettings, type WorkHours, } from '@/services/api' -const typeMeta: Record = { - web: { icon: , label: '网页聊天' }, - wechat: { icon: , label: '微信公众号' }, - app: { icon: , label: 'APP 内嵌' }, - phone: { icon: , label: '电话客服' }, - email: { icon: , label: '邮件工单' }, +const typeMeta: Record = { + web: { icon: , label: '网页聊天', desc: '嵌入官网或任意网页的在线客服窗口' }, + wechat: { icon: , label: '微信公众号', desc: '对接微信公众号客服消息(预留)' }, + app: { icon: , label: 'APP 内嵌', desc: '移动端 App 内嵌客服(预留)' }, + phone: { icon: , label: '电话客服', desc: '电话进线与回拨(预留)' }, + email: { icon: , label: '邮件工单', desc: '邮件自动创建工单(预留)' }, } -const weekDays: { key: keyof WorkHours | string; label: string }[] = [ +const weekDays: { key: string; label: string }[] = [ { key: 'monday', label: '周一' }, { key: 'tuesday', label: '周二' }, { key: 'wednesday', label: '周三' }, @@ -33,6 +35,16 @@ const hourOptions = [ { value: '全天', label: '全天' }, ] +const tabItems: { key: string; label: string; desc: string; icon: ReactNode }[] = [ + { key: 'basic', label: '基本设置', desc: '租户展示名称、默认昵称与时区', icon: }, + { key: 'channels', label: '渠道管理', desc: '配置客户服务接入渠道与嵌入代码', icon: }, + { key: 'assignment', label: '客服分配规则', desc: '会话自动分配策略说明', icon: }, + { key: 'permission', label: '权限管理', desc: '角色与能力说明', icon: }, + { key: 'autoreply', label: '自动回复', desc: '欢迎语与离线留言提示', icon: }, + { key: 'worktime', label: '工作时间', desc: '在线服务时段与非工作时间提示', icon: }, + { key: 'notify', label: '通知设置', desc: '新会话、留言与日报偏好', icon: }, +] + const Settings = () => { const [activeTab, setActiveTab] = useState('basic') const [basicForm] = Form.useForm() @@ -46,15 +58,7 @@ const Settings = () => { const [saving, setSaving] = useState(false) const [togglingId, setTogglingId] = useState(null) - const tabItems = [ - { key: 'basic', label: '基本设置' }, - { key: 'channels', label: '渠道管理' }, - { key: 'assignment', label: '分配规则' }, - { key: 'permission', label: '权限管理' }, - { key: 'autoreply', label: '自动回复' }, - { key: 'worktime', label: '工作时间' }, - { key: 'notify', label: '通知设置' }, - ] + const currentTab = tabItems.find(t => t.key === activeTab) || tabItems[0] const loadChannels = async () => { setLoadingChannels(true) @@ -136,7 +140,7 @@ const Settings = () => { 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)) + setChannels(prev => prev.map(c => (c.id === ch.id ? res.data : c))) message.success(enabled ? '渠道已启用' : '渠道已停用') } catch (e) { message.error(e instanceof Error ? e.message : '更新失败') @@ -155,323 +159,388 @@ const Settings = () => { } } + 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 + return ( -
-
-
- 系统设置 +
+ {/* 左侧设置导航 — 顶栏 56px 对齐 */} + + + {/* 右侧内容 */} +
+
+
+

{currentTab.label}

- ))} -
+ {panelHeaderAction} +
-
- {activeTab === 'basic' && ( - - {loadingSettings && !settings ? ( -
- ) : ( -
savePartial({ - display_name: values.display_name, - agent_nickname: values.agent_nickname, - timezone: values.timezone, - })} - > - - - - - - - - - - - - -
- )} -
- )} +
+
+

{currentTab.desc}

- {activeTab === 'channels' && ( -
-
-
-

渠道管理

-

配置接入渠道开关与网页聊天嵌入代码

-
- {missingTypes.includes('web') && ( - - )} -
- - {loadingChannels ? ( -
- ) : channels.length === 0 ? ( - - - - ) : ( -
- {channels.map(ch => { - const meta = typeMeta[ch.type] || { icon: , label: ch.name || ch.type } - const enabled = ch.status === 'enabled' - return ( - - {meta.icon} - {ch.name || meta.label} - {enabled ? '已启用' : '未启用'} -
- )} - > -
- {ch.channel_key && ( -
- 渠道 ID - {ch.channel_key} -
- )} - {ch.type === 'web' && ch.script_code && ( -
-
接入代码
-
- - {ch.script_code.replace('src="/widget.js"', `src="${window.location.origin}/widget.js"`)} - - copyScript(ch.script_code)} - /> -
-
- )} - {ch.type !== 'web' && ( -
本期仅网页渠道可完整接入,其他渠道预留开关。
- )} -
- 启用状态 - toggleChannel(ch, v)} /> -
-
- - ) - })} -
- )} - - {missingTypes.length > 0 && channels.length > 0 && ( -
-
可添加渠道类型
-
- {missingTypes.map(t => ( - - ))} -
-
- )} -
- )} - - {activeTab === 'assignment' && ( - -
-
-
当前生效:负载最低优先 + 工作时间
-
- 工作时间内,自动分配给进行中会话最少的在线客服;非工作时间或无客服在线时,访客可留言。 -
-
-
-
-
-
轮询分配
-
后续版本可切换
-
- -
-
-
-
- )} - - {activeTab === 'permission' && ( - -

- 角色权限由系统内置(平台管理员 / 租户管理员 / 主管 / 一线客服),自定义角色将在后续版本开放。 -

-
- )} - - {activeTab === 'autoreply' && ( - - {loadingSettings && !settings ? ( -
- ) : ( -
savePartial({ - welcome_message: values.welcome_message, - offline_prompt: values.offline_prompt, - }, '自动回复已保存')} - > - - - - - - - - - -
- )} -
- )} - - {activeTab === 'worktime' && ( - - {loadingSettings && !settings ? ( -
- ) : ( -
{ - const work_hours: WorkHours = {} - weekDays.forEach(d => { - work_hours[d.key] = values[d.key] || '' - }) - return savePartial({ - work_hours, - worktime_prompt: values.worktime_prompt, - }, '工作时间已保存') - }} - > -

- 非工作时间访客将看到下方提示并可留言;清空某天时段表示休息。 -

- {weekDays.map(day => ( -
- {day.label} - - + + + + + + - - - - - + + + +
+ ))} +
+ + + + + + + + )} +
+ )} + + {activeTab === 'notify' && ( +
+ {loadingSettings && !settings ? ( +
+ ) : ( +
savePartial({ + notify_new_session: values.notify_new_session, + notify_offline_leave: values.notify_offline_leave, + notify_daily_report: values.notify_daily_report, + }, '通知设置已保存')} + > +
+ {[ + { name: 'notify_new_session', title: '新会话提醒', desc: '有新访客进线时提醒(推送通道后续接入)' }, + { name: 'notify_offline_leave', title: '离线留言通知', desc: '访客提交离线留言时提醒' }, + { name: 'notify_daily_report', title: '日报推送', desc: '每日服务数据摘要' }, + ].map(item => ( +
+
+
{item.title}
+
{item.desc}
+
+ + + +
+ ))} +
+ + + +
+ )} +
+ )} +
+
+
) }