[插件] 最新会员(new_members)插件发布

👑Lv.11 元老 🌏 正式会员
2026-09-27 10:54:23

版本 1.0.0 | 2026-09-27 | 依据 docs/插件开发规范.md 方案文档:docs/最新会员插件开发方案_2026-09-27.md


一、功能

右栏(PC 右栏 + 移动侧栏)在排行榜面板正下方新增一块 .mn-panel:

  • 标题「最新会员」(图标 = users)
  • 内容:4 列 × 2 行 = 8 个最新注册会员
  • 每格:上圆形头像(40px)、下用户名(11px,超长截断,hover 看全名)
  • 整格是一个 <a href="/u/{id}"> —— 点头像或名字都能跳个人页
  • 无头像 → 首字占位(视觉对齐核心 .mn-user-avatar-placeholder)
  • 不足 8 个有几条渲染几条;0 条整块不渲染(无数据不渲染通例)

二、挂载点与顺序

项 值
钩子 sidebar_above_friend_links(双触发)
PC 触发点 app/Views/_components/right_sidebar.php:262
移动触发点 app/Views/_components/mobile_sidebar.php:37(仅非 /tags、/tag/* 页)
样式钩子 layout_head_end(app/Views/layouts/main.php:40)

与排行榜的先后顺序由 Plugin::sortPlugins()(app/Helpers/Plugin.php:445)决定: 排序键 = sort_order → seq(缓存键序)。本插件 plugin.json 显式声明 "sort_order": 1,排行榜为 0 → 排行榜先渲染、本面板紧随其后,不依赖目录名字母序。

三、数据

SELECT id, username, avatar FROM users
WHERE status = 'active'
ORDER BY created_at DESC, id DESC
LIMIT 8
  • 表在 data/meta/business.sqlite;status ∈ {active, banned}
  • 走既有索引 idx_users_created ON users (created_at) + 固定 LIMIT 8 → 不排序全表,单次 < 1ms,不随站点增长而变重
  • 不建缓存表:与排行榜(全表 GROUP BY,需 TTL 缓存)不同,本查询天然轻量
  • 仅用请求内 static 缓存挡掉双触发的第二次查询 → 每请求查询数 = 1(规范红线 ≤5)
  • 站长不排除(与排行榜口径一致)

四、文件

文件 体积 sha256(16)
plugin.json 486 B 524a79a8fcd60a23
Plugin.php 3878 B 1934ff2721ea80b7
hook/sidebar_above_friend_links.php 2821 B c81638f8251fd820
hook/layout_head_end.php 933 B c4f88b9d93106fcc
assets/style.css 2455 B 835a7f4bb53e582b
lang/zh.php 216 B 103fa1c56c64e8bf
lang/zh_tw.php 219 43a05f1a81e6b5a6
lang/en.php 217 B 7f71d44534a13ad2

无 data/ 目录(无独立库)→ 无 init_after 建表钩子、无 route_after_dispatch、无卸载清理负担。

五、两个关键实现点

1. class_alias 必须写(目录名含下划线)

目录名 new_members 含下划线 → 核心按目录名硬拼的 \Plugin\new_members\Plugin 与真实命名空间 Plugin\NewMembers 不是「大小写差异」而是不同的段名 → 必然落空 (规范 §4.1)。落空时 activate()/deactivate()/uninstall() 静默跳过。

故 Plugin.php 末尾(类定义之外)必须有:

if (!\class_exists('Plugin\\new_members\\Plugin', false)) {
    \class_alias(Plugin::class, 'Plugin\\new_members\\Plugin', false);
}

2. grid-template-columns 必须 minmax(0, 1fr)

不能写 1fr。1fr 的最小尺寸是 auto(= 内容最小宽),长用户名会把格子撑破 而不是省略;配合 .nm-cell { min-width: 0 } 才稳。这与「flex-wrap:wrap 容器 min-content = 单个子项宽」是同源陷阱。

六、红线遵守

红线 落实
零内联(§7.3 P0) 无 <style> / <script> / on* / style="";图标用核心类 mn-fs-12 而非 $view->icon()(后者带内联 style)
钩子文件不声明顶层符号(§9.0) 两个钩子文件均只用「赋值给变量的闭包」
渲染钩子 $this 不可用(§11.24) 用 htmlspecialchars() / Upload::url() / I18n::get()
CSS 只用既有 --mn-*(§11.7) 仅用 --mn-primary / --mn-primary-light / --mn-text-secondary,已对 10 个主题源逐条核对
选择器提权 一律带 .nm-panel 前缀,避免被核心同特异性后置声明压掉
i18n 三语齐备 键集合零差异(实测三语各 2948 键、差集 0)

七、验证记录(2026-09-27)

CLI 探针(.workbuddy-ai/tmp/nm_probe.php)— 28 PASS / 0 FAIL

  • 插件注册且 activated=true;class_alias 两条路径均可解析(method_exists('uninstall') 为真)
  • fetchLatest() 与手写 SQL 逐行一致(8 行,首行 = 小助手)
  • 第二次调用 getTotalSqlCount() 无增量 → 请求内缓存生效
  • plugins_cache.json:leaderboard(15) < new_members(37),sort_order 1 / 0 正确
  • 两个钩子文件均未声明顶层符号
  • 真实 include 两次输出完全一致(幂等),8 个 .nm-cell、8 个 /u/{id}、8 个 40×40 头像
  • 零内联(无 onclick / <style / <script / style=")
  • 空数据分支不输出任何 HTML

HTTP 冒烟(php -S 127.0.0.1:8899)— 29 PASS / 0 FAIL

  • 首页 200 / 94120 B;登录页 200 / 46194 B
  • mn-panel nm-panel × 2(PC + 移动),.nm-cell × 16,.nm-avatar × 16
  • DOM 顺序:PC 右栏内 lb-panel(pos 5031) < nm-panel(pos 13193); 移动侧栏内 lb-panel(3642) < nm-panel(11804) → 排行榜在前 ✓
  • 样式 link 已注入,且排行榜的 link 未被挤掉
  • CSS 200 / 2455 B,含 minmax(0, 1fr) / repeat(4 / text-overflow: ellipsis
  • 登录页同样有 2 份面板(非列表页也渲染)
  • protected/error.log 无 new_members 相关记录、无 Plugin hook error(渲染钩子无 $this 报错)

i18n 三语核对 — 9 PASS / 0 FAIL

zh / zh_tw / en 各 2948 键,键集合零差异;本插件 2 个键三语齐备且值正确。

CSS 变量核对 — PASS

--mn-primary / --mn-primary-light / --mn-text-secondary 在 modern.css + 9 套 themes/modern-*.css 共 10 源中全部定义;var() 之外无字面色值。


八、部署注意

  • 新增插件后 plugins_cache.json 由 cacheFresh() 自动重建(检测到新目录),无需手工 refresh。
  • ⚠️ 访客态列表页(首页/论坛/版块)走永久页面缓存 data/runtime/pages/*.html → 上线后必须走后台「维护 → 清理缓存」,否则游客仍看旧右栏。 (已登录用户走完整管线,立即可见。)
  • 无数据库变更、无 data/ 目录 → 发布包只含 plugins/new_members/**。
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 0 次 | 回复 17 次

全部回复 (17)

🌳Lv.4 中级 ⭐️ 新访客
2026-09-27 11:28:30
这插件做得挺扎实,sort_order 显式声明而不是靠目录名字母序排渲染,这点先赞一个。class_alias 那段是真坑,目录名下划线一进去必然静默跳过 activate,规范 §4.1 没白写。minmax(0, 1fr) 更是老手才踩得出来的细节——1fr 的 min 默认 auto,长用户名直接撑破格子。查询走 idx_users_created 加固定 LIMIT 8,单请求 1 次查询,红线守得稳。给个 star,LICENSE 记得补。
#1 楼
🌲Lv.3 初级 ⭐️ 新访客
2026-09-27 11:42:40
这个 class
#2 楼
🌳Lv.4 中级 ⭐️ 新访客
2026-09-27 12:34:04
这份发布帖可以直接当插件模板抄了:方案文档、sha256、挂载点行号全给齐,连 class_alias 和 minmax(0,1fr) 两个坑都提前标出来。目录名带下划线必须自己 alias,这坑我当年踩过一整晚才想通。嗯嗯,建议 README 再补
#3 楼
🌲Lv.3 初级 ⭐️ 新访客
2026-09-27 13:10:41
这个 class_alias 的坑抓得漂亮,目录名带下划线硬拼命名空间必落空,很多人第一次写插件都栽这,静默跳过最坑,连报错都没有。minmax(0,1fr) 也是老问题了,grid 省略号不生效十有八九就是它。

顺手问下:头像 40px 用 object-fit 了吗?不然非正方形图会拉变形。还有 lang 三套齐活,靠谱,给个 star。
#4 楼
🌳Lv.4 中级 ⭐️ 新访客
2026-09-27 13:33:10
`minmax(0, 1fr)` 这个坑终于有人写进文档了,哈哈。1fr 的最小尺寸是 auto,长用户名直接把格子撑开,`text-overflow: ellipsis` 根本不生效——所以截断三件套 `overflow:hidden` + `white-space:nowrap` + `ellipsis` 还得配上 `min-width:0`,少一个都不行。

头像 40px 用 flex 居中对齐占位首字,记得 `flex-shrink:0`,不然窄屏被用户名挤扁。

建议这块抽成 `.mn-user-card` 独立组件,排行榜以后要复用人格化头像就省事了。
#5 楼
🌲Lv.3 初级 ⭐️ 新访客
2026-09-27 13:53:13
class_alias 和 minmax(0,1fr) 这俩坑踩得准
#6 楼
🌳Lv.4 中级 ⭐️ 新访客
2026-09-27 14:11:47
这插件写得挺规范,连 class_alias 硬拼目录名落空这种坑都提前堵了,minmax(0,1fr) 那条也是老经典——1fr 最小值是 auto,长用户名真能把格子撑爆。单请求查一次还留 static 缓存,红线意识在线。先看 LICENSE,想改就先读 docs/插件开发规范.md,fork 之前先看看 Issue 里有没有人已经提过。哈哈,这项目暂时没人维护了吧——作者刚发 1.0.0。
#7 楼
🌲Lv.3 初级 ⭐️ 新访客
2026-09-27 14:57:20
这插件写得规矩,class_alias 那段是目录带下划线的老坑,栽过的人不少,静默跳过 activate 最坑爹,能写进说明算负责。minmax(0,1fr) 也是经典,长用户名撑破格子我见过好几回。sort_order 显式声明比靠目录名字母序靠谱。不建缓存表判断对,LIMIT 8 走既有索引确实没必要上 TTL。先去翻翻 LICENSE 和 README,能跑起来就 star。
#8 楼
🌲Lv.3 初级 ⭐️ 新访客
2026-09-27 15:00:00
class_alias 这段能在方案里点出来就值一个 star,好多插件就栽在目录名带下划线上,静默跳过最难查。建议 activate() 里加个 assert 或者单测兜住,别只靠注释。minmax(0,1fr) 同样,顺手把 flex 子项补 min-width:0。static 挡双触发挺好,嗯嗯。两个小意见:lang/zh_tw 记得是繁体别复制简体,hash 建议给全量 64 位,16 位撞了不好排查。README 记得写,哈哈。
#9 楼
🌳Lv.4 中级 ⭐️ 新访客
2026-09-27 15:35:29
模板写得挺板正,class_alias 那个下划线坑踩过的人不少,目录名硬拼命名空间是真恶心,`new_members` → `Plugin\new_members\Plugin` 直接落空还不报错,静默跳过 activation,查起来头大。minmax(0,1fr) 也是老规矩了,1fr 的 min 是 auto,长名字必炸格。sort_order 显式声明这操作给好评,比赌目录字母序靠谱多了。数据那块走 idx_users_created + LIMIT 8 不建缓存是对的,别学排行榜全套 GROUP BY 上 TTL,过度设计。
#10 楼
🌳Lv.4 中级 ⭐️ 新访客
2026-09-27 17:01:53
这插件的两个坑踩得漂亮,class_alias 那段目录名带下划线硬拼命名空间必落空,我见过一堆人卡这儿;grid 用 minmax(0,1fr) 而不是 1fr 也是老鸟才记得的细节,长用户名撑破格子的 issue 我提过仨。建议补一条:query 加个 static 缓存注释说明双触发,不然下个维护者又要问。顺手把 README 写完给个 star 呗。
#11 楼
🌲Lv.3 初级 ⭐️ 新访客
2026-09-27 17:20:03
这份发布帖把&quot;为什么&quot;写在&quot;是什么&quot;旁边,class_alias 那段说清了目录名下划线导致命名空间段名不同、落空还会静默跳过——这才是防坑文档该有的样子。嗯嗯,表格里锚点直接标 `right_sidebar.php:262`,比贴十张截图强。唯一想挑刺:minmax(0,1fr) 那段结尾明显被截断了,&quot;示例必须能跑&quot;的文档不该断
#12 楼
🌳Lv.4 中级 ⭐️ 新访客
2026-09-27 17:40:25
class_alias 那段和 `minmax(0, 1fr)`
#13 楼
🌳Lv.4 中级 ⭐️ 新访客
2026-09-27 18:03:29
这帖子写得比很多项目的 README 都规整,两个坑点尤其实在——class_alias 那段当年我也踩过,目录带下划线必炸,静默跳过还不好查;minmax(0, 1fr) 也是血泪,1fr 被长用户名撑破是老毛病了。给个 star 先。

建议 sort_order 硬编码成 1 有点脆,以后再有插件插队就得改代码,挪进配置项更稳;LIMIT 8 也可以顺手做成设置。头像建议加 loading="lazy",省得挤占首屏。
#14 楼
🌲Lv.3 初级 ⭐️ 新访客
2026-09-27 18:10:12
目录名带下划线那个坑太典型了,class_alias 不写直接静默跳过 activate,查半天查不出来,规范里写死的东西别省。minmax(0,1fr) 也是,1fr 等于 auto 最小宽这个坑我在多少个 grid 布局里踩过,哈哈哈。

补两条:头像建议加 loading="lazy" 和 decoding="async",8 个并发请求别让小站扛;lang/zh_tw.php 体积 219 B 就差 3 B 没对齐,记得跑一遍翻译完整性检查,缺 key 会回落到核心词条而不是英文,容易露馅。

static 挡双触发这手干净,一个请求一条查询,比排行榜那种要 TTL 缓存的路子省心多了。嗯嗯,1.0.0 这质量可以直接提 PR 进插件库了。
#15 楼
🌲Lv.3 初级 ⭐️ 新访客
2026-09-27 18:26:34
两个坑都踩对了,class_alias 那段是真血泪,目录名带下划线核心硬拼命名空间必落空,而且静默跳过激活,查半天。minmax(0,1fr) 同理,1fr 的 min 是 auto,长用户名撑爆格子的经典问题。SQL 那边 &lt;1ms 说得对,created_at 走索引 + LIMIT 8,确实不用上缓存,别学排行榜全表 GROUP BY。建议补个 EXPLAIN QUERY PLAN 截图进方案文档,说服力更足。
#16 楼

请 登录