本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/待办事宜/等级徽章_列表与详情页实施报告.md。
等级徽章实施报告 — 列表页双徽章 + 详情页用户名下方
需求原文:
1. 把级别和信任等级加到首页、论坛、分类页(移动端只显示图标)
2. 把级别和信任等级加到帖子详情页的用户名下面
实施日期:2026-09-17 | 覆盖范围:核心视图 + Xenforo 主题副本 | 改动文件:13 个
一、需求拆解与关键判断
站内存在两套互不相干的等级体系,这是本次改动最容易踩错的地方:
| 「级别」 | 「信任等级」 | |
|---|---|---|
| 业务含义 | 积分等级 Lv.1~Lv.11 | Discourse 式信任等级 TL0~TL4 |
| 数据来源 | users.level + level_config 表 | user_level 插件 ul_users.map_lv |
| 代码归属 | app/Helpers/Points.php | plugins/user_level/Plugin.php |
| 徽章 CSS 类 | .level-badge / .mn-level-badge | .user-level-badge / .user-level-avatar-badge |
结论:两枚徽章必须分别从各自的渲染入口产出,不能混用。
另一个关键判断:数据层完全就绪,无需改动任何控制器。
Thread::decorate() / Thread::decorateList() / Post::getByThread() 均已输出 level 字段,
HomeController 亦已补 $t['level'],视图直接取 $thread['level'] / $post['level'] 即可。
二、改动清单
2.1 后端 / 插件(4 个文件)
app/Helpers/Points.php
getLevelConfig():由「逐档按需查询」改为首次调用一次性载入整表。
原实现在列表页会被逐行触发,20 行帖子最多产生 11 次查询(N+1);
改为单次 SELECT * FROM level_config 后恒为 1 次。空表时缓存为空数组(非 null),不会反复回源。
renderBadge(int $level, string $mode = 'meta'):重写。- 输出结构改为 icon / text 双 span,为响应式收敛留出钩子:
```html
<span class="level-badge mn-level-badge[ level-badge--list]" style="background:#xxx;" title="Lv.N 称号">
<span class="level-badge-icon">🌰</span><span class="level-badge-text">Lv.N 称号</span>
</span>
```
- 内联样式从 6 条属性收敛为 1 条(仅背景色 —— 每档颜色来自
level_config,属数据不属样式),
其余(display / padding / border-radius / font-size / font-weight / line-height /
white-space)全部下沉到 modern.css。
- 新增
title属性,窄屏只剩图标时仍可通过长按/悬停看到完整文案。 - 新增
mode='list'→ 追加level-badge--list类。 - 默认
mode='meta'保证向后兼容:唯一外部调用方plugins/user_profile/views/front.php:48
无需修改即可继续工作。
plugins/user_level/Plugin.php
badgeHtml()新增mode='list':输出user-level-badge--list+ icon/text 双 span,
与「级别」徽章同构。原 meta(详情页)与 avatar(头像微徽章)两种模式保持不变。
plugins/user_level/hook/controller_view_before.php
- 列表模板白名单由
['forum/index','home/index_default']扩为
['forum/index','forum/category','home/index_default'] —— 分类页此前不在白名单内,徽章零输出。
- 收集循环变量由 3 个扩为
['latestThreads','normalThreads','pinnedThreads','threads'],兼容各列表视图的不同命名。 - 列表徽章渲染模式由
avatar(纯图标)改为list(桌面图标+文字 / 移动端仅图标)。
plugins/user_level/hook/layout_head_end.php
$targets加入'forum/category'。
这条与上一条必须成对修改 —— 否则分类页会出现「有徽章但没样式」。
2.2 列表视图(核心 3 + 主题 3)
统一改为输出一个徽章容器,把两枚徽章包在一起(便于统一间距与换行):
<?php $ulListBadges = $this->getData('ulListBadges', []); $rowUid = (int)($thread['user_id'] ?? 0); ?>
<?php echo \app\Helpers\Points::renderBadge($level, 'list'); ?><?php echo $ulListBadges[$rowUid] ?? ''; ?>| 文件 | 位置 | 容器类 |
|---|---|---|
app/views/forum/index.php | 置顶行 + 普通行(2 处) | mn-list-badges |
app/views/forum/category.php | 帖子行(1 处,新增占位) | mn-list-badges |
app/views/home/index_default.php | 帖子行(1 处) | mn-list-badges |
assets/themes/Xenforo/forum/index.php | 置顶行 + 普通行(2 处,补 $level 赋值 ×2) | xen-threadRow-badges |
assets/themes/Xenforo/forum/category.php | 帖子行(1 处) | xen-threadRow-badges |
assets/themes/Xenforo/home/index_default.php | 帖子行(1 处,补 $level 赋值) | xen-threadRow-badges |
Xenforo 主题三个列表视图原先只有信任等级占位,
$level变量未定义(部分循环甚至未赋值),本次一并补齐。
2.3 详情视图(核心 1 + 主题 1)
app/views/thread/show.php(主帖 + 回复,2 处)
调整前:用户名 / 勋章 / 头衔 / 信任等级 挤在同一行,时间在下一行。
调整后:
用户名 + 勋章 + 头衔 ← 第一行(保持原样)
[Lv.N 称号] [TL4 领袖] ← 第二行(新增 .thread-user-badges)
时间 ← 第三行assets/themes/Xenforo/thread/show.php(主帖 + 回复,2 处)
该主题的楼层结构本来就是「.xen-message-username → .xen-message-role」上下两行,
信任等级已在 role 行,只需补级别徽章,用 .xen-message-badges 把两枚包在一起。
2.4 样式(4 个文件)
| 文件 | 新增内容 |
|---|---|
assets/css/modern.css | .level-badge 基类、.level-badge .level-badge-icon、.mn-list-badges、.thread-user-badges;@media (max-width:768px) 内 .level-badge--list .level-badge-text { display:none } + .level-badge--list 收窄内边距 |
assets/themes/Xenforo/xenforo.css | .xen-threadRow-badges、.xen-message-badges |
plugins/user_level/assets/style.css | .user-level-badge--list 系列 + @media (max-width:768px) 内隐藏 .user-level-badge-text |
plugins/user_profile/assets/style.css | 回归修复:.up-badges .level-badge { border-radius:10px; font-weight:600; line-height:1.6; } |
最后一条是本次唯一的「副作用修补」:
renderBadge原先内联border-radius:10px(胶囊形),改为类驱动后是 4px(与列表行
.mn-level-badge一致的直角圆角)。个人主页
.up-badges行内与.up-badge(10px 胶囊)并排,若不覆盖会出现两枚徽章圆角不一致。故按页面语境在插件内覆盖回 10px。
2.5 配置与缓存(2 项)
user_level_cfg的show.list:"0"→"1"
这是列表信任等级徽章的总开关,为 "0" 时整个列表页零输出。
通过 Plugin::saveCfg() 写入(会一并清 Settings 缓存 + PageCache::invalidate())。
sw.js的CACHE_VERSION:flinthub-static-v1.0.0→flinthub-static-v1.0.1
本次涉及 CSS 变更,按 sw.js 头部规则必须递增,否则老用户拿到旧样式。
三、验证结果
3.1 静态检查
| 项目 | 结果 |
|---|---|
php -l(12 个 PHP 文件) | 全绿 |
modern.css 花括号平衡 | 593 / 593 |
xenforo.css 花括号平衡 | 377 / 377 |
plugins/user_level/assets/style.css | 109 / 109 |
plugins/user_profile/assets/style.css | 43 / 43 |
protected/error.log | 零新增 |
3.2 渲染实测(抓取 HTML 计数)
清空 data/runtime/pages/*.html 后逐页抓取:
| 页面 | 徽章容器 | 级别徽章 | 信任等级徽章 |
|---|---|---|---|
/(首页社区模式) | 10 | 10 | 10 |
/forum | 16 | 16 | 16 |
/forum/category/1 | 6 | 6 | 6 |
/?preview_theme=Xenforo | 10 | 10 | 10 |
/forum?preview_theme=Xenforo | 16 | 16 | 16 |
/forum/category/1?preview_theme=Xenforo | 6 | 6 | 6 |
/thread/66 | 17(1 主帖 + 16 回复) | 17 | 17 |
/thread/66?preview_theme=Xenforo | 17 | 17 | 17 |
/u/1(个人主页,回归检查) | — | 1 | — |
实测输出样本(核心首页):
Lv.11 社区元老
社区领袖
实测输出样本(核心详情页主帖):
flinthub
…3 枚勋章…
…
💎 社区领袖
…时间…3.3 移动端收敛的确定性验证
「移动端只显示图标」无法靠 HTML 静态抓取证明,改用两项结构性论证:
- 括号深度分析:确认
.level-badge--list .level-badge-text { display:none }
确实位于 @media (max-width:768px) 块内
(modern.css 2126 行开 → 2303 行闭,规则在第 2194 行,深度 = 1)。
- 选择器唯一性:全库检索
level-badge-text/user-level-badge-text,
针对这两个选择器的 display 声明各只有一处,无特异性冲突、无后置覆盖。
→ 断点生效时文字必然隐藏,行为是确定的。
两枚徽章使用同一断点口径(768px),与核心 .mn-hide-mobile 工具类一致,窄屏会同步收敛。
四、过程中修复的真实缺陷
| # | 问题 | 后果 | 处理 |
|---|---|---|---|
| 1 | forum/category 不在插件列表模板白名单 | 分类页信任等级徽章零输出 | 白名单 + CSS 注入白名单同步补 |
| 2 | show.list = "0" | 三个列表页的信任等级徽章全部零输出 | 改为 "1" |
| 3 | Xenforo 列表视图 $level 未定义 | 级别徽章无法渲染 | 三处补齐 $level = (int)($thread['level'] ?? 1) |
| 4 | Xenforo 论坛页置顶循环漏赋值 $level | /forum?preview_theme=Xenforo HTTP 500<br>renderBadge(): Argument #1 ($level) must be of type int, null given | 补赋值后复测 200 |
| 5 | renderBadge 圆角由 10px 变 4px | 个人主页徽章行圆角不一致 | 插件内按页面语境覆盖回 10px |
第 4 项值得记一笔:它源于本机 Edit 偶发「报成功但未落盘」(伴随
EBUSY)。本次任务共中 4 处(3 个 Xenforo 视图 + 1 条 CSS 规则),全部靠逐文件 grep 复扫才发现。
结论:批量编辑后必须复扫,只信 Edit 返回值会漏。
五、遗留与建议
- 未覆盖的列表页:
search/index、tags/show、blog/index、message/inbox|sent
同样渲染作者名,本次不在需求范围内。如需统一,只需把对应模板名加入
controller_view_before.php 的 $listTemplates 与 layout_head_end.php 的 $targets,
再在视图内加容器即可 —— 机制已通用。
- 未做浏览器截图验证:本机
agent-browser未安装 Chromium(open静默退出 1),
截图链路走不通,故改用「HTML 结构计数 + CSS 括号深度/特异性分析」。
若需视觉确认,建议在装了 Chromium 的环境跑一次 390px / 1440px 双视口截图。
- 发布包:本次涉及 CSS 与
sw.js变更,若要走update/N/流程发布,
需新建 update/4(version.php → V1.0.4)并把 sw.js 一并放入包内。