better-staridc-MNBT
1<?php
2/**
3 * TDesign 用户端 SPA 公共启动片段
4 * 注入 window.__TD_BOOT__ 并挂载构建产物
5 */
6if (!defined('IN_CRONLITE')) {
7 exit('Access Denied');
8}
9
10$td_dist = __DIR__ . '/dist';
11$td_js = $td_dist . '/assets/index.js';
12$td_css = $td_dist . '/assets/index.css';
13$td_ver = is_file($td_js) ? (string)@filemtime($td_js) : (string)time();
14
15$boot = [
16 'siteName' => $conf['name'] ?? 'MNBT',
17 'footer' => $conf['hxp'] ?? '',
18 'loggedIn' => isset($islogins) && (int)$islogins === 1,
19 'needCaptcha' => isset($conf['yzm']) && $conf['yzm'] === 'true',
20 'ajaxBase' => './ajax.php',
21 'codeUrl' => './code.php',
22 'logo' => mnbt_asset_url('upload_logo/logo.login.png'),
23 'logoHead' => mnbt_asset_url('upload_logo/logo.head.png'),
24 'logoIndex' => mnbt_asset_url('upload_logo/logo.index.png'),
25 'auther' => $conf['auther'] ?? '',
26 'theme' => 'tdesign',
27 'version' => '0.3.0',
28 'entry' => $td_entry ?? 'dashboard',
29 'hash' => $td_hash ?? '',
30 'pluginMenuHtml' => '',
31 'conf' => $conf ?? [],
32 'user' => $user ?? '',
33 'zjid' => $zjid ?? 0,
34 'ssbt' => $ssbt ?? '',
35 'yhc' => $yhc ?? [],
36 'serverHost' => $_SERVER['HTTP_HOST'] ?? '',
37 'serverProto' => (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ($_SERVER['SERVER_PORT'] ?? '') == '443' ? 'https://' : 'http://',
38 'pluginSettingsTabs' => function_exists('mnbt_plugin_settings_tabs') ? array_map(function ($tab) {
39 if (empty($tab['icon']) && !empty($tab['plugin'])) {
40 $meta = $GLOBALS['mnbt_plugin_meta'][$tab['plugin']] ?? [];
41 $tab['icon'] = !empty($meta['icon']) ? $meta['icon'] : 'mdi-cog-outline';
42 }
43 return $tab;
44 }, mnbt_plugin_settings_tabs()) : [],
45];
46
47// 视图可在 include 前设置 $td_inject(数组),把页面级数据注入 boot
48if (isset($td_inject) && is_array($td_inject)) {
49 foreach ($td_inject as $k => $v) {
50 $boot[$k] = $v;
51 }
52}
53
54// 插件菜单 HTML(独立"扩展与工具"分组,所有插件项通过 SPA 路由在 layout 内打开)
55$boot['pluginMenuHtml'] = _tdboot_render_plugin_menu_html(function_exists('mnbt_plugin_menus') ? mnbt_plugin_menus('user') : []);
56
57/**
58 * 渲染插件菜单区 HTML
59 * 输出形如:
60 * <li class="td-side-group"><div class="td-side-group-label">...扩展与工具...</div></li>
61 * <li class="td-side-submenu">分组项...<ul>叶子项(带 data-td-route)</ul></li>
62 * <li class="td-side-leaf">独立叶子项(带 data-td-route)</li>
63 */
64function _tdboot_render_plugin_menu_html($items)
65{
66 if (empty($items)) {
67 return '';
68 }
69
70 $groups = [];
71 $leafs = [];
72 $html = '';
73 foreach ($items as $it) {
74 if (!empty($it['children'])) {
75 $groups[] = $it;
76 } else {
77 $leafs[] = $it;
78 }
79 }
80
81 foreach ($groups as $g) {
82 $html .= _tdboot_render_plugin_group($g);
83 }
84
85 foreach ($leafs as $lf) {
86 // 独立叶子项直接作为顶层菜单项,与其它业务项视觉一致
87 $html .= _tdboot_render_plugin_leaf_item($lf);
88 }
89
90 return $html;
91}
92
93function _tdboot_render_plugin_group($it)
94{
95 $title = htmlspecialchars($it['title'] ?? '', ENT_QUOTES, 'UTF-8');
96 $icon = !empty($it['icon']) ? htmlspecialchars($it['icon'], ENT_QUOTES, 'UTF-8') : 'mdi-puzzle';
97
98 $childrenHtml = '';
99 foreach ($it['children'] as $child) {
100 $childrenHtml .= _tdboot_render_plugin_menu_child($child);
101 }
102
103 return '<li class="td-side-submenu">'
104 . '<a href="javascript:;" onclick="this.parentElement.classList.toggle(\'open\')">'
105 . '<i class="mdi ' . $icon . '"></i><span>' . $title . '</span>'
106 . '<i class="td-arrow mdi mdi-chevron-down"></i></a>'
107 . '<ul class="td-side-subnav">' . $childrenHtml . '</ul></li>';
108}
109
110function _tdboot_render_plugin_menu_child($it)
111{
112 $title = htmlspecialchars($it['title'] ?? '', ENT_QUOTES, 'UTF-8');
113 $icon = !empty($it['icon']) ? htmlspecialchars($it['icon'], ENT_QUOTES, 'UTF-8') : 'mdi-puzzle';
114
115 if (!empty($it['children'])) {
116 $childrenHtml = '';
117 foreach ($it['children'] as $child) {
118 $childrenHtml .= _tdboot_render_plugin_menu_child($child);
119 }
120 return '<li class="td-side-submenu">'
121 . '<a href="javascript:;" onclick="this.parentElement.classList.toggle(\'open\')">'
122 . '<i class="mdi ' . $icon . '"></i><span>' . $title . '</span>'
123 . '<i class="td-arrow mdi mdi-chevron-down"></i></a>'
124 . '<ul class="td-side-subnav">' . $childrenHtml . '</ul></li>';
125 }
126
127 return _tdboot_render_plugin_leaf_item($it);
128}
129
130/**
131 * 渲染插件叶子菜单项
132 * 为 plugin.php 链接生成 data-td-route 属性,由 SPA 拦截并在 layout 内通过 iframe 加载
133 */
134function _tdboot_render_plugin_leaf_item($it)
135{
136 $title = htmlspecialchars($it['title'] ?? '', ENT_QUOTES, 'UTF-8');
137 $icon = !empty($it['icon']) ? htmlspecialchars($it['icon'], ENT_QUOTES, 'UTF-8') : 'mdi-puzzle';
138 $url = $it['url'] ?? 'javascript:void(0)';
139 $urlH = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
140
141 $routeAttr = '';
142 if (strpos($url, 'plugin.php') !== false) {
143 // 解析 query,生成 SPA 路由路径
144 $query = parse_url($url, PHP_URL_QUERY) ?? '';
145 $routeAttr = ' data-td-route="/plugin?' . $query . '"';
146 }
147
148 return '<li class="td-side-leaf">'
149 . '<a href="' . $urlH . '"' . $routeAttr . '>'
150 . '<i class="mdi ' . $icon . '"></i><span>' . $title . '</span></a></li>';
151}
152?>
153<!DOCTYPE html>
154<html lang="zh-CN">
155<head>
156<meta charset="UTF-8" />
157<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
158<title><?= htmlspecialchars(($title ?? '控制面板') . ' · ' . ($conf['name'] ?? 'MNBT'), ENT_QUOTES, 'UTF-8') ?></title>
159<link rel="icon" href="<?= htmlspecialchars(mnbt_asset_url('images/logo-ico.png'), ENT_QUOTES, 'UTF-8') ?>" type="image/ico" />
160<link rel="stylesheet" href="<?= htmlspecialchars(mnbt_asset_url('css/materialdesignicons.min.css'), ENT_QUOTES, 'UTF-8') ?>" />
161<?php if (is_file($td_css)): ?>
162<link rel="stylesheet" href="<?= htmlspecialchars(mnbt_theme_url('dist/assets/index.css', 'user'), ENT_QUOTES, 'UTF-8') ?>?v=<?= $td_ver ?>" />
163<?php endif; ?>
164<style>
165 html, body, #app { margin: 0; padding: 0; height: 100%; background: #f2f3f5; }
166 .td-boot-missing { max-width: 540px; margin: 12vh auto; padding: 32px; border-radius: 12px; background: #fff; border: 1px solid #e7e7e7; font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; color: #1a2e28; }
167 .td-boot-missing h2 { margin: 0 0 12px; font-size: 18px; color: #d54941; }
168 .td-boot-missing code { background: #f3f3f3; padding: 2px 8px; border-radius: 4px; font-size: 13px; }
169 .td-boot-missing p { margin: 10px 0; line-height: 1.7; font-size: 14px; }
170</style>
171</head>
172<body>
173<div id="app">
174<?php if (!is_file($td_js)): ?>
175 <div class="td-boot-missing">
176 <h2>TDesign 用户端尚未构建</h2>
177 <p>请在服务器或本机执行:</p>
178 <p><code>cd templates/tdesign/spa && npm install && npm run build:user</code></p>
179 <p>构建产物应位于 <code>templates/tdesign/user/dist/</code></p>
180 </div>
181<?php endif; ?>
182</div>
183<script>
184window.__TD_BOOT__ = <?= json_encode($boot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
185<?php if (!empty($td_hash)): ?>
186if (window.__TD_BOOT__.hash) {
187 if (!location.hash || location.hash === '#' || location.hash === '#/') {
188 location.hash = window.__TD_BOOT__.hash;
189 }
190}
191<?php endif; ?>
192</script>
193<?php if (is_file($td_js)): ?>
194<script type="module" src="<?= htmlspecialchars(mnbt_theme_url('dist/assets/index.js', 'user'), ENT_QUOTES, 'UTF-8') ?>?v=<?= $td_ver ?>"></script>
195<?php endif; ?>
196</body>
197</html>