From 9966f03347030630b55ee94364cbdf825ec52606 Mon Sep 17 00:00:00 2001 From: skycurtain Date: Mon, 12 Oct 2026 15:00:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E6=97=B6=E9=97=B4=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=99=A8=E5=A2=9E=E5=8A=A0=E3=80=8C=E4=BB=8A=E5=A4=A9?= =?UTF-8?q?=E3=80=8D=E5=BF=AB=E6=8D=B7=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 北京日界左闭右开;相对窗口可跨日自动重算。 --- apps/web/src/components/common/time-range-picker.vue | 12 ++++++++++++ apps/web/src/composables/use-live-time-range.ts | 7 ++++--- apps/web/src/stores/member-range.ts | 2 +- apps/web/src/utils/time.ts | 8 ++++++++ apps/web/src/views/admin/logs-view.vue | 7 +++++-- apps/web/src/views/member/logs-view.vue | 3 ++- docs/design/交互设计-总览与共性.md | 2 +- 7 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/common/time-range-picker.vue b/apps/web/src/components/common/time-range-picker.vue index dc9f094..1d9f033 100644 --- a/apps/web/src/components/common/time-range-picker.vue +++ b/apps/web/src/components/common/time-range-picker.vue @@ -4,6 +4,7 @@ import dayjs from "dayjs"; import { computed } from "vue"; import { recentDaysRange, + todayRange, wallAsBeijingIso, type RangeValue, } from "@/utils/time.ts"; @@ -28,6 +29,10 @@ const pickerValue = computed<[Dayjs, Dayjs] | null>(() => { const presets = computed(() => { const builtIn = [ + { + label: "今天", + value: rangeToDayjs(todayRange()), + }, { label: "最近 7 天", value: rangeToDayjs(recentDaysRange(7)), @@ -51,6 +56,13 @@ function rangeToDayjs(range: RangeValue): [Dayjs, Dayjs] { function detectKind(value: RangeValue): RangeKind { const fromD = dayjs(value.from).format("YYYY-MM-DD"); const toD = dayjs(value.to).format("YYYY-MM-DD"); + const today = todayRange(); + if ( + dayjs(today.from).format("YYYY-MM-DD") === fromD && + dayjs(today.to).format("YYYY-MM-DD") === toD + ) { + return "today"; + } for (const days of [7, 30] as const) { const r = recentDaysRange(days); if ( diff --git a/apps/web/src/composables/use-live-time-range.ts b/apps/web/src/composables/use-live-time-range.ts index 99a62b3..af16dca 100644 --- a/apps/web/src/composables/use-live-time-range.ts +++ b/apps/web/src/composables/use-live-time-range.ts @@ -1,12 +1,12 @@ import { computed, onUnmounted, ref } from "vue"; -import { recentDaysRange } from "@/utils/time.ts"; +import { recentDaysRange, todayRange } from "@/utils/time.ts"; export type RangeValue = { from: string; to: string }; -export type RangeKind = "recent-7" | "recent-30" | "fixed"; +export type RangeKind = "today" | "recent-7" | "recent-30" | "fixed"; /** * 可跟随「今天」推进的时间范围: - * - 最近 7/30 天:定时重算窗口(页面挂过夜也不会陈旧) + * - 今天 / 最近 7/30 天:定时重算窗口(页面挂过夜也不会陈旧) * - fixed:自定义/订阅期间等绝对区间,窗口不挪 */ export function useLiveTimeRange(initial: Exclude = "recent-30") { @@ -21,6 +21,7 @@ export function useLiveTimeRange(initial: Exclude = "recent- const range = computed(() => { void nowTick.value; + if (kind.value === "today") return todayRange(); if (kind.value === "recent-7") return recentDaysRange(7); if (kind.value === "recent-30") return recentDaysRange(30); return fixed.value; diff --git a/apps/web/src/stores/member-range.ts b/apps/web/src/stores/member-range.ts index 75ba8b5..d9af646 100644 --- a/apps/web/src/stores/member-range.ts +++ b/apps/web/src/stores/member-range.ts @@ -3,7 +3,7 @@ import { computed, ref } from "vue"; import { recentDaysRange } from "@/utils/time.ts"; export type RangeValue = { from: string; to: string }; -export type RangeKind = "recent-7" | "recent-30" | "fixed"; +export type RangeKind = "today" | "recent-7" | "recent-30" | "fixed"; /** * 成员端全局时间范围——总览 / 调用日志 / 支付参数共用。 diff --git a/apps/web/src/utils/time.ts b/apps/web/src/utils/time.ts index a84e0d7..1a7c9b5 100644 --- a/apps/web/src/utils/time.ts +++ b/apps/web/src/utils/time.ts @@ -83,6 +83,14 @@ export function recentDaysRange( return { from: shanghaiIso(from), to: shanghaiIso(to) }; } +/** 快捷项:今天(北京时间日界,左闭右开) */ +export function todayRange(nowMs: number = Date.now()): { + from: string; + to: string; +} { + return recentDaysRange(1, nowMs); +} + /** 统计口径标注:「2026-09-02 ~ 10-01」(北京时间日) */ export function formatRangeCaption(fromIso: string, toIso: string): string { if (!fromIso || !toIso) return "—"; diff --git a/apps/web/src/views/admin/logs-view.vue b/apps/web/src/views/admin/logs-view.vue index 5391367..1de857e 100644 --- a/apps/web/src/views/admin/logs-view.vue +++ b/apps/web/src/views/admin/logs-view.vue @@ -9,7 +9,10 @@ import PageHeader from "@/components/common/page-header.vue"; import TableRefresh from "@/components/common/table-refresh.vue"; import TimeRangePicker from "@/components/common/time-range-picker.vue"; import QuotaText from "@/components/common/quota-text.vue"; -import { useLiveTimeRange } from "@/composables/use-live-time-range.ts"; +import { + useLiveTimeRange, + type RangeKind, +} from "@/composables/use-live-time-range.ts"; import { useAdminLogs, useKeys, useMembers } from "@/queries/admin.ts"; import { formatDateTime } from "@/utils/time.ts"; import { formatInt } from "@/utils/format.ts"; @@ -177,7 +180,7 @@ let ro: ResizeObserver | null = null; function onRangeChange( v: { from: string; to: string }, - k: "recent-7" | "recent-30" | "fixed", + k: RangeKind, ) { setRange(v, k); page.value = 1; diff --git a/apps/web/src/views/member/logs-view.vue b/apps/web/src/views/member/logs-view.vue index 6bfbe17..88da0ff 100644 --- a/apps/web/src/views/member/logs-view.vue +++ b/apps/web/src/views/member/logs-view.vue @@ -13,6 +13,7 @@ import TableRefresh from "@/components/common/table-refresh.vue"; import TimeRangePicker from "@/components/common/time-range-picker.vue"; import QuotaText from "@/components/common/quota-text.vue"; import { useMemberRange } from "@/composables/use-member-range.ts"; +import type { RangeKind } from "@/stores/member-range.ts"; import { useMemberLogs, useMemberOverview } from "@/queries/member.ts"; import { formatDateTime } from "@/utils/time.ts"; import { formatInt, formatPaidAmount } from "@/utils/format.ts"; @@ -177,7 +178,7 @@ watch([apiKeyId, billingSource, subscriptionId, typeFilter], () => { }); watch(page, () => pushQuery()); -function onRangeChange(v: { from: string; to: string }, k: "recent-7" | "recent-30" | "fixed") { +function onRangeChange(v: { from: string; to: string }, k: RangeKind) { setRange(v, k); page.value = 1; pushQuery(); diff --git a/docs/design/交互设计-总览与共性.md b/docs/design/交互设计-总览与共性.md index 4b8e70a..1abbc91 100644 --- a/docs/design/交互设计-总览与共性.md +++ b/docs/design/交互设计-总览与共性.md @@ -45,7 +45,7 @@ ### 3.1 时间范围选择器(核心组件) - 一切统计的公共维度:**任意时段** + 快捷项 -- 快捷项:最近 7 天 / 最近 30 天(默认)/ **本订阅期间**(订阅期间作为预置选项——R10 裁撤后的「周期」形态;依赖支付记录,无登记的部署形态自动隐藏该项,见 §3.8)/ 自定义起止 +- 快捷项:**今天** / 最近 7 天 / 最近 30 天(默认)/ **本订阅期间**(订阅期间作为预置选项——R10 裁撤后的「周期」形态;依赖支付记录,无登记的部署形态自动隐藏该项,见 §3.8)/ 自定义起止 - 所有统计数字标注其口径(「2026-09-02 ~ 10-01」),杜绝无口径数字 ### 3.2 数值呈现三约定