87 lines
4.8 KiB
TypeScript
87 lines
4.8 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { Input, Select, Tag, Empty, Spin } from 'antd'
|
|
import { SearchOutlined, UserOutlined } from '@ant-design/icons'
|
|
import { getSessions, type Session as SessionType } from '@/services/api'
|
|
|
|
const statusColors: Record<string, string> = { active: 'blue', ended: 'green', archived: 'default' }
|
|
const statusLabels: Record<string, string> = { active: '进行中', ended: '已结束', waiting: '等待中' }
|
|
|
|
const ChatHistory = () => {
|
|
const [sessions, setSessions] = useState<SessionType[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [search, setSearch] = useState('')
|
|
const [statusFilter, setStatusFilter] = useState<string>()
|
|
const [selectedId, setSelectedId] = useState<number | null>(null)
|
|
|
|
useEffect(() => { loadSessions() }, [statusFilter])
|
|
|
|
const loadSessions = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const res = await getSessions({ status: statusFilter })
|
|
setSessions(res.list)
|
|
if (res.list.length > 0 && !selectedId) setSelectedId(res.list[0].id)
|
|
} catch { setSessions([]) } finally { setLoading(false) }
|
|
}
|
|
|
|
const selected = sessions.find(s => s.id === selectedId)
|
|
|
|
return (
|
|
<div className="h-full flex">
|
|
<div className="w-[360px] flex-shrink-0 bg-white border-r border-neutral-200 flex flex-col">
|
|
<div className="p-3 border-b border-neutral-100 space-y-2">
|
|
<Input prefix={<SearchOutlined />} placeholder="搜索访客..." value={search} onChange={e => setSearch(e.target.value)} allowClear size="small" />
|
|
<Select placeholder="状态" value={statusFilter} onChange={setStatusFilter} allowClear size="small" className="w-full" options={Object.entries(statusLabels).map(([k, v]) => ({ value: k, label: v }))} />
|
|
</div>
|
|
<div className="flex-1 overflow-auto">
|
|
{loading ? <div className="flex justify-center py-10"><Spin /></div> :
|
|
sessions.filter(s => !search || s.status.includes(search) || String(s.customer_id).includes(search)).map(s => (
|
|
<div key={s.id} className={`px-3 py-3 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors ${selectedId === s.id ? 'bg-blue-50' : ''}`}
|
|
onClick={() => setSelectedId(s.id)}>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-8 h-8 rounded-full bg-neutral-100 flex items-center justify-center flex-shrink-0"><UserOutlined className="text-neutral-400 text-sm" /></div>
|
|
<div><div className="text-sm font-medium text-neutral-800">客户{s.customer_id}</div><div className="text-xs text-neutral-400">ID: {s.id}</div></div>
|
|
</div>
|
|
<Tag color={statusColors[s.status]} className="text-xs">{statusLabels[s.status] || s.status}</Tag>
|
|
</div>
|
|
<div className="flex items-center justify-between mt-1">
|
|
<span className="text-xs text-neutral-300">{new Date(s.created_at).toLocaleString('zh-CN')}</span>
|
|
{s.satisfaction_score && <span className="text-xs text-yellow-500">{'★'.repeat(s.satisfaction_score)}</span>}
|
|
</div>
|
|
</div>
|
|
))}
|
|
{!loading && sessions.length === 0 && <div className="flex items-center justify-center h-full"><Empty description="暂无对话记录" /></div>}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 bg-white overflow-auto">
|
|
{selected ? (
|
|
<div className="p-6 max-w-3xl mx-auto">
|
|
<div className="flex items-center justify-between mb-6 pb-4 border-b border-neutral-100">
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="text-lg font-semibold text-neutral-800">会话 {selected.id}</h3>
|
|
<Tag color={statusColors[selected.status]}>{statusLabels[selected.status] || selected.status}</Tag>
|
|
</div>
|
|
<div className="text-sm text-neutral-400 mt-1">客户ID: {selected.customer_id} · {new Date(selected.created_at).toLocaleString('zh-CN')}</div>
|
|
</div>
|
|
{selected.satisfaction_score && (
|
|
<div className="text-right">
|
|
<div className="text-xl text-yellow-500">{'★'.repeat(selected.satisfaction_score)}{'☆'.repeat(5 - selected.satisfaction_score)}</div>
|
|
<div className="text-xs text-neutral-400">满意度评分</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div className="text-sm text-neutral-400 text-center">消息内容通过 WebSocket 实时传输</div>
|
|
</div>
|
|
</div>
|
|
) : <div className="h-full flex items-center justify-center text-neutral-400">选择一个对话查看详情</div>}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ChatHistory
|