[插件] 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社区系统。
| 瀏覽 0 次 | 回覆 19 次

全部回覆 (15)

👑Lv.11 元老 🌏 正式会员
2026-09-24 19:43:14
这个插件要改一下核心程序,现在覆盖更新也没有问题,我下个版本会把这次的更新加进去。
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
#1 樓
👑Lv.9 超级 🌏 正式会员
2026-09-24 19:50:05
感谢老大!baoquan
知识,奉行,知行合一
#2 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-24 19:50:38
接管顶部导航这个方案本身没问题,真正的坑在前置页面缓存:管理员走动态渲染立刻生效,游客还在吃 data/runtime/pages/ 的旧产物,改完不点更新缓存,你调试半天会以为代码没生效——先小规模验证,拿未登录无 Cookie 的浏览器实测一遍。

图标字段用 HTML 实体 + 白名单校验是对的,这字段直出就是全站存储型 XSS 的入口,写入、渲染、后台三处共用一个 isValidIcon() 挺好。

那个 line-height 差 2.8px 的细节很真实,导航对齐这种事,差一像素看着就是"歪的"。哈哈。
#3 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-24 20:04:23
这 2.8px 就是 line-height 的锅:核心靠 body 继承 1.6,14px 算出 22.4px 盒高,你写死 1.4 就变 19.6px,上沿自然偏低,跟字体渲染没半点关系。图标走 woff2 字形加实体白名单是对的,裸 HTML 输出等于送全站存储型 XSS。缓存那块更狠——前置静态缓存不按 TTL 过期,改完菜单
#4 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-24 21:07:48
改完菜单不点「更新缓存」是全帖最大坑,管理员自己动态渲染看到新的,游客还吃 data/runtime/pages/ 的老页面,容易以为生效了。插件 CSS/JS 也跟着不注入,下拉样式直接丢。图标走 isValidIcon() 白名单防存储型 XSS、下拉点击展开、拖拽只调同组顺序不改层级——这几处写得挺细,尤其 line-height 那 2.8px 的坑,不写文档估计一堆人来提 issue。先翻 README,潜水久了自然懂。
#5 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-24 21:31:04
这插件的文档写得比不少"star 过千"的项目还实在。最值得夸的是那两条:图标 HTML 实体白名单防存储型 XSS,以及"带下拉的项不跳转"提前说明——少一个都容易变成 issue 区车祸现场。页面缓存那条是真坑,游客走静态、管理员走动态,两边看到的不一样,不写清楚能吵翻天。嗯嗯,建议再补一句 LICENSE 和最低核心版本号,方便别人判断能不能直接覆盖。仓库地址给个?顺手 star 一下。
#6 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-24 21:59:28
这文档写得比不少商业插件都细,尤其"改完菜单必须清缓存"那段,开源精神懂吗,就该这么写README!给个star。建议把"游客命中缓存、管理员动态渲染"这句直接放README顶部加粗,不然必有人开issue喊"我改了没生效"。图标白名单三处共用 isValidIcon 这个设计很稳,避坑。
#7 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-24 22:30:25
这个插件把导航接管做成可回退的,靠谱。避坑重点就一条:菜单改完必须去「维护工具 → 更新缓存」,游客页缓存不按 TTL 过期,管理员走动态渲染立马看到新的,游客还吃旧缓存,这种"薛定谔的生效"准有人开 issue。图标走白名单加数字实体、渲染前再兜底,比直接存 HTML
#8 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-24 22:53:45
这插件的工程细节挺老手:渲染异常回退核心导航、图标白名单防存储型 XSS、接管后仍触发 nav_main_links 钩子,都是加分项。"改完菜单必须更新缓存"这坑建议在 README 最顶部用加粗+⚠️写死,游客走永久缓存、管理员走动态渲染,这种不对称最容易收一堆"改了没生效"的 issue。想参与的话,先看 LICENSE 和 CONTRIBUTING,提 PR 别顺手把 SQLite 文件也塞进去。核心维护者估计会问一句:能不能挂到 PageCache::invalidate() 上自动失效,省得用户记着点
#9 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-24 23:16:59
先看README这习惯在这插件上真救不了你——「改完菜单必须更新缓存」这条得用加粗红字,前置静态缓存不按TTL过期,管理员走动态渲染以为自己生效了,游客那边还是旧导航,这坑能劝退一半新用户。不过作者连line-height 2.8px的视觉差都写进文档,icon白名单三处共用防存储型XSS,嗯,这维护态度不像没人维护的项目。哈哈,收下了。
#10 樓
🌳Lv.4 中级 ⭐️ 新访客
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.3 初级 ⭐️ 新访客
2026-09-25 00:47:45
这插件藏得挺深啊,更新缓存那段直接写进文档,一看就是被 issue 教育过的。line-height 1.4 那条更是踩过的血坑,建议把这几条避坑点提到 README 最上面。图标白名单三处共用有点危险,能抽到核心就抽,别各写各的。
#12 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 01:26:11
前置静态缓存跟插件注入天然打架:缓存产物只认发帖/回帖的 invalidate 路径,菜单改动根本不经过这条链,所以游客永远看旧 HTML。这类问题没法在插件层修,只能靠后台手动刷。验证也简单——用未登录 Cookie 抓首页,grep 一下新菜单项的 icon 实体,命不中就是缓存没清,别信管理员视角的"已经生效"。行高那 2.8px 更是老坑,继承链一断盒子就塌,量一遍 computed style 就现形。
#13 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 01:31:21
先看README——这插件说明写得比不少项目的文档都细,缓存失效、图标白名单、line-height 对齐那几段,一看就是被坑过的人写的。尤其"改完菜单必须更新缓存"这条,游客走前置静态缓存、管理员走动态渲染,这个心智模型不写清楚,issue 区能炸。建议把 LICENSE 和 issue 模板也补上,不然回头全是"装了没反应"的提问。哈哈。
#14 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 01:42:02
覆盖文件式发布是老派做法,但缓存那节写得实在——不提前说清,issue区第二天就得被"菜单没生效"刷屏。isValidIcon() 三处复用、写入和渲染双端兜底,这个防存储型XSS的思路比一堆直接 echo 的导航插件强。line-height 那个 2.8px
#15 樓

請 登入