feat(stats): 新增公开数据页 /stats,入口收敛到页脚

首页底部那张「站点统计」卡片下线,完整数据移到公开只读页 /stats:
今日实时数、概览六格、近 30 天折线与 12 个月柱状、三个占比环、
7×24 时段热力图、提问字数与回答耗时分布。

- 聚合在 internal/db/analytics.go(SUM(CASE WHEN),方言差异收敛在 analyticsDialectFor)
- 结果按自然日缓存,跨日重算一次后全站共用;重算失败降级返回上一份快照
- 图表零前端依赖:服务端算坐标的 SVG + CSS grid,禁用 JS 页面仍完整
- 环比取「上月 vs 上上月」:本月未过完,否则每月 1 号会得出 -95%
- 入口只在页脚「社区规则」与「找站长」之间;route.Home 不再计算统计
- 补 questions(created_at) 索引;启动后异步预热当天快照
- 测试:analytics 连真库验证软删除不计入、分桶下标、DOW 对齐
This commit is contained in:
tamakyi
2026-10-24 00:57:08 +08:00
parent c680671b7e
commit a5441c99fd
25 changed files with 2280 additions and 80 deletions
+77
View File
@@ -951,4 +951,81 @@ var zhCNDict = map[string]string{
"form.register.recaptcha": "无法加载人机验证,请刷新页面或切换网络后重试",
"form.register.agreeRules": "请先阅读并同意社区规则",
"form.register.repeatPassword": "两次输入的密码不一致",
// 全站数据页(/stats):只展示聚合数字,不含任何单条内容
"site.footer.stats": "数据",
"stats.title": "全站数据",
"stats.lead": "这里只有汇总后的数字:全站收到了多少提问、回答得有多快、大家习惯什么时候来。不含任何一条具体提问的内容、提问者身份或来源地址。",
"stats.unavailable": "数据暂时不可用,稍后再刷新看看。",
"stats.today.questions": "今日提问",
"stats.today.answers": "今日回答",
"stats.today.tag": "(实时)",
"stats.kpi.users": "注册用户",
"stats.kpi.questions": "累计提问",
"stats.kpi.answered": "已回答",
"stats.kpi.answerRate": "回答率",
"stats.kpi.activeBoxes": "收到过提问的箱子",
"stats.kpi.messages": "累计私聊消息",
"stats.insight.title": "数据速览",
"stats.insight.note": "下面几句是从上面的数字直接读出来的,没有推测。",
"stats.insight.empty": "提问数据还太少,暂时看不出什么。",
"stats.insight.daily30": "近 30 天共收到 %s 条提问,日均 %.1f 条。",
"stats.insight.peak": "最忙的一天是 %s,收到 %s 条提问。",
"stats.insight.busiest": "提问最集中在 %s %d 点前后。",
"stats.insight.mom": "上月提问量比前一个月 %s%%。",
"stats.insight.fastReply": "已回答的提问中,%s%% 在 24 小时内拿到了回复。",
"stats.insight.private": "%s%% 的提问选择了私密(不公开)。",
"stats.trend.dailyTitle": "近 30 天提问趋势",
"stats.trend.dailyNote": "按天统计的提问量。",
"stats.trend.monthlyTitle": "近 12 个月提问量",
"stats.trend.monthlyNote": "最后一根柱子是当月,还没过完。",
"stats.mix.title": "构成",
"stats.mix.note": "三组比例:公开还是私密、有没有被回答、提问者有没有登录。",
"stats.mix.public": "公开提问",
"stats.mix.answered": "已回答",
"stats.mix.login": "登录用户提问",
"stats.heat.title": "一周内的提问时段",
"stats.heat.note": "最近 %d 天的提问按「星期 × 小时」聚合,颜色越深表示越集中。",
"stats.heat.less": "少",
"stats.heat.more": "多",
"stats.content.title": "内容与节奏",
"stats.content.note": "提问有多长,回答有多快。",
"stats.content.lengthTitle": "提问字数分布",
"stats.content.replyTitle": "回答耗时分布",
"stats.len.1": "20 字以内",
"stats.len.2": "20–49 字",
"stats.len.3": "50–99 字",
"stats.len.4": "100–199 字",
"stats.len.5": "200–499 字",
"stats.len.6": "500 字以上",
"stats.reply.1": "1 小时内",
"stats.reply.2": "1–6 小时",
"stats.reply.3": "6–24 小时",
"stats.reply.4": "1–3 天",
"stats.reply.5": "3–7 天",
"stats.reply.6": "7 天以上",
"stats.wd.1": "一",
"stats.wd.2": "二",
"stats.wd.3": "三",
"stats.wd.4": "四",
"stats.wd.5": "五",
"stats.wd.6": "六",
"stats.wd.7": "日",
"stats.foot.dataDate": "数据基准日:",
"stats.foot.updated": "计算于",
"stats.foot.prevMonth": "最后一条提问:",
"stats.foot.prevAnswer": "最后一条回答:",
"stats.foot.privacy": "所有数字均为聚合结果,不展示单条提问内容、提问者身份或来源地址。",
"stats.foot.cacheNote": "累计数字与图表是当日 0 点的快照,每天只更新一次;「今日」两项为实时数字。",
}