From efa47ee86ce9b826e1974a2f74791491fd18d10d Mon Sep 17 00:00:00 2001
From: zhaozhaozz <6450679+zhaozhaozz@users.noreply.github.com>
Date: Thu, 15 Oct 2026 11:20:11 +0000
Subject: [PATCH] Align open-age charts with list controls and preserve scroll
position
---
docs/dashboard-pages.md | 2 +-
static/app.js | 46 +++++++++++-------
static/board.js | 9 ++--
static/index.html | 8 ++--
static/style.css | 19 +++++++-
test/journey-board.spec.cjs | 95 ++++++++++++++++++++++++++++++++-----
6 files changed, 139 insertions(+), 40 deletions(-)
diff --git a/docs/dashboard-pages.md b/docs/dashboard-pages.md
index e1a2ef81b..905ca905a 100644
--- a/docs/dashboard-pages.md
+++ b/docs/dashboard-pages.md
@@ -40,7 +40,7 @@ CI、PR、Issue 数据不可读取时显示“状态未知”,不会显示为
- **口径**:按 UTC 日期统计。存量按每个 Issue/PR 当前的创建、关闭时间回推,关闭后又重开的从创建起都算开放。历史尚未回填完时,说明里会提示较早日期可能偏少。
- 范围和开关只保存在当前页面,重新打开回到最近 30 天;定时刷新数据时保留。
-列表正上方是**开启时长**饼图:按创建时间把当前开放项分进 `<7d`、`7-30d`、`30-90d`、`>90d` 四桶(边界与采集器的年龄统计一致),图例同时给出数量和占比;`age_days` 缺失的项不计入,数量为 0 的桶只在图例里列出、不可点。点击扇区或图例只筛选本页下方的开放列表(表格和看板都吃同一个筛选),再点一次选中的扇区取消;筛选与搜索、标签、认领人等条件同时生效(取交集),Issue 与 PR 的选择互不影响。筛选生效时筛选条出现当前时长桶,可点 × 清除;结果为 0 时列表显示空状态,饼图保持可见。“最近更新”“最久未动”“最近合并”等旁路表不受这个筛选影响。Issue 页用这张图代替原来的“年龄分布”条形图。
+**开启时长**饼图与开放列表标题、切换及筛选条同一行(窄屏上下堆叠),四桶使用分类色 `--s2`、`--s4`、`--s7`、`--s3`:按创建时间把当前开放项分进 `<7d`、`7-30d`、`30-90d`、`>90d` 四桶(边界与采集器的年龄统计一致),图例同时给出数量和占比;`age_days` 缺失的项不计入,数量为 0 的桶只在图例里列出、不可点。点击扇区或图例只筛选本页下方的开放列表(表格和看板都吃同一个筛选),再点一次选中的扇区取消;筛选与搜索、标签、认领人等条件同时生效(取交集),Issue 与 PR 的选择互不影响。筛选生效时筛选条出现当前时长桶,可点 × 清除;结果为 0 时列表显示空状态,饼图保持可见。“最近更新”“最久未动”“最近合并”等旁路表不受这个筛选影响。Issue 页用这张图代替原来的“年龄分布”条形图。
数据由看板仓的采集在 Actions 中算好写进快照,不读 Bot 的数据库,计算方式见[增量同步与历史](incremental-history.md)。
diff --git a/static/app.js b/static/app.js
index a29290906..b04e970ff 100644
--- a/static/app.js
+++ b/static/app.js
@@ -332,7 +332,7 @@
// Open-age pie above each open list: same bucket boundaries as the collector's aging
// summary ([0,7), [7,30), [30,90), [90,∞)), computed from the items the list shows.
const AGE_BUCKETS = [['<7d', 0, 7], ['7-30d', 7, 30], ['30-90d', 30, 90], ['>90d', 90, null]];
- const AGE_BUCKET_COLOR = ['seq-3', 'seq-4', 'seq-5', 'seq-6'];
+ const AGE_BUCKET_COLOR = ['s2', 's4', 's7', 's3'];
const ageBucketOf = (age) => {
if (age == null || Number.isNaN(Number(age))) return -1;
return AGE_BUCKETS.findIndex(([, lo, hi]) => age >= lo && (hi == null || age < hi));
@@ -367,8 +367,7 @@
const [x1, y1] = point(rO, a1), [u1, v1] = point(rI, a1), large = span > Math.PI ? 1 : 0;
d = `M${x0.toFixed(2)},${y0.toFixed(2)} A${rO},${rO} 0 ${large} 1 ${x1.toFixed(2)},${y1.toFixed(2)} L${u1.toFixed(2)},${v1.toFixed(2)} A${rI},${rI} 0 ${large} 0 ${u0.toFixed(2)},${v0.toFixed(2)} Z`;
}
- const mid = (a0 + a1) / 2, offset = on ? ` transform="translate(${(Math.cos(mid) * 5).toFixed(2)},${(Math.sin(mid) * 5).toFixed(2)})"` : '';
- sectors.push(`
点击扇区或图例筛选,再次点击取消
${agePie(tab, items, unit)}${esc(p.head)} → ${esc(p.base)}${p.labels.length ? ' · ' + labelChips(p.labels) : ''}