修复访客浏览轨迹延迟与排序,增加演示站
保留 SPA hash 以正确区分页面;延迟采集 title 避免慢一页;轨迹改为最新在上;补充 demo-shop 测试页与 Vite 静态路由。
This commit is contained in:
@@ -136,10 +136,11 @@ function shortPagePath(url?: string): string {
|
||||
if (!url) return '—'
|
||||
try {
|
||||
const u = new URL(url)
|
||||
const path = u.pathname + (u.search || '')
|
||||
return path.length > 48 ? `${path.slice(0, 46)}…` : path || '/'
|
||||
// 必须带 hash:demo-shop 等 SPA 靠 #/cart 区分页面
|
||||
const path = u.pathname + (u.search || '') + (u.hash || '')
|
||||
return path.length > 56 ? `${path.slice(0, 54)}…` : path || '/'
|
||||
} catch {
|
||||
return url.length > 48 ? `${url.slice(0, 46)}…` : url
|
||||
return url.length > 56 ? `${url.slice(0, 54)}…` : url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +260,10 @@ const Dashboard = () => {
|
||||
setDetail({
|
||||
messages,
|
||||
events: data.events || [],
|
||||
pageViews: Array.isArray(data.page_views) ? data.page_views : [],
|
||||
// 接口已 desc;再保险按时间新→旧
|
||||
pageViews: (Array.isArray(data.page_views) ? data.page_views : [])
|
||||
.slice()
|
||||
.sort((a, b) => new Date(b.entered_at).getTime() - new Date(a.entered_at).getTime()),
|
||||
pendingCount: data.pending_count || 0,
|
||||
})
|
||||
rememberMessagesSeq(id, messages)
|
||||
@@ -464,20 +468,29 @@ const Dashboard = () => {
|
||||
}
|
||||
|
||||
if (payload.type === 'page_view' && sameSession && payload.data) {
|
||||
const pv = payload.data as unknown as VisitorPageView
|
||||
if (pv?.url) {
|
||||
const raw = payload.data as unknown as VisitorPageView & { title_fix?: boolean }
|
||||
if (raw?.url) {
|
||||
setDetail(prev => {
|
||||
if (!prev) return prev
|
||||
if (prev.pageViews.some(p => p.id === pv.id)) return prev
|
||||
return { ...prev, pageViews: [...prev.pageViews, pv] }
|
||||
const idx = prev.pageViews.findIndex(p => p.id === raw.id)
|
||||
if (idx >= 0) {
|
||||
// 标题纠正:更新已有条目,并移到最前(仍是当前页)
|
||||
const next = prev.pageViews.slice()
|
||||
const item = { ...next[idx], title: raw.title || next[idx].title, url: raw.url }
|
||||
next.splice(idx, 1)
|
||||
return { ...prev, pageViews: [item, ...next] }
|
||||
}
|
||||
if (prev.pageViews.some(p => p.url === raw.url && !raw.id)) return prev
|
||||
// 最新轨迹插到最上方
|
||||
return { ...prev, pageViews: [raw, ...prev.pageViews] }
|
||||
})
|
||||
setSessions(previous => previous.map(session =>
|
||||
session.id === sid
|
||||
? {
|
||||
...session,
|
||||
current_url: pv.url,
|
||||
current_title: pv.title || session.current_title,
|
||||
last_seen_at: pv.entered_at || session.last_seen_at,
|
||||
current_url: raw.url,
|
||||
current_title: raw.title || session.current_title,
|
||||
last_seen_at: raw.entered_at || session.last_seen_at,
|
||||
}
|
||||
: session,
|
||||
))
|
||||
@@ -602,11 +615,19 @@ const Dashboard = () => {
|
||||
const selectedCustomer = selected ? customers[selected.customer_id] : null
|
||||
const canOperate = Boolean(selected && (isManager || selected.agent_id === user?.user_id) && selected.status === 'active')
|
||||
|
||||
// 进行中会话:在线时长每秒刷新
|
||||
// 进行中会话:在线时长每秒刷新;轨迹每 8s 静默拉一次(防 WS 漏推)
|
||||
useEffect(() => {
|
||||
if (!selected || selected.status === 'ended' || selected.status === 'archived') return
|
||||
const t = window.setInterval(() => setClockTick(n => n + 1), 1000)
|
||||
return () => clearInterval(t)
|
||||
const poll = window.setInterval(() => {
|
||||
if (selectedIdRef.current != null) {
|
||||
void loadDetailRef.current(selectedIdRef.current, false, { silent: true })
|
||||
}
|
||||
}, 8000)
|
||||
return () => {
|
||||
clearInterval(t)
|
||||
clearInterval(poll)
|
||||
}
|
||||
}, [selected?.id, selected?.status])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1491,14 +1512,14 @@ const Dashboard = () => {
|
||||
<div
|
||||
key={pv.id || `${pv.url}-${pv.entered_at}`}
|
||||
className={`px-2.5 py-2 text-xs ${idx > 0 ? 'border-t border-neutral-50' : ''} ${
|
||||
idx === detail.pageViews.length - 1 ? 'bg-blue-50/50' : 'bg-white'
|
||||
idx === 0 ? 'bg-blue-50/50' : 'bg-white'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2 mb-0.5">
|
||||
<span className="text-neutral-400 tabular-nums shrink-0">
|
||||
{new Date(pv.entered_at).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
||||
</span>
|
||||
{idx === detail.pageViews.length - 1 && (
|
||||
{idx === 0 && (
|
||||
<span className="text-[10px] text-[#2563eb] font-medium">当前</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user