feat: 移动端设备信息最大化采集(型号/UA-CH/视口/方向/触点/内存/网络/深浅色)
SDK:buildEnvContext 同步扩展 12 键(dpr/viewport/orientation/maxTouchPoints/ deviceMemory/languages/colorScheme/standalone/network downlink·rtt·saveData/ 微信版本),逐键 try/catch 静默缺则不挂;新增 collectHighEntropyHints(UA-CH model/platformVersion/architecture/bitness),init 后 Promise.race 2s 超时 异步并入 __ctk_ctx——批次 context flush 时读取,不阻塞首屏。 后端:Visitor 加 device_model/env_extra(迁移 0005);ingest 首批快照 EXTENDED_ENV_KEYS 白名单(值截 100),非首批不覆盖、唯 device_model 空且 本批有值时补填一次(晚到 hints);列表/详情 API 随行输出。 前端:详情访问环境卡设备行升级显示型号(手机 · Pixel 7),追加像素比/ 视口/屏幕方向/触点/内存/网络质量/深浅色/微信版本行(env_extra 逐键判空, 旧数据整块不显示);列表设备列主行拼型号(手机·Pixel 7)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,8 @@ export interface VisitorRow {
|
||||
province: string
|
||||
city: string
|
||||
device_type: string
|
||||
/** 设备型号(UA-CH model,如 Pixel 7;未采集/晚到前为空串) */
|
||||
device_model: string
|
||||
os: string
|
||||
browser: string
|
||||
language: string
|
||||
@@ -99,6 +101,8 @@ export interface AttributionBlock {
|
||||
export interface VisitorDetail extends VisitorRow {
|
||||
screen: string
|
||||
timezone_name: string
|
||||
/** 扩展环境快照(EXTENDED_ENV_KEYS 白名单键,值均为字符串;旧数据为空对象——逐键判空跳行) */
|
||||
env_extra: Record<string, string>
|
||||
/** GenericIPAddressField(null=True),未采集到时为 null */
|
||||
last_ip: string | null
|
||||
first_utm: Record<string, string>
|
||||
|
||||
@@ -428,6 +428,7 @@
|
||||
import {
|
||||
Back as BackIcon,
|
||||
Calendar as CalendarIcon,
|
||||
Cellphone as CellphoneIcon,
|
||||
ChatDotRound as ChatDotRoundIcon,
|
||||
Clock as ClockIcon,
|
||||
Compass as CompassIcon,
|
||||
@@ -435,9 +436,13 @@
|
||||
Cpu as CpuIcon,
|
||||
Document as DocumentIcon,
|
||||
FullScreen as FullScreenIcon,
|
||||
Iphone as IphoneIcon,
|
||||
Lightning as LightningIcon,
|
||||
Link as LinkIcon,
|
||||
Location as LocationIcon,
|
||||
Monitor as MonitorIcon,
|
||||
Moon as MoonIcon,
|
||||
Sunny as SunnyIcon,
|
||||
Timer as TimerIcon,
|
||||
User as UserIcon,
|
||||
VideoPlay as VideoPlayIcon
|
||||
@@ -617,14 +622,62 @@
|
||||
{ label: '原始提问', value: searchTermText.value }
|
||||
])
|
||||
|
||||
// ---------- 访问环境(复用档案字段;空值显示 —) ----------
|
||||
// ---------- 访问环境(档案字段 + 扩展环境快照;空值显示 —,扩展键逐键判空跳行) ----------
|
||||
/** env_extra 单键取值(空串/缺键 → undefined,对应行跳过——旧数据无扩展键时整块不显示) */
|
||||
const extra = (key: string): string | undefined => {
|
||||
const v = detail.value?.env_extra?.[key]
|
||||
return v ? v : undefined
|
||||
}
|
||||
|
||||
/** orientation 归一显示:portrait* → 竖屏 / landscape* → 横屏(其余原样) */
|
||||
const formatOrientation = (v: string): string =>
|
||||
v.startsWith('portrait') ? '竖屏' : v.startsWith('landscape') ? '横屏' : v
|
||||
|
||||
/** 网络质量组合值:downlink Mbps + rtt ms + 省流标记(各自存在才拼入;全缺 → undefined 跳行) */
|
||||
const networkQuality = (): string | undefined => {
|
||||
const parts: string[] = []
|
||||
if (extra('network_downlink')) parts.push(`${extra('network_downlink')} Mbps`)
|
||||
if (extra('network_rtt')) parts.push(`${extra('network_rtt')} ms`)
|
||||
if (extra('save_data') === '1') parts.push('省流')
|
||||
return parts.length ? parts.join(' · ') : undefined
|
||||
}
|
||||
|
||||
/** 扩展行构造:值存在才成行(undefined → null 剔除;fmt 做展示格式化) */
|
||||
const extendedRow = (
|
||||
value: string | undefined,
|
||||
icon: Component,
|
||||
label: string,
|
||||
fmt: (v: string) => string = (v) => v
|
||||
): { icon: Component; label: string; value: string } | null =>
|
||||
value ? { icon: markRaw(icon), label, value: fmt(value) } : null
|
||||
|
||||
const envRows = computed<{ icon: Component; label: string; value: string }[]>(() => {
|
||||
const d = detail.value
|
||||
const location = d ? [d.country, d.province, d.city].filter(Boolean).join(' · ') : ''
|
||||
// 设备行升级:有型号时拼 `手机 · Pixel 7`(device_model 是独立列——hints 晚到补填也生效)
|
||||
const type = d?.device_type || '—'
|
||||
const deviceValue = d?.device_model ? `${type} · ${d.device_model}` : type
|
||||
const dark = extra('color_scheme') === 'dark'
|
||||
// 扩展行只显示存在的键:旧数据 env_extra 为空对象时整块不显示
|
||||
const extended = [
|
||||
extendedRow(extra('device_pixel_ratio'), FullScreenIcon, '像素比'),
|
||||
extendedRow(extra('viewport'), MonitorIcon, '视口'),
|
||||
extendedRow(extra('orientation'), CellphoneIcon, '屏幕方向', formatOrientation),
|
||||
extendedRow(extra('max_touch_points'), IphoneIcon, '触点'),
|
||||
extendedRow(extra('device_memory'), CpuIcon, '内存', (v) => `${v} GB`),
|
||||
extendedRow(networkQuality(), LightningIcon, '网络质量'),
|
||||
extendedRow(
|
||||
extra('color_scheme'),
|
||||
dark ? MoonIcon : SunnyIcon,
|
||||
'深浅色',
|
||||
() => (dark ? '深色' : '浅色')
|
||||
),
|
||||
extendedRow(extra('wechat_version'), ChatDotRoundIcon, '微信版本')
|
||||
].filter((r): r is { icon: Component; label: string; value: string } => r !== null)
|
||||
return [
|
||||
{ icon: markRaw(CompassIcon), label: '浏览器', value: d?.browser || '—' },
|
||||
{ icon: markRaw(CpuIcon), label: '操作系统', value: d?.os || '—' },
|
||||
{ icon: markRaw(MonitorIcon), label: '设备类型', value: d?.device_type || '—' },
|
||||
{ icon: markRaw(MonitorIcon), label: '设备类型', value: deviceValue },
|
||||
{ icon: markRaw(FullScreenIcon), label: '分辨率', value: d?.screen || '—' },
|
||||
{
|
||||
icon: markRaw(LocationIcon),
|
||||
@@ -633,7 +686,8 @@
|
||||
},
|
||||
{ icon: markRaw(ChatDotRoundIcon), label: '语言', value: d?.language || '—' },
|
||||
{ icon: markRaw(ClockIcon), label: '时区', value: d?.timezone_name || '—' },
|
||||
{ icon: markRaw(ConnectionIcon), label: 'IP', value: d?.last_ip || '—' }
|
||||
{ icon: markRaw(ConnectionIcon), label: 'IP', value: d?.last_ip || '—' },
|
||||
...extended
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -230,8 +230,12 @@
|
||||
|
||||
const renderDevice = (row: VisitorRow) => {
|
||||
const env = [row.os, row.browser].filter(Boolean).join(' · ')
|
||||
// 有型号时主行拼 `手机·Pixel 7`(UA-CH model,未采集/晚到前只有设备类型)
|
||||
const type = row.device_model
|
||||
? `${row.device_type || '—'}·${row.device_model}`
|
||||
: row.device_type || '—'
|
||||
return h('div', null, [
|
||||
h('div', { class: 'whitespace-nowrap' }, row.device_type || '—'),
|
||||
h('div', { class: 'whitespace-nowrap' }, type),
|
||||
env ? h('div', { class: 'text-xs text-g-500 whitespace-nowrap mt-0.5' }, env) : null
|
||||
])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user