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:
@@ -100,36 +100,9 @@
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.tb-changelog[open] summary::before { transform: rotate(90deg); }
|
||||
.tb-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.tb-stat-num {
|
||||
display: block;
|
||||
font-size: var(--tb-fs-xl);
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
color: var(--tb-text);
|
||||
}
|
||||
.tb-stat-label {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
font-size: var(--tb-fs-xs);
|
||||
color: var(--tb-text-muted);
|
||||
}
|
||||
.tb-stats-meta {
|
||||
margin: 14px 0 0;
|
||||
font-size: var(--tb-fs-xs);
|
||||
line-height: 1.8;
|
||||
color: var(--tb-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.tb-hero { padding: 28px 16px 24px; }
|
||||
.tb-hero-title { font-size: var(--tb-fs-xl); }
|
||||
.tb-stat-num { font-size: var(--tb-fs-lg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -196,50 +169,6 @@
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<div class="uk-card uk-card-default uk-card-body">
|
||||
<h2 class="tb-section-title">{{t .LangCode "site.stats"}}</h2>
|
||||
<div class="tb-stats">
|
||||
<div>
|
||||
<span class="tb-stat-num tb-num" data-stat="registered_users">{{if .SiteStats}}{{.SiteStats.RegisteredUsers}}{{else}}-{{end}}</span>
|
||||
<span class="tb-stat-label">注册用户</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="tb-stat-num tb-num" data-stat="total_questions">{{if .SiteStats}}{{.SiteStats.TotalQuestions}}{{else}}-{{end}}</span>
|
||||
<span class="tb-stat-label">累计提问</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="tb-stat-num tb-num" data-stat="answered_questions">{{if .SiteStats}}{{.SiteStats.AnsweredQuestions}}{{else}}-{{end}}</span>
|
||||
<span class="tb-stat-label">已获回答</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="tb-stats-meta">
|
||||
最后提问:<span data-stat="last_question_at">{{if .SiteStats}}{{.SiteStats.LastQuestionAt}}{{else}}-{{end}}</span><br>
|
||||
最后回答:<span data-stat="last_answer_at">{{if .SiteStats}}{{.SiteStats.LastAnswerAt}}{{else}}-{{end}}</span><br>
|
||||
数据更新于:<span data-stat="updated_at">{{if .SiteStats}}{{.SiteStats.UpdatedAt}}{{else}}-{{end}}</span>
|
||||
</p>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
// 统计数字保持服务端 30 分钟缓存,页面每 30 分钟拉一次最新值就地更新(不整页刷新)。
|
||||
// 与原来写 textContent 整块文本的做法等价,只是改为按字段更新三张数字卡。
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
setInterval(function () {
|
||||
fetch("/api/v1/site-stats")
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (resp) {
|
||||
var d = resp.data;
|
||||
if (!d) return;
|
||||
["registered_users", "total_questions", "answered_questions",
|
||||
"last_question_at", "last_answer_at", "updated_at"].forEach(function (k) {
|
||||
var v = d[k];
|
||||
if (v === undefined || v === null || v === "") return;
|
||||
var el = document.querySelector('[data-stat="' + k + '"]');
|
||||
if (el) el.textContent = v;
|
||||
});
|
||||
})
|
||||
.catch(function () {});
|
||||
}, 30 * 60 * 1000);
|
||||
});
|
||||
</script>
|
||||
<div id="waline"></div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
Reference in New Issue
Block a user