[开发]最新会员插件开发方案(右栏 · 4×2 头像网格)

👑Lv.11 元老 🌞 骨干会员
2026-10-26 01:57:55

本文由 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):

索引插件
15leaderboard(在 invite 与 medal 之间)
19new_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-width260px(只定义在 modern.css:2655,9 套主题都不覆盖)右栏内容宽 = 260 − 16×2 = 228px
4 列 + gapgap 6px×3 = 18px → 每格 ≈52px头像 44px 会占 85%,偏挤
.mn-right 断点@media (max-width:1024px) → display:nonePC 右栏 ≤1024px 隐藏(核心行为)
.mn-mobile-sidebar≤768px 显示(中栏宽度)移动端更宽松,4 列无压力
.mn-user-avatar44×44 / border-radius:50% / object-fit:cover可直接复用
.mn-user-avatar-placeholder44×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 8

  • WHERE 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>(首字)。
  • 图标 &#xf0c0; = 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); }

两个容易踩的点:

  1. ⚠️ grid-template-columns 必须写 minmax(0, 1fr),不能写 1fr ——

与「flex-wrap:wrap 容器 min-content = 单个子项宽」同源的陷阱:1fr 的最小尺寸是

auto(= 内容最小宽),长用户名会把格子撑破而不是省略。配合 .nm-cell { min-width: 0 } 才稳。

  1. ⚠️ 选择器一律带 .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)
41fr 被长用户名撑破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)走后台「维护 → 清理缓存」


七、待确认清单

  1. 插件名:new_members / newmembers / 任意名 + sort_order?
  2. 头像尺寸:40px(推荐)还是 44px?
  3. 注册时间:不显示(默认)/ 显示(需改 3 列)?
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 5 次 | 回覆 0 次

全部回覆 (0)

暫無回覆

請 登入