支持坐席切换在线状态,转接列表展示状态

侧栏可切换在线/忙碌/离线;转接弹窗显示状态点与标签,列表含在线与忙碌坐席。
This commit is contained in:
yml2213
2026-07-19 00:29:33 +08:00
parent cbc76f3258
commit ada7533ffa
7 changed files with 336 additions and 41 deletions
+78 -10
View File
@@ -148,6 +148,20 @@ function listStatusMeta(session: Session) {
return { label: '进行中', bar: '#2563eb', avatarBg: '#eff6ff', avatarColor: '#2563eb', dot: '#2563eb' }
}
/** 坐席在线状态(转接列表) */
function agentStatusMeta(status: string) {
switch (status) {
case 'online':
return { text: '在线', dot: '#16a34a', bg: '#ecfdf5', color: '#15803d' }
case 'busy':
return { text: '忙碌', dot: '#d97706', bg: '#fffbeb', color: '#b45309' }
case 'disabled':
return { text: '停用', dot: '#94a3b8', bg: '#f1f5f9', color: '#64748b' }
default:
return { text: '离线', dot: '#94a3b8', bg: '#f1f5f9', color: '#64748b' }
}
}
function relativeTime(iso?: string | null) {
if (!iso) return ''
const diff = Date.now() - new Date(iso).getTime()
@@ -964,17 +978,23 @@ const Dashboard = () => {
const openTransfer = async () => {
if (!selected) return
try {
// 默认返回在线/忙碌坐席(含 status)
const response = await getAvailableAgents()
setAvailableAgents((response.data || []).filter(agent => agent.id !== user?.user_id))
setTargetAgentID(undefined)
setTransferOpen(true)
} catch {
antMsg.error('加载在线客服失败')
antMsg.error('加载可转接坐席失败')
}
}
const handleTransfer = async () => {
if (!selected || !targetAgentID) return
const target = availableAgents.find(a => a.id === targetAgentID)
if (target && target.status !== 'online' && target.status !== 'busy') {
antMsg.warning('只能转接给在线或忙碌的坐席')
return
}
try {
await transferSession(selected.id, targetAgentID)
antMsg.success('会话已转接')
@@ -1883,15 +1903,63 @@ const Dashboard = () => {
)}
</aside>
<Modal title="转接会话" open={transferOpen} onCancel={() => setTransferOpen(false)} onOk={handleTransfer} okButtonProps={{ disabled: !targetAgentID }}>
<p className="text-sm text-neutral-500 mb-3">线</p>
<Select
className="w-full"
placeholder="选择客服"
value={targetAgentID}
onChange={setTargetAgentID}
options={availableAgents.map(agent => ({ value: agent.id, label: agent.nickname }))}
/>
<Modal
title="转接会话"
open={transferOpen}
onCancel={() => setTransferOpen(false)}
onOk={handleTransfer}
okButtonProps={{
disabled: !targetAgentID || (() => {
const t = availableAgents.find(a => a.id === targetAgentID)
return Boolean(t && t.status !== 'online' && t.status !== 'busy')
})(),
}}
>
<p className="text-sm text-neutral-500 mb-3">绿线 / </p>
{availableAgents.length === 0 ? (
<div className="text-sm text-neutral-400 py-6 text-center border border-dashed border-neutral-200 rounded-lg">
</div>
) : (
<Select
className="w-full"
placeholder="选择坐席"
value={targetAgentID}
onChange={setTargetAgentID}
optionLabelProp="label"
options={availableAgents.map(agent => {
const st = agentStatusMeta(agent.status)
return {
value: agent.id,
// 选中后输入框展示:昵称 + 状态
label: `${agent.nickname}${st.text}`,
disabled: agent.status !== 'online' && agent.status !== 'busy',
}
})}
optionRender={option => {
const agent = availableAgents.find(a => a.id === option.value)
const st = agentStatusMeta(agent?.status || 'offline')
return (
<div className="flex items-center gap-2 py-0.5 min-w-0">
<span
className="inline-block w-2 h-2 rounded-full shrink-0"
style={{ background: st.dot }}
title={st.text}
/>
<span className="truncate text-neutral-800 flex-1 min-w-0">
{agent?.nickname || option.label}
</span>
<span
className="shrink-0 text-[11px] px-1.5 py-0 rounded font-medium"
style={{ background: st.bg, color: st.color }}
>
{st.text}
</span>
</div>
)
}}
/>
)}
</Modal>
<Modal title="结束会话" open={endingOpen} onCancel={() => setEndingOpen(false)} onOk={handleEnd} okText="确认结束">
<p className="text-sm text-neutral-500 mb-3"></p>