better-staridc-MNBT
1---
2title: 菜单与页面
3description: 插件菜单注册、页面注册、页面接管(Page Override)与 Partial 接管(Partial Override)详解
4---
5
6# 菜单与页面
7
8本文是 [插件开发手册](./guide.md) §3.4 的拆分章节,介绍插件菜单注册、页面注册、页面接管(Page Override)与 Partial 接管。
9
10## 3.4 菜单与页面
11
12```php
13// 单个入口:会被自动归入「插件管理」分组
14mnbt_register_menu('admin', [
15 'title' => '标题',
16 'page' => 'settings', // 自动生成 url: plugin.php?p=slug&page=settings
17 // 或 'url' => 'https://...',
18 'icon' => 'mdi-webhook', // Material Design Icons 类名(不含 mdi 前缀时引擎会加)
19 'order' => 20,
20 'multitabs' => true,
21]);
22
23// 独立分组(推荐:一个插件有多个入口时)
24mnbt_register_menu('user', [
25 'title' => '域名服务',
26 'icon' => 'mdi-web',
27 'order' => 40,
28 'children' => [
29 ['title' => '域名绑定', 'page' => 'bind', 'icon' => 'mdi-domain', 'multitabs' => true],
30 ['title' => 'DNS 记录', 'page' => 'dns_records', 'icon' => 'mdi-dns', 'multitabs' => true],
31 ],
32]);
33
34mnbt_register_page('admin', 'settings', 'admin/settings.php', '页面标题');
35mnbt_register_page('user', 'index', 'user/index.php', '用户页');
36```
37
38| 入口 | URL |
39|------|-----|
40| 管理页 | `admin/plugin.php?p={slug}&page={page}` |
41| 用户页 | `user/plugin.php?p={slug}&page={page}` |
42
43**菜单渲染规则**:
44- 注册时带 `children` 的项 → 渲染为独立侧边栏分组(如「域名服务」)
45- 注册时不带 `children` 的项 → 统一归入「插件管理」分组
46- 分组和叶子项都按 `order` 升序排列
47
48**多主题适配**:
49- 插件只负责提供**菜单数据树**,不要写死 HTML 结构
50- 每个主题可注册自己的菜单渲染器(详见 [主题开发手册](../theme/index.md) §7.4)
51- 引擎会自动使用当前主题渲染器,未注册时回退到 default 主题结构
52- 因此插件新增/修改菜单项后,**无需修改任何主题文件**
53
54页面文件内可:
55
56- 管理端:`mnbt_admin_include('head');`
57- 用户端:`mnbt_theme_include('head');`(与 default 主题一致时)
58- 使用全局 `$DB`、`$conf`、`$yhc`(用户端)、`$islogin` / `$islogins`
59
60### 3.4.1 页面接管(Page Override)
61
62让插件**接管或包裹已有主题页面的整页输出**,无需修改主题文件。当核心入口(`user/set.php`、`admin/list.php` 等)调用 `mnbt_render($view)` 时,引擎会按 priority 升序遍历所有注册的 override 回调,第一个返回非 null 的值即生效,后续回调不再调用(短路语义)。
63
64```php
65// 模式 1:完全接管(替换整页)
66mnbt_register_page_override('user', 'set', function ($vars) {
67 if (($_GET['gn'] ?? '') !== 'my_section') return null; // 其他 gn 走原逻辑
68 return '<div>我的自定义内容</div>';
69});
70
71// 模式 2:包裹模式(在原页面前后插入 banner)
72mnbt_register_page_override('user', 'index', function ($vars) {
73 return [
74 'before' => '<div class="banner">公告</div>',
75 'after' => '<script>console.log("page loaded")</script>',
76 ];
77});
78
79// 模式 3:管理端接管 + 优先级控制
80mnbt_register_page_override('admin', 'list', function ($vars) {
81 if (($_GET['gn'] ?? '') === 'plugin_section') {
82 return render_my_plugin_page();
83 }
84 return null;
85}, 5); // priority=5,比默认 10 更早执行
86```
87
88| 参数 | 说明 |
89|------|------|
90| `scope` | `'user'` 或 `'admin'` |
91| `view` | 视图名(如 `'set'`、`'list'`、`'sy'`、`'index'`),对应 `mnbt_render($view)` 的参数 |
92| `callback` | 签名 `function(array $vars): mixed`,详见下方回调返回值 |
93| `priority` | 优先级(数字越小越先执行),默认 10 |
94
95**回调返回值(三选一)**:
96- `null` → 不接管,继续加载原主题文件(默认行为)
97- `string` → 完全接管,直接输出该字符串,跳过原主题文件
98- `['before' => string, 'after' => string]` → 包裹模式,在原主题文件输出前后插入内容(`before`/`after` 任一可省略)
99
100**多插件协作**:
101- 多个插件注册同一 view 的 override 时,按 priority 升序执行
102- 第一个返回非 null 的回调生效,后续回调不再调用(短路语义)
103- 想让某插件优先接管,给它更低的 priority(如 `5`、`1`)
104
105**适用场景**:
106- 插件化原核心功能(如接管 `set.php` 的某个 `gn` 分支)
107- 在所有页面注入全局 banner / 公告 / 统计代码(包裹模式 + 高 priority)
108- 替换整个页面输出(如自定义登录页、自定义后台首页)
109- 根据请求参数(`$_GET['gn']`)决定是否接管
110
111### 3.4.2 Partial 接管(Partial Override)
112
113让插件**接管或包裹主题局部模板**(如 `head`、`footer`)的输出。当主题调用 `mnbt_theme_include($view)` 时,引擎会按 priority 升序遍历所有注册的 override 回调,第一个返回非 null 的值即生效。
114
115```php
116// 模式 1:完全接管 head
117mnbt_register_partial_override('user', 'head', function ($vars) {
118 return '<!-- 自定义 head -->...';
119});
120
121// 模式 2:在 head 末尾追加自定义 CSS(包裹模式)
122mnbt_register_partial_override('user', 'head', function ($vars) {
123 return ['after' => '<style>.my-plugin-banner{color:red}</style>'];
124});
125
126// 模式 3:在 footer 开头插入统计代码
127mnbt_register_partial_override('user', 'footer', function ($vars) {
128 return ['before' => '<script src="analytics.js"></script>'];
129});
130```
131
132参数与回调返回值与 `mnbt_register_page_override` 完全相同。