仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ docs/development/theme/guide.md 14.1 KB · 375 行 原始文件
Z zfhsh first commit 2 天前
1---
2title: 主题开发手册
3description: 新建主题最短路径、控制器与视图约定、静态资源隔离、公共交互函数与 DOM 契约、框架页实现要点与常见问题
4---
5
6# MNBT 主题开发手册
7
8本文面向需要**新建主题**或**改版默认皮肤**的开发者。
9阅读前请先看 [主题系统总览](./index.md) 了解切换方式与目录结构。
10
11## 1. 设计原则
12
131. **路由不变**:访问地址仍是 `/user/login.php`、`/admin/set.php?gn=wz`,不要改控制器 URL。
142. **逻辑与视图分离**:鉴权、查库、调宝塔 API 放在 `user/`、`admin/` 控制器;HTML 放主题。
153. **AJAX 路径不变**:页面内请求仍使用 `./ajax.php`、`../user/ajax.php` 等现有接口。
164. **缺页回退**:自定义主题只需覆盖要改的页面,其余自动使用 `default`。
175. **双端独立**:用户端主题与管理端主题可分别选择。
186. **DOM 契约保留**:表单 `id`、关键按钮 `onclick` 函数名、`msalert` / `msloading` 等公共函数不能改名(详见 [§6](#6-公共交互函数与-dom-契约))。
19
20## 2. 新建主题(最短路径)
21
22### 步骤 1:复制骨架
23
24```text
25templates/
26└── my_theme/ # 目录名 = 主题 ID(仅字母数字下划线横线)
27 ├── theme.json
28 ├── user/
29 │ └── assets/
30 └── admin/
31 └── assets/
32```
33
34> 当前官方主题包含 `default`(Bootstrap 4 + jQuery)与 `layui`(Layui 2.x)。新建主题建议以 `default` 为基准复制后修改,或参考 `layui` 主题的"混合栈"做法(见 [主题引擎进阶 §9](./engine.md#9-混合-ui-框架主题策略))。
35
36### 步骤 2:编写 `theme.json`
37
38```json
39{
40 "name": "my_theme",
41 "title": "我的主题",
42 "version": "1.0.0",
43 "description": "自定义用户端与管理端皮肤",
44 "author": "YourName",
45 "scope": ["user", "admin"]
46}
47```
48
49| 字段 | 必填 | 说明 |
50|------|------|------|
51| `name` | 建议 | 与目录名一致 |
52| `title` | 是 | 后台「前端模板」列表显示名 |
53| `version` | 否 | 版本号 |
54| `description` | 否 | 简介 |
55| `author` | 否 | 作者 |
56| `scope` | 否 | 文档用;实际以是否存在 `user/`、`admin/` 目录为准 |
57
58### 步骤 3:覆盖页面
59
60从 `templates/default/user/` 或 `admin/` **复制**要改的文件到 `my_theme` 对应目录,再修改 HTML/CSS。示例:只改用户登录页外观(其余用户页仍走 `default`):
61
62```text
63templates/my_theme/user/login.php
64```
65
66### 步骤 4:启用
67
681. 确保 `templates/` 可写
692. 后台 → 系统管理 → **前端模板** → 选择 `my_theme` → 保存
703. 或写入 `templates/active_user_theme` / `templates/active_admin_theme`
71
72### 步骤 5:自测清单
73
74- [ ] 登录 / 退出
75- [ ] 框架页侧栏与多标签(index)
76- [ ] 至少 2~3 个业务子页(仪表盘、设置、列表)
77- [ ] 表单提交与 AJAX 弹窗
78- [ ] 静态资源 404 检查(CSS/JS/图片)
79- [ ] **回退页**(未覆盖的页面)样式是否正常
80
81## 3. 必选 / 可选视图清单
82
83完整视图清单已拆分到独立小节 [视图清单(views.md)](./views.md),包含:
84
85- 3.1 用户端 `templates/{theme}/user/`
86- 3.2 管理端 `templates/{theme}/admin/`
87- 3.3 Docker 控制台 `templates/{theme}/docker/`(V1.83 新增)
88- 3.5 主页 `templates/{theme}/home/`(V1.84 新增)
89- 3.6 不走主题的路径(一般不要动)
90
91核心原则:不提供的文件会回退 `default`,不必一次抄全。
92
93## 4. 控制器与视图约定
94
95### 4.1 用户端控制器示例
96
97```php
98<?php
99// user/sy.php
100include("../MPHX/common.php");
101$title = 'MN宝塔主机首页目录';
102mnbt_user_require_login();
103// 此处可准备 $data 等变量(会进入 $GLOBALS,视图可直接使用)
104mnbt_render('sy');
105```
106
107### 4.2 管理端控制器示例
108
109```php
110<?php
111// admin/set.php
112include("../MPHX/common.php");
113$title = 'MN宝塔主机系统设置';
114mnbt_admin_require_login();
115mnbt_admin_render('set');
116```
117
118### 4.3 视图内引入公共头
119
120```php
121<?php mnbt_theme_include('head'); ?>
122<!-- 或管理端 -->
123<?php mnbt_admin_include('head'); ?>
124```
125
126`index.php` 一般是完整 HTML 文档,**可不** include head。
127
128### 4.4 视图中可用的常见变量
129
130由 `common.php` / `member.php` 注入,视图可直接使用:
131
132| 变量 | 端 | 说明 |
133|------|----|------|
134| `$conf` | 双端 | 系统配置行(`MN_config`) |
135| `$DB` | 双端 | 数据库对象 |
136| `$date` | 双端 | 当前时间字符串 |
137| `$title` | 双端 | 页面标题(控制器设置) |
138| `$islogins` / `$yhc` | 用户端 | 登录态 / 主机信息 |
139| `$user` / `$zjid` / `$ssbt` | 用户端 | 账号、站点 ID、所属宝塔代号 |
140| `$islogin` | 管理端 | 管理员登录态 |
141| `$siteid` | 双端 | 配置站点 ID(通常 1) |
142| `$mn_conf` | 双端 | 内部运行配置(含 `xf` 修复标记) |
143
144部分页面控制器还会准备专用变量,例如 `monitor.php` 的 `$tasks`、`$task_count`,`monitor_log.php` / `notice.php` 的 `$logs`、`$page`、`$total`,`sqlgl.php` 的 `$bf_data`、`$hxd`。
145
146## 5. 静态资源隔离
147
148### 5.1 两类资源(必须分清)
149
150| 类型 | 目录 | API | 是否随主题切换 |
151|------|------|-----|----------------|
152| **公共资源** | `imsetes/` | `mnbt_asset_url()` | 否 |
153| **主题私有** | `templates/{theme}/{scope}/assets/` | `mnbt_theme_asset()` / `mnbt_theme_url()` | 是(缺文件回退 default) |
154
155**公共资源**(不要复制进主题):Bootstrap、jQuery、CodeMirror、图表库、上传 logo(`upload_logo/` / `admin_logo/`)、业务脚本(`fn-hs.js`、`xtset.js` 等)。
156
157**主题私有**(改皮肤放这里):覆盖样式、登录页背景、主题专属 JS/图片。
158
159### 5.2 公共资源写法
160
161```php
162<link href="<?= mnbt_asset_url('css/bootstrap.min.css') ?>" rel="stylesheet">
163<script src="<?= mnbt_asset_url('js/jquery.min.js') ?>"></script>
164<img src="<?= mnbt_asset_url('upload_logo/logo.login.png') ?>?<?= $conf['auther'] ?>">
165```
166
167等价于 `../imsetes/...`。**模板中禁止再写死 `../imsetes/`**,便于以后改公共资源根路径。
168
169### 5.3 主题私有资源
170
171```text
172templates/my_theme/user/assets/login.css
173templates/my_theme/admin/assets/set-page.css
174templates/my_theme/admin/assets/admin-common.css
175```
176
177推荐写法(自动加 `assets/` 前缀):
178
179```php
180<link href="<?= mnbt_theme_asset('login.css') ?>" rel="stylesheet">
181<link href="<?= mnbt_theme_asset('set-page.css', 'admin') ?>" rel="stylesheet">
182```
183
184等价于:
185
186```php
187<link href="<?= mnbt_theme_url('assets/login.css') ?>" rel="stylesheet">
188```
189
190### 5.4 资源回退规则
191
192与页面模板相同:当前主题 `templates/{theme}/{scope}/assets/xxx.css` → 不存在则 `templates/default/{scope}/assets/xxx.css` → 仍不存在则返回当前主题 URL(便于你补文件时定位 404)。
193
194因此自定义主题**只需覆盖要改的 CSS**,其余私有资源会用 default 的。
195
196### 5.5 缓存
197
198```php
199<script src="<?= mnbt_asset_url('js/fn-hs.js') ?>?1.80"></script>
200```
201
202Logo 等已使用 `$conf['auther']` 作为缓存戳。
203
204### 5.6 引入第三方 UI 库(如 Layui)
205
206第三方库**不属于公共资源**,可由主题自行引入:
207
208- 优先:CDN(如 `https://unpkg.com/layui@2.9.8/dist/css/layui.css`)
209- 离线:放入 `templates/{theme}/user/assets/lib/` 后用 `mnbt_theme_asset('lib/layui.css')` 引用
210
211> 注意:如果主题仅覆盖部分页面(其余回退 default),第三方库需在 `head.php` 中加载,使回退页也能取到;但同时**不要移除** Bootstrap / jQuery / `fn-hs.js`,否则回退页会样式错乱或脚本失效(详见 [主题引擎进阶 §9 混合 UI 框架主题策略](./engine.md#9-混合-ui-框架主题策略))。
212
213## 6. 公共交互函数与 DOM 契约
214
215现有大量页面 JS 依赖固定元素 `id`、class 与全局函数。改外观时**必须保留**以下契约。
216
217### 6.1 全局函数(来自 `imsetes/js/fn-hs.js`)
218
219| 函数 | 用途 |
220|------|------|
221| `msalert(type, msg, timeout)` | 消息提示(1 成功 / 2 公告 / 3 警告 / 4 错误) |
222| `msalertb(type, title, content, ...)` | 带标题的弹窗 |
223| `msloading(text, ...)` | 显示加载遮罩 |
224| `msloadingde()` | 关闭加载遮罩 |
225| `ylalert(text)` | 用量超限提示 |
226
227### 6.2 登录页契约(用户端 + 管理端)
228
229| 元素 / 函数 | 说明 |
230|------|------|
231| `#username`、`#password` | 输入框 id,登录 JS 直接读取 |
232| `#csyzmiq` | 验证码输入框(开启验证码时) |
233| `#captcha` | 验证码图片(刷新用) |
234| `chkre()` | 登录提交函数(`onclick="chkre()"`) |
235
236### 6.3 框架页契约
237
238| 元素 / 函数 | 说明 |
239|------|------|
240| `#iframe-content` | iframe 容器(多标签插件挂载点) |
241| `#iframe_shuax` | 刷新当前标签按钮 |
242| `chteci()` | 退出登录函数 |
243| `.multitabs` | 多标签点击触发类 |
244| `xiaole()` | 邮箱绑定弹窗(用户端,按需) |
245
246### 6.4 改外观的边界
247
248- ✅ 可以改:class、布局、颜色、字体、间距、图标库
249- ❌ 不要改:表单控件 `id`、关键按钮 `onclick` 函数名、`<script>` 内联逻辑的函数名;若必须改结构,需同步修改页面内 JS 或独立 `assets/*.js`
250
251## 7. 框架页(index.php)实现要点
252
253`index.php` 是整套皮肤中**最复杂**的页面,承担:左侧导航菜单、顶部工具栏(侧栏开关、刷新、用户菜单、配色切换)、多标签 iframe 容器(`#iframe-content`)、退出登录与系统修复等弹窗逻辑。
254
255### 7.1 默认主题的实现
256
257默认主题使用 `bootstrap-multitabs` 插件驱动多标签:
258
259```php
260<script src="<?= mnbt_asset_url('js/bootstrap-multitabs/multitabs.min.js') ?>"></script>
261<script src="<?= mnbt_asset_url('js/index.min.js') ?>"></script>
262```
263
264菜单项添加 `class="multitabs"` 即可被插件劫持为 iframe 标签页。
265
266### 7.2 自定义框架页注意
267
268- 必须保留 `#iframe-content` 容器,否则多标签插件无处挂载
269- 保留 `.multitabs` 类与 `data-url` / `data-title` 属性
270- 保留 `#iframe_shuax` 刷新按钮与 `chteci()` 退出函数
271- 保留 iframe loading 占位 HTML(含 `css/index.loading.css`)的结构
272
273### 7.3 插件菜单挂载点
274
275```php
276<?php
277if (function_exists('mnbt_plugin_render_menu_user_html')) {
278 echo mnbt_plugin_render_menu_user_html();
279}
280?>
281```
282
283管理端用 `mnbt_plugin_render_menu_admin_html()`。**必须保留**,否则插件菜单不会显示。
284
285### 7.4 插件菜单多主题适配(菜单渲染器)
286
287从 V1.82 起,引擎支持**主题注册自己的菜单渲染器**,解决"插件菜单在不同主题下结构不兼容"问题。
288
289**原理**:插件通过 `mnbt_register_menu('user', ...)` 注册的是**菜单数据树**(title/icon/url/order/children),不带 HTML 结构;主题通过 `mnbt_register_theme_menu_renderer('user', $callback)` 注册**渲染器**,把这棵树转换成当前主题需要的 HTML。主题 `index.php` 中调用 `mnbt_plugin_render_menu_user_html()` 时,引擎自动使用当前主题注册的渲染器;若主题未注册渲染器,引擎回退到 default 主题(lyear)结构。
290
291**主题开发者需要做的两件事**:
292
2931. 在主题根目录创建 `theme.php`,注册渲染器:
294
295```php
296<?php
297// templates/my_theme/theme.php
298if (!defined('IN_CRONLITE')) exit;
299
300mnbt_register_theme_menu_renderer('user', function ($items) {
301 $html = '';
302 foreach ($items as $it) {
303 $title = htmlspecialchars($it['title'] ?? '');
304 $icon = htmlspecialchars($it['icon'] ?? 'mdi-puzzle');
305 if (!empty($it['children'])) {
306 // 分组
307 $html .= '<li class="my-submenu">'
308 . '<a href="javascript:;"><i class="mdi ' . $icon . '"></i> ' . $title . '</a>'
309 . '<ul class="my-subnav">';
310 foreach ($it['children'] as $child) {
311 $childTitle = htmlspecialchars($child['title'] ?? '');
312 $childUrl = htmlspecialchars($child['url'] ?? 'javascript:void(0)');
313 $mt = !empty($child['multitabs']) || strpos($childUrl, 'plugin.php') !== false ? ' multitabs' : '';
314 $html .= '<li><a href="' . $childUrl . '" class="' . trim($mt) . '">' . $childTitle . '</a></li>';
315 }
316 $html .= '</ul></li>';
317 } else {
318 // 叶子项
319 $url = htmlspecialchars($it['url'] ?? 'javascript:void(0)');
320 $mt = !empty($it['multitabs']) || strpos($url, 'plugin.php') !== false ? ' multitabs' : '';
321 $html .= '<li><a href="' . $url . '" class="' . trim($mt) . '"><i class="mdi ' . $icon . '"></i> ' . $title . '</a></li>';
322 }
323 }
324 return $html;
325});
326```
327
3282. 在 `index.php` 的侧边栏合适位置调用:
329
330```php
331<?php
332if (function_exists('mnbt_plugin_render_menu_user_html')) {
333 echo mnbt_plugin_render_menu_user_html();
334}
335?>
336```
337
338**注意事项**:`theme.php` 会在引擎首次解析该主题视图时**自动加载**(通过 `mnbt_theme_ensure_loaded`),无需手动 include;叶子项建议统一归入一个分组(如「插件管理」),或按主题风格平铺。必须给链接加 `multitabs` 类,才能让多标签插件正确接管;管理端菜单同理,注册 `'admin'` scope 的渲染器。
339
340## 12. 常见问题
341
342### Q: 改了主题文件不生效?
343
3441. 确认当前激活主题名(`active_*` 或后台显示),并确认文件路径是否为 `templates/{主题}/{user|admin}/xxx.php`
3452. 清理浏览器 / CDN / OPcache;检查是否改错了 `user/xxx.php` 控制器(控制器里不应再写大段 HTML)
346
347### Q: 只想换颜色,不想复制整页?
348
349优先改 `user/assets/*.css` / `admin/assets/*.css`,或 `head.php` 里增加覆盖样式,不必复制所有业务页。
350
351### Q: 管理端设置页样式在哪?
352
353默认主题布局:`templates/default/admin/set.php`,样式:`templates/default/admin/assets/set-page.css`。
354
355### Q: 主题里能否直接查数据库?
356
357技术上可以(`$DB` 可用),但**不推荐**——查询应放控制器,视图只负责展示,便于换皮与维护。
358
359### Q: 如何调试当前加载的是哪个文件?
360
361可在视图临时输出:
362
363```php
364<?php /* echo mnbt_theme_resolve('login', 'user'); */ ?>
365```
366
367或查看 `mnbt_theme_name('user')` / `mnbt_theme_name('admin')`。
368
369### Q: 回退页样式错乱?
370
371通常是 `head.php` 漏加载了 Bootstrap / jQuery / `fn-hs.js` / `style.min.css`。检查 head.php 是否完整保留了 default 主题的公共资源引用,再追加新 UI 库。
372
373### Q: 主题加载了 Layui 但 `layui.form.render()` 报错?
374
375业务回退页里没有 `.layui-form` 容器,调用 `render` 无意义。仅在自定义页面(如登录页、框架页)里使用 `layui.form.render()`,不要放到 `head.php` 全局执行。