本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/最新会员插件开发方案_2026-09-27.md。
最新会员插件开发方案(右栏 · 4×2 头像网格)
日期:2026-09-27 | 依据:
docs/插件开发规范.md状态:待确认(本文只出方案,不动手写代码)
建议插件名 / 目录名:
new_members(含下划线 → 必须在Plugin.php末尾加class_alias,见 §四)
一、结论摘要
在右栏排行榜面板正下方再加一个 .mn-panel:标题「最新会员」,内容为 4 列 × 2 行 = 8 个
最新注册会员的网格(每格:上圆形头像、下用户名,居中均分,长名截断)。
- 零核心改动:与排行榜插件复用同一个钩子
sidebar_above_friend_links,
靠核心既有的插件遍历顺序排在排行榜之后(已实测验证,见 §二·1)。
- 无独立库、无缓存表:查询走
users.created_at既有索引 +LIMIT 8,极轻;
仅用请求内 static 缓存规避双触发时的重复查询 → 每请求 1 次查询(红线 ≤5)。
- 视觉复用核心头像类,尺寸在插件作用域内微调(44px → 40px),与右栏其余面板同一套设计语言。
二、现状核查(实测,非推断)
1. 与排行榜的先后顺序 —— 可控,已实测
右栏钩子位置只有一个可用:sidebar_above_friend_links
(right_sidebar.php:262 + mobile_sidebar.php:37)。两个插件同时注册它,输出顺序由
app/Helpers/Plugin.php:608 的 foreach (self::$plugins as $name => $config) 决定,
而该数组在 loadPlugins() 末尾经 sortPlugins()(:445)排序:
usort($indexed, fn($a, $b) => $a['order'] <=> $b['order'] ?: $a['seq'] <=> $b['seq']);
// ↑ sort_order(全部插件当前均为 0) ↑ 缓存键序 ≈ 目录名字母序实测当前 plugins/plugins_cache.json 键序(全站插件 sort_order 均为 0):
| 索引 | 插件 |
|---|---|
| 15 | leaderboard(在 invite 与 medal 之间) |
| 19 | new_members(在 nav_manager 与 points_mall 之间) |
→ leaderboard(15) < new_members(19),排行榜先输出、最新会员紧随其后 ✓
加固手段(可选):给 plugin.json 显式写 "sort_order": 1(排行榜保持 0),
顺序就不再依赖字母序。代价是后台插件列表里它也会排到排行榜之后 —— 与前台顺序一致,无害。
2. 数据源
| 项 | 实测值 |
|---|---|
| 表 | data/meta/business.sqlite → users |
| 索引 | idx_users_created ON users (created_at) 已存在 ✓ |
created_at 格式 | 2026-09-23 18:45:16(ISO 风格 → 字典序 = 时间序 ✓),空值 0 行 |
| 头像字段 | avatar = 相对 uploads 的路径(如 seed_avatars/2026-09/285.svg) |
| 最新 8 个会员 | 小助手 / 陈先森 / baiduseo / rock / aniya / justry / 栀子花开 / feixiang |
3. 布局约束
| 项 | 实测值 | 影响 |
|---|---|---|
--mn-right-width | 260px(只定义在 modern.css:2655,9 套主题都不覆盖) | 右栏内容宽 = 260 − 16×2 = 228px |
| 4 列 + gap | gap 6px×3 = 18px → 每格 ≈52px | 头像 44px 会占 85%,偏挤 |
.mn-right 断点 | @media (max-width:1024px) → display:none | PC 右栏 ≤1024px 隐藏(核心行为) |
.mn-mobile-sidebar | ≤768px 显示(中栏宽度) | 移动端更宽松,4 列无压力 |
.mn-user-avatar | 44×44 / border-radius:50% / object-fit:cover | 可直接复用 |
.mn-user-avatar-placeholder | 44×44 / --mn-primary-light 底 / --mn-primary 字 / 17px | 可直接复用 |
| 现有 grid 工具类 | 只有 .mn-grid-3(3 列),无 4 列 | 插件自建 4 列 grid |
三、技术设计
3.1 数据查询
SELECT id, username, avatar
FROM users
WHERE status = 'active'
ORDER BY created_at DESC, id DESC
LIMIT 8WHERE status='active'→ 排除封禁账号;ORDER BY created_at DESC, id DESC→ 同秒注册时按 id 稳定排序。- 走
idx_users_created索引 +LIMIT 8→ 不排序全表,代价极低。 - 站长不排除(与排行榜口径一致)。
- 无头像 → 首字占位(复用核心
.mn-user-avatar-placeholder的视觉)。 - 不足 8 个 → 有几条渲染几条(grid 自然留空);0 条 → 整块不渲染(「无数据不渲染」通例)。
不做缓存表:该查询单次 < 1ms 且不随站点增长而变重(走索引 + 固定 LIMIT),
比排行榜那种全表 GROUP BY 轻得多。仅用请求内 static 缓存:
钩子一次渲染触发两次(PC + 移动),第二次直接复用第一次的结果,不重复查库。
3.2 面板结构
最新会员
小助手
... ×8
- 整格是
<a>→ 点头像或用户名都能跳/u/{id}(触控友好,无需分别加链接)。 - 无头像时把
<img>换成<span class="nm-avatar nm-avatar-ph">小</span>(首字)。 - 图标
=TemplateCompiler $map的users;用核心字号类mn-fs-12
(⚠️ 不要用 $view->icon(),其返回值带内联 style,违反零内联红线)。
3.3 关键 CSS(零内联,颜色全走 --mn-*)
.nm-panel .nm-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); /* ★ 必须 minmax(0,1fr) */
gap: 10px 6px;
}
.nm-panel .nm-cell {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
min-width: 0; /* ★ 允许子元素 ellipsis,否则长名撑破格子 */
text-decoration: none;
}
.nm-panel .nm-avatar {
width: 40px; height: 40px; /* 复用核心 44px 类名,网格内微调为 40px */
border-radius: 50%;
object-fit: cover;
flex: none;
}
.nm-panel .nm-avatar-ph {
display: flex; align-items: center; justify-content: center;
font-size: 15px; font-weight: 600;
color: var(--mn-primary, #0066cc);
background: var(--mn-primary-light, #e6f0fa);
}
.nm-panel .nm-name {
max-width: 100%;
font-size: 11px;
line-height: 1.4;
text-align: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--mn-text-secondary, #666);
}
.nm-panel .nm-name:hover { color: var(--mn-primary, #0066cc); }两个容易踩的点:
- ⚠️
grid-template-columns必须写minmax(0, 1fr),不能写1fr——
与「flex-wrap:wrap 容器 min-content = 单个子项宽」同源的陷阱:1fr 的最小尺寸是
auto(= 内容最小宽),长用户名会把格子撑破而不是省略。配合 .nm-cell { min-width: 0 } 才稳。
- ⚠️ 选择器一律带
.nm-panel前缀提权 —— 核心同特异性声明因源序靠后会静默压掉插件样式。
尺寸取舍(待你定,见 §四):每格 ≈52px,
- 头像 40px + 用户名 11px(约 4.7 个中文字)→ 宽松、推荐;
- 头像 44px(严格等于核心
.mn-user-avatar)+ 用户名 11px → 更"原样一致",但上下会顶满格子。
3.4 文件清单
plugins/new_members/
├── plugin.json # hooks: sidebar_above_friend_links / layout_head_end
├── Plugin.php # 查询 + 请求内缓存 + 生命周期
│ # ⚠️ 末尾必须有 class_alias(目录名含下划线)
├── hook/
│ ├── sidebar_above_friend_links.php # 面板渲染(双触发钩子)
│ └── layout_head_end.php # 注入 style.css(?v=filemtime)
├── assets/style.css # 仅网格 + 单元格样式
├── lang/{zh,zh_tw,en}.php # plugin.new_members.*
└── 说明.md- 无
data/目录(不建独立库)→ 无init_after建表钩子、无uninstall清理负担。 - 无
route_after_dispatch(不缓存、无异步收尾)。 - 无后台配置页(8 个是硬需求;若以后要可调再加常量)。
3.5 i18n 键(初版)
plugin.new_members.title 最新会员
plugin.new_members.empty 暂无会员 ← 实际用不到(0 条不渲染),保留以备改为"显示空态"验收 = 三语键集合零差异(不是「值非空」)。
四、待你拍板的 3 个点
决策 1:插件名(决定能否零核心改动)
| 选项 | 顺序保障 | 代价 |
|---|---|---|
new_members(推荐) | 字母序 l < n ✓ 实测索引 15 < 19 | 含下划线 → 必须加 class_alias(规范 §4.1 硬性要求,我会加对) |
newmembers(无下划线) | 同样 l < n ✓ | 免 class_alias,但可读性差 |
任意名 + sort_order: 1 | 不依赖字母序,最确定 | 后台插件列表顺序随之改变(无害) |
若你想「顺序绝对不依赖名字」,就选第三行(我推荐
new_members+sort_order: 1双保险)。
决策 2:头像尺寸
- 40px(推荐):每格 52px 内留 12px 余量,4 列看着舒展;
- 44px:严格等于核心
.mn-user-avatar,最"原样",但格子顶满。
决策 3:是否显示注册时间
你说的是「上头像下面用户名」→ 默认不显示时间。
若想加,第二行可放相对时间(如「3 天前」),但 52px 格子放不下,需要改成 3 列。
五、实施阶段(每阶段末暂停确认)
| 阶段 | 内容 | 验收 |
|---|---|---|
| P0 | 骨架:plugin.json / Plugin.php(含 class_alias)/ 两个钩子 / 三语 | 激活后 isActivated 为真,无 data/ 残留 |
| P1 | 数据层:查询方法 + 请求内缓存 | CLI 探针:结果与手写 SQL 逐行一致;双触发只查一次 |
| P2 | 渲染:面板 HTML + CSS 网格 | 真实 HTTP 抓 HTML:4×2 布局、8 格、截断生效、双份 DOM |
| P3 | 顺序验证 | 实测页面 DOM 里「排行榜」面板出现在「最新会员」之前(PC 与移动端各一次) |
| P4 | 收尾:零内联 / i18n 三语 / 双主题走查 / 说明.md | 可交付 |
六、风险与坑(逐条对应规范)
| # | 风险 | 处置 |
|---|---|---|
| 1 | 渲染钩子里 $this 不可用 → HTML 渲染一半断掉、HTTP 仍 200、无告警(§11.24) | 用 htmlspecialchars() / Upload::url() / I18n::get();发真实 HTTP + 查 error.log 才算验过 |
| 2 | 钩子文件声明顶层函数/类 → 双触发第二处静默消失(§9.0) | 钩子文件不声明任何顶层符号 |
| 3 | 目录名含下划线 → 生命周期三方法静默跳过(§4.1) | Plugin.php 末尾加 class_alias(Plugin::class,'Plugin\new_members\Plugin',false) |
| 4 | 1fr 被长用户名撑破 | minmax(0,1fr) + .nm-cell{min-width:0} |
| 5 | 与排行榜顺序反了 | §二·1 已实测;P3 阶段再发真实 HTTP 复核 DOM 顺序 |
| 6 | 夜间主题浅底浅字(§11.7) | 只用既有 --mn-*,写前逐条 grep 核对(9 主题 + modern.css = 10 源) |
| 7 | 内联 <style> / on*(§7.3 P0) | 全部收敛到 assets/style.css;发布前跑零内联 grep |
| 8 | 改 hooks 后不生效(§三) | 改完必须 Plugin::refresh() 重建 plugins_cache.json |
| 9 | 部署后仍看旧右栏(§9.5) | 走后台「维护 → 清理缓存」 |
七、待确认清单
- 插件名:
new_members/newmembers/ 任意名 +sort_order? - 头像尺寸:40px(推荐)还是 44px?
- 注册时间:不显示(默认)/ 显示(需改 3 列)?