本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/announcements/说明.md。
站点公告(announcements)插件说明
版本:1.4.0 | 作者:SoSite | 状态:✅ 已启用
功能:站点公告系统 —— 后台统一维护多条公告,前台按「容器形态 × 条目布局 × 配色」自由组合展示,
支持标题、图标、自定义前缀标签与逐条关闭。面向站长/运营,不涉及积分与权限体系。
一、功能简介
1.1 展示模型:三个正交维度
一条公告的最终外观 = 容器形态(多条怎么排)× 条目布局(一条长什么样)× 配色。
| 维度 | 数量 | 取值 |
|---|---|---|
容器形态 mode(全局设置,5 选 1) | 5 | normal / scroll / carousel / list / marquee |
条目布局 layout(逐条设置,6 选 1) | 6 | bar / notice / badge / minimal / alert / card |
配色 style(逐条设置,12 选 1) | 12 | theme primary success warning danger dark yellow red gray bluegray blue green |
1.2 容器形态(5 种)
| 取值 | 名称 | 行为 |
|---|---|---|
normal | 普通堆叠 | 多条纵向排列,各自带 × 可独立关闭(默认值) |
scroll | 上下轮播 | 多条循环单条显示,无箭头(1.3.0 原有形态) |
carousel | 箭头轮播 | 循环单条显示,带 ‹ › 箭头,鼠标悬停暂停;>1 条才出箭头 |
list | 公告列表卡 | 多条合并为一张卡片,每行一条(图标 + 文本 + 日期 + 箭头) |
marquee | 横向跑马灯 | 多条合并为一行横向滚动,时长按文本量自动估算(14~60 秒) |
list/marquee不使用条目布局(由容器统一排版),后台预览会置灰并提示。
1.3 条目布局(6 种)
| 取值 | 名称 | 结构 | 关闭按钮 |
|---|---|---|---|
bar | 经典条 | 彩色底 + 边框 + 正文(1.3.0 原有样式) | 有(normal 下) |
notice | 通知条 | 胶囊卡片:图标 + 加粗前缀 + 正文 + 右侧日期 | 无 |
badge | 标签式 | 左侧实心圆角标签(图标 + 前缀)+ 右侧强调色文本 | 无 |
minimal | 极简左边框 | 无底无边,仅左侧 3px 强调色竖线 | 无 |
alert | 图标提示条 | 圆形图标 + 标题 + 描述 | 有 |
card | 图文卡片 | 方形图标 + 标题 + 摘要 + 「查看 →」 | 无 |
1.4 其他能力
- 标题
title:仅alert/card布局显示;留空则card用正文摘要兜底 - 图标
icon:20 个可选图标;留空 = 跟随布局默认(见 §四) - 前缀标签:
notice/badge显示的公告:前缀,全局可改(默认取 i18ndefault_label) - 跳转链接
url:非空时正文变链接,target="_blank" rel="noopener" - 独立关闭:前端
dismissed动画,不持久化(刷新后重新出现) - 后台实时预览:改布局 / 配色 / 图标即时预览,无需先保存
二、路由与接口
2.1 后台路由(hook/admin_route_register.php)
⚠️ 注册时不写 /admin 前缀(核心已按 admin 分组注册);实际访问路径见下表。
| 方法 | 路径 | 说明 | 登录 | CSRF |
|---|---|---|---|---|
| GET | /admin/announcements | 公告列表 + 新增表单 + 展示设置 | ✅ 管理员 | – |
| POST | /admin/announcements/add | 新增公告 | ✅ 管理员 | ✅ |
| POST | /admin/announcements/edit | 编辑公告 | ✅ 管理员 | ✅ |
| POST | /admin/announcements/delete | 删除公告 | ✅ 管理员 | ✅ |
| POST | /admin/announcements/set-mode | 保存容器形态(全局) | ✅ 管理员 | ✅ |
| POST | /admin/announcements/set-label | 保存前缀标签(全局) | ✅ 管理员 | ✅ |
「管理员」判定沿用核心
Auth::isAdmin()(role==='admin'且group_id===4)+admin_verified_at二次验证,插件不自造判定。
2.2 前台输出(无路由,靠钩子注入)
announcements_display 钩子被核心 3 处调用,输出同一段公告区:
| 文件 | 行 |
|---|---|
app/Views/home/index_default.php | 17 |
app/Views/forum/index.php | 22 |
app/Views/blog/index.php | 20 |
2.3 后台可配项
| 设置项 | 存储位置 | 默认 | 取值范围 | 说明 |
|---|---|---|---|---|
announcement_mode | 核心 settings 表 | normal | 5 个白名单值 | 非法值归一为 normal |
announcement_label | 核心 settings 表 | ''(回落 i18n「公告」) | ≤ 20 字(mb_substr 截断) | 仅 notice / badge 使用 |
两条都是全局设置(
Settings::update(),需声明system:settings权限),不在插件独立库。
三、数据表
| 表 | 用途 | 所在库 |
|---|---|---|
announcements | 公告记录 | 插件独立库 plugins/announcements/data/announcements.sqlite |
| 列 | 类型 | 默认 | 说明 |
|---|---|---|---|
id | INTEGER | 主键 | |
content | TEXT | – | 公告正文(存富文本原文,渲染前 Content::decode() + 转义) |
url | TEXT | NULL | 跳转链接(可空) |
title | TEXT | '' | 标题(1.4.0 新增) |
style | TEXT | 'yellow' | 配色键 |
layout | TEXT | 'bar' | 条目布局键(1.4.0 新增) |
icon | TEXT | '' | 图标名(1.4.0 新增) |
sort_order | INTEGER | 0 | 升序排列 |
created_at | TEXT | – | Y-m-d H:i:s |
无自定义索引(表量级为个位数,ORDER BY sort_order ASC, id ASC 全表扫即可)。
3.1 表结构演进
| 版本 | 变更 |
|---|---|
| 1.2.0 及以前 | id / content / url / style / sort_order / created_at |
| 1.4.0 | 新增 title / layout / icon 三列,SCHEMA_VERSION = 2 |
补列走 Plugin::ensureColumnPatches()(PRAGMA table_info + 逐列 ALTER TABLE ADD COLUMN,幂等),
在 activate() 内、写戳之前执行。老站覆盖上传后由 init_after 钩子自动补齐。
四、常量与白名单(Plugin.php)
const SCHEMA_VERSION = 2;
const LAYOUTS = ['bar','notice','badge','minimal','alert','card']; // 6
const MODES = ['normal','scroll','carousel','list','marquee']; // 5
const COLORS = ['theme','primary','success','warning','danger','dark',
'yellow','red','gray','bluegray','blue','green']; // 12
const ICONS = [ /* 20 项:图标名 → HTML 实体 */ ];
const LAYOUT_ICON = [ 'bar'=>'', 'notice'=>'announcement', 'badge'=>'announcement',
'minimal'=>'', 'alert'=>'info', 'card'=>'newspaper' ];20 个图标名(括号内为中文标签):
announcement(喇叭) bell(铃铛) volume-up(广播) info(信息) circle-exclamation(提醒)
warning(警告) newspaper(新闻) flag(旗帜) lightbulb(灯泡) rocket(火箭) gift(礼物)
star(星标) tag(标签) heart(爱心) comment(评论) book-open(手册) clock(时钟)
calendar(日历) check-circle(完成) crown(皇冠)
归一函数(渲染与入库共用同一口径):
| 函数 | 非法值回落 |
|---|---|
normalizeLayout() | bar |
normalizeMode() | normal |
normalizeColor() | yellow(与 1.3.0 行为一致) |
normalizeIcon() | ''(渲染端不输出图标元素) |
五、目录结构
plugins/announcements/
├── plugin.json # 清单:version 1.4.0,hooks×3,permissions: route:admin / system:settings
├── Plugin.php # 生命周期 + 建表/补列 + CRUD + 白名单常量与归一函数
├── AdminController.php # 后台控制器(列表/增删改/设置容器形态/设置标签)
├── hook/
│ ├── init_after.php # 惰性建表(戳文件门控,命中即零查询短路)
│ ├── announcements_display.php # 前台输出(容器×布局 渲染核心)
│ └── admin_route_register.php # 注册 6 条后台路由
├── assets/
│ ├── style.css # 全部样式(配色变量 + 6 布局 + 5 容器 + 后台表单/预览/弹窗)
│ └── script.js # 轮播/跑马灯/关闭/后台预览/删除确认
├── lang/{zh,en,zh_tw}.php # 三语语言包,各 96 键
├── views/admin.php # 后台管理视图
└── data/ # 运行时(勿手动改)
├── announcements.sqlite # 插件独立库
├── .announcements_schema_v2.ok # 建表门控戳(文件名含 SCHEMA_VERSION)
├── .htaccess / web.config # 禁止 HTTP 直接下载数据目录源文件 12 个(不含 data/ 与本文档)。
六、注意事项
announcements_display是「双触发」钩子(核心 3 处调用) —— 钩子文件里禁止声明顶层函数/类。
一旦声明,该文件会被降级为 include_once,第二处注入点静默消失(无报错、无日志)。
闭包赋值给变量($fn = function () {})不算顶层符号,安全。
- 改表结构必须
SCHEMA_VERSION +1—— 戳文件名含版本号(data/.announcements_schema_v{N}.ok)。
若沿用固定文件名 + 只判 is_file(),老站覆盖上传后补丁永远不跑,新列建不出来且日志干净。
改完记得手删旧戳(或靠版本号变化自动失效)。
- 改
style.css/script.js不需要手动 bump 版本号 —— 钩子用?v=filemtime()自动破缓存。
但改完必须确认 ?v= 确实变了(改文件内容没改 mtime 的场景不存在,改文件名则必须同步钩子)。
- 图标码点必须同时在「核心
TemplateCompiler::icon()的$map」和「字体子集」里 ——
否则静默渲染成方块(无报错)。当前字体子集 assets/fonts/fontawesome-webfont.subset.v2.{ttf,woff2}
为 448 字形;新增核心 $map 之外的码点须重跑 .workbuddy-ai/tools/build_font_subset.py。
- 禁止用
$view->icon()输出图标 —— 它返回值恒带style="font-size:Npx",违反零内联红线。
本插件改用字面量 HTML 实体(<i class="fa ann-ico"></i>),字号走 mn-fs-* / CSS。
- **配色一律走
var(--mn-*, fallback)** —— 注意--mn-danger/--mn-card-bg/--mn-muted
在核心 CSS 里没有定义(--mn-danger 只在 modern.css:639/643 被使用过,靠 fallback 兜底)。
红色必须用 --mn-error,卡片底用 --mn-bg-card,弱化文字用 --mn-text-muted。
- 后台弹层 markup 必须留在
<main class="admin-content">内 —— 核心后台表单/按钮样式
(.form-input / .btn / label)全部是 .admin-content 作用域。放到 </main> 之后 = 无 UI,零日志。
- **删除确认不用内联
on*** —— 视图给<button data-ann-confirm="…">,由script.js里
addEventListener 拦截。这是本仓 P0 红线(CSP script-src 下内联事件会失效)。
- 首次激活存在并发竞态 —— 两个请求同时进入
ensureColumnPatches()时,后到者会拿到
duplicate column name。这是预期竞态(列已存在 = 目的达成),已做静默处理,
不会污染 error.log。
- 老数据向后兼容 —— 升级前建的公告
layout默认'bar',title/icon默认空。
bar 分支的输出与 1.3.0 除空白与注释外逐字节一致;6 组旧配色(yellow/red/gray/bluegray/blue/green)
色值原样保留。⚠️ 但 scroll 容器的类名已改为 .ann-rotator(旧 .announcement-scroll-* 不再使用),
即「上下轮播」形态本身是一次重绘,非零变更。
list/marquee会忽略逐条的layout—— 由容器统一排版,这是设计而非缺陷。- **
uninstall()只 DROP 插件自己的表 + 删戳,不清核心settings里的
announcement_mode / announcement_label —— 属已知残留**(当前未处理)。
重装插件时这两项会沿用旧值;需要干净重来请手动删除这两个键。
plugin.json未声明min_core—— 商店发布时会兜底为1.1.2,即版本门禁形同虚设。
本插件未使用 1.1.0 之后的新钩子,暂不影响;正式发版前建议补上。
- 公告内容经
Content::decode()+htmlspecialchars()双重处理后输出(防 XSS);
card 摘要走 strip_tags() 后再转义 —— 因此 card 布局会剥掉正文里的 HTML 标签,只留纯文本。
七、当前状态
- 启用状态:✅ 已启用(
plugin.json的activated: true) - 数据表
announcements:3 行(截至 2026-10-11) SCHEMA_VERSION:2- 语言包:
zh/en/zh_tw各 96 键(键集合与键序一致) - 容器形态当前值:见核心
settings.announcement_mode - 前缀标签当前值:见核心
settings.announcement_label(未设置则回落 i18n「公告」)