仰望星辰工作室

better-staridc-MNBT

Z zfhsh first commit 2 天前
1<?php
2/**
3 * domain_shop - 用户端域名绑定(自包含页面,不依赖主题外壳)
4 *
5 * 功能:
6 * - 查看已绑定域名列表(含端口/目录)
7 * - 添加自定义域名 / 本站二级域名(免费直接添加,付费弹窗购买)
8 * - 修改域名前缀、删除域名
9 *
10 * AJAX 端点(由 user/api/bind.php 注册,POST 到 ajax.php):
11 * urllist / erurl / p_domain_tjurl / p_domain_seturl / p_domain_scurl
12 * 购买下单:POST 到 /domain/buy 路由(mnbt_register_route)
13 */
14if (!defined('IN_CRONLITE')) exit;
15
16global $yhc, $ssbt, $DB;
17$cert = $DB->get_row_prepare("SELECT * FROM MN_bt WHERE btdh=? limit 1", [$ssbt]);
18if (!is_array($cert)) $cert = [];
19
20// 可购二级域名价格来自 erurl(前端拉取);支付方式在后端渲染
21$pay_methods = function_exists('mnbt_get_enabled_payment_methods') ? mnbt_get_enabled_payment_methods() : [];
22
23// 站点根 index.php(购买表单 action,避免相对路径落到 /user/index.php)
24$script = isset($_SERVER['SCRIPT_NAME']) ? str_replace('\\', '/', $_SERVER['SCRIPT_NAME']) : '';
25$buyRoot = rtrim(dirname(dirname($script)), '/');
26if ($buyRoot === '.' || $buyRoot === '' || $buyRoot === '/') $buyRoot = '';
27$buyUrl = $buyRoot . '/index.php?_r=/domain/buy';
28
29$hint = '';
30if (!empty($cert)) {
31 $hint = ($cert['als'] ?? 'false') === 'false'
32 ? '请将域名 A 记录指向:' . htmlspecialchars((string)($cert['btip'] ?? ''), ENT_QUOTES, 'UTF-8')
33 : htmlspecialchars((string)($cert['als'] ?? ''), ENT_QUOTES, 'UTF-8');
34}
35?>
36<!DOCTYPE html>
37<html lang="zh-CN">
38<head>
39<meta charset="UTF-8">
40<meta name="viewport" content="width=device-width, initial-scale=1.0">
41<title>域名绑定 - 域名服务</title>
42<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css">
43<style>
44:root {
45 --brand: #4f46e5;
46 --brand-dark: #4338ca;
47 --brand-soft: #eef2ff;
48 --bg: #f4f6fb;
49 --card: #fff;
50 --border: #e6e8f0;
51 --text: #1f2937;
52 --text-2: #6b7280;
53 --text-3: #9ca3af;
54 --danger: #dc2626;
55 --success: #16a34a;
56 --radius: 12px;
57}
58* { box-sizing: border-box; }
59body {
60 margin: 0;
61 background: var(--bg);
62 color: var(--text);
63 font: 14px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
64 -webkit-font-smoothing: antialiased;
65 padding: 24px 16px 48px;
66}
67.ds-wrap { width: 100%; max-width: 920px; margin: 0 auto; }
68.ds-head { margin-bottom: 20px; }
69.ds-head h1 { margin: 0; font-size: 22px; }
70.ds-head p { margin: 6px 0 0; color: var(--text-2); }
71
72.ds-card {
73 background: var(--card);
74 border: 1px solid var(--border);
75 border-radius: var(--radius);
76 box-shadow: 0 1px 2px rgba(16,24,40,.04), 0 8px 24px -12px rgba(16,24,40,.12);
77 margin-bottom: 20px;
78}
79.ds-card-head {
80 padding: 14px 20px;
81 border-bottom: 1px solid var(--border);
82 font-weight: 600;
83 font-size: 15px;
84 display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
85}
86.ds-card-head small { color: var(--text-3); font-weight: 400; }
87.ds-card-body { padding: 20px; }
88
89/* 提示 */
90.ds-alert {
91 padding: 10px 14px;
92 border-radius: 10px;
93 font-size: 13px;
94 background: var(--brand-soft);
95 color: var(--brand);
96 border: 1px solid #dbe1ff;
97 margin-bottom: 16px;
98}
99
100/* 表格 */
101.ds-table-wrap { overflow-x: auto; }
102table.ds-table { width: 100%; border-collapse: collapse; }
103.ds-table th, .ds-table td { padding: 11px 14px; text-align: left; white-space: nowrap; }
104.ds-table thead th {
105 background: #fafbfe;
106 color: var(--text-2);
107 font-size: 12px;
108 font-weight: 600;
109 border-bottom: 1px solid var(--border);
110}
111.ds-table tbody td { border-bottom: 1px solid #f1f2f7; font-size: 13.5px; }
112.ds-table tbody tr:hover { background: #fafbff; }
113.ds-table tbody tr:last-child td { border-bottom: none; }
114.ds-empty { text-align: center; padding: 40px 16px; color: var(--text-3); }
115.ds-mono { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12.5px; }
116.ds-domain a { color: var(--brand); font-weight: 600; }
117
118/* 按钮 */
119.ds-btn {
120 display: inline-flex; align-items: center; justify-content: center; gap: 6px;
121 padding: 9px 18px; border: 1px solid transparent; border-radius: 10px;
122 font-size: 14px; font-weight: 600; cursor: pointer; transition: all .15s;
123 background: var(--brand-soft); color: var(--brand); text-decoration: none;
124}
125.ds-btn:hover { background: #e0e7ff; }
126.ds-btn-primary { background: var(--brand); color: #fff; box-shadow: 0 4px 12px -3px rgba(79,70,229,.55); }
127.ds-btn-primary:hover { background: var(--brand-dark); color: #fff; }
128.ds-btn-ghost { background: #fff; border-color: var(--border); color: var(--text-2); }
129.ds-btn-ghost:hover { border-color: var(--brand); color: var(--brand); }
130.ds-btn-sm { padding: 5px 12px; font-size: 12.5px; border-radius: 8px; }
131.ds-btn-block { width: 100%; }
132.ds-btn:disabled { opacity: .6; cursor: not-allowed; }
133
134/* 表单 */
135.ds-field { margin-bottom: 14px; }
136.ds-field label { display: block; margin-bottom: 6px; font-size: 13px; color: var(--text-2); font-weight: 500; }
137.ds-field input[type="text"],
138.ds-field input[type="password"],
139.ds-field select {
140 width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: 10px;
141 font-size: 14px; color: var(--text); background: #fafbfe; outline: none; transition: all .15s;
142}
143.ds-field input:focus, .ds-field select:focus { border-color: var(--brand); background: #fff; box-shadow: 0 0 0 3px rgba(79,70,229,.12); }
144.ds-hint { font-size: 12.5px; color: var(--text-3); margin-top: 6px; }
145.ds-price { color: var(--brand); font-weight: 700; }
146
147/* 分段选择 */
148.ds-tabs { display: flex; gap: 8px; margin-bottom: 16px; }
149.ds-tab {
150 padding: 8px 16px; border: 1px solid var(--border); border-radius: 9px;
151 background: #fff; color: var(--text-2); font-size: 13px; font-weight: 500; cursor: pointer; transition: all .15s;
152}
153.ds-tab:hover { border-color: var(--brand); color: var(--brand); }
154.ds-tab.active { border-color: var(--brand); background: var(--brand-soft); color: var(--brand); font-weight: 600; }
155
156/* 弹窗 */
157.ds-mask {
158 position: fixed; inset: 0; z-index: 50;
159 background: rgba(15,23,42,.45);
160 display: none; align-items: center; justify-content: center; padding: 16px;
161}
162.ds-mask.show { display: flex; }
163.ds-modal {
164 width: 100%; max-width: 460px; max-height: 90vh; overflow-y: auto;
165 background: #fff; border-radius: 14px; box-shadow: 0 24px 60px -20px rgba(15,23,42,.4);
166}
167.ds-modal-head {
168 padding: 16px 20px; border-bottom: 1px solid var(--border);
169 font-weight: 600; font-size: 16px;
170 display: flex; align-items: center; justify-content: space-between;
171}
172.ds-modal-body { padding: 20px; }
173.ds-close { border: none; background: none; font-size: 22px; line-height: 1; cursor: pointer; color: var(--text-3); padding: 0 4px; }
174.ds-close:hover { color: var(--text); }
175.ds-buy-domain { margin: 0 0 6px; font-size: 18px; font-weight: 700; }
176.ds-buy-price { margin: 0 0 12px; color: var(--brand); font-weight: 700; font-size: 16px; }
177
178/* 支付方式 */
179.ds-choices { display: flex; flex-wrap: wrap; gap: 8px; }
180.ds-choice {
181 display: inline-flex; align-items: center; gap: 6px;
182 padding: 8px 14px; border: 1px solid var(--border); border-radius: 9px; background: #fff; cursor: pointer; font-size: 13.5px;
183}
184.ds-choice input { accent-color: var(--brand); }
185.ds-choice:has(input:checked), .ds-choice.active { border-color: var(--brand); background: var(--brand-soft); color: var(--brand); font-weight: 600; }
186
187/* 轻提示 */
188.ds-toast {
189 position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%);
190 z-index: 100; padding: 10px 18px; border-radius: 10px; font-size: 13.5px; color: #fff;
191 background: #1f2937; box-shadow: 0 8px 24px rgba(0,0,0,.2); opacity: 0; pointer-events: none; transition: opacity .2s;
192}
193.ds-toast.show { opacity: 1; }
194.ds-toast.ok { background: var(--success); }
195.ds-toast.err { background: var(--danger); }
196</style>
197</head>
198<body>
199<div class="ds-wrap">
200
201 <div class="ds-head">
202 <h1>域名绑定</h1>
203 <p>管理绑定到您主机的域名;如需购买本站二级域名,请选择「本站二级域名」模式。</p>
204 </div>
205
206 <?php if ($hint !== ''): ?>
207 <div class="ds-alert"><?= $hint ?></div>
208 <?php endif; ?>
209
210 <!-- 已绑定域名 -->
211 <div class="ds-card">
212 <div class="ds-card-head">
213 <span>已绑定域名</span>
214 <small>修改前缀 / 删除后立即生效</small>
215 </div>
216 <div class="ds-card-body ds-table-wrap">
217 <table class="ds-table">
218 <thead><tr><th>域名</th><th>端口</th><th>目录</th><th>操作</th></tr></thead>
219 <tbody id="ds-list"></tbody>
220 </table>
221 <div id="ds-empty" class="ds-empty">暂无绑定域名</div>
222 </div>
223 </div>
224
225 <!-- 添加域名 -->
226 <div class="ds-card">
227 <div class="ds-card-head">
228 <span>添加域名</span>
229 <small id="ds-add-desc">请输入要绑定的完整域名</small>
230 </div>
231 <div class="ds-card-body">
232 <div class="ds-tabs">
233 <button type="button" class="ds-tab active" data-mode="custom" id="ds-tab-custom">自定义域名</button>
234 <button type="button" class="ds-tab" data-mode="sub" id="ds-tab-sub">本站二级域名</button>
235 </div>
236
237 <form id="ds-add-form">
238 <div class="ds-field">
239 <label for="ds-add-input" id="ds-add-label">域名</label>
240 <input type="text" id="ds-add-input" autocomplete="off" placeholder="例如 www.example.com">
241 </div>
242
243 <div class="ds-field" id="ds-sub-wrap" style="display:none">
244 <label for="ds-sub-domain">选择二级域名</label>
245 <select id="ds-sub-domain"></select>
246 <div class="ds-hint" id="ds-sub-price"></div>
247 </div>
248
249 <div class="ds-field">
250 <label for="ds-dir">子目录</label>
251 <select id="ds-dir"></select>
252 <div class="ds-hint">如果无特殊需求则推荐默认;会自动显示主机目录</div>
253 </div>
254
255 <button type="submit" class="ds-btn ds-btn-primary" id="ds-add-btn">添加</button>
256 </form>
257 </div>
258 </div>
259</div>
260
261<!-- 修改前缀弹窗 -->
262<div class="ds-mask" id="ds-edit-mask">
263 <div class="ds-modal">
264 <div class="ds-modal-head">修改域名前缀 <button type="button" class="ds-close" data-close="ds-edit-mask">&times;</button></div>
265 <div class="ds-modal-body">
266 <div class="ds-field">
267 <label>新前缀(数字和字母,1~24 位)</label>
268 <input type="text" id="ds-edit-prefix" maxlength="24" autocomplete="off">
269 </div>
270 <div class="ds-field">
271 <label>子目录</label>
272 <select id="ds-edit-dir"></select>
273 </div>
274 <button type="button" class="ds-btn ds-btn-primary" id="ds-edit-confirm">确定修改</button>
275 </div>
276 </div>
277</div>
278
279<!-- 购买弹窗 -->
280<div class="ds-mask" id="ds-buy-mask">
281 <div class="ds-modal">
282 <div class="ds-modal-head">购置二级域名 <button type="button" class="ds-close" data-close="ds-buy-mask">&times;</button></div>
283 <div class="ds-modal-body">
284 <p class="ds-buy-domain" id="ds-buy-domain"></p>
285 <p class="ds-buy-price" id="ds-buy-price"></p>
286 <p class="ds-hint">支付完成后将自动添加该域名;若前缀已被占用,系统会随机化一个新前缀,您可以随时再次修改。</p>
287
288 <form id="ds-buy-form" method="post" action="<?= htmlspecialchars($buyUrl, ENT_QUOTES, 'UTF-8') ?>" target="_blank">
289 <input type="hidden" name="urla" id="ds-buy-urla">
290 <input type="hidden" name="urlb" id="ds-buy-urlb">
291 <input type="hidden" name="urlzml" id="ds-buy-urlzml" value="/">
292 <input type="hidden" name="pay_lx" value="ymgm">
293 <div class="ds-field">
294 <label>选择支付方式</label>
295 <?php if (!empty($pay_methods)): ?>
296 <div class="ds-choices">
297 <?php foreach ($pay_methods as $i => $m): ?>
298 <?php $type = $m['plugin'] . '__' . $m['method']; ?>
299 <label class="ds-choice">
300 <input type="radio" name="type" value="<?= htmlspecialchars($type, ENT_QUOTES, 'UTF-8') ?>"<?= $i === 0 ? ' checked' : '' ?>>
301 <i class="mdi <?= htmlspecialchars($m['icon'] ?? 'mdi-payment', ENT_QUOTES, 'UTF-8') ?>"></i>
302 <?= htmlspecialchars($m['display_name'] ?? ($m['plugin'] . ' / ' . $m['method']), ENT_QUOTES, 'UTF-8') ?>
303 </label>
304 <?php endforeach; ?>
305 </div>
306 <?php else: ?>
307 <p class="ds-hint">暂无可用的支付方式,请联系管理员。</p>
308 <?php endif; ?>
309 </div>
310 <button type="submit" class="ds-btn ds-btn-primary ds-btn-block"<?= empty($pay_methods) ? ' disabled' : '' ?>>确认支付</button>
311 </form>
312 </div>
313 </div>
314</div>
315
316<div class="ds-toast" id="ds-toast"></div>
317
318<script>
319(function () {
320 'use strict';
321
322 var listEl = document.getElementById('ds-list');
323 var emptyEl = document.getElementById('ds-empty');
324 var dirSelect = document.getElementById('ds-dir');
325 var addForm = document.getElementById('ds-add-form');
326 var addInput = document.getElementById('ds-add-input');
327 var addLabel = document.getElementById('ds-add-label');
328 var addDesc = document.getElementById('ds-add-desc');
329 var addBtn = document.getElementById('ds-add-btn');
330 var subWrap = document.getElementById('ds-sub-wrap');
331 var subDomainSelect = document.getElementById('ds-sub-domain');
332 var subPrice = document.getElementById('ds-sub-price');
333 var tabCustom = document.getElementById('ds-tab-custom');
334 var tabSub = document.getElementById('ds-tab-sub');
335
336 var dirs = ['/'];
337 var subDomains = []; // erurl: [{url, jg, jj}]
338 var selectedSub = null; // 当前选中的二级域名
339 var currentMode = 'custom';
340 var editing = null; // 正在修改的域名信息 {name, port, path, zym, prefix}
341
342 function esc(s) {
343 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
344 return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
345 });
346 }
347
348 var toastTimer = null;
349 function toast(msg, type) {
350 var el = document.getElementById('ds-toast');
351 el.textContent = msg;
352 el.className = 'ds-toast show' + (type === 'ok' ? ' ok' : type === 'err' ? ' err' : '');
353 clearTimeout(toastTimer);
354 toastTimer = setTimeout(function () { el.className = 'ds-toast'; }, 2600);
355 }
356
357 function post(data) {
358 var body = new URLSearchParams();
359 Object.keys(data).forEach(function (k) { body.append(k, data[k]); });
360 return fetch('ajax.php', {
361 method: 'POST',
362 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
363 body: body.toString()
364 }).then(function (r) { return r.json().catch(function () { return { code: '响应异常' }; }); });
365 }
366
367 function parseCode(res) {
368 if (res && typeof res === 'object' && typeof res.code === 'string') return res.code;
369 return '';
370 }
371
372 /* ---------- 列表 ---------- */
373 function loadList() {
374 return post({ gn: 'urllist' }).then(function (res) {
375 if (!res || typeof res !== 'object' || !Array.isArray(res.url)) {
376 renderList([]);
377 return;
378 }
379 dirs = Array.isArray(res.dir) ? res.dir : ['/'];
380 if (dirs.indexOf('/') === -1) dirs.unshift('/');
381 fillDirs(dirSelect, dirs);
382 renderList(res.url);
383 }).catch(function () { renderList([]); });
384 }
385
386 function renderList(urls) {
387 emptyEl.style.display = urls.length ? 'none' : 'block';
388 var html = '';
389 urls.forEach(function (u) {
390 html += '<tr>' +
391 '<td class="ds-domain"><a target="_blank" rel="noopener" href="http://' + esc(u.name) + ':' + esc(u.port) + '/">' + esc(u.name) + '</a></td>' +
392 '<td>' + esc(u.port) + '</td>' +
393 '<td>' + esc(u.path) + '</td>' +
394 '<td>' +
395 '<button type="button" class="ds-btn ds-btn-ghost ds-btn-sm" data-edit-name="' + esc(u.name) + '" data-edit-port="' + esc(u.port) + '" data-edit-path="' + esc(u.path) + '">修改前缀</button> ' +
396 '<button type="button" class="ds-btn ds-btn-ghost ds-btn-sm" data-del-name="' + esc(u.name) + '" data-del-port="' + esc(u.port) + '" data-del-path="' + esc(u.path) + '">删除</button>' +
397 '</td>' +
398 '</tr>';
399 });
400 listEl.innerHTML = html;
401 }
402
403 function fillDirs(select, list) {
404 var html = '';
405 list.forEach(function (d) {
406 html += '<option value="' + esc(d) + '">' + esc(d) + '</option>';
407 });
408 select.innerHTML = html || '<option value="/">/</option>';
409 }
410
411 /* ---------- 可购二级域名 ---------- */
412 function loadSubDomains() {
413 return post({ gn: 'erurl' }).then(function (res) {
414 subDomains = Array.isArray(res) ? res : [];
415 renderSubSelect();
416 }).catch(function () { subDomains = []; renderSubSelect(); });
417 }
418
419 function renderSubSelect() {
420 if (!subDomains.length) {
421 subDomainSelect.innerHTML = '<option value="">暂无可购二级域名</option>';
422 subPrice.textContent = '';
423 return;
424 }
425 var html = '<option value="">请选择</option>';
426 subDomains.forEach(function (s, i) {
427 html += '<option value="' + esc(s.url) + '" data-price="' + esc(s.jg) + '" data-desc="' + esc(s.jj) + '">' + esc(s.url) + '</option>';
428 });
429 subDomainSelect.innerHTML = html;
430 onSubChange();
431 }
432
433 function onSubChange() {
434 var opt = subDomainSelect.options[subDomainSelect.selectedIndex];
435 if (!opt || !opt.value) { selectedSub = null; subPrice.textContent = ''; return; }
436 var price = parseInt(opt.getAttribute('data-price') || '0', 10);
437 var desc = opt.getAttribute('data-desc') || '';
438 selectedSub = { url: opt.value, jg: price, jj: desc };
439 subPrice.innerHTML = '价格:<span class="ds-price">' + (price > 0 ? '¥' + price : '免费') + '</span>' + (desc ? ' ' + esc(desc) : '');
440 }
441
442 /* ---------- 模式切换 ---------- */
443 function setMode(mode) {
444 currentMode = mode;
445 tabCustom.classList.toggle('active', mode === 'custom');
446 tabSub.classList.toggle('active', mode === 'sub');
447 var isSub = mode === 'sub';
448 subWrap.style.display = isSub ? '' : 'none';
449 addLabel.textContent = isSub ? '前缀' : '域名';
450 addInput.placeholder = isSub ? '例如:www(数字和字母,1~24 位)' : '例如:www.example.com';
451 addDesc.textContent = isSub ? '选择本站提供的二级域名,付费域名将弹窗购买' : '请输入要绑定的完整域名';
452 addInput.value = '';
453 }
454
455 /* ---------- 添加 ---------- */
456 function addDomain(fullUrl) {
457 if (!fullUrl) { toast('请填写域名', 'err'); return; }
458 var dir = dirSelect.value || '/';
459 addBtn.disabled = true;
460 post({ gn: 'p_domain_tjurl', url: fullUrl, dirs: dir }).then(function (res) {
461 var code = parseCode(res);
462 if (code === '添加成功') { toast('添加成功', 'ok'); addInput.value = ''; loadList(); }
463 else toast(code || '添加失败', 'err');
464 }).catch(function () { toast('网络错误,请重试', 'err'); })
465 .then(function () { addBtn.disabled = false; });
466 }
467
468 addForm.addEventListener('submit', function (e) {
469 e.preventDefault();
470 if (currentMode === 'sub') {
471 if (!selectedSub) { toast('请选择二级域名', 'err'); return; }
472 var prefix = addInput.value.trim();
473 if (!/^[0-9a-zA-Z]{1,24}$/.test(prefix)) { toast('前缀只能为数字和字母(1~24 位)', 'err'); return; }
474 if (selectedSub.jg > 0) {
475 openBuy(selectedSub, prefix);
476 return;
477 }
478 addDomain(prefix + '.' + selectedSub.url);
479 } else {
480 addDomain(addInput.value.trim());
481 }
482 });
483
484 /* ---------- 删除 ---------- */
485 listEl.addEventListener('click', function (e) {
486 var del = e.target.closest('[data-del-name]');
487 if (del) {
488 var name = del.getAttribute('data-del-name');
489 if (!window.confirm('确定删除域名 ' + name + ' 吗?')) return;
490 post({
491 gn: 'p_domain_scurl',
492 url: name,
493 port: del.getAttribute('data-del-port'),
494 dir: del.getAttribute('data-del-path')
495 }).then(function (res) {
496 var code = parseCode(res);
497 if (code === '删除成功') { toast('删除成功', 'ok'); loadList(); }
498 else toast(code || '删除失败', 'err');
499 }).catch(function () { toast('网络错误,请重试', 'err'); });
500 return;
501 }
502
503 var edit = e.target.closest('[data-edit-name]');
504 if (edit) {
505 var name = edit.getAttribute('data-edit-name');
506 var dot = name.indexOf('.');
507 var prefix = dot > 0 ? name.substring(0, dot) : '';
508 var zym = dot > 0 ? name.substring(dot + 1) : name;
509 editing = {
510 name: name,
511 port: edit.getAttribute('data-edit-port'),
512 path: edit.getAttribute('data-edit-path'),
513 zym: zym,
514 prefix: prefix
515 };
516 openEdit(editing);
517 }
518 });
519
520 /* ---------- 修改前缀 ---------- */
521 function openEdit(item) {
522 document.getElementById('ds-edit-prefix').value = item.prefix;
523 fillDirs(document.getElementById('ds-edit-dir'), dirs);
524 document.getElementById('ds-edit-dir').value = item.path;
525 showMask('ds-edit-mask');
526 document.getElementById('ds-edit-prefix').focus();
527 }
528
529 document.getElementById('ds-edit-confirm').addEventListener('click', function () {
530 if (!editing) return;
531 var xqz = document.getElementById('ds-edit-prefix').value.trim();
532 if (!/^[0-9a-zA-Z]{1,24}$/.test(xqz)) { toast('前缀只能为数字和字母(1~24 位)', 'err'); return; }
533 var btn = this;
534 btn.disabled = true;
535 post({
536 gn: 'p_domain_seturl',
537 zym: editing.zym,
538 port: editing.port,
539 jqz: editing.prefix,
540 xqz: xqz,
541 path: document.getElementById('ds-edit-dir').value
542 }).then(function (res) {
543 var code = parseCode(res);
544 if (code === '添加成功') { toast('修改成功', 'ok'); hideMask('ds-edit-mask'); loadList(); }
545 else toast(code || '修改失败', 'err');
546 }).catch(function () { toast('网络错误,请重试', 'err'); })
547 .then(function () { btn.disabled = false; });
548 });
549
550 /* ---------- 购买 ---------- */
551 function openBuy(sub, prefix) {
552 document.getElementById('ds-buy-domain').textContent = prefix + '.' + sub.url;
553 document.getElementById('ds-buy-price').textContent = '价格:¥' + sub.jg;
554 document.getElementById('ds-buy-urla').value = sub.url;
555 document.getElementById('ds-buy-urlb').value = prefix;
556 document.getElementById('ds-buy-urlzml').value = dirSelect.value || '/';
557 showMask('ds-buy-mask');
558 }
559
560 document.getElementById('ds-buy-form').addEventListener('submit', function () {
561 hideMask('ds-buy-mask');
562 toast('已在新窗口打开支付,请完成付款', 'ok');
563 });
564 document.getElementById('ds-buy-form').addEventListener('change', function () {
565 var form = this;
566 form.querySelectorAll('.ds-choice').forEach(function (l) { l.classList.remove('active'); });
567 var checked = form.querySelector('input[name="type"]:checked');
568 if (checked) {
569 var p = checked.closest('.ds-choice');
570 if (p) p.classList.add('active');
571 }
572 });
573
574 /* ---------- 弹窗 ---------- */
575 function showMask(id) { document.getElementById(id).classList.add('show'); }
576 function hideMask(id) { document.getElementById(id).classList.remove('show'); }
577 document.querySelectorAll('[data-close]').forEach(function (btn) {
578 btn.addEventListener('click', function () { hideMask(btn.getAttribute('data-close')); });
579 });
580 document.querySelectorAll('.ds-mask').forEach(function (mask) {
581 mask.addEventListener('click', function (e) { if (e.target === mask) mask.classList.remove('show'); });
582 });
583 document.addEventListener('keydown', function (e) {
584 if (e.key === 'Escape') document.querySelectorAll('.ds-mask.show').forEach(function (m) { m.classList.remove('show'); });
585 });
586
587 /* ---------- 初始化 ---------- */
588 tabCustom.addEventListener('click', function () { setMode('custom'); });
589 tabSub.addEventListener('click', function () { setMode('sub'); });
590 subDomainSelect.addEventListener('change', onSubChange);
591 setMode('custom');
592 loadList();
593 loadSubDomains();
594})();
595</script>
596</body>
597</html>