mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-10-03 23:54:06 +00:00
fix(ui): 统计区间文案在日视图恒为空、天数按小时桶误报
v1.9.0 加的「统计区间:起 → 止 (Nd)」正是用来解释"周 > 月"的,但它自己 在日视图上从来不出现在屏幕上。 原因:区间终点从最后一个 bucket 的标签拼出来,而 bucket 粒度随视图变化 —— 日视图按小时("2026-10-03T09"),周/月按天("2026-10-01")。代码无条件 拼 "T00:00:00Z",日视图就成了 "2026-10-03T09T00:00:00Z",Date 拒绝该值, toISOString() 抛 RangeError,整个赋值语句丢失。页面无任何报错,只是那行 字不见了——恰好是用户最常看的"今日"视图。 第二个问题同源:天数直接用了 st.buckets.length。日视图 5 个桶是 5 个小时, 界面却显示 "(5d)"。改为按窗口起止算天数。 CDP 实测(修复后): day 统计区间: 2026-10-03 → 2026-10-03 (1d) ← 原为空,且曾误报 5d week 统计区间: 2026-09-28 → 2026-10-03 (6d) month 统计区间: 2026-10-01 → 2026-10-03 (3d) all (空,正确:终身累计没有窗口) 判据一条 + 变异:恢复原来的盲目拼接 → 判红。 Co-Authored-By: ModelRouter <noreply@modelrouter.dev>
This commit is contained in:
@ -2016,16 +2016,41 @@ const pnote = $("#period-note");
|
|||||||
// like a bug unless the dates are on screen — so print them.
|
// like a bug unless the dates are on screen — so print them.
|
||||||
const prange = $("#period-range");
|
const prange = $("#period-range");
|
||||||
if (prange && st.period && st.period !== "all" && st.from) {
|
if (prange && st.period && st.period !== "all" && st.from) {
|
||||||
const d0 = new Date(st.from);
|
// Bucket labels are hourly for a day ("2026-10-03T09") and daily
|
||||||
const d1 = st.buckets && st.buckets.length
|
// for a week or month ("2026-10-01"). Appending "T00:00:00Z" to
|
||||||
? new Date(st.buckets[st.buckets.length - 1].bucket + "T00:00:00Z")
|
// the hourly form builds "2026-10-03T09T00:00:00Z", which Date
|
||||||
: d0;
|
// rejects — toISOString() then throws and the whole assignment is
|
||||||
const f = (d) =>
|
// lost, leaving the range line silently empty on the day view (the
|
||||||
d.toISOString().slice(0, 10);
|
// one people look at most).
|
||||||
prange.textContent =
|
const f = (d) => d.toISOString().slice(0, 10);
|
||||||
t("periodRange") +
|
const label = st.buckets && st.buckets.length
|
||||||
" " + f(d0) + " → " + f(d1) +
|
? st.buckets[st.buckets.length - 1].bucket
|
||||||
(st.buckets ? " (" + st.buckets.length + "d)" : "");
|
: "";
|
||||||
|
let endMs = 0;
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}$/.test(label)) {
|
||||||
|
endMs = new Date(label + "T00:00:00Z").getTime();
|
||||||
|
} else if (/^\d{4}-\d{2}-\d{2}T\d{2}$/.test(label)) {
|
||||||
|
// An hourly bucket covers its hour, so the day it belongs to is
|
||||||
|
// what the range should end on.
|
||||||
|
endMs = new Date(label + ":00:00Z").getTime();
|
||||||
|
}
|
||||||
|
const from = new Date(st.from);
|
||||||
|
const end = endMs ? new Date(endMs) : from;
|
||||||
|
// The bucket count is not a day count: the day view buckets by
|
||||||
|
// HOUR, so "(5d)" for five hours of today was simply wrong. Days
|
||||||
|
// covered comes from the window itself, which is what the reader
|
||||||
|
// is trying to establish.
|
||||||
|
let days = 1;
|
||||||
|
if (endMs) {
|
||||||
|
days = Math.max(
|
||||||
|
1,
|
||||||
|
Math.round((endMs - from.getTime()) / 86400000) + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const tail = " (" + days + "d)";
|
||||||
|
prange.textContent = endMs
|
||||||
|
? t("periodRange") + " " + f(from) + " → " + f(end) + tail
|
||||||
|
: t("periodRange") + " " + f(from) + tail;
|
||||||
} else if (prange) {
|
} else if (prange) {
|
||||||
prange.textContent = "";
|
prange.textContent = "";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -266,3 +266,24 @@ func TestUIKeyAutoEditsDoNotAutoPersist(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The stats range line formats its end date from the LAST bucket label.
|
||||||
|
// Bucket labels are hourly for the day view ("2026-10-03T09") and daily for
|
||||||
|
// week/month ("2026-10-01"), so blindly appending "T00:00:00Z" to the hourly
|
||||||
|
// form builds "2026-10-03T09T00:00:00Z" — Date rejects it, toISOString()
|
||||||
|
// throws, and the assignment is lost. The symptom is an empty range line on
|
||||||
|
// exactly the view people look at most, with no error anywhere.
|
||||||
|
func TestUIStatsPeriodRangeHandlesHourlyBuckets(t *testing.T) {
|
||||||
|
src := uiSource(t)
|
||||||
|
el := readFuncBody(t, src, "paintStats")
|
||||||
|
// It must branch on the label shape instead of concatenating blindly.
|
||||||
|
if !strings.Contains(el, `T\d{2}$`) && !strings.Contains(el, `\d{2}-\d{2}T\d{2}`) {
|
||||||
|
t.Fatal("paintStats must recognise the hourly bucket label shape " +
|
||||||
|
"(YYYY-MM-DDTHH); concatenating a time onto it yields an invalid Date")
|
||||||
|
}
|
||||||
|
// And it must not contain the naive form that broke it.
|
||||||
|
if strings.Contains(el, `bucket + "T00:00:00Z"`) {
|
||||||
|
t.Fatal("paintStats still appends T00:00:00Z to the bucket label — " +
|
||||||
|
"this throws on the day view and leaves the range line empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user