fix(stats): 数据速览用完整星期名,脚注时间改逐行对齐

- 「提问最集中在 三 19 点前后」:洞察复用了热力图列头的短标签
  stats.wd.*(一…日 / Mon…),那是为窄列头设计的单字词条,
  进句子就成了「三」。新增 stats.weekday.1..7 完整名(周一 / Monday /
  月曜日),洞察改用该组,stats.wd.* 只留给列头;补测试断言锁住。
- 页脚入口文案「数据」→「统计数据」(四语言同步)。
- 页面脚注四条时间由「基准日 x · 计算于 y」一行改为两列网格逐行排列,
  标签统一带冒号,下面两段说明保持独立段落。
This commit is contained in:
tamakyi
2026-10-24 01:08:50 +08:00
parent a5441c99fd
commit 64436d9d61
8 changed files with 114 additions and 46 deletions
+12
View File
@@ -13,3 +13,15 @@
重新生成预览确认首页无 `tb-stats-entry`,页脚渲染为「社区规则 | 数据 | 找站长」。 重新生成预览确认首页无 `tb-stats-entry`,页脚渲染为「社区规则 | 数据 | 找站长」。
- 教训:删掉页面唯一消费方后,服务端的取数调用(`stats.Get`)要同步删, - 教训:删掉页面唯一消费方后,服务端的取数调用(`stats.Get`)要同步删,
否则每次访问首页白算一次快照;对应 i18n 词条也要一起清,避免留死条目。 否则每次访问首页白算一次快照;对应 i18n 词条也要一起清,避免留死条目。
## /stats 文案与脚注排版修正
- 页脚入口文案「数据」→「统计数据」(`site.footer.stats`,四语言 `Statistics` / `統計データ` / `統計數據`)。
- **「提问最集中在三 19 点前后」的 bug**:洞察句子复用了热力图列头的 `stats.wd.*`
("一"…"日" / "Mon"…),那是为窄列头设计的单字标签,进句子就成了"三"、英文是 "Wed"。
新增一组完整名 `stats.weekday.1..7`(周一/週一/Monday/月曜日),洞察改用这组;
`stats.wd.*` 只留给热力图列头。已在 `overview_test.go` 加断言锁住,防止回退。
→ 教训:**短标签和完整名必须分两套词条**,别为了少写一组 key 复用。
- 页面脚注四条时间(基准日 / 计算于 / 最后一条提问 / 最后一条回答)由
「A:x · B:y」挤成一行改为 `grid-template-columns: max-content 1fr` 各占一行,
标签列与值列对齐;下面两段说明仍为独立段落。四个标签统一带冒号。
+15 -8
View File
@@ -953,7 +953,7 @@ var enDict = map[string]string{
"theme.name.new-sakura": "New Sakura", "theme.name.new-sakura": "New Sakura",
// Site statistics page (/stats): aggregate numbers only // Site statistics page (/stats): aggregate numbers only
"site.footer.stats": "Stats", "site.footer.stats": "Statistics",
"stats.title": "Site statistics", "stats.title": "Site statistics",
"stats.lead": "Only aggregate numbers here: how many questions the site receives, how fast they get answered, and when people tend to show up. No individual question, asker identity, or source address is shown.", "stats.lead": "Only aggregate numbers here: how many questions the site receives, how fast they get answered, and when people tend to show up. No individual question, asker identity, or source address is shown.",
"stats.unavailable": "Statistics are temporarily unavailable. Please check back later.", "stats.unavailable": "Statistics are temporarily unavailable. Please check back later.",
@@ -1014,13 +1014,20 @@ var enDict = map[string]string{
"stats.reply.5": "3–7 days", "stats.reply.5": "3–7 days",
"stats.reply.6": "over 7 days", "stats.reply.6": "over 7 days",
"stats.wd.1": "Mon", "stats.wd.1": "Mon",
"stats.wd.2": "Tue", "stats.wd.2": "Tue",
"stats.wd.3": "Wed", "stats.wd.3": "Wed",
"stats.wd.4": "Thu", "stats.wd.4": "Thu",
"stats.wd.5": "Fri", "stats.wd.5": "Fri",
"stats.wd.6": "Sat", "stats.wd.6": "Sat",
"stats.wd.7": "Sun", "stats.wd.7": "Sun",
"stats.weekday.1": "Monday",
"stats.weekday.2": "Tuesday",
"stats.weekday.3": "Wednesday",
"stats.weekday.4": "Thursday",
"stats.weekday.5": "Friday",
"stats.weekday.6": "Saturday",
"stats.weekday.7": "Sunday",
"stats.foot.dataDate": "Data as of", "stats.foot.dataDate": "Data as of",
"stats.foot.updated": "computed at", "stats.foot.updated": "computed at",
+16 -9
View File
@@ -952,7 +952,7 @@ var jaDict = map[string]string{
"theme.name.new-sakura": "新さくら", "theme.name.new-sakura": "新さくら",
// サイト統計ページ(/stats):集計値のみ // サイト統計ページ(/stats):集計値のみ
"site.footer.stats": "統計", "site.footer.stats": "統計データ",
"stats.title": "サイト統計", "stats.title": "サイト統計",
"stats.lead": "ここにあるのは集計値だけです。サイト全体にどれだけ質問が届き、どれくらいで回答され、いつ人が来るのか。個々の質問内容・質問者の身元・送信元アドレスは一切表示しません。", "stats.lead": "ここにあるのは集計値だけです。サイト全体にどれだけ質問が届き、どれくらいで回答され、いつ人が来るのか。個々の質問内容・質問者の身元・送信元アドレスは一切表示しません。",
"stats.unavailable": "統計データを一時的に取得できません。しばらくしてから再度お試しください。", "stats.unavailable": "統計データを一時的に取得できません。しばらくしてから再度お試しください。",
@@ -1013,16 +1013,23 @@ var jaDict = map[string]string{
"stats.reply.5": "3–7 日", "stats.reply.5": "3–7 日",
"stats.reply.6": "7 日以上", "stats.reply.6": "7 日以上",
"stats.wd.1": "月", "stats.wd.1": "月",
"stats.wd.2": "火", "stats.wd.2": "火",
"stats.wd.3": "水", "stats.wd.3": "水",
"stats.wd.4": "木", "stats.wd.4": "木",
"stats.wd.5": "金", "stats.wd.5": "金",
"stats.wd.6": "土", "stats.wd.6": "土",
"stats.wd.7": "日", "stats.wd.7": "日",
"stats.weekday.1": "月曜日",
"stats.weekday.2": "火曜日",
"stats.weekday.3": "水曜日",
"stats.weekday.4": "木曜日",
"stats.weekday.5": "金曜日",
"stats.weekday.6": "土曜日",
"stats.weekday.7": "日曜日",
"stats.foot.dataDate": "集計基準日:", "stats.foot.dataDate": "集計基準日:",
"stats.foot.updated": "算出時刻", "stats.foot.updated": "算出時刻:",
"stats.foot.prevMonth": "最後の質問:", "stats.foot.prevMonth": "最後の質問:",
"stats.foot.prevAnswer": "最後の回答:", "stats.foot.prevAnswer": "最後の回答:",
"stats.foot.privacy": "数値はすべて集計値です。個々の質問内容・質問者の身元・送信元アドレスは表示しません。", "stats.foot.privacy": "数値はすべて集計値です。個々の質問内容・質問者の身元・送信元アドレスは表示しません。",
+17 -10
View File
@@ -953,7 +953,7 @@ var zhCNDict = map[string]string{
"form.register.repeatPassword": "两次输入的密码不一致", "form.register.repeatPassword": "两次输入的密码不一致",
// 全站数据页(/stats):只展示聚合数字,不含任何单条内容 // 全站数据页(/stats):只展示聚合数字,不含任何单条内容
"site.footer.stats": "数据", "site.footer.stats": "统计数据",
"stats.title": "全站数据", "stats.title": "全站数据",
"stats.lead": "这里只有汇总后的数字:全站收到了多少提问、回答得有多快、大家习惯什么时候来。不含任何一条具体提问的内容、提问者身份或来源地址。", "stats.lead": "这里只有汇总后的数字:全站收到了多少提问、回答得有多快、大家习惯什么时候来。不含任何一条具体提问的内容、提问者身份或来源地址。",
"stats.unavailable": "数据暂时不可用,稍后再刷新看看。", "stats.unavailable": "数据暂时不可用,稍后再刷新看看。",
@@ -974,7 +974,7 @@ var zhCNDict = map[string]string{
"stats.insight.empty": "提问数据还太少,暂时看不出什么。", "stats.insight.empty": "提问数据还太少,暂时看不出什么。",
"stats.insight.daily30": "近 30 天共收到 %s 条提问,日均 %.1f 条。", "stats.insight.daily30": "近 30 天共收到 %s 条提问,日均 %.1f 条。",
"stats.insight.peak": "最忙的一天是 %s,收到 %s 条提问。", "stats.insight.peak": "最忙的一天是 %s,收到 %s 条提问。",
"stats.insight.busiest": "提问最集中在 %s %d 点前后。", "stats.insight.busiest": "提问最集中在%s %d 点前后。",
"stats.insight.mom": "上月提问量比前一个月 %s%%。", "stats.insight.mom": "上月提问量比前一个月 %s%%。",
"stats.insight.fastReply": "已回答的提问中,%s%% 在 24 小时内拿到了回复。", "stats.insight.fastReply": "已回答的提问中,%s%% 在 24 小时内拿到了回复。",
"stats.insight.private": "%s%% 的提问选择了私密(不公开)。", "stats.insight.private": "%s%% 的提问选择了私密(不公开)。",
@@ -1014,16 +1014,23 @@ var zhCNDict = map[string]string{
"stats.reply.5": "3–7 天", "stats.reply.5": "3–7 天",
"stats.reply.6": "7 天以上", "stats.reply.6": "7 天以上",
"stats.wd.1": "一", "stats.wd.1": "一",
"stats.wd.2": "二", "stats.wd.2": "二",
"stats.wd.3": "三", "stats.wd.3": "三",
"stats.wd.4": "四", "stats.wd.4": "四",
"stats.wd.5": "五", "stats.wd.5": "五",
"stats.wd.6": "六", "stats.wd.6": "六",
"stats.wd.7": "日", "stats.wd.7": "日",
"stats.weekday.1": "周一",
"stats.weekday.2": "周二",
"stats.weekday.3": "周三",
"stats.weekday.4": "周四",
"stats.weekday.5": "周五",
"stats.weekday.6": "周六",
"stats.weekday.7": "周日",
"stats.foot.dataDate": "数据基准日:", "stats.foot.dataDate": "数据基准日:",
"stats.foot.updated": "计算于", "stats.foot.updated": "计算于:",
"stats.foot.prevMonth": "最后一条提问:", "stats.foot.prevMonth": "最后一条提问:",
"stats.foot.prevAnswer": "最后一条回答:", "stats.foot.prevAnswer": "最后一条回答:",
"stats.foot.privacy": "所有数字均为聚合结果,不展示单条提问内容、提问者身份或来源地址。", "stats.foot.privacy": "所有数字均为聚合结果,不展示单条提问内容、提问者身份或来源地址。",
+17 -10
View File
@@ -952,7 +952,7 @@ var zhTWDict = map[string]string{
"theme.name.new-sakura": "新·櫻花", "theme.name.new-sakura": "新·櫻花",
// 全站數據頁(/stats):只顯示彙總數字,不含任何單則內容 // 全站數據頁(/stats):只顯示彙總數字,不含任何單則內容
"site.footer.stats": "數據", "site.footer.stats": "統計數據",
"stats.title": "全站數據", "stats.title": "全站數據",
"stats.lead": "這裡只有彙總後的數字:全站收到多少提問、回答得有多快、大家習慣什麼時候來。不含任何一則具體提問的內容、提問者身分或來源位址。", "stats.lead": "這裡只有彙總後的數字:全站收到多少提問、回答得有多快、大家習慣什麼時候來。不含任何一則具體提問的內容、提問者身分或來源位址。",
"stats.unavailable": "數據暫時無法使用,稍後再重新整理看看。", "stats.unavailable": "數據暫時無法使用,稍後再重新整理看看。",
@@ -973,7 +973,7 @@ var zhTWDict = map[string]string{
"stats.insight.empty": "提問數據還太少,暫時看不出什麼。", "stats.insight.empty": "提問數據還太少,暫時看不出什麼。",
"stats.insight.daily30": "近 30 天共收到 %s 則提問,日均 %.1f 則。", "stats.insight.daily30": "近 30 天共收到 %s 則提問,日均 %.1f 則。",
"stats.insight.peak": "最忙的一天是 %s,收到 %s 則提問。", "stats.insight.peak": "最忙的一天是 %s,收到 %s 則提問。",
"stats.insight.busiest": "提問最集中在 %s %d 點前後。", "stats.insight.busiest": "提問最集中在%s %d 點前後。",
"stats.insight.mom": "上月提問量比前一個月 %s%%。", "stats.insight.mom": "上月提問量比前一個月 %s%%。",
"stats.insight.fastReply": "已回答的提問中,%s%% 在 24 小時內得到回覆。", "stats.insight.fastReply": "已回答的提問中,%s%% 在 24 小時內得到回覆。",
"stats.insight.private": "%s%% 的提問選擇了私密(不公開)。", "stats.insight.private": "%s%% 的提問選擇了私密(不公開)。",
@@ -1013,16 +1013,23 @@ var zhTWDict = map[string]string{
"stats.reply.5": "3–7 天", "stats.reply.5": "3–7 天",
"stats.reply.6": "7 天以上", "stats.reply.6": "7 天以上",
"stats.wd.1": "一", "stats.wd.1": "一",
"stats.wd.2": "二", "stats.wd.2": "二",
"stats.wd.3": "三", "stats.wd.3": "三",
"stats.wd.4": "四", "stats.wd.4": "四",
"stats.wd.5": "五", "stats.wd.5": "五",
"stats.wd.6": "六", "stats.wd.6": "六",
"stats.wd.7": "日", "stats.wd.7": "日",
"stats.weekday.1": "週一",
"stats.weekday.2": "週二",
"stats.weekday.3": "週三",
"stats.weekday.4": "週四",
"stats.weekday.5": "週五",
"stats.weekday.6": "週六",
"stats.weekday.7": "週日",
"stats.foot.dataDate": "數據基準日:", "stats.foot.dataDate": "數據基準日:",
"stats.foot.updated": "計算於", "stats.foot.updated": "計算於:",
"stats.foot.prevMonth": "最後一則提問:", "stats.foot.prevMonth": "最後一則提問:",
"stats.foot.prevAnswer": "最後一則回答:", "stats.foot.prevAnswer": "最後一則回答:",
"stats.foot.privacy": "所有數字均為彙總結果,不展示單則提問內容、提問者身分或來源位址。", "stats.foot.privacy": "所有數字均為彙總結果,不展示單則提問內容、提問者身分或來源位址。",
+5
View File
@@ -5,6 +5,7 @@
package stats package stats
import ( import (
"strings"
"testing" "testing"
"time" "time"
@@ -104,4 +105,8 @@ func TestOverviewDerived(t *testing.T) {
require.Len(t, page.WeekdayLabels, 7) require.Len(t, page.WeekdayLabels, 7)
require.Len(t, page.LengthLabels, 6) require.Len(t, page.LengthLabels, 6)
require.Len(t, page.ReplyLabels, 6) require.Len(t, page.ReplyLabels, 6)
// 句子里必须是完整星期名:热力图列头的 stats.wd.* 是单字("一"/"三"),
// 复用到句子里会读成「提问最集中在三 8 点前后」。
require.Contains(t, strings.Join(page.Insights, "\n"), "提问最集中在周一 8 点前后。")
} }
+6 -2
View File
@@ -108,10 +108,14 @@ func insights(lang string, ov *Overview) []string {
} }
if ov.HeatmapMax > 0 { if ov.HeatmapMax > 0 {
// DOW 以周日为 0,展示顺序以周一为 1,换算到 stats.wd.N。 // DOW 以周日为 0,展示顺序以周一为 1,换算到 stats.weekday.N。
//
// 句子里必须用完整星期名(stats.weekday.*),不能用热力图列头那套
// 单字标签(stats.wd.* = "一"/"Mon"):列头要窄,句子里"三 19 点"
// 会被读成数字 3,英文那边则直接是 "Wed 19:00"。
wd := (ov.BusiestWD+6)%7 + 1 wd := (ov.BusiestWD+6)%7 + 1
out = append(out, t("stats.insight.busiest", out = append(out, t("stats.insight.busiest",
i18n.T(lang, fmt.Sprintf("stats.wd.%d", wd)), ov.BusiestHr)) i18n.T(lang, fmt.Sprintf("stats.weekday.%d", wd)), ov.BusiestHr))
} }
if ov.HasPrevMonth { if ov.HasPrevMonth {
+26 -7
View File
@@ -350,6 +350,18 @@
line-height: 1.8; line-height: 1.8;
color: var(--tb-text-muted); color: var(--tb-text-muted);
} }
/* 四条时间各自成行、标签左列对齐:挤在「A:x · B:y」一行里,两组日期
靠一个间隔号分开,扫一眼分不清哪个是基准日哪个是最后提问。 */
.tb-foot-times {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
column-gap: 8px;
row-gap: 2px;
margin-bottom: 8px;
}
.tb-foot-note {
margin: 0;
}
.tb-dash-empty { .tb-dash-empty {
padding: 40px 16px; padding: 40px 16px;
text-align: center; text-align: center;
@@ -546,13 +558,20 @@
</div> </div>
</div> </div>
<p class="tb-dash-foot"> <div class="tb-dash-foot">
{{t $.LangCode "stats.foot.dataDate"}} {{ .DataDate }} · {{t $.LangCode "stats.foot.updated"}} {{ .UpdatedAt }} <div class="tb-foot-times">
<br>{{t $.LangCode "stats.foot.prevMonth"}}{{ if .LastQuestionAt }}{{ .LastQuestionAt }}{{ else }}-{{ end }} <span>{{t $.LangCode "stats.foot.dataDate"}}</span>
· {{t $.LangCode "stats.foot.prevAnswer"}}{{ if .LastAnswerAt }}{{ .LastAnswerAt }}{{ else }}-{{ end }} <span>{{ .DataDate }}</span>
<br>{{t $.LangCode "stats.foot.cacheNote"}} <span>{{t $.LangCode "stats.foot.updated"}}</span>
<br>{{t $.LangCode "stats.foot.privacy"}} <span>{{ .UpdatedAt }}</span>
</p> <span>{{t $.LangCode "stats.foot.prevMonth"}}</span>
<span>{{ if .LastQuestionAt }}{{ .LastQuestionAt }}{{ else }}-{{ end }}</span>
<span>{{t $.LangCode "stats.foot.prevAnswer"}}</span>
<span>{{ if .LastAnswerAt }}{{ .LastAnswerAt }}{{ else }}-{{ end }}</span>
</div>
<p class="tb-foot-note">{{t $.LangCode "stats.foot.cacheNote"}}</p>
<p class="tb-foot-note">{{t $.LangCode "stats.foot.privacy"}}</p>
</div>
</div> </div>
{{ else }} {{ else }}
<div class="tb-dash-empty">{{t .LangCode "stats.unavailable"}}</div> <div class="tb-dash-empty">{{t .LangCode "stats.unavailable"}}</div>