From 99d2a300baf766e52d114c502be093113f736fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 15 Sep 2026 19:58:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E8=AE=BF=E5=AE=A2=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 访客管理页:useTable 分页表(后端固定 20/页,paginationKey 映射 page, size 不发送),列含 截断 ID+复制 / 国旗 emoji+国家·省市 / 设备主行+os·browser 小字 / 首访·最近访问(共享 formatDateTime,上海时间直显)/ 访问次数 / 活跃天数 / 总停留 formatStay / 首触·最近来源 / 在线绿点;行点击进详情路由 - 筛选栏:关键词(visitor_id/IP 前缀)回车或查询触发、设备下拉、国家精确、 在线开关(online='1' 哨兵)、日期范围 date_from/date_to、显示爬虫开关 (默认排除);筛选变化重置 page=1,分页切换保留筛选 - 共享工具提取 admin/src/utils/tracker:formatDateTime/formatStay/countryFlag, site 页改引用;formatStay 智能省略(0 单位不显示,全 0 显示 0秒) - 红线:无 v-html,访客数据不绑 href/src;加载/空态齐全(错误态走请求层统一 toast) Co-Authored-By: Claude Code --- admin/src/utils/tracker/index.ts | 57 +++++ admin/src/views/tracker/site/index.vue | 3 +- admin/src/views/tracker/visitor/index.vue | 299 +++++++++++++++++++++- 3 files changed, 353 insertions(+), 6 deletions(-) create mode 100644 admin/src/utils/tracker/index.ts diff --git a/admin/src/utils/tracker/index.ts b/admin/src/utils/tracker/index.ts new file mode 100644 index 0000000..a56a957 --- /dev/null +++ b/admin/src/utils/tracker/index.ts @@ -0,0 +1,57 @@ +/** + * 访客统计共享格式化工具(列表/详情页复用) + * + * 后端时间戳已统一 timezone.localtime().isoformat()(上海时间)序列化, + * 前端切串直显即可,不做时区换算。 + */ + +/** 国家 → 国旗 emoji(ip2region 中国 IP 为中文"中国",海外为英文名;未命中默认 🌐) */ +const COUNTRY_FLAGS: Record = { + 中国: '🇨🇳', + 'United States': '🇺🇸', + Japan: '🇯🇵', + 'South Korea': '🇰🇷', + Singapore: '🇸🇬', + 'United Kingdom': '🇬🇧', + Germany: '🇩🇪', + France: '🇫🇷', + Canada: '🇨🇦', + Australia: '🇦🇺', + Russia: '🇷🇺', + India: '🇮🇳', + Brazil: '🇧🇷', + Vietnam: '🇻🇳', + Thailand: '🇹🇭', + Malaysia: '🇲🇾', + Indonesia: '🇮🇩', + Philippines: '🇵🇭', + Netherlands: '🇳🇱', + Italy: '🇮🇹', + Spain: '🇪🇸', + Turkey: '🇹🇷', + Iran: '🇮🇷' +} + +/** 国家名 → 国旗 emoji(空/未命中返回 🌐) */ +export const countryFlag = (country?: string | null): string => + (country && COUNTRY_FLAGS[country]) || '🌐' + +/** ISO 时间 → YYYY-MM-DD HH:mm(后端已是上海时间;空值显示 —) */ +export const formatDateTime = (iso?: string | null): string => + iso ? iso.replace('T', ' ').slice(0, 16) : '—' + +/** 毫秒 → x天x小时x分x秒(智能省略:为 0 的单位不显示;全 0 / 不足 1 秒显示 0秒) */ +export const formatStay = (ms?: number | null): string => { + const totalSec = Math.max(0, Math.floor((ms ?? 0) / 1000)) + const days = Math.floor(totalSec / 86400) + const hours = Math.floor((totalSec % 86400) / 3600) + const minutes = Math.floor((totalSec % 3600) / 60) + const seconds = totalSec % 60 + const parts: string[] = [] + if (days) parts.push(`${days}天`) + if (hours) parts.push(`${hours}小时`) + if (minutes) parts.push(`${minutes}分`) + // 秒恒显示(含 0),避免"0秒/不足 1 秒"整行变空 + if (seconds || !parts.length) parts.push(`${seconds}秒`) + return parts.join('') +} diff --git a/admin/src/views/tracker/site/index.vue b/admin/src/views/tracker/site/index.vue index 63d4662..bdb12fd 100644 --- a/admin/src/views/tracker/site/index.vue +++ b/admin/src/views/tracker/site/index.vue @@ -78,6 +78,7 @@ } from '@/api/tracker/sites' import { ElTag, ElButton, ElMessageBox } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus' + import { formatDateTime } from '@/utils/tracker' defineOptions({ name: 'TrackerSite' }) @@ -145,8 +146,6 @@ } } - const formatDateTime = (iso: string) => (iso ? iso.replace('T', ' ').slice(0, 19) : '—') - const { columns, columnChecks, data, loading, refreshData } = useTable({ core: { apiFn: getSites, diff --git a/admin/src/views/tracker/visitor/index.vue b/admin/src/views/tracker/visitor/index.vue index 05995c5..998d249 100644 --- a/admin/src/views/tracker/visitor/index.vue +++ b/admin/src/views/tracker/visitor/index.vue @@ -1,10 +1,301 @@ + + + +