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 'adminUser' => $conf['user'] ?? 'admin',
19 'loggedIn' => isset($islogin) && (int)$islogin === 1,
20 'needCaptcha' => isset($conf['yzm']) && $conf['yzm'] === 'true',
21 'ajaxBase' => './ajax.php',
22 'codeUrl' => './code.php',
23 'logo' => mnbt_asset_url('admin_logo/logo.login.png'),
24 'logoHead' => mnbt_asset_url('admin_logo/logo.head.png'),
25 'logoIndex' => mnbt_asset_url('admin_logo/logo.index.png'),
26 'auther' => $conf['auther'] ?? '',
27 'theme' => 'tdesign',
28 'version' => '0.3.0',
29 'entry' => $td_entry ?? 'dashboard',
30 'hash' => $td_hash ?? '',
31 'pluginMenuHtml' => '',
32 'conf' => $conf ?? [],
33 'serverHost' => $_SERVER['HTTP_HOST'] ?? '',
34 'serverProto' => (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ($_SERVER['SERVER_PORT'] ?? '') == '443' ? 'https://' : 'http://',
35 // 主题列表(给设置页主题切换使用)
36 // mnbt_theme_list 返回关联数组,json_encode 后变成对象;SPA 需要数组,所以用 array_values 重建索引
37 'themeList' => function_exists('mnbt_theme_list') ? array_values(mnbt_theme_list(null)) : [],
38 'curUserTheme' => function_exists('mnbt_theme_name') ? mnbt_theme_name('user') : '',
39 'curAdminTheme'=> function_exists('mnbt_theme_name') ? mnbt_theme_name('admin') : '',
40 'curDockerTheme'=> function_exists('mnbt_theme_name') ? mnbt_theme_name('docker') : '',
41 'curHomeTheme' => function_exists('mnbt_theme_name') ? mnbt_theme_name('home') : '',
42 'homeThemeSettingsFields' => function_exists('mnbt_home_settings_view_data') ? mnbt_home_settings_view_data() : [],
43 // 支付插件信息(给支付设置页使用)
44 // mnbt_get_payment_plugins 返回关联数组,json_encode 后变成对象;SPA 需要数组,并补充 plugin_id 字段
45 'paymentPlugins' => function_exists('mnbt_get_payment_plugins') ? array_values(array_map(function ($config, $pluginId) {
46 $config['plugin_id'] = $pluginId;
47 return $config;
48 }, mnbt_get_payment_plugins(), array_keys(mnbt_get_payment_plugins()))) : [],
49 'enabledPayments' => function_exists('mnbt_get_enabled_payment_methods') ? mnbt_get_enabled_payment_methods() : [],
50 'pluginSettingsTabs' => function_exists('mnbt_plugin_settings_tabs') ? array_map(function ($tab) {
51 if (empty($tab['icon']) && !empty($tab['plugin'])) {
52 $meta = $GLOBALS['mnbt_plugin_meta'][$tab['plugin']] ?? [];
53 $tab['icon'] = !empty($meta['icon']) ? $meta['icon'] : 'mdi-cog-outline';
54 }
55 return $tab;
56 }, mnbt_plugin_settings_tabs()) : [],
57];
58
59// 视图可在 include 前设置 $td_inject(数组),把页面级数据注入 boot
60if (isset($td_inject) && is_array($td_inject)) {
61 foreach ($td_inject as $k => $v) {
62 $boot[$k] = $v;
63 }
64}
65
66// 插件菜单 HTML 直接在 boot 中构造,避免依赖服务器上可能未更新的 theme.php 渲染器
67$boot['pluginMenuHtml'] = _tdboot_render_plugin_menu_html(function_exists('mnbt_plugin_menus') ? mnbt_plugin_menus('admin') : []);
68
69/**
70 * 渲染插件菜单区 HTML
71 * 结构与默认主题一致:
72 * - 插件列表(独立项)
73 * - 有 children 的插件分组(独立 submenu)
74 * - 无 children 的插件菜单归入"插件管理"分组
75 */
76function _tdboot_render_plugin_menu_html($items)
77{
78 $html = '<li class="td-side-item">'
79 . '<a href="javascript:;" data-td-route="/plugin">'
80 . '<i class="mdi mdi-puzzle-outline"></i><span>插件列表</span></a></li>';
81
82 if (empty($items)) {
83 return $html;
84 }
85
86 $groups = [];
87 $leafs = [];
88 foreach ($items as $it) {
89 if (!empty($it['children'])) {
90 $groups[] = $it;
91 } else {
92 $leafs[] = $it;
93 }
94 }
95
96 foreach ($groups as $g) {
97 $html .= _tdboot_render_plugin_group($g);
98 }
99
100 if (!empty($leafs)) {
101 $html .= '<li class="td-side-submenu">'
102 . '<a href="javascript:;" onclick="this.parentElement.classList.toggle(\'open\')">'
103 . '<i class="mdi mdi-puzzle"></i><span>插件管理</span>'
104 . '<i class="td-arrow mdi mdi-chevron-down"></i></a>'
105 . '<ul class="td-side-subnav">' . _tdboot_render_plugin_leafs($leafs) . '</ul></li>';
106 }
107
108 return $html;
109}
110
111function _tdboot_render_plugin_group($it)
112{
113 $title = htmlspecialchars($it['title'] ?? '', ENT_QUOTES, 'UTF-8');
114 $icon = !empty($it['icon']) ? htmlspecialchars($it['icon'], ENT_QUOTES, 'UTF-8') : 'mdi-puzzle';
115
116 $childrenHtml = '';
117 foreach ($it['children'] as $child) {
118 $childrenHtml .= _tdboot_render_plugin_menu_child($child);
119 }
120
121 return '<li class="td-side-submenu">'
122 . '<a href="javascript:;" onclick="this.parentElement.classList.toggle(\'open\')">'
123 . '<i class="mdi ' . $icon . '"></i><span>' . $title . '</span>'
124 . '<i class="td-arrow mdi mdi-chevron-down"></i></a>'
125 . '<ul class="td-side-subnav">' . $childrenHtml . '</ul></li>';
126}
127
128function _tdboot_render_plugin_menu_child($it)
129{
130 $title = htmlspecialchars($it['title'] ?? '', ENT_QUOTES, 'UTF-8');
131 $icon = !empty($it['icon']) ? htmlspecialchars($it['icon'], ENT_QUOTES, 'UTF-8') : 'mdi-puzzle';
132
133 if (!empty($it['children'])) {
134 $childrenHtml = '';
135 foreach ($it['children'] as $child) {
136 $childrenHtml .= _tdboot_render_plugin_menu_child($child);
137 }
138 return '<li class="td-side-submenu">'
139 . '<a href="javascript:;" onclick="this.parentElement.classList.toggle(\'open\')">'
140 . '<i class="mdi ' . $icon . '"></i><span>' . $title . '</span>'
141 . '<i class="td-arrow mdi mdi-chevron-down"></i></a>'
142 . '<ul class="td-side-subnav">' . $childrenHtml . '</ul></li>';
143 }
144
145 $url = htmlspecialchars($it['url'] ?? 'javascript:void(0)', ENT_QUOTES, 'UTF-8');
146 return '<li class="td-side-leaf">'
147 . '<a href="' . $url . '">'
148 . '<i class="mdi ' . $icon . '"></i><span>' . $title . '</span></a></li>';
149}
150
151function _tdboot_render_plugin_leafs($leafs)
152{
153 $html = '';
154 foreach ($leafs as $it) {
155 $html .= _tdboot_render_plugin_menu_child($it);
156 }
157 return $html;
158}
159?>
160<!DOCTYPE html>
161<html lang="zh-CN">
162<head>
163<meta charset="UTF-8" />
164<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
165<title><?= htmlspecialchars(($title ?? '管理后台') . ' · ' . ($conf['name'] ?? 'MNBT'), ENT_QUOTES, 'UTF-8') ?></title>
166<link rel="icon" href="<?= htmlspecialchars(mnbt_asset_url('images/logo-ico.png'), ENT_QUOTES, 'UTF-8') ?>" type="image/ico" />
167<link rel="stylesheet" href="<?= htmlspecialchars(mnbt_asset_url('css/materialdesignicons.min.css'), ENT_QUOTES, 'UTF-8') ?>" />
168<?php if (is_file($td_css)): ?>
169<link rel="stylesheet" href="<?= htmlspecialchars(mnbt_theme_url('dist/assets/index.css', 'admin'), ENT_QUOTES, 'UTF-8') ?>?v=<?= $td_ver ?>" />
170<?php endif; ?>
171<style>
172 html, body, #app { margin: 0; padding: 0; height: 100%; background: #f2f3f5; }
173 .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; }
174 .td-boot-missing h2 { margin: 0 0 12px; font-size: 18px; color: #d54941; }
175 .td-boot-missing code { background: #f3f3f3; padding: 2px 8px; border-radius: 4px; font-size: 13px; }
176 .td-boot-missing p { margin: 10px 0; line-height: 1.7; font-size: 14px; }
177</style>
178</head>
179<body>
180<div id="app">
181<?php if (!is_file($td_js)): ?>
182 <div class="td-boot-missing">
183 <h2>TDesign 管理后台尚未构建</h2>
184 <p>请在服务器或本机执行:</p>
185 <p><code>cd templates/tdesign/spa && npm install && npm run build:admin</code></p>
186 <p>构建产物应位于 <code>templates/tdesign/admin/dist/</code></p>
187 </div>
188<?php endif; ?>
189</div>
190<script>
191window.__TD_BOOT__ = <?= json_encode($boot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
192<?php if (!empty($td_hash)): ?>
193if (window.__TD_BOOT__.hash) {
194 if (!location.hash || location.hash === '#' || location.hash === '#/') {
195 location.hash = window.__TD_BOOT__.hash;
196 }
197}
198<?php endif; ?>
199</script>
200<?php if (is_file($td_js)): ?>
201<script type="module" src="<?= htmlspecialchars(mnbt_theme_url('dist/assets/index.js', 'admin'), ENT_QUOTES, 'UTF-8') ?>?v=<?= $td_ver ?>"></script>
202<?php endif; ?>
203</body>
204</html>