feat(insights): 箱主数据面板——访问量、提问趋势、处理漏斗
新增 /user/insights,让箱主第一次能看到「有多少人来过我的箱、提问处理得怎么样」。 数据来源: - 新表 box_views:一行 = 一访客×一天×一箱,views 累加,唯一索引 (user_id, viewed_on, visitor)。刻意不内嵌 dbutil.Model —— 软删除 + 唯一索引 会让被删行永久占槽,且删过的槽位再也插不进去。 - 访客标识 = HMAC(日盐, IP) 前 32 hex:同日可去重(UV 准)、跨日不可关联(隐私)。 代价是近 7/30 天 UV = 各日 UV 之和,页面上已写明口径。 写入是异步的:buffered channel 512 + 2s 批量 flush,队列满即丢(统计尽力而为, 不该因为记一笔访问就拖慢页面)。过滤箱主本人、空 UA、爬虫 UA、预取请求。 密钥复用 server.xsrf_key;每日清理 400 天前的明细。 页面(纯 CSS 画图,不引图表库): - 今日/近 7 天/近 30 天 KPI + 上周对比 - 30 天 PV/UV 双柱趋势、24 小时时段分布 - 提问处理漏斗 + 回答率进度条 + 自动结论 聚合层 internal/db/box_analytics.go 复用 analyticsDialectFor 的时区处理, 与全站 stats 分开(那个不含单箱维度、按自然日缓存)。 测试:box_views 4 项(含「同批同键先内存合并」——PG 的 ON CONFLICT 不允许 同条 INSERT 二次命中同一行)、insights 的 pct/窗口切分/visitorID/爬虫过滤。
This commit is contained in:
@@ -100,6 +100,44 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
.profile-panel[hidden] { display: none !important; }
|
||||
|
||||
/* 数据面板入口卡片 */
|
||||
.tb-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 14px;
|
||||
border: 1px solid var(--tb-border);
|
||||
border-left: 3px solid var(--tb-primary);
|
||||
border-radius: var(--tb-radius-sm);
|
||||
background: var(--tb-surface);
|
||||
text-decoration: none;
|
||||
transition: border-color .2s ease, background .2s ease;
|
||||
}
|
||||
.tb-entry:hover {
|
||||
background: var(--tb-primary-weak);
|
||||
text-decoration: none;
|
||||
}
|
||||
.tb-entry-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
.tb-entry-title {
|
||||
font-size: var(--tb-fs-md);
|
||||
font-weight: 600;
|
||||
color: var(--tb-text);
|
||||
}
|
||||
.tb-entry-desc {
|
||||
font-size: var(--tb-fs-xs);
|
||||
color: var(--tb-text-muted);
|
||||
}
|
||||
.tb-entry-arrow {
|
||||
margin-left: auto;
|
||||
color: var(--tb-text-muted);
|
||||
}
|
||||
html[data-theme="dark"] .profile-tabs {
|
||||
border-color: var(--tb-border);
|
||||
background: var(--tb-surface-2);
|
||||
@@ -471,6 +509,16 @@
|
||||
<!-- 提示条置于面板之外:任何面板保存后的 flash 都可见(面板会被 hidden 隐藏) -->
|
||||
{{template "base/alert" .}}
|
||||
|
||||
<!-- 数据面板入口。刻意不放进上面的 tab 栏:tabs 是「改设置」的横向切换,
|
||||
而数据面板是一个独立的只读页面,混进 tab 会让 tab 栏在窄屏上挤成六个。 -->
|
||||
<a class="tb-entry" href="/user/insights">
|
||||
<span class="tb-entry-body">
|
||||
<span class="tb-entry-title">{{t .LangCode "insights.entryTitle"}}</span>
|
||||
<span class="tb-entry-desc">{{t .LangCode "insights.entryDesc"}}</span>
|
||||
</span>
|
||||
<span class="tb-entry-arrow" aria-hidden="true">→</span>
|
||||
</a>
|
||||
|
||||
<!-- 头像/背景裁剪弹窗(Cropper.js):确认后把裁剪结果写回对应的文件域,随主表单一并提交 -->
|
||||
<div id="media-crop-modal" uk-modal>
|
||||
<div class="uk-modal-dialog">
|
||||
|
||||
Reference in New Issue
Block a user