Files
fangke/admin/src/views/tracker/visitor/components/EventTimeline.vue
T
浅念 1cb199e99e fix(admin): 详情页时间线兜底/事件组件抽取/失败态区分/换访客清理
- timelineSessionSource 查不到会话摘要时改显示 —:无事实依据不做
  "直接访问"断言(sessionSourceText 内的直接访问是后端同口径语义,保留)
- 抽取 EventTimeline 子组件(components/EventTimeline.vue):
  轨迹 Tab 与会话抽屉的 page_view/page_stay/其他三分支事件行模板
  去重为单一实现,两处各一行引用;stay-line/title-line 样式随迁
- 时间线失败态三分支:有数据渲染分组 / 请求结束且无数据对象提示
  "时间线加载失败" / 成功但窗口内无事件提示"暂无访问轨迹"
- ensureLoad 换访客时同步清 detail,与 timeline/抽屉清理对齐,
  防旧访客 ID 在 loading 遮罩下闪现

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-15 21:21:20 +08:00

90 lines
3.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 访客统计 · 事件时间线(详情页轨迹 Tab 与会话抽屉共用) -->
<!-- 事件行三分支:page_view(浏览+停留副行+title)/ page_stay 独立条目 / sdk_loaded 及自定义事件 -->
<template>
<ElTimeline>
<ElTimelineItem
v-for="(e, i) in events"
:key="i"
:icon="eventMeta(e.event_name).icon"
:type="eventMeta(e.event_name).type"
:timestamp="formatTime(e.time)"
>
<!-- page_view:浏览 + 停留副行(active_ms>0 才显示)+ title 小字 -->
<template v-if="e.event_name === 'page_view'">
<div>
浏览
<span class="mono-path">{{ e.path || '—' }}</span>
</div>
<div v-if="(e.active_ms ?? 0) > 0" class="stay-line"
>停留 {{ formatStay(e.active_ms) }}</div
>
<div v-if="e.title" class="title-line">{{ e.title }}</div>
</template>
<!-- page_stay 独立条目(无匹配 page_view 时服务端保留原事件名) -->
<template v-else-if="e.event_name === 'page_stay'">
<div>
停留
<span class="mono-path">{{ e.path || '—' }}</span>
({{ formatStay(e.active_ms ?? 0) }})
</div>
<div v-if="e.title" class="title-line">{{ e.title }}</div>
</template>
<!-- sdk_loaded / 其他自定义事件 -->
<template v-else>
<div>
{{ eventMeta(e.event_name).label }}
<span v-if="e.path" class="mono-path">{{ e.path }}</span>
</div>
</template>
</ElTimelineItem>
</ElTimeline>
</template>
<script setup lang="ts">
import type { Component } from 'vue'
import { Cpu, InfoFilled, Timer, View } from '@element-plus/icons-vue'
import type { TimelineEvent } from '@/api/tracker/visitors'
import { formatStay } from '@/utils/tracker'
defineOptions({ name: 'EventTimeline' })
defineProps<{
/** 已按服务端合并规则处理过的序列(page_stay 已并入 page_view 的 active_ms) */
events: TimelineEvent[]
}>()
/** ISO(后端上海时间)→ HH:mm */
const formatTime = (iso?: string | null): string => (iso ? iso.slice(11, 16) : '—')
/** 事件行图标/节点色:page_view=浏览、page_stay=计时、sdk_loaded=加载、其余兜底 */
const EVENT_META: Record<
string,
{ icon: Component; type: 'primary' | 'warning' | 'info'; label: string }
> = {
page_view: { icon: View, type: 'primary', label: '浏览' },
page_stay: { icon: Timer, type: 'warning', label: '停留' },
sdk_loaded: { icon: Cpu, type: 'info', label: 'SDK 加载' }
}
const eventMeta = (name: string) =>
EVENT_META[name] ?? { icon: InfoFilled, type: 'info', label: name }
</script>
<style lang="scss" scoped>
.mono-path {
font-family: var(--el-font-family-mono, monospace);
font-size: 12px;
}
.stay-line {
margin-top: 2px;
font-size: 12px;
color: var(--el-color-warning);
}
.title-line {
margin-top: 2px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
</style>