refactor(web): 拆分 Dashboard/Settings/VisitorChat 超大组件为子模块
- Dashboard 2591→1944 行,抽出 dashboard/ 纯函数、客户资料字段与 5 个状态自持弹窗 - Settings 1602→478 行,抽出 settings/ 16 个分区模块(设置卡片、弹窗与工具函数) - VisitorChat 1343→1045 行,抽出 visitor/ 头部、消息区、输入区与评价浮层 - 均为纯代码搬移:行为、路由入口与导出不变;tsc -b 与 vite build 通过
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { Button, Form, Input, Select, Spin } from 'antd'
|
||||
import { TeamOutlined } from '@ant-design/icons'
|
||||
import type { FormInstance } from 'antd'
|
||||
import type { SaveSettingsFn } from './types'
|
||||
|
||||
interface AssignmentSectionProps {
|
||||
assignForm: FormInstance
|
||||
loading: boolean
|
||||
isAdmin: boolean
|
||||
saving: boolean
|
||||
onSave: SaveSettingsFn
|
||||
}
|
||||
|
||||
/** 客服分配规则:自动分配策略与并发上限 */
|
||||
const AssignmentSection = ({ assignForm, loading, isAdmin, saving, onSave }: AssignmentSectionProps) => {
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
{loading ? (
|
||||
<div className="py-16 text-center"><Spin /></div>
|
||||
) : (
|
||||
<>
|
||||
<div className="bg-blue-50/50 rounded-2xl border border-blue-100 p-5">
|
||||
<div className="flex items-start gap-3.5">
|
||||
<div className="w-10 h-10 rounded-xl bg-blue-100 text-blue-600 flex items-center justify-center shrink-0 text-lg">
|
||||
<TeamOutlined />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-bold text-slate-900">自动分配规则</div>
|
||||
<div className="text-xs text-slate-600 mt-1 leading-relaxed">
|
||||
工作时间内,访客进线会按下列策略自动分配给<strong className="font-semibold text-blue-700">在线一线客服</strong>;
|
||||
非工作时间或无可接坐席时进入排队或离线留言。人工领取与内部转接不受此策略限制。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-2xl border border-slate-200/80 shadow-xs p-6 md:p-8">
|
||||
<Form
|
||||
form={assignForm}
|
||||
layout="vertical"
|
||||
disabled={!isAdmin}
|
||||
onFinish={async (values: {
|
||||
assign_strategy: 'least_load' | 'round_robin'
|
||||
max_active_per_agent: number
|
||||
}) => {
|
||||
await onSave({
|
||||
assign_strategy: values.assign_strategy,
|
||||
max_active_per_agent: Number(values.max_active_per_agent) || 0,
|
||||
}, '分配规则已保存')
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="assign_strategy"
|
||||
label={<span className="text-xs font-semibold text-slate-700">分配策略</span>}
|
||||
rules={[{ required: true, message: '请选择策略' }]}
|
||||
extra={<span className="text-[11px] text-slate-400">负载最低:优先分配给进行中会话最少的坐席;轮询:在可接坐席中均匀依次分发。</span>}
|
||||
>
|
||||
<Select
|
||||
className="!rounded-lg"
|
||||
options={[
|
||||
{ value: 'least_load', label: '负载最低策略(推荐)' },
|
||||
{ value: 'round_robin', label: '轮询轮转分配策略' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="max_active_per_agent"
|
||||
label={<span className="text-xs font-semibold text-slate-700">每位坐席最大并发会话数</span>}
|
||||
rules={[
|
||||
{ required: true, message: '请填写上限' },
|
||||
{
|
||||
validator: async (_, v) => {
|
||||
const n = Number(v)
|
||||
if (!Number.isFinite(n) || n < 0 || n > 200) {
|
||||
throw new Error('请填写 0–200 的整数,0 表示不限制')
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
extra={<span className="text-[11px] text-slate-400">达到上限后不再自动接入新访客,全部坐席满载时访客将进入等待队列。0 表示不限上限。</span>}
|
||||
>
|
||||
<Input type="number" min={0} max={200} placeholder="0 表示不限制" className="!rounded-lg" />
|
||||
</Form.Item>
|
||||
|
||||
<div className="pt-4 border-t border-slate-100 flex items-center justify-between">
|
||||
{isAdmin ? (
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={saving}
|
||||
className="!h-9 !px-6 !rounded-xl !text-xs !font-semibold !bg-gradient-to-r !from-blue-600 !to-blue-500 shadow-sm shadow-blue-500/20"
|
||||
>
|
||||
保存分配规则
|
||||
</Button>
|
||||
) : (
|
||||
<p className="text-xs text-slate-400 m-0">仅租户管理员可修改分配规则</p>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-50/80 rounded-2xl border border-slate-200/80 p-4 text-xs text-slate-500 leading-relaxed space-y-1">
|
||||
<div className="font-bold text-slate-700 mb-1">使用说明</div>
|
||||
<p className="m-0">· 自动分发候选范围:角色为客服、当前状态为「在线」的账号(忙碌/离线状态不参与自动分发)。</p>
|
||||
<p className="m-0">· 专属坐席组与渠道绑定策略将在后续版本逐步上线。</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AssignmentSection
|
||||
Reference in New Issue
Block a user