本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/待办事宜/FlintHub_公告插件_样式增强_方案.md。
FlintHub 公告插件「样式增强版」规划方案
状态:待拍板(未动一行代码)
对象:
plugins/announcements(已启用,plugin.jsonversion1.3.0)依据:
docs/插件开发规范.md、docs/插件审计报告.md、本机实测编写:2026-10-11
0. 一句话结论
在现有 plugins/announcements 上做增强(不新建插件):把「样式」从一维配色升级为
二维正交模型(容器模式 × 条目布局),新增 4 种容器模式 + 5 种条目布局 + 7 种配色 + 每条可选图标,
并顺手整改 4 处既有缺陷。全部走主题变量、零内联、向后兼容(老数据零迁移成本)。
1. 现状盘点(有据)
| 事实 | 证据 |
|---|---|
| 插件已存在且已启用 | plugins/announcements/plugin.json → "activated": true, "version": "1.3.0" |
| 前台注入点 3 处 | app/Views/home/index_default.php:17、app/Views/forum/index.php:22、app/Views/blog/index.php:20 |
| 现有「样式」= 6 种配色 | Plugin.php:123-133 getStyles() → yellow/red/gray/bluegray/blue/green |
| 现有「模式」= 2 种 | Settings::get('announcement_mode') → normal(堆叠)/ scroll(上下滚动),见 AdminController.php:37-46 |
| 数据表结构 | announcements(id, content, url, style, sort_order, created_at) —— 无 layout / icon / title 列 |
| 现存真实数据 | 3 条:id1 green、id2 red、id3 blue,sort_order = 1/2/3 |
| 现外观唯一 | assets/style.css:2 .announcement-bar —— 圆角 8px 彩色底条 + 右侧 × |
| 图标能力 | 核心 TemplateCompiler::icon() 的 $map 有 492 项,其中 announcement = (喇叭)✅ |
| 字体子集覆盖 | assets/fonts/fontawesome-webfont.subset.v2.{ttf,woff2} 448 字形,实测含 F0A1/F0F3/F05A/F071/F1EA/F0EB/F135/F053/F054 ✅ |
1.1 顺带发现的 4 处既有缺陷(本次一并整改)
| # | 问题 | 位置 | 后果 |
|---|---|---|---|
| D1 | 说明.md 与实现严重脱节 | 说明.md:3,21,28,37,49 | 写了 status 字段、admin_sidebar_links 钩子、版本 1.2.0 —— 实际都没有(表里无 status,plugin.json 无该钩子,版本是 1.3.0)。下一个人会照文档改错 |
| D2 | 后台编辑弹窗在 </main> 之外 | views/admin.php:94(</main>)→ :98(#editModal) | 违反「后台弹层 markup 必须在 .admin-content 内」。当前靠 .fl-modal .form-input 自含样式硬撑(:47-77),能跑但脆:核心一改后台控件样式,弹窗就脱相 |
| D3 | 6 套配色全是硬编码浅色 | assets/style.css:5-10(#fff8e1 等) | 违反 §11.7「样式必须变量化」。暗夜星辰主题下 = 白底发光。审计报告已记为 announcements 4 处(插件审计报告.md:189) |
| D4 | 钩子在正文区输出 <link> + <script> | hook/announcements_display.php:19-20 | <script> 无 defer,阻塞解析;且资源标签落在 body 中部 |
2. 设计原则(5 条硬约束)
- 零内联:配色走 class(
ann-style-*),图标写字面量<i class="fa mn-fs-16"></i>。
⚠️ 不用 $view->icon() —— 它返回值恒带 style="font-size:Npx",是零内联门禁的漏网点(规范 §9.3 模板本身就有这个毛病)。
- **颜色一律
--mn-*变量 + fallback**(§11.7)。已实测 10/10 样式源都有定义的:
--mn-bg-card / --mn-border / --mn-border-light / --mn-text / --mn-text-secondary /
--mn-text-muted / --mn-primary / --mn-primary-subtle / --mn-success / --mn-warning /
--mn-error / --mn-shadow-sm / --mn-bg-body / --mn-bg-input / --mn-tag-bg。
⚠️ --mn-danger 不存在(0/10),别用;要红色用 --mn-error。
- 向后兼容:
style字段语义不变(仍是配色),老数据green/red/blue渲染结果逐像素不变;
新增列全部有 DEFAULT,老行自动取值。
- 极简:默认不出现无意义控件 —— 无
url不出链接/箭头;只有 1 条不出切换器;布局本身不需要关闭按钮的就不给。 - 不动核心:不新增钩子、不改
app/。现有 3 个注入点够用。
3. 样式体系设计(核心)
3.1 两个正交维度
条目布局(每条 1 个) 配色(每条 1 个)
容器模式(全局 1 个) ── 一条公告「长什么样」 × ── 强调色是什么
└ 多条公告「怎么排」为什么拆两维而不是做「样式模板」:模板是「布局 × 配色」的笛卡尔积,12 个模板 = 12 份 CSS 且不能混搭;
两维只需 5 份布局 CSS + 13 份配色变量,任意组合且 CSS 不膨胀。
3.2 容器模式(全局,announcement_mode,5 种)
| key | 名称 | 形态 | 条目布局是否生效 | 状态 |
|---|---|---|---|---|
normal | 堆叠 | 多条纵向排列,各自可关 | ✅ 生效 | 现有 |
scroll | 上下滚动 | 单条轮播,4s 切换,无控件 | ✅ 生效 | 现有 |
carousel | 箭头轮播 | 单条轮播 + ‹ › 箭头 + 圆点指示器 + 自动播放 + hover 暂停 | ✅ 生效 | 新增 |
list | 公告列表卡 | 一个卡片容器,多条各占一行(图标 + 文本 + 日期 + ›),可折叠 | ❌ 忽略 | 新增 |
marquee | 横向跑马灯 | 单行横向滚动(纯 CSS animation) | ❌ 忽略 | 新增 |
carousel就是图 1(FlatBB)右侧那对箭头的正确归属 —— 它是「多条怎么排」,不是「一条长什么样」。
3.3 条目布局(每条,layout 字段,6 种)
L1 notice — 通知条(图 1 FlatBB 同款)
┌────────────────────────────────────────────────────────────────┐
│ 🔊 Notice: 站点将于今晚 23:00 维护 2026-09-06 ‹ › │
└────────────────────────────────────────────────────────────────┘- 容器:
--mn-bg-card底 +--mn-border细边 + 圆角 999px(胶囊),padding: 9px 18px - 左:图标(默认
announcement喇叭,16px,色 = 强调色) - 前缀标签:加粗 + 强调色,文案取全局设置
announcement_label(默认「公告」) - 正文:
--mn-text,600 字重 - 右:日期(
--mn-text-muted,13px,取created_at的Y-m-d) - 最右:
‹ ›箭头(仅当 mode=carousel且条数 > 1) - 无
×(图 1 没有)
L2 badge — 标签式(图 2 XIUNOX 同款)
┌────────────────────────────────────────────────────────────────┐
│ [ 📢 公告 ] XIUNOX站点出错了怎么办?怎么反馈 Bug? │
└────────────────────────────────────────────────────────────────┘- 容器:
--mn-bg-card底 +--mn-border细边 + 圆角 8px,padding: 8px 14px - 左:实心圆角色块徽章(底 = 强调色,白字,圆角 6px,
padding: 4px 10px,内为 14px 图标 + 标签文案) - 右:文本,色 = 强调色,hover 下划线(整条可点,有
url时) - 无
×、无日期(图 2 没有)
L3 bar — 经典条(现有默认,视觉不变)
┌────────────────────────────────────────────────────────────────┐
│ 站点将于今晚 23:00 维护,预计 30 分钟。 × │
└────────────────────────────────────────────────────────────────┘- 完全沿用
assets/style.css:2的现有定义 → 老数据零视觉变更 - 配色决定整条「底色 + 边框 + 文字色」
L4 minimal — 极简左边框
┃ 站点将于今晚 23:00 维护,预计 30 分钟。- 无图标、无边框、无背景;左侧 4px 强调色竖条(
border-left),圆角 0 - 文字
--mn-text;最不打扰的一种,适合「常驻但不抢眼」
L5 alert — 图标提示条(类 Bootstrap alert)
┌────────────────────────────────────────────────────────────────┐
│ ( ⓘ ) 站点维护通知 × │
│ 今晚 23:00–23:30 暂停访问 │
└────────────────────────────────────────────────────────────────┘- 左:36px 圆形强调色底 + 白图标
- 右侧两行:标题(
title字段,粗,--mn-text)+ 描述(content,--mn-text-secondary) - 右:
×关闭 - ⚠️ 需要新字段
title
L6 card — 图文卡片
┌────────────────────────────────────────────────────────────────┐
│ [ 📰 ] 站点全面升级 FlintHub 查看 → │
│ 基于 PHP + SplitDB 的新一代社区系统 │
└────────────────────────────────────────────────────────────────┘- 左:48px 方形圆角强调色淡底(
--mn-primary-subtle类)+ 强调色图标 - 右:标题(
title)+ 一行摘要(content截断,Content::excerpt) - 最右:
查看 →(有url才出现) - ⚠️ 需要新字段
title
3.4 配色(每条,style 字段,13 种)
配色统一只定义 4 个 CSS 变量:--ann-accent(强调色)/ --ann-bg / --ann-border / --ann-text。
布局类决定怎么用这 4 个变量 → 布局 × 配色 = 6 × 13 = 78 种组合,零额外 CSS。
| key | 名称 | 来源 | 状态 |
|---|---|---|---|
yellow / red / gray / bluegray / blue / green | 现有 6 色 | 硬编码保持原值(向后兼容,视觉零变更) | 现有 |
theme | 跟随主题 | 全用 --mn-*(--mn-bg-card / --mn-border / --mn-text / --mn-primary)→ 暗色主题自动适配 | 新增(推荐默认) |
primary | 主题主色 | --mn-primary / --mn-primary-subtle | 新增 |
success | 成功绿 | --mn-success | 新增 |
warning | 警告黄 | --mn-warning | 新增 |
danger | 危险红 | --mn-error | 新增 |
dark | 深色通栏 | #1f2937 底 + 白字(不随主题变) | 新增 |
设计要点:
notice/badge/minimal/alert/card这 5 种布局的底色统一走--mn-bg-card,配色只决定强调色(图标/徽章/左边框/链接色)。这样:
① 暗色主题下这 5 种布局天然可读(不会白底发光);② 配色切换只换强调色,观感克制。
3.5 图标(每条,icon 字段,新增)
- 下拉选 20 个已在核心
$map内的公告向图标(已实测 448 字形子集全覆盖):
announcement(喇叭) / bell / info / warning / newspaper / flag / lightbulb / rocket /
gift / star / tag / tags / heart / comment / book-open / clock / calendar /
check / plus / fire
- 空值 → 按布局给默认(
notice/badge/alert→announcement;card→newspaper;minimal/bar→ 无图标) - ⚠️ 只从
$map里挑,不自造码点(陷阱 §A.14:自造码点 = 静默方块,须重跑build_font_subset.py)
3.6 其余行为约定
| 项 | 约定 |
|---|---|
| 前缀标签文案 | 全局设置 announcement_label(默认「公告」);notice 布局默认显示为 公告: |
| 日期 | 取 created_at → Y-m-d;仅 notice / list 显示 |
| 关闭按钮 | bar / alert 有;其余无(跟随参考图) |
| 关闭行为 | 保持现状:前端 dismiss,不持久化(刷新重现) |
| 链接 | url 非空才渲染 <a>;target="_blank" rel="noopener" 保持 |
| 转义 | 保持 Content::decode() + htmlspecialchars(..., ENT_QUOTES, 'UTF-8') |
| 移动端 | notice 在 ≤768px 隐藏日期与箭头(flex-wrap 兜底);alert/card 图标缩到 32px |
4. 数据模型变更
ALTER TABLE announcements ADD COLUMN layout TEXT NOT NULL DEFAULT 'bar'; -- 条目布局
ALTER TABLE announcements ADD COLUMN icon TEXT NOT NULL DEFAULT ''; -- 图标名(空=按布局默认)
ALTER TABLE announcements ADD COLUMN title TEXT NOT NULL DEFAULT ''; -- alert/card 的标题行迁移方式(三步,缺一即静默失效 —— 规范 §11.33 / 陷阱 142):
Plugin.php的ddl()里的CREATE TABLE同步加上这 3 列(只对新装站点生效);- 新增
ensureColumnPatches():PRAGMA table_info判存在 → 缺则ALTER TABLE ADD COLUMN(对存量库生效,幂等); - 改门控标记:现有
hook/init_after.php:14用Settings::get('_announcements_migrated')门控
→ 改成 _announcements_schema_v = '2'(值随版本变,否则老站覆盖上传后补丁根本不跑)。
⚠️ 现有
init_after.php无「戳文件」门控,每次请求都跑一遍activate()(内含CREATE TABLE IF NOT EXISTS)→ 已超「单插件每请求所有 hook 查询总和 ≤ 5」红线。本次顺带补上戳文件门控(照
points_mall/qa范式)。
5. 文件改动清单
| 文件 | 改动 | 量级 |
|---|---|---|
plugins/announcements/Plugin.php | 新增 LAYOUTS / ICONS 常量与 getLayouts() / getIcons();getStyles() 扩到 13 项;add()/update() 增 3 参;新增 ensureColumnPatches();SCHEMA_VERSION = 2 | ~+120 行 |
plugins/announcements/hook/announcements_display.php | 按 mode 分流 5 种容器;按 layout 渲染 6 种条目;<script> 加 defer | 重写,~+160 行 |
plugins/announcements/hook/init_after.php | 改门控标记 + 戳文件 + 调 ensureColumnPatches() | ~+25 行 |
plugins/announcements/assets/style.css | 新增 5 种布局 CSS + 7 组配色变量 + 容器模式 CSS;把现有 6 色抽成 4 变量;修 D3(D2 的弹窗样式保留) | ~+260 行 |
plugins/announcements/assets/script.js | carousel(箭头/圆点/自动播放/hover 暂停);现有 scroll 的 style.display 改 class(去内联);list 折叠 | ~+90 行 |
plugins/announcements/views/admin.php | 新增「布局 / 图标 / 标题」表单控件 + 布局缩略预览;全局「展示模式」下拉扩到 5 项 + 「标签文案」输入;弹窗移入 .admin-content(修 D2) | ~+130 行 |
plugins/announcements/AdminController.php | 白名单校验新字段;setMode 扩到 5 值;新增 setLabel | ~+40 行 |
plugins/announcements/lang/{zh,en,zh_tw}.php | 新增 ~26 个键(5 模式 + 6 布局 + 13 配色名 + 图标名 + 提示) | 三语同步 |
plugins/announcements/plugin.json | version → 1.4.0 | 1 行 |
plugins/announcements/说明.md | 按实现重写(修 D1) | 重写 |
不改动:app/ 任何文件、assets/css/*、其他插件。
6. 后台 UI 设计
┌─ 站点公告管理 ────────────────────────────────────────────────┐
│ │
│ ① 添加公告 │
│ 内容 * [__________________________] │
│ 标题 [__________________] ← 仅 alert / card 布局用 │
│ 链接 [https://...] │
│ 条目布局 [经典条 ▾] 配色 [跟随主题 ▾] 图标 [🔊 喇叭 ▾] │
│ 排序 [0] │
│ ┌──────────────────────────────────────────────┐ │
│ │ 🔊 公告: 预览文本… 2026-10-11 │ │ ← 实时预览
│ └──────────────────────────────────────────────┘ │
│ [ 添加公告 ] │
│ │
│ ② 展示设置(全局) │
│ 展示模式 [箭头轮播 ▾] 标签文案 [公告] [ 保存 ] │
│ 提示:箭头轮播 / 列表卡 / 跑马灯 会忽略每条公告的条目布局 │
│ │
│ ③ 已有公告(共 3 条) │
│ [经典条·浅绿] 本站全面升级 FlintHub… [编辑] [删除] │
│ … │
└───────────────────────────────────────────────────────────────┘布局选择器要点:下拉每项显示「名称 + 一句话说明」;下方给实时预览条(用当前表单值渲染,纯 CSS,无网络请求)。
选 list / marquee 容器模式时,条目布局下拉置灰并提示「该模式下不生效」(避免「选了没反应」的困惑)。
7. 风险与红线(写代码时必须逐条对)
| # | 风险 | 对策 |
|---|---|---|
| R1 | 零内联:$view->icon() 带内联 style | 图标一律写字面量 <i class="fa mn-fs-16"></i> |
| R2 | 暗色主题白底发光(D3 同类) | 新布局底色全走 --mn-bg-card;--mn-danger 不存在,红用 --mn-error |
| R3 | 改 CSS 后用户拿不到新样式 | ?v=filemtime 已具备(announcements_display.php:16-17),保持 |
| R4 | 插件 CSS 早于核心注入(main.php:40) | 同权重核心后胜 → 只写核心没定义的属性;隐藏类一律 display:none !important |
| R5 | 钩子文件声明顶层符号 → 双触发静默丢一次 | announcements_display 被规范列为双触发钩子(插件开发规范.md:1989)→ 钩子文件所有逻辑写在顶层语句,不包函数 |
| R6 | 同一页面出现重复 id | 新布局的箭头/圆点容器 id 加渲染序号(陷阱 50 范式) |
| R7 | 游客页永久缓存导致「改了没生效」 | 改完必须清 data/runtime/pages/*.html;⚠️ 先查 sessions.sqlite 有无近几分钟会话,站长在浏览时不清 |
| R8 | 新增列对老库无效 | 三步迁移(§4),并回读 PRAGMA table_info + 标记文件内容验证 |
| R9 | 后台保存静默写最小值 | 数值/文本键用 array_key_exists() 判缺失即跳过(陷阱 75) |
| R10 | i18n 三语键不一致 | 新增 ~26 键三语同步,键集合零差异 + 键序一致,跑 fh_i18n_check.php |
| R11 | 改动影响正在运行的生产站 | 本机先跑通;出包走增量包流程;.htaccess 只拦 plugins/*/data/,assets 可直读 |
| R12 | list 模式下的「每条配色」 | 列表卡每行用自己的强调色(小圆点/图标着色),不做整卡换色 |
8. 分阶段实施计划(建议)
P0 — 骨架 + 二维模型落地(最小可用)
- 数据模型三步迁移(
layout/icon/title) Plugin.php常量与取值函数;AdminController校验hook/announcements_display.php按layout分流,先实现bar(现有)+badge(图 2)+notice(图 1)- CSS:抽 4 变量 + 3 种布局
- 后台:3 个新控件 + 预览条
- 验收:老 3 条数据渲染逐像素不变;图 1 / 图 2 效果可复现
P1 — 容器模式扩充
- 新增
carousel(箭头/圆点/自动播放)、list、marquee - 后台「展示模式」下拉 5 项 + 联动置灰
- 验收:5 种模式各跑一遍;单条时不出现切换器;
hover暂停生效
P2 — 布局与配色补全 + 既有缺陷整改
- 新增
minimal/alert/card布局 - 配色扩到 13 项(含
theme跟随主题) - 修 D1(重写
说明.md)、D2(弹窗移入.admin-content)、D3(配色变量化)、D4(defer) - 验收:暗夜星辰主题下 6 种布局逐一走查可读
P3(可选,需你拍板)— 全站通栏注入
- 新增
layout_body_end注入点,让公告可显示在全站每页顶部(不限于 3 个列表页) - ⚠️ 代价:游客页永久缓存会缓存公告 → 每次改公告都要清缓存;且会出现在后台页(需按 template 排除)
工作量粗估:P0 ≈ 半天,P1 ≈ 半天,P2 ≈ 半天,P3 ≈ 2 小时。总计 1.5~2 个工作日(不含你验收与出包)。
9. 验证方案
| 层 | 手段 | 断言要点 |
|---|---|---|
| 静态 | php -l(8.2 + 8.0 双版本) | 零语法错误 |
| 静态 | plugin_hygiene.py | 三语键 / 零内联 / BOM / CRLF / hooks 文件存在,FAIL = 0 |
| 静态 | fh_i18n_check.php | 视图引用键 ↔ 三语字典零缺失 |
| 静态 | `grep -nE '<style\ | <script>\ |
| 静态 | CSS 变量核对脚本 | 新增 CSS 里每个 var(--x) 都在 10 个样式源有定义 |
| 逻辑 | CLI 探针 | 迁移幂等(跑两次列数不变);getLayouts/getStyles/getIcons 白名单过滤;非法 layout/icon 回落默认 |
| HTTP | 真实请求 3 个注入页 | 每页含公告;id 唯一;(问号兜底)出现 0 次 |
| HTTP | 后台页 | 3 个新控件渲染;保存后同时回读 DB + settings_cache.php;非法值被拒 |
| 反证 | A/B | 老 3 条数据在改动前后渲染 HTML 逐字节比对(证明向后兼容) |
| 视觉 | 交你自测 | 浅色 + 暗夜星辰双主题、PC + 移动端 |
10. 待你拍板的 6 个点
| # | 问题 | 我的建议 |
|---|---|---|
| Q1 | 增强现有插件,还是新建一个(如 notice_bar)? | 增强现有 —— 避免两套公告功能并存;老数据零迁移 |
| Q2 | 采用二维模型(容器模式 × 条目布局),还是单一「样式模板」下拉? | 二维 —— 灵活且 CSS 不膨胀;后台只是多两个下拉 |
| Q3 | 图 1 的 ‹ › 箭头归到「容器模式 carousel」还是「布局 notice 自带」? | 容器模式 —— 箭头是「多条怎么排」,单条时不该出现 |
| Q4 | 保留现有 6 个硬编码浅色配色吗? | 保留(向后兼容,视觉零变更)+ 新增 7 个变量化配色,并把 theme 设为新公告默认 |
| Q5 | 新增 title 字段(alert / card 需要标题行)吗? | 新增 —— 不加的话这两种布局只能拿正文首行当标题,很脏 |
| Q6 | 要不要做 P3「全站通栏」? | 先不做 —— 与游客页缓存冲突,且你现在的公告只挂 3 个列表页 |
附:两张参考图的样式归属
| 参考图 | 特征 | 本方案对应 |
|---|---|---|
| 图 1(FlatBB Notice) | 白底胶囊 + 🔊 喇叭 + 橙色加粗 Notice: + 正文 + 日期 + 右侧 ‹ › | layout = notice + mode = carousel + style = warning |
| 图 2(XIUNOX 公告) | 白底细边 + 蓝色实心圆角「📢 公告」徽章 + 蓝色链接文本 | layout = badge + mode = normal + style = primary |