完善访客结束流程与转接可见性
- 会话结束后支持重新咨询,评价改为选星+快捷文案再提交 - 转接/分配事件写入操作人与目标坐席,工作台时间线与备注可见
This commit is contained in:
@@ -422,7 +422,8 @@ const Dashboard = () => {
|
||||
|
||||
if (payload.type === 'session_created' || payload.type === 'session_updated') {
|
||||
void loadAllRef.current({ silent: true })
|
||||
if (sameSession) void syncAfterSeqRef.current(sid, { markRead: false })
|
||||
// 转接/分配/结束等会写 SessionEvent,需全量刷新详情(含 events)
|
||||
if (sameSession) void loadDetailRef.current(sid, false, { silent: true })
|
||||
}
|
||||
} catch {
|
||||
// ignore malformed frames
|
||||
@@ -524,7 +525,51 @@ const Dashboard = () => {
|
||||
return tb - ta
|
||||
})
|
||||
|
||||
const notes = detail?.events.filter(event => event.action === 'note' || event.action === 'offline_leave' || event.action === 'auto_assign').slice().reverse() || []
|
||||
const timelineEvents = (detail?.events || []).filter(event =>
|
||||
event.action === 'transfer'
|
||||
|| event.action === 'assign'
|
||||
|| event.action === 'auto_assign'
|
||||
|| event.action === 'end'
|
||||
|| event.action === 'offline_leave',
|
||||
)
|
||||
const notes = detail?.events
|
||||
.filter(event =>
|
||||
event.action === 'note'
|
||||
|| event.action === 'offline_leave'
|
||||
|| event.action === 'auto_assign'
|
||||
|| event.action === 'assign'
|
||||
|| event.action === 'transfer'
|
||||
|| event.action === 'end',
|
||||
)
|
||||
.slice()
|
||||
.reverse() || []
|
||||
|
||||
type ChatTimelineItem =
|
||||
| { kind: 'message'; at: number; message: Message }
|
||||
| { kind: 'event'; at: number; event: SessionEvent }
|
||||
|
||||
const chatTimeline: ChatTimelineItem[] = (() => {
|
||||
const items: ChatTimelineItem[] = []
|
||||
for (const message of detail?.messages || []) {
|
||||
items.push({ kind: 'message', at: new Date(message.sent_at).getTime(), message })
|
||||
}
|
||||
for (const event of timelineEvents) {
|
||||
items.push({ kind: 'event', at: new Date(event.created_at).getTime(), event })
|
||||
}
|
||||
items.sort((a, b) => a.at - b.at || (a.kind === 'event' ? -1 : 1))
|
||||
return items
|
||||
})()
|
||||
|
||||
const eventLabel = (action: string) => {
|
||||
switch (action) {
|
||||
case 'transfer': return '会话转接'
|
||||
case 'assign': return '人工分配'
|
||||
case 'auto_assign': return '自动分配'
|
||||
case 'end': return '结束会话'
|
||||
case 'offline_leave': return '离线留言'
|
||||
default: return '系统记录'
|
||||
}
|
||||
}
|
||||
|
||||
const emitTyping = () => {
|
||||
if (!selectedId || !canOperate) return
|
||||
@@ -939,12 +984,29 @@ const Dashboard = () => {
|
||||
会话开始 — {new Date(selected.created_at).toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
</div>
|
||||
{(detail?.messages || []).map(message => {
|
||||
{chatTimeline.map(item => {
|
||||
if (item.kind === 'event') {
|
||||
const { event } = item
|
||||
return (
|
||||
<div key={`ev-${event.id}`} className="flex justify-center mb-4">
|
||||
<span className="inline-flex flex-col items-center max-w-[90%] px-3 py-1.5 rounded-lg text-xs text-violet-700 bg-violet-50 border border-violet-100">
|
||||
<span className="font-medium text-violet-800">{eventLabel(event.action)}</span>
|
||||
<span className="text-center leading-relaxed mt-0.5">{event.detail || eventLabel(event.action)}</span>
|
||||
<span className="text-[11px] text-violet-500/80 mt-0.5">
|
||||
{new Date(event.created_at).toLocaleString('zh-CN', {
|
||||
month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const message = item.message
|
||||
const isAgent = message.sender_type === 'agent'
|
||||
const visitorInitial = selectedCustomer.name.slice(0, 1)
|
||||
return (
|
||||
<div
|
||||
key={message.id}
|
||||
key={`msg-${message.id}`}
|
||||
className={`flex items-start gap-2.5 mb-4 ${isAgent ? 'flex-row-reverse' : ''}`}
|
||||
>
|
||||
<div
|
||||
@@ -976,7 +1038,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{detail?.messages.length === 0 && (
|
||||
{chatTimeline.length === 0 && (
|
||||
<div className="text-center text-sm text-neutral-400 py-10">暂无消息,开始对话吧</div>
|
||||
)}
|
||||
{visitorTyping && selectedCustomer && (
|
||||
@@ -1207,18 +1269,42 @@ const Dashboard = () => {
|
||||
<div className="text-xs text-neutral-400">暂无内部备注</div>
|
||||
) : notes.map(note => {
|
||||
const isLeave = note.action === 'offline_leave'
|
||||
const isAssign = note.action === 'auto_assign'
|
||||
const isAuto = note.action === 'auto_assign'
|
||||
const isTransfer = note.action === 'transfer'
|
||||
const isAssign = note.action === 'assign'
|
||||
const isEnd = note.action === 'end'
|
||||
const box = isLeave
|
||||
? 'bg-orange-50 text-orange-900 border-orange-100'
|
||||
: isAssign
|
||||
? 'bg-blue-50 text-blue-900 border-blue-100'
|
||||
: 'bg-amber-50 text-amber-900 border-amber-100'
|
||||
const timeCls = isLeave ? 'text-orange-600/70' : isAssign ? 'text-blue-600/70' : 'text-amber-600/70'
|
||||
: isTransfer
|
||||
? 'bg-violet-50 text-violet-900 border-violet-100'
|
||||
: (isAuto || isAssign)
|
||||
? 'bg-blue-50 text-blue-900 border-blue-100'
|
||||
: isEnd
|
||||
? 'bg-neutral-100 text-neutral-700 border-neutral-200'
|
||||
: 'bg-amber-50 text-amber-900 border-amber-100'
|
||||
const timeCls = isLeave
|
||||
? 'text-orange-600/70'
|
||||
: isTransfer
|
||||
? 'text-violet-600/70'
|
||||
: (isAuto || isAssign)
|
||||
? 'text-blue-600/70'
|
||||
: isEnd
|
||||
? 'text-neutral-500'
|
||||
: 'text-amber-600/70'
|
||||
const title = isLeave
|
||||
? '离线留言'
|
||||
: isTransfer
|
||||
? '会话转接'
|
||||
: isAuto
|
||||
? '自动分配'
|
||||
: isAssign
|
||||
? '人工分配'
|
||||
: isEnd
|
||||
? '结束会话'
|
||||
: null
|
||||
return (
|
||||
<div key={note.id} className={`rounded-lg p-2 text-xs whitespace-pre-wrap border ${box}`}>
|
||||
{(isLeave || isAssign) && (
|
||||
<div className="font-medium mb-0.5">{isLeave ? '离线留言' : '自动分配'}</div>
|
||||
)}
|
||||
{title && <div className="font-medium mb-0.5">{title}</div>}
|
||||
{note.detail}
|
||||
<div className={`mt-1 ${timeCls}`}>{new Date(note.created_at).toLocaleString('zh-CN')}</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user