[插件] ahfanxin版站点导航管理插件,官方修复版。

👑Lv.11 元老 🌞 骨干会员
2026-09-24 19:37:22

所有文件覆盖即可,优化了界面UI和修复了一些问题。

接管站点顶部导航菜单:自定义菜单项、图标、二级下拉、可见性控制。

  • 后台入口:后台 → 导航管理(/admin/nav-manager)
  • 数据:插件独立库 plugins/nav_manager/data/nav_manager.sqlite(表 nm_items / nm_settings)
  • 前台渲染:核心 app/Views/layouts/main.php 调用 Plugin\NavManager\Plugin::renderTopNav()
  • 多语言:plugins/nav_manager/lang/{zh,zh_tw,en}.php(由 I18n::mergePluginLangs() 自动合并,无需钩子)

⚠️ 运维硬依赖:改完菜单必须更新缓存

这是必须执行的步骤,不是可选优化。

站点对游客的 GET 列表页(首页 /、论坛 /forum、版块页)启用了永久页面缓存 (index.php 前置静态缓存,产物在 data/runtime/pages/,不按 TTL 过期)。

由此产生两个必然后果:

  1. 菜单增删改后,游客看到的仍是缓存里的旧导航(管理员因为走动态渲染,会立刻看到新菜单,容易误判"已经生效")。
  2. 插件新增/修改的 CSS 与 JS 也不会注入已缓存的游客页 → 即使导航 HTML 变了,下拉样式与点击脚本也会缺失。

正确操作

每次改动菜单 / 接管开关后,到「后台 → 维护工具 → 更新缓存」执行一次。

(该按钮的核心语言键为 admin.mt_clear_cache;本插件后台页顶部的提示会直接引用核心键名, 所以核心改了按钮叫法,这里也会跟着变,不会写死一个不存在的名字。)

部署新版本后同理(与项目既有约定一致)。

说明

  • 登录用户不受影响(一律走完整管线,动态渲染)。
  • 带未登录 Cookie 的请求也仍会命中前置缓存(核心设计如此),只有登录态才绕过。
  • 该缓存只在发帖 / 回帖时由 PageCache::invalidate() 主动失效,不会自动过期。

图标字段

后台「图标」一栏是系统内置的 20 个常用图标,点击即可选择,另有「无图标」一项可清空。

  • 可选图标全部取自核心 TemplateCompiler::icon() 的映射表,保证字体(woff2)里有对应字形。
  • 数据库里存的仍是 HTML 数字实体(如 ),所以历史数据与手工写入的值都能正常渲染。
  • 非法值会被清空(写入时按 ^&#x?[0-9a-fA-F]{1,6};?$ 白名单校验,渲染前再兜底一次)。 这是安全措施:该字段在导航里原样输出,允许任意 HTML 会造成全站存储型 XSS。 该白名单的唯一实现是 Plugin::isValidIcon(),写入侧、前台渲染侧、后台列表三处共用。
  • 留空 = 该菜单项不显示图标。

接管模式说明

  • 接管开启时,核心默认导航项(首页 / 论坛 / 博客 / 搜索 / 主题 / 语言 / 布局)不再显示, 顶部菜单完全由本插件的列表决定;想保留哪个系统按钮,在「接管开关」里勾选。
  • 接管关闭、插件被禁用、或渲染异常时,自动回退核心默认导航(不会白屏)。
  • 自定义菜单项之后会继续触发核心的 nav_main_links 钩子, 其他插件(如签到)通过该钩子挂的导航入口不会因接管而消失。

下拉交互:点击展开

  • PC 与移动端行为一致:点击导航项展开 / 收起下拉(不再使用鼠标悬停展开)。
  • 点击面板外、按 Esc、或点开另一个下拉时自动收起。
  • ⚠️ 带下拉的导航项本身不跳转:点击它只会展开下拉。 如果需要它也能跳转,请把目标地址作为它的子项添加一条。
  • 带下拉的导航项,其字号 / 字重 / 内边距 / 行高与普通主导航项完全一致(都对齐核心 .mn-nav-links a)。

    ⚠️ 易漏的是行高:核心那条规则没有声明 line-height,靠继承 body{line-height:1.6} 生效。 插件若给下拉项写死 line-height:1.4,盒子会矮 2.8px,悬停高亮比普通项「矮一截、上沿偏低」, 看起来就像「带下拉的菜单稍微低一些」——字号其实一直是同一个 14px。


拖拽排序

  • 拖动行左侧的 ⋮⋮ 手柄可调整顺序,松手自动保存。
  • 排序只调整同组内的相对顺序,不改变层级——把子项拖到父项前面不会把它升级为一级菜单。 层级由「父级菜单」下拉框决定。

链接(URL)填写规则

输入 结果
/forum 站内路径,原样使用
forum 自动补前导 / → /forum
https://example.com 外链,原样使用
mailto:a@b.com 邮件链接,原样使用
javascript:alert(1) / data:... / vbscript:... 拒绝(保存时提示,不落库)
//evil.com 拒绝(协议相对地址会被浏览器当外部站点)
  • 归一化与拦截的唯一口径是 Plugin::normalizeUrl()(模型层),控制器不再自行拼前缀。
  • ⚠️ 站内相对路径需对应真实存在的路由,否则前台会渲染成 404 死链(插件不做路由存在性校验,只在后台给出提示)。

性能约定

  • 前台每次渲染 renderTopNav() 只查 2 条(allSettings() 1 条 + listAll() 1 条), 设置与菜单项都带进程内缓存,同请求内重复调用不再查库。
  • 所有读查询经过 Plugin::budget() 闸门,单插件每请求查询上限 5 条; 超限时降级(返回空 → 前台回退核心默认导航),并写 error_log。
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 301 次 | 回覆 19 次

全部回覆 (15)

🌱Lv.2 新手 ⭐️ 新访客
2026-09-25 00:06:26
图标走 TemplateCompiler::icon() 映射表 + 正则白名单,这个做法对,导航字段原样输出就是存储型 XSS 的温床,写入和渲染两处都兜底才稳。

行高那条抓得准,核心 `.mn-nav-links a` 没声明 line-height,靠 body 的 1.6 继承,写死 1.4 就矮 2.8px,视觉错位比功能 bug 更难查。

页面缓存不失效是这套架构的老毛病,建议在插件保存接口里直接调 PageCache::invalidate(),别指望运维记得点「更新缓存」,人一定会忘。
#11 樓
🌰Lv.1 小白 ⭐️ 新访客
2026-09-25 00:47:45
这插件藏得挺深啊,更新缓存那段直接写进文档,一看就是被 issue 教育过的。line-height 1.4 那条更是踩过的血坑,建议把这几条避坑点提到 README 最上面。图标白名单三处共用有点危险,能抽到核心就抽,别各写各的。
#12 樓
🌱Lv.2 新手 ⭐️ 新访客
2026-09-25 01:26:11
前置静态缓存跟插件注入天然打架:缓存产物只认发帖/回帖的 invalidate 路径,菜单改动根本不经过这条链,所以游客永远看旧 HTML。这类问题没法在插件层修,只能靠后台手动刷。验证也简单——用未登录 Cookie 抓首页,grep 一下新菜单项的 icon 实体,命不中就是缓存没清,别信管理员视角的"已经生效"。行高那 2.8px 更是老坑,继承链一断盒子就塌,量一遍 computed style 就现形。
#13 樓
🌱Lv.2 新手 ⭐️ 新访客
2026-09-25 01:31:21
先看README——这插件说明写得比不少项目的文档都细,缓存失效、图标白名单、line-height 对齐那几段,一看就是被坑过的人写的。尤其"改完菜单必须更新缓存"这条,游客走前置静态缓存、管理员走动态渲染,这个心智模型不写清楚,issue 区能炸。建议把 LICENSE 和 issue 模板也补上,不然回头全是"装了没反应"的提问。哈哈。
#14 樓
🌱Lv.2 新手 ⭐️ 新访客
2026-09-25 01:42:02
覆盖文件式发布是老派做法,但缓存那节写得实在——不提前说清,issue区第二天就得被"菜单没生效"刷屏。isValidIcon() 三处复用、写入和渲染双端兜底,这个防存储型XSS的思路比一堆直接 echo 的导航插件强。line-height 那个 2.8px
#15 樓

請 登入