feat: 来源组合显示——徽章/识别卡拼装落地页·国家·语言(搜索意图线索)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,8 @@ export interface VisitorRow {
|
||||
total_stay_ms: number
|
||||
first_source: string
|
||||
last_source: string
|
||||
/** 首次访问落地页(空串=未采集,如直达首页;列表 tooltip / 详情识别卡用) */
|
||||
first_landing_path: string
|
||||
/** 首触来源归因(first_touch 分类;无分类行/未重算的历史访客为 '',前端回落 first_source 展示) */
|
||||
channel: string
|
||||
platform: string
|
||||
@@ -87,7 +89,6 @@ export interface VisitorDetail extends VisitorRow {
|
||||
/** GenericIPAddressField(null=True),未采集到时为 null */
|
||||
last_ip: string | null
|
||||
first_utm: Record<string, string>
|
||||
first_landing_path: string
|
||||
first_referrer_domain: string
|
||||
/** 来源归因块(first_touch=null 时前端回落 first_source 展示) */
|
||||
attribution: AttributionBlock
|
||||
|
||||
@@ -94,6 +94,22 @@
|
||||
<span class="shrink-0 text-xs text-g-600">搜索词</span>
|
||||
<span class="search-term">{{ firstTouch.search_term }}</span>
|
||||
</div>
|
||||
<!-- 入口页:落地页是搜索意图的直接线索(如搜"挖掘机"落地产品页) -->
|
||||
<div class="search-term-row">
|
||||
<span class="shrink-0 text-xs text-g-600">入口页</span>
|
||||
<template v-if="detail.first_landing_path">
|
||||
<ElTooltip :content="detail.first_landing_path" placement="top">
|
||||
<span class="mono-path landing-path">{{ detail.first_landing_path }}</span>
|
||||
</ElTooltip>
|
||||
<span class="text-xs text-g-500 shrink-0">落地页是搜索意图的直接线索</span>
|
||||
</template>
|
||||
<span v-else>—</span>
|
||||
</div>
|
||||
<!-- 上下文:国家·语言·设备一句话组合(空段省略;只做来源故事线,不重复描述列表) -->
|
||||
<div v-if="sourceContextText" class="search-term-row">
|
||||
<span class="shrink-0 text-xs text-g-600">上下文</span>
|
||||
<span class="text-xs">{{ sourceContextText }}</span>
|
||||
</div>
|
||||
<!-- 冲突时并列展示两路证据(文本插值渲染,禁 v-html) -->
|
||||
<div
|
||||
v-if="firstTouch.resolution === 'conflict' && conflictEvidences.length"
|
||||
@@ -512,6 +528,13 @@
|
||||
() => CHANNEL_LABELS[firstTouch.value?.channel ?? ''] ?? CHANNEL_LABELS.unknown
|
||||
)
|
||||
|
||||
/** 识别卡上下文一句话:国家 · 语言 · 设备类型(空段省略,全空不渲染该行) */
|
||||
const sourceContextText = computed(() => {
|
||||
const d = detail.value
|
||||
if (!d) return ''
|
||||
return [d.country, d.language, d.device_type].filter(Boolean).join(' · ')
|
||||
})
|
||||
|
||||
/** conflict 时并列的两路证据(referrer 观察 vs UTM 标签),文本插值渲染 */
|
||||
const conflictEvidences = computed(() => {
|
||||
const d = firstTouch.value?.detail
|
||||
|
||||
@@ -252,14 +252,28 @@
|
||||
|
||||
const renderSource = (val: string) => h('span', { class: 'whitespace-nowrap' }, val || '—')
|
||||
|
||||
/** 首触来源:有归因走渠道徽章(icon+platform/variant+搜索词+诚实后缀),first_source 原文进 tooltip 兜底 */
|
||||
/** 首触来源 tooltip:意图与上下文组合多行(渠道按 channelBadge 同款数据拼装,搜索词有才显示) */
|
||||
const firstSourceTooltipLines = (row: VisitorRow): string[] => {
|
||||
const meta = CHANNEL_LABELS[row.channel] ?? CHANNEL_LABELS.unknown
|
||||
const platform = [row.platform, row.variant].filter(Boolean).join('·')
|
||||
const lines = [`渠道:${meta.icon} ${[meta.label, platform].filter(Boolean).join(' · ')}`]
|
||||
if (row.search_term) lines.push(`搜索词:${row.search_term}`)
|
||||
lines.push(`落地页:${row.first_landing_path || '—'}`)
|
||||
lines.push(`国家·语言:${[row.country, row.language].filter(Boolean).join(' · ') || '—'}`)
|
||||
return lines
|
||||
}
|
||||
|
||||
/** 首触来源:有归因走渠道徽章(icon+platform/variant+搜索词+诚实后缀),tooltip 展示来源组合信息;
|
||||
* 无归因(历史数据)回落 first_source 原文 */
|
||||
const renderFirstSource = (row: VisitorRow) => {
|
||||
if (!row.channel) return renderSource(row.first_source)
|
||||
return h(
|
||||
ElTooltip,
|
||||
{ content: row.first_source || '—', placement: 'top' },
|
||||
{ placement: 'top' },
|
||||
{
|
||||
default: () => h('span', { class: 'whitespace-nowrap' }, channelBadge(row))
|
||||
default: () => h('span', { class: 'whitespace-nowrap' }, channelBadge(row)),
|
||||
content: () =>
|
||||
firstSourceTooltipLines(row).map((line, i) => h('div', { key: i }, line))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user