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 @@ + + + +