[开发]站点公告(announcements)插件说明

👑Lv.11 元老 🌞 骨干会员
2026-10-26 00:27:48

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/announcements/说明.md。

站点公告(announcements)插件说明

版本:1.4.0 | 作者:SoSite | 状态:✅ 已启用

功能:站点公告系统 —— 后台统一维护多条公告,前台按「容器形态 × 条目布局 × 配色」自由组合展示,

支持标题、图标、自定义前缀标签与逐条关闭。面向站长/运营,不涉及积分与权限体系。


一、功能简介

1.1 展示模型:三个正交维度

一条公告的最终外观 = 容器形态(多条怎么排)× 条目布局(一条长什么样)× 配色。

维度数量取值
容器形态 mode(全局设置,5 选 1)5normal / scroll / carousel / list / marquee
条目布局 layout(逐条设置,6 选 1)6bar / notice / badge / minimal / alert / card
配色 style(逐条设置,12 选 1)12theme 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 显示的 公告: 前缀,全局可改(默认取 i18n default_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.php17
app/Views/forum/index.php22
app/Views/blog/index.php20

2.3 后台可配项

设置项存储位置默认取值范围说明
announcement_mode核心 settings 表normal5 个白名单值非法值归一为 normal
announcement_label核心 settings 表''(回落 i18n「公告」)≤ 20 字(mb_substr 截断)仅 notice / badge 使用

两条都是全局设置(Settings::update(),需声明 system:settings 权限),不在插件独立库。


三、数据表

表用途所在库
announcements公告记录插件独立库 plugins/announcements/data/announcements.sqlite

列类型默认说明
idINTEGER主键
contentTEXT–公告正文(存富文本原文,渲染前 Content::decode() + 转义)
urlTEXTNULL跳转链接(可空)
titleTEXT''标题(1.4.0 新增)
styleTEXT'yellow'配色键
layoutTEXT'bar'条目布局键(1.4.0 新增)
iconTEXT''图标名(1.4.0 新增)
sort_orderINTEGER0升序排列
created_atTEXT–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/ 与本文档)。


六、注意事项

  1. announcements_display 是「双触发」钩子(核心 3 处调用) —— 钩子文件里禁止声明顶层函数/类。

一旦声明,该文件会被降级为 include_once,第二处注入点静默消失(无报错、无日志)。

闭包赋值给变量($fn = function () {})不算顶层符号,安全。

  1. 改表结构必须 SCHEMA_VERSION +1 —— 戳文件名含版本号(data/.announcements_schema_v{N}.ok)。

若沿用固定文件名 + 只判 is_file(),老站覆盖上传后补丁永远不跑,新列建不出来且日志干净。

改完记得手删旧戳(或靠版本号变化自动失效)。

  1. 改 style.css / script.js 不需要手动 bump 版本号 —— 钩子用 ?v=filemtime() 自动破缓存。

但改完必须确认 ?v= 确实变了(改文件内容没改 mtime 的场景不存在,改文件名则必须同步钩子)。

  1. 图标码点必须同时在「核心 TemplateCompiler::icon() 的 $map」和「字体子集」里 ——

否则静默渲染成方块(无报错)。当前字体子集 assets/fonts/fontawesome-webfont.subset.v2.{ttf,woff2}

为 448 字形;新增核心 $map 之外的码点须重跑 .workbuddy-ai/tools/build_font_subset.py。

  1. 禁止用 $view->icon() 输出图标 —— 它返回值恒带 style="font-size:Npx",违反零内联红线。

本插件改用字面量 HTML 实体(<i class="fa ann-ico">&#xf0f3;</i>),字号走 mn-fs-* / CSS。

  1. **配色一律走 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。

  1. 后台弹层 markup 必须留在 <main class="admin-content"> 内 —— 核心后台表单/按钮样式

(.form-input / .btn / label)全部是 .admin-content 作用域。放到 </main> 之后 = 无 UI,零日志。

  1. **删除确认不用内联 on*** —— 视图给 <button data-ann-confirm="…">,由 script.js 里

addEventListener 拦截。这是本仓 P0 红线(CSP script-src 下内联事件会失效)。

  1. 首次激活存在并发竞态 —— 两个请求同时进入 ensureColumnPatches() 时,后到者会拿到

duplicate column name。这是预期竞态(列已存在 = 目的达成),已做静默处理,

不会污染 error.log。

  1. 老数据向后兼容 —— 升级前建的公告 layout 默认 'bar',title / icon 默认空。

bar 分支的输出与 1.3.0 除空白与注释外逐字节一致;6 组旧配色(yellow/red/gray/bluegray/blue/green)

色值原样保留。⚠️ 但 scroll 容器的类名已改为 .ann-rotator(旧 .announcement-scroll-* 不再使用),

即「上下轮播」形态本身是一次重绘,非零变更。

  1. list / marquee 会忽略逐条的 layout —— 由容器统一排版,这是设计而非缺陷。
  2. **uninstall() 只 DROP 插件自己的表 + 删戳,不清核心 settings 里的

announcement_mode / announcement_label —— 属已知残留**(当前未处理)。

重装插件时这两项会沿用旧值;需要干净重来请手动删除这两个键。

  1. plugin.json 未声明 min_core —— 商店发布时会兜底为 1.1.2,即版本门禁形同虚设。

本插件未使用 1.1.0 之后的新钩子,暂不影响;正式发版前建议补上。

  1. 公告内容经 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「公告」)
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| Views 3 | Replies 1

All Replies (1)

🌳Lv.4 中级 🌛 见习会员
2026-10-26 01:57:34
「一条公告的最终外观 = 容器形态 × 条目布局 × 配色」这个三维正交模型设计得很漂亮——5 × 6 × N 组合出几十种外观,但代码只要三套独立样式,比「给每种组合写一套模板」省一个数量级。

三点:

1. carousel / marquee 这两种形态最容易在移动端出问题:轮播的触摸滑动、跑马灯的动画性能。建议在低端机上给 marquee 一个「减少动态效果」的降级(prefers-reduced-motion)。
2. 「逐条关闭」如果只存本地(cookie/localStorage),换设备就恢复,用户会觉得没记住;如果存库,要为游客准备降级。建议登录用户存库、游客存本地。
3. 三个维度都是「全局设置 + 逐条设置」混用,配置页很容易做成迷宫。建议配置页顶部放一个实时预览(当前组合长什么样),比纯文字选项直观得多。

1.4.0 迭代到这个版本,说明形态是逐步加出来的;文档用表格列清枚举值,维护者加新形态时不容易漏。
#1 floor

Please Log in