diff --git a/apps/web/src/api/admin/overview.ts b/apps/web/src/api/admin/overview.ts index 3059516..435c319 100644 --- a/apps/web/src/api/admin/overview.ts +++ b/apps/web/src/api/admin/overview.ts @@ -21,6 +21,7 @@ export type AdminOverview = { key_id: number; token_name: string; member_id: number | null; + member_name: string | null; status: string; data_cutoff: UnixSec | null; usage: UsageTotals; diff --git a/apps/web/src/components/admin/health-band.vue b/apps/web/src/components/admin/health-band.vue new file mode 100644 index 0000000..7c35915 --- /dev/null +++ b/apps/web/src/components/admin/health-band.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/apps/web/src/components/common/page-header.vue b/apps/web/src/components/common/page-header.vue index 745f50b..6e3ef79 100644 --- a/apps/web/src/components/common/page-header.vue +++ b/apps/web/src/components/common/page-header.vue @@ -47,7 +47,10 @@ defineProps<{ .page-header__actions { display: flex; + align-items: center; gap: 8px; flex-shrink: 0; + /* 与标题块上缘对齐时,控件 32px 高与 label 竖向居中 */ + min-height: 32px; } diff --git a/apps/web/src/components/common/quota-text.vue b/apps/web/src/components/common/quota-text.vue new file mode 100644 index 0000000..d72b4a6 --- /dev/null +++ b/apps/web/src/components/common/quota-text.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/apps/web/src/components/common/source-bar.vue b/apps/web/src/components/common/source-bar.vue new file mode 100644 index 0000000..15734fc --- /dev/null +++ b/apps/web/src/components/common/source-bar.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/apps/web/src/components/common/time-range-picker.vue b/apps/web/src/components/common/time-range-picker.vue new file mode 100644 index 0000000..8207d76 --- /dev/null +++ b/apps/web/src/components/common/time-range-picker.vue @@ -0,0 +1,63 @@ + + + diff --git a/apps/web/src/utils/time.ts b/apps/web/src/utils/time.ts index 4b696fa..4c16515 100644 --- a/apps/web/src/utils/time.ts +++ b/apps/web/src/utils/time.ts @@ -45,3 +45,23 @@ export function recentDaysRange(days: number): { from: string; to: string } { const from = to.subtract(days, "day"); return { from: from.toISOString(), to: to.toISOString() }; } + +/** 统计口径标注:「2026-09-02 ~ 10-01」(北京时间日) */ +export function formatRangeCaption(fromIso: string, toIso: string): string { + if (!fromIso || !toIso) return "—"; + const from = dayjs(fromIso).tz(DISPLAY_TZ).format("YYYY-MM-DD"); + const to = dayjs(toIso).tz(DISPLAY_TZ).format("YYYY-MM-DD"); + return `${from} ~ ${to}`; +} + +/** 「数据截止」到分钟(交互共性 §3.3) */ +export function formatMinute(unixSec: number | null | undefined): string { + if (unixSec == null) return "—"; + return dayjs.unix(unixSec).tz(DISPLAY_TZ).format("YYYY-MM-DD HH:mm"); +} + +/** 距今小时数(用于数据截止停滞判定) */ +export function hoursSince(unixSec: number | null | undefined): number | null { + if (unixSec == null) return null; + return (Date.now() / 1000 - unixSec) / 3600; +} diff --git a/apps/web/src/views/admin/overview-view.vue b/apps/web/src/views/admin/overview-view.vue index 8c3e0a7..5b2b261 100644 --- a/apps/web/src/views/admin/overview-view.vue +++ b/apps/web/src/views/admin/overview-view.vue @@ -1,13 +1,314 @@ + +