仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ app_plugins/shop_frontend/bootstrap.php 11.9 KB · 314 行 原始文件
Z zfhsh first commit 1 天前
1<?php
2/**
3 * 售卖前端 - 统一商店前端皮肤
4 *
5 * 1) 接管站点首页 /,渲染品牌落地页(原功能)
6 * 2) 通过"低优先级通用路由"接管 user_info / balance / hosting_shop 的用户端 GET 页面,
7 * 提供统一的现代化前端布局。业务逻辑(登录鉴权、下单、充值、支付回调)仍由原插件处理。
8 *
9 * 说明:
10 * - 页面接管路由 priority = 5(低于原插件默认 10),保证先于原插件匹配并渲染;
11 * - 仅在目标插件启用时注册对应路由;
12 * - POST API(/account/api/*、/shop/api/*、/balance/api/*)与支付回调 /pay/* 不接管。
13 */
14
15if (!defined('IN_CRONLITE')) {
16 exit;
17}
18
19require_once __DIR__ . '/lib/shop_frontend.php';
20
21mnbt_plugin_register('shop_frontend', [
22 'name' => '售卖前端',
23 'description' => '统一商店前端:接管首页落地页与 user_info / balance / hosting_shop 的全部用户端页面,提供一致的现代化皮肤',
24]);
25
26/* ============================================================
27 * 1) 首页接管
28 * ============================================================ */
29mnbt_register_home(function ($ctx) {
30 shop_frontend_render_homepage();
31 return true;
32}, 100);
33
34function shop_frontend_render_homepage()
35{
36 $title = shop_frontend_option('site_title', 'MNBT 主机售卖');
37 $logo = shop_frontend_option('site_logo', '');
38 $primary = shop_frontend_option('site_primary', '#4f46e5');
39 $accent = shop_frontend_option('site_accent', '#FF5722');
40 $hero = shop_frontend_option('site_hero', '高性能虚拟主机,即买即用');
41 $footer = shop_frontend_option('site_footer', '© ' . date('Y') . ' MNBT. All rights reserved.');
42 $favicon = shop_frontend_favicon();
43
44 $plans = shop_frontend_get_plans();
45 $user = shop_frontend_get_current_user();
46
47 // 共用顶栏导航所需的变量
48 $brand = $title;
49 $brand_logo = $logo;
50 $current_user = $user;
51 $active = 'home';
52 $has_balance = function_exists('balance_get');
53 $has_hosting = function_exists('hosting_plan_list_active');
54
55 // URL 构建 — 与 user_info / balance / hosting_shop 插件完全一致的格式
56 $url = function (string $path) {
57 return shop_frontend_url($path);
58 };
59
60 $features = [
61 ['icon' => 'mdi-shield-check', 'title' => '99.9% 在线率', 'desc' => '企业级硬件架构,稳定可靠,SLA 保障'],
62 ['icon' => 'mdi-flash', 'title' => '极速部署', 'desc' => '支付成功后自动开通,即买即用,无需等待'],
63 ['icon' => 'mdi-headset', 'title' => '专业支持', 'desc' => '技术团队 7×24 小时响应,工单优先处理'],
64 ['icon' => 'mdi-server', 'title' => '高性能节点', 'desc' => '全 SSD 存储,BGP 多线接入,低延迟体验'],
65 ['icon' => 'mdi-backup-restore','title' => '自动备份', 'desc' => '每日自动备份数据,灾备无忧'],
66 ['icon' => 'mdi-lock', 'title' => '安全防护', 'desc' => 'DDoS 清洗 + WAF 规则,网站安全有保障'],
67 ];
68
69 $planCards = [];
70 foreach ($plans as $p) {
71 $minPrice = 0;
72 if ($p['price_month_cents'] > 0) {
73 $minPrice = $p['price_month_cents'] / 100;
74 }
75 if ($p['price_year_cents'] > 0) {
76 $yearPrice = $p['price_year_cents'] / 100;
77 if ($minPrice == 0 || $yearPrice / 12 < $minPrice) {
78 $minPrice = $yearPrice / 12;
79 }
80 }
81 $priceStr = $minPrice > 0 ? '¥' . number_format($minPrice, 2) . ' 起/月' : '免费';
82 $feats = [];
83 if (!empty($p['spec_web'])) $feats[] = "网页空间 " . $p['spec_web'] . " MB";
84 if (!empty($p['spec_sql'])) $feats[] = "数据库 " . $p['spec_sql'] . " MB";
85 if (!empty($p['spec_flow'])) $feats[] = "月流量 " . $p['spec_flow'] . " GB";
86 if (!empty($p['spec_domain'])) $feats[] = "可绑定 " . $p['spec_domain'] . " 个域名";
87 $planCards[] = [
88 'id' => $p['id'],
89 'name' => $p['name'],
90 'desc' => $p['description'] ?? '',
91 'price' => $priceStr,
92 'feats' => $feats,
93 ];
94 }
95
96 include __DIR__ . '/views/homepage.php';
97 exit;
98}
99
100/* ============================================================
101 * 2) 管理员页面与设置
102 * ============================================================ */
103mnbt_register_page('admin', 'settings', 'views/admin/settings.php', '售卖前端设置');
104
105mnbt_register_menu('admin', [
106 'title' => '售卖前端',
107 'icon' => 'mdi-store',
108 'order' => 59,
109 'children' => [
110 ['title' => '前端设置', 'page' => 'settings', 'icon' => 'mdi-cog', 'multitabs' => true],
111 ],
112]);
113
114mnbt_register_ajax('admin', 'shop_frontend_save_settings', function () {
115 mnbt_plugin_require_admin();
116 $fields = ['site_title', 'site_logo', 'site_primary', 'site_accent', 'site_hero', 'site_footer', 'site_favicon'];
117 foreach ($fields as $f) {
118 mnbt_plugin_option_set('shop_frontend', $f, trim((string)($_POST[$f] ?? '')));
119 }
120 echo json_encode(['code' => '保存成功'], JSON_UNESCAPED_UNICODE);
121 exit;
122});
123
124/* 站点 Logo / Favicon 上传(仅支持 ICO) */
125mnbt_register_ajax('admin', 'shop_frontend_upload_icon', function () {
126 mnbt_plugin_require_admin();
127
128 $target = ($_POST['target'] ?? '') === 'favicon' ? 'favicon' : 'logo';
129 $optionKey = $target === 'logo' ? 'site_logo' : 'site_favicon';
130
131 if (empty($_FILES['icon']) || !is_array($_FILES['icon']) || (int)($_FILES['icon']['error'] ?? 1) !== 0) {
132 json_exit_error('未收到文件或上传失败');
133 }
134 $file = $_FILES['icon'];
135 $name = (string)($file['name'] ?? '');
136 if (strtolower(pathinfo($name, PATHINFO_EXTENSION)) !== 'ico') {
137 json_exit_error('仅支持 ICO 格式');
138 }
139 $size = (int)($file['size'] ?? 0);
140 if ($size <= 0 || $size > 2 * 1024 * 1024) {
141 json_exit_error('ICO 文件大小需在 2MB 以内');
142 }
143 $tmp = (string)($file['tmp_name'] ?? '');
144 if ($tmp === '' || !is_uploaded_file($tmp)) {
145 json_exit_error('文件上传异常');
146 }
147 // 校验 ICO 魔数:前 4 字节为 00 00 01 00
148 $head = @file_get_contents($tmp, false, null, 0, 4);
149 if ($head === false || $head !== "\x00\x00\x01\x00") {
150 json_exit_error('文件不是有效的 ICO 图片');
151 }
152
153 $dir = mnbt_plugin_path('shop_frontend') . 'assets/';
154 if (!is_dir($dir) && !@mkdir($dir, 0755, true)) {
155 json_exit_error('插件资源目录不可写');
156 }
157 if (!move_uploaded_file($tmp, $dir . $target . '.ico')) {
158 json_exit_error('保存文件失败,请检查插件目录权限');
159 }
160
161 $url = mnbt_plugin_url('shop_frontend', 'assets/' . $target . '.ico');
162 mnbt_plugin_option_set('shop_frontend', $optionKey, $url);
163 json_exit_success('上传成功', ['url' => $url]);
164});
165
166/* ============================================================
167 * 3) 页面接管(priority 5 < 原插件默认 10)
168 * ============================================================ */
169
170$sf_priority = 5;
171
172/* ---- user_info:/account/* ---- */
173if (mnbt_plugin_enabled('user_info')) {
174
175 mnbt_register_route('GET', '/account/login', function ($params, $ctx) {
176 $user = shop_frontend_get_current_user();
177 if ($user) {
178 header('Location: ' . shop_frontend_url('account/profile'));
179 exit;
180 }
181 shop_frontend_render('auth_login', ['page_title' => '登录', 'active' => 'login']);
182 }, $sf_priority);
183
184 mnbt_register_route('GET', '/account/register', function ($params, $ctx) {
185 $user = shop_frontend_get_current_user();
186 if ($user) {
187 header('Location: ' . shop_frontend_url('account/profile'));
188 exit;
189 }
190 shop_frontend_render('auth_register', ['page_title' => '注册', 'active' => 'register']);
191 }, $sf_priority);
192
193 mnbt_register_route('GET', '/account/profile', function ($params, $ctx) {
194 $user = shop_frontend_get_current_user();
195 if (!$user) {
196 header('Location: ' . shop_frontend_url('account/login'));
197 exit;
198 }
199 shop_frontend_render('auth_profile', ['page_title' => '个人信息', 'active' => 'profile']);
200 }, $sf_priority);
201
202 mnbt_register_route('GET', '/account/password', function ($params, $ctx) {
203 $user = shop_frontend_get_current_user();
204 if (!$user) {
205 header('Location: ' . shop_frontend_url('account/login'));
206 exit;
207 }
208 shop_frontend_render('auth_password', ['page_title' => '修改密码', 'active' => 'password']);
209 }, $sf_priority);
210}
211
212/* ---- balance:/balance/* ---- */
213if (mnbt_plugin_enabled('balance')) {
214
215 mnbt_register_route('GET', '/balance', function ($params, $ctx) {
216 if (!function_exists('balance_require_user') || !function_exists('balance_get') || !function_exists('balance_logs')) {
217 return false;
218 }
219 $user = balance_require_user();
220 $balance = balance_get((int)$user['id']);
221 $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
222 $logs = balance_logs((int)$user['id'], $page, 15);
223 shop_frontend_render('balance', [
224 'page_title' => '我的余额',
225 'active' => 'balance',
226 'balance_cents' => $balance,
227 'logs' => $logs,
228 ]);
229 }, $sf_priority);
230
231 mnbt_register_route('GET', '/balance/recharge', function ($params, $ctx) {
232 if (!function_exists('balance_require_user')) return false;
233 $user = balance_require_user();
234 $methods = [];
235 if (function_exists('mnbt_get_enabled_payment_methods')) {
236 foreach (mnbt_get_enabled_payment_methods() as $m) {
237 if (($m['plugin'] ?? '') === 'balance') continue; // 充值不能用余额支付
238 $methods[] = $m;
239 }
240 }
241 shop_frontend_render('balance_recharge', [
242 'page_title' => '余额充值',
243 'active' => 'recharge',
244 'methods' => $methods,
245 ]);
246 }, $sf_priority);
247}
248
249/* ---- hosting_shop:/shop/* ---- */
250if (mnbt_plugin_enabled('hosting_shop')) {
251
252 mnbt_register_route('GET', '/shop', function ($params, $ctx) {
253 if (!function_exists('hosting_require_user') || !function_exists('hosting_plan_list_active')) {
254 return false;
255 }
256 hosting_require_user();
257 $plans = hosting_plan_list_active();
258 shop_frontend_render('shop', ['page_title' => '主机套餐', 'active' => 'shop', 'plans' => $plans]);
259 }, $sf_priority);
260
261 mnbt_register_route('GET', '/shop/order/{plan_id}', function ($params, $ctx) {
262 if (!function_exists('hosting_require_user') || !function_exists('hosting_plan_get')) {
263 return false;
264 }
265 hosting_require_user();
266 $plan_id = (int)($params['plan_id'] ?? 0);
267 $plan = hosting_plan_get($plan_id);
268 if (!$plan || $plan['status'] !== 'active') {
269 http_response_code(404);
270 echo '套餐不存在或已下架';
271 return;
272 }
273 $methods = function_exists('mnbt_get_enabled_payment_methods') ? mnbt_get_enabled_payment_methods() : [];
274 shop_frontend_render('shop_order', [
275 'page_title' => '购买:' . $plan['name'],
276 'active' => 'shop',
277 'plan' => $plan,
278 'methods' => $methods,
279 ]);
280 }, $sf_priority);
281
282 mnbt_register_route('GET', '/shop/assets', function ($params, $ctx) {
283 if (!function_exists('hosting_require_user') || !function_exists('hosting_asset_list_by_user')) {
284 return false;
285 }
286 $user = hosting_require_user();
287 $assets = hosting_asset_list_by_user((int)$user['id']);
288 shop_frontend_render('shop_assets', ['page_title' => '我的主机', 'active' => 'assets', 'assets' => $assets]);
289 }, $sf_priority);
290
291 mnbt_register_route('GET', '/shop/orders', function ($params, $ctx) {
292 if (!function_exists('hosting_require_user') || !function_exists('hosting_order_list_by_user')) {
293 return false;
294 }
295 $user = hosting_require_user();
296 $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
297 $orders = hosting_order_list_by_user((int)$user['id'], $page, 15);
298 shop_frontend_render('shop_orders', ['page_title' => '我的订单', 'active' => 'orders', 'orders' => $orders]);
299 }, $sf_priority);
300}
301
302/* ============================================================
303 * 4) 支付同步返回接管
304 * epay / alipay_official 的同步返回默认跳 $base.'/user'(核心登录),
305 * 用户走 user_info 账户体系时会被踢到核心「主机登录页」;
306 * 且若返回路径无任何路由匹配,index.php 也会兜底跳到 /user。
307 * 这里用低优先级通用路由接管所有 /pay/{plugin}/return,
308 * 支付后回到统一前端(按订单类型跳订单列表或余额页)。
309 * 订单最终状态仍由异步通知 /pay/{slug}/notify 决定。
310 * ============================================================ */
311mnbt_register_route('GET', '/pay/{plugin}/return', function ($params, $ctx) {
312 @header('Location: ' . shop_frontend_pay_return_url());
313 exit;
314}, $sf_priority);