新增管理后台独立收银台模式:管理员可为指定玩家代购商品(商品浏览+购物车+背包/潜影盒结算+颜色可选收费+≥1000债券密码确认)
This commit is contained in:
@@ -110,6 +110,7 @@
|
||||
<div class="si" data-p="tickets" onclick="go('tickets')">📋 工单管理</div>
|
||||
<div class="si" data-p="lands" onclick="go('lands')">🏡 领地管理</div>
|
||||
<div class="si" data-p="usergroups" onclick="go('usergroups')">👥 用户组</div>
|
||||
<div class="si" data-p="cashier" onclick="go('cashier')">🧾 收银台</div>
|
||||
</div>
|
||||
<div class="content" id="C"></div>
|
||||
</div>
|
||||
@@ -174,6 +175,7 @@ function go(p) {
|
||||
else if (p==='tickets') loadTickets(c);
|
||||
else if (p==='lands') loadLands(c);
|
||||
else if (p==='usergroups') loadUserGroups(c);
|
||||
else if (p==='cashier') loadCashier(c);
|
||||
}
|
||||
|
||||
// 检查登录状态
|
||||
@@ -3124,5 +3126,6 @@ function glassPrompt(label, currentVal, hint = '') {
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script src="cashier.js?<?php echo filemtime(__FILE__); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -58,6 +58,7 @@ switch ($action) {
|
||||
case 'all_tx': adminAllTx(); break;
|
||||
case 'player_tx': adminPlayerTx(); break;
|
||||
case 'gen_token': adminGenToken(); break;
|
||||
case 'cashier_player_check': adminCashierPlayerCheck(); break;
|
||||
case 'cdk_batch': adminGenCDK(); break;
|
||||
case 'notify_sync': adminNotifySync(); break;
|
||||
case 'sync_now': adminSyncNow(); break;
|
||||
@@ -483,6 +484,43 @@ function adminGenToken() {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 收银台:查询目标玩家余额/在线状态 =====
|
||||
function adminCashierPlayerCheck() {
|
||||
requireAdminSession();
|
||||
$player = trim(getParam('player'));
|
||||
if (!$player) exit(json_encode(['success' => false, 'message' => '请输入玩家名'], JSON_UNESCAPED_UNICODE));
|
||||
if (!preg_match('/^[a-zA-Z0-9_]{3,16}$/', $player)) {
|
||||
exit(json_encode(['success' => false, 'message' => '玩家名格式不正确(3-16位英文/数字/下划线)'], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
$db = getDB();
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS bond_cache (player_name TEXT PRIMARY KEY, amount INTEGER DEFAULT 0, updated_at INTEGER DEFAULT 0)");
|
||||
try {
|
||||
// 确保玩家有余额记录(不存在则建0余额行,便于后续代购扣减)
|
||||
$db->exec("INSERT OR IGNORE INTO bond_cache (player_name, amount, updated_at) VALUES ('" . str_replace("'", "''", $player) . "', 0, " . time() . ")");
|
||||
$stmt = $db->prepare("SELECT amount FROM bond_cache WHERE player_name = :name");
|
||||
$stmt->bindValue(':name', $player, SQLITE3_TEXT);
|
||||
$row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
$balance = $row ? (int)$row['amount'] : 0;
|
||||
|
||||
// 在线状态(表不存在则忽略)
|
||||
$online = false;
|
||||
try {
|
||||
$ostmt = $db->prepare("SELECT player_name FROM online_players WHERE player_name = :name");
|
||||
$ostmt->bindValue(':name', $player, SQLITE3_TEXT);
|
||||
$orow = $ostmt->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
$online = $orow ? true : false;
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
exit(json_encode([
|
||||
'success' => true,
|
||||
'data' => ['player' => $player, 'exists' => true, 'balance' => $balance, 'online' => $online]
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
} catch (Exception $e) {
|
||||
@error_log('[Cashier] Player check error: ' . $e->getMessage());
|
||||
exit(json_encode(['success' => false, 'message' => '查询失败: ' . $e->getMessage()], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 批量生成CDK =====
|
||||
function adminGenCDK() {
|
||||
requireAdminSession();
|
||||
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
// ===== 收银台(管理员/收银员为玩家代购)=====
|
||||
// 与 admin.php 共享全局作用域,依赖 jsonApi / postApi / toast / escAdmHtml 等全局函数。
|
||||
let _cashierCart = [];
|
||||
let _cashierCfg = {backpack_rate:0.98, shulker_rate:1.00};
|
||||
let _cashierSettlement = 'backpack';
|
||||
let _cashierColor = 'purple';
|
||||
let _cashierItems = [];
|
||||
let _cashierCategories = [];
|
||||
let _cashierTarget = '';
|
||||
let _cashierTargetBalance = null;
|
||||
|
||||
const CASHIER_COLORS = [
|
||||
{id:'purple', name:'紫色', price:0, css:'#AA00FF'},
|
||||
{id:'white', name:'白色', price:2, css:'#E8E8E8'},
|
||||
{id:'black', name:'黑色', price:2, css:'#2A2A2A'},
|
||||
{id:'red', name:'红色', price:2, css:'#FF4444'},
|
||||
{id:'blue', name:'蓝色', price:2, css:'#4488FF'},
|
||||
{id:'green', name:'绿色', price:2, css:'#44FF77'},
|
||||
{id:'yellow', name:'黄色', price:2, css:'#FFEE44'},
|
||||
{id:'orange', name:'橙色', price:2, css:'#FF9944'}
|
||||
];
|
||||
|
||||
function cashierMatIcon(mat) {
|
||||
const m = (mat || '').toUpperCase();
|
||||
if (m.indexOf('DIAMOND') >= 0) return '💎';
|
||||
if (m.indexOf('IRON') >= 0) return '⛏️';
|
||||
if (m.indexOf('GOLD') >= 0) return '🪙';
|
||||
if (m.indexOf('EMERALD') >= 0) return '🟢';
|
||||
if (m.indexOf('NETHERITE') >= 0) return '⬛';
|
||||
if (m.indexOf('WOOD') >= 0 || m.indexOf('PLANK') >= 0 || m.indexOf('LOG') >= 0) return '🪵';
|
||||
if (m.indexOf('STONE') >= 0 || m.indexOf('COBBLE') >= 0) return '🪨';
|
||||
if (m.indexOf('APPLE') >= 0 || m.indexOf('BREAD') >= 0 || m.indexOf('MEAT') >= 0 || m.indexOf('FISH') >= 0 || m.indexOf('PORK') >= 0 || m.indexOf('BEEF') >= 0 || m.indexOf('CHICKEN') >= 0) return '🍖';
|
||||
if (m.indexOf('POTION') >= 0 || m.indexOf('BOTTLE') >= 0) return '🧪';
|
||||
if (m.indexOf('BOOK') >= 0 || m.indexOf('ENCHANT') >= 0) return '📕';
|
||||
if (m.indexOf('SWORD') >= 0 || m.indexOf('AXE') >= 0 || m.indexOf('PICK') >= 0 || m.indexOf('SHOVEL') >= 0 || m.indexOf('HOE') >= 0) return '🗡️';
|
||||
if (m.indexOf('HELMET') >= 0 || m.indexOf('CHEST') >= 0 || m.indexOf('LEGGING') >= 0 || m.indexOf('BOOT') >= 0) return '🛡️';
|
||||
if (m.indexOf('BED') >= 0) return '🛏️';
|
||||
if (m.indexOf('TNT') >= 0) return '🧨';
|
||||
return '📦';
|
||||
}
|
||||
|
||||
async function loadCashier(el) {
|
||||
el.innerHTML = '<div class="card" style="text-align:center;padding:40px">加载中...</div>';
|
||||
try { const r = await jsonApi('shop.php?action=cart_config'); if (r && r.success && r.data) _cashierCfg = r.data; } catch (e) {}
|
||||
try { const r = await jsonApi('shop.php?action=list'); _cashierItems = (r && r.data) ? r.data : []; } catch (e) { _cashierItems = []; }
|
||||
_cashierCategories = [...new Set(_cashierItems.map(i => i.category || '默认'))];
|
||||
_cashierTarget = '';
|
||||
_cashierTargetBalance = null;
|
||||
_cashierCart = [];
|
||||
_cashierSettlement = 'backpack';
|
||||
_cashierColor = 'purple';
|
||||
|
||||
let catTabs = '';
|
||||
if (_cashierCategories.length > 0) {
|
||||
catTabs = '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px">';
|
||||
_cashierCategories.forEach((cat, idx) => {
|
||||
const active = idx === 0 ? 'color:#fff;background:var(--accent);border-color:var(--accent)' : '';
|
||||
const cnt = _cashierItems.filter(i => (i.category || '默认') === cat).length;
|
||||
catTabs += '<div class="cashier-cat-tab" data-cat="' + escAdmHtml(cat) + '" style="padding:6px 14px;border:1px solid var(--border);border-radius:16px;cursor:pointer;font-size:13px;transition:all .2s;' + active + '">' + escAdmHtml(cat) + ' <span style="opacity:.7;font-size:11px">(' + cnt + ')</span></div>';
|
||||
});
|
||||
catTabs += '</div>';
|
||||
}
|
||||
|
||||
el.innerHTML = `
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;flex-wrap:wrap;gap:8px">
|
||||
<h2 style="margin:0;color:var(--accent)">🧾 收银台 <span style="font-size:12px;color:var(--dim);font-weight:400">为玩家代购商品</span></h2>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 340px;gap:16px;align-items:start">
|
||||
<div>
|
||||
<div class="card">
|
||||
<h2 style="margin-bottom:10px">① 选择目标玩家</h2>
|
||||
<div class="form-row">
|
||||
<input id="cashierPlayer" placeholder="输入玩家游戏名" onkeydown="if(event.key==='Enter')cashierCheckPlayer()" style="flex:1">
|
||||
<button class="btn btn-blue" onclick="cashierCheckPlayer()">查询</button>
|
||||
</div>
|
||||
<div id="cashierTargetInfo" style="font-size:13px;color:var(--dim);margin-top:4px">输入玩家名后查询其债券余额</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2 style="margin-bottom:10px">② 选择商品</h2>
|
||||
${catTabs}
|
||||
<div id="cashierItems" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="position:sticky;top:12px">
|
||||
<h2 style="margin-bottom:10px">🛒 代购清单</h2>
|
||||
<div id="cashierCartList" style="max-height:46vh;overflow-y:auto;margin-bottom:10px"></div>
|
||||
<div id="cashierSettleWrap" style="border-top:1px solid var(--border);padding-top:10px"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
document.querySelectorAll('.cashier-cat-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => cashierSwitchCat(tab.dataset.cat));
|
||||
});
|
||||
cashierSwitchCat(_cashierCategories[0] || '');
|
||||
renderCashierCart();
|
||||
}
|
||||
|
||||
function cashierSwitchCat(cat) {
|
||||
document.querySelectorAll('.cashier-cat-tab').forEach(c => {
|
||||
const active = c.dataset.cat === cat;
|
||||
c.style.background = active ? 'var(--accent)' : '';
|
||||
c.style.color = active ? '#fff' : '';
|
||||
c.style.borderColor = active ? 'var(--accent)' : '';
|
||||
});
|
||||
const wrap = document.getElementById('cashierItems');
|
||||
if (!wrap) return;
|
||||
const items = _cashierItems.filter(i => (i.category || '默认') === cat);
|
||||
if (items.length === 0) { wrap.innerHTML = '<div style="color:var(--dim);font-size:13px;padding:20px;text-align:center">该分类暂无商品</div>'; return; }
|
||||
let h = '';
|
||||
items.forEach(i => {
|
||||
const price = parseInt(i.buy_price) || 0;
|
||||
const stock = parseInt(i.stock);
|
||||
const stockTxt = stock > 0 ? ('库存 ' + stock) : '无限';
|
||||
h += '<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:10px;display:flex;flex-direction:column;gap:6px">';
|
||||
h += '<div style="display:flex;align-items:center;gap:8px"><span style="font-size:22px">' + cashierMatIcon(i.material) + '</span><span style="font-size:13px;color:var(--text);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escAdmHtml(i.display_name || i.id) + '</span></div>';
|
||||
h += '<div style="font-size:12px;color:var(--dim)">' + price + ' 债券 · ' + stockTxt + '</div>';
|
||||
h += '<div style="display:flex;align-items:center;gap:6px">';
|
||||
h += '<button data-minus="' + i.id + '" style="width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer">−</button>';
|
||||
h += '<input id="cqty_' + i.id + '" value="1" style="width:42px;text-align:center;padding:4px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px">';
|
||||
h += '<button data-plus="' + i.id + '" style="width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer">+</button>';
|
||||
h += '<button data-add="' + i.id + '" style="flex:1;padding:5px;border:none;border-radius:6px;background:var(--accent);color:#fff;cursor:pointer;font-size:12px;font-weight:600">加入</button>';
|
||||
h += '</div></div>';
|
||||
});
|
||||
wrap.innerHTML = h;
|
||||
wrap.querySelectorAll('[data-minus]').forEach(b => b.addEventListener('click', () => cashierItemQty(b.dataset.minus, -1)));
|
||||
wrap.querySelectorAll('[data-plus]').forEach(b => b.addEventListener('click', () => cashierItemQty(b.dataset.plus, 1)));
|
||||
wrap.querySelectorAll('[data-add]').forEach(b => b.addEventListener('click', () => cashierAddItem(b.dataset.add)));
|
||||
}
|
||||
|
||||
function cashierItemQty(id, delta) {
|
||||
const inp = document.getElementById('cqty_' + id);
|
||||
if (!inp) return;
|
||||
let v = parseInt(inp.value) || 1;
|
||||
v = Math.max(1, v + delta);
|
||||
inp.value = v;
|
||||
}
|
||||
|
||||
function cashierAddItem(id) {
|
||||
const item = _cashierItems.find(i => i.id == id);
|
||||
if (!item) return;
|
||||
const inp = document.getElementById('cqty_' + id);
|
||||
let amount = parseInt(inp ? inp.value : 1) || 1;
|
||||
amount = Math.max(1, amount);
|
||||
const price = parseInt(item.buy_price) || 0;
|
||||
const stock = parseInt(item.stock);
|
||||
const exist = _cashierCart.find(c => c.item_id == id);
|
||||
if (exist) {
|
||||
const max = stock > 0 ? stock : 9999;
|
||||
exist.amount = Math.min(exist.amount + amount, max);
|
||||
} else {
|
||||
_cashierCart.push({item_id: id, name: item.display_name || item.id, price: price, material: item.material || '', stock: stock, amount: amount});
|
||||
}
|
||||
renderCashierCart();
|
||||
toast('已加入代购清单: ' + (item.display_name || item.id), 'ok');
|
||||
}
|
||||
|
||||
function cashierChangeQty(id, delta) {
|
||||
const i = _cashierCart.find(c => c.item_id == id);
|
||||
if (!i) return;
|
||||
const max = i.stock > 0 ? i.stock : 9999;
|
||||
i.amount = Math.min(Math.max(i.amount + delta, 1), max);
|
||||
renderCashierCart();
|
||||
}
|
||||
function cashierRemoveItem(id) {
|
||||
_cashierCart = _cashierCart.filter(c => c.item_id != id);
|
||||
renderCashierCart();
|
||||
}
|
||||
function cashierClearCart() {
|
||||
_cashierCart = [];
|
||||
renderCashierCart();
|
||||
}
|
||||
|
||||
function cashierColorFee() {
|
||||
const c = CASHIER_COLORS.find(x => x.id === _cashierColor);
|
||||
return c ? c.price : 0;
|
||||
}
|
||||
function cashierSubtotal() {
|
||||
return _cashierCart.reduce((s, i) => s + i.price * i.amount, 0);
|
||||
}
|
||||
function cashierTotal() {
|
||||
const sub = cashierSubtotal();
|
||||
const rate = _cashierSettlement === 'shulker' ? _cashierCfg.shulker_rate : _cashierCfg.backpack_rate;
|
||||
return Math.round(sub * rate) + (_cashierSettlement === 'shulker' ? cashierColorFee() : 0);
|
||||
}
|
||||
|
||||
function cashierModeHtml(mode, title, total, desc) {
|
||||
const sel = _cashierSettlement === mode;
|
||||
return '<div onclick="cashierSelectMode(\'' + mode + '\')" style="padding:9px 12px;border:1px solid ' + (sel ? 'var(--accent)' : 'var(--border)') + ';border-radius:10px;cursor:pointer;background:' + (sel ? 'color-mix(in srgb,var(--accent) 10%,transparent)' : 'transparent') + '"><div style="display:flex;justify-content:space-between;align-items:center"><span style="font-size:13px;font-weight:600;color:var(--text)">' + title + '</span><span style="font-size:14px;font-weight:700;color:var(--accent)">' + total + ' 债券</span></div><div style="font-size:11px;color:var(--dim);margin-top:2px">' + desc + '</div></div>';
|
||||
}
|
||||
|
||||
function renderCashierCart() {
|
||||
const list = document.getElementById('cashierCartList');
|
||||
const settleWrap = document.getElementById('cashierSettleWrap');
|
||||
if (!list || !settleWrap) return;
|
||||
if (_cashierCart.length === 0) {
|
||||
list.innerHTML = '<div style="color:var(--dim);font-size:13px;padding:20px;text-align:center">清单为空,请从左侧添加商品</div>';
|
||||
} else {
|
||||
let h = '';
|
||||
_cashierCart.forEach(i => {
|
||||
h += '<div style="display:flex;gap:8px;align-items:center;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:10px;margin-bottom:6px">';
|
||||
h += '<span style="font-size:18px">' + cashierMatIcon(i.material) + '</span>';
|
||||
h += '<div style="flex:1;min-width:0"><div style="font-size:13px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escAdmHtml(i.name) + '</div>';
|
||||
h += '<div style="font-size:11px;color:var(--dim)">' + i.price + ' 债券/个</div></div>';
|
||||
h += '<div style="display:flex;align-items:center;gap:4px"><button onclick="cashierChangeQty(\'' + i.item_id + '\',-1)" style="width:22px;height:22px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer">−</button>';
|
||||
h += '<span style="min-width:22px;text-align:center;font-size:13px">' + i.amount + '</span>';
|
||||
h += '<button onclick="cashierChangeQty(\'' + i.item_id + '\',1)" style="width:22px;height:22px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer">+</button></div>';
|
||||
h += '<div style="text-align:right;min-width:56px"><div style="font-size:13px;font-weight:600;color:var(--text)">' + (i.price * i.amount) + '</div><button onclick="cashierRemoveItem(\'' + i.item_id + '\')" style="background:none;border:none;color:var(--red);font-size:11px;cursor:pointer">移除</button></div>';
|
||||
h += '</div>';
|
||||
});
|
||||
list.innerHTML = h;
|
||||
}
|
||||
|
||||
const sub = cashierSubtotal();
|
||||
const total = cashierTotal();
|
||||
let html = '';
|
||||
html += '<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px"><span style="color:var(--dim)">原价合计</span><span>' + sub + ' 债券</span></div>';
|
||||
html += '<div style="display:flex;flex-direction:column;gap:6px;margin-bottom:8px">';
|
||||
const bpTotal = Math.round(sub * _cashierCfg.backpack_rate);
|
||||
const shTotal = Math.round(sub * _cashierCfg.shulker_rate) + cashierColorFee();
|
||||
html += cashierModeHtml('backpack', '🎒 塞背包', bpTotal, '享 ' + (_cashierCfg.backpack_rate * 10).toFixed(1) + ' 折');
|
||||
html += cashierModeHtml('shulker', '📦 潜影盒打包', shTotal, '加价 ' + ((_cashierCfg.shulker_rate - 1) * 100).toFixed(0) + '%' + (cashierColorFee() > 0 ? (' +' + cashierColorFee() + '元') : ''));
|
||||
html += '</div>';
|
||||
if (_cashierSettlement === 'shulker') {
|
||||
html += '<div style="margin-bottom:8px;padding:8px;border:1px solid var(--border);border-radius:8px;background:var(--bg)">';
|
||||
html += '<div style="font-size:12px;color:var(--text);margin-bottom:6px">🎨 潜影盒颜色 <span style="color:var(--dim)">(紫色免费,其它+2元)</span></div>';
|
||||
html += '<div style="display:flex;flex-wrap:wrap;gap:5px">';
|
||||
CASHIER_COLORS.forEach(c => {
|
||||
const sel = c.id === _cashierColor;
|
||||
const extra = c.price > 0 ? ' (+' + c.price + ')' : '';
|
||||
html += '<button onclick="cashierSelectColor(\'' + c.id + '\')" style="display:inline-flex;align-items:center;gap:4px;padding:4px 8px;border-radius:6px;cursor:pointer;font-size:11px;color:var(--text);border:1px solid ' + (sel ? 'var(--accent)' : 'var(--border)') + ';background:' + (sel ? 'color-mix(in srgb,var(--accent) 12%,transparent)' : 'transparent') + '"><span style="width:12px;height:12px;border-radius:3px;display:inline-block;background:' + c.css + ';border:1px solid rgba(255,255,255,.2)"></span>' + c.name + extra + '</button>';
|
||||
});
|
||||
html += '</div></div>';
|
||||
}
|
||||
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin:8px 0"><span style="font-size:14px">预估总价</span><span id="cashierTotalLabel" style="font-size:18px;font-weight:700;color:var(--accent)">' + total + ' 债券</span></div>';
|
||||
html += '<div style="display:flex;gap:8px"><button class="btn btn-yellow" style="flex:1" onclick="cashierClearCart()">清空</button><button class="btn btn-green" style="flex:2" id="cashierSettleBtn" onclick="cashierSettle()">确认代购</button></div>';
|
||||
html += '<div id="cashierBalanceHint" style="font-size:11px;color:var(--dim);margin-top:6px;text-align:center"></div>';
|
||||
settleWrap.innerHTML = html;
|
||||
|
||||
const balHint = document.getElementById('cashierBalanceHint');
|
||||
if (balHint) {
|
||||
if (!_cashierTarget) balHint.textContent = '未选择目标玩家';
|
||||
else if (_cashierTargetBalance != null) {
|
||||
balHint.textContent = '目标余额: ' + _cashierTargetBalance + ' 债券' + (_cashierTargetBalance < total ? '(余额不足!)' : '');
|
||||
balHint.style.color = _cashierTargetBalance < total ? 'var(--red)' : 'var(--dim)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cashierSelectMode(mode) {
|
||||
_cashierSettlement = mode;
|
||||
renderCashierCart();
|
||||
}
|
||||
function cashierSelectColor(id) {
|
||||
_cashierColor = id;
|
||||
renderCashierCart();
|
||||
}
|
||||
|
||||
async function cashierCheckPlayer() {
|
||||
const inp = document.getElementById('cashierPlayer');
|
||||
const info = document.getElementById('cashierTargetInfo');
|
||||
if (!inp || !info) return;
|
||||
const player = inp.value.trim();
|
||||
if (!player) { info.textContent = '请输入玩家名'; info.style.color = 'var(--red)'; return; }
|
||||
info.textContent = '查询中...';
|
||||
info.style.color = 'var(--dim)';
|
||||
try {
|
||||
const r = await postApi('cashier_player_check', {player: player});
|
||||
if (r && r.success && r.data) {
|
||||
_cashierTarget = r.data.player;
|
||||
_cashierTargetBalance = r.data.balance;
|
||||
const online = r.data.online ? ' <span style="color:var(--green)">●在线</span>' : ' <span style="color:var(--dim)">○离线</span>';
|
||||
info.innerHTML = '目标玩家: <b style="color:var(--text)">' + escAdmHtml(r.data.player) + '</b>' + online + ' 债券余额: <b style="color:var(--accent)">' + r.data.balance + '</b>';
|
||||
info.style.color = 'var(--text)';
|
||||
} else {
|
||||
_cashierTarget = '';
|
||||
_cashierTargetBalance = null;
|
||||
info.textContent = '查询失败: ' + (r ? r.message : '未知错误');
|
||||
info.style.color = 'var(--red)';
|
||||
}
|
||||
} catch (e) {
|
||||
_cashierTarget = '';
|
||||
_cashierTargetBalance = null;
|
||||
info.textContent = '查询失败: ' + e.message;
|
||||
info.style.color = 'var(--red)';
|
||||
}
|
||||
renderCashierCart();
|
||||
}
|
||||
|
||||
async function cashierSettle() {
|
||||
if (_cashierCart.length === 0) { toast('代购清单为空', 'err'); return; }
|
||||
if (!_cashierTarget) { toast('请先选择并查询目标玩家', 'err'); return; }
|
||||
const total = cashierTotal();
|
||||
const sub = cashierSubtotal();
|
||||
if (_cashierTargetBalance != null && _cashierTargetBalance < total) {
|
||||
toast('目标玩家余额不足(' + _cashierTargetBalance + ' < ' + total + ')', 'err');
|
||||
return;
|
||||
}
|
||||
const lines = _cashierCart.map(i => '· ' + i.name + ' x' + i.amount + ' = ' + (i.price * i.amount) + ' 债券').join('\n');
|
||||
const modeName = _cashierSettlement === 'shulker' ? ('潜影盒打包(' + (CASHIER_COLORS.find(c => c.id === _cashierColor) ? CASHIER_COLORS.find(c => c.id === _cashierColor).name : '') + ')') : '塞背包';
|
||||
const summary = '目标玩家: ' + _cashierTarget + '\n结算方式: ' + modeName + '\n原价: ' + sub + ' 债券\n预估总价: ' + total + ' 债券\n\n' + lines;
|
||||
cashierShowConfirm(total, summary);
|
||||
}
|
||||
|
||||
function cashierShowConfirm(total, summary) {
|
||||
const needPwd = total > 1000;
|
||||
let overlay = document.getElementById('cashierConfirmOverlay');
|
||||
if (overlay) overlay.remove();
|
||||
overlay = document.createElement('div');
|
||||
overlay.id = 'cashierConfirmOverlay';
|
||||
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.5);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:6000;display:flex;justify-content:center;align-items:center;animation:glassFadeIn .25s ease';
|
||||
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
|
||||
let html = '<div style="width:420px;max-width:92%;max-height:88vh;overflow-y:auto;background:var(--card);border:1px solid rgba(88,166,255,0.25);border-radius:16px;padding:24px;box-shadow:0 12px 48px rgba(0,0,0,0.5)">';
|
||||
html += '<h3 style="margin:0 0 12px;color:var(--accent)">🧾 确认代购</h3>';
|
||||
html += '<pre style="white-space:pre-wrap;word-break:break-word;font-family:inherit;font-size:13px;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:12px;margin:0 0 14px;line-height:1.6">' + escAdmHtml(summary) + '</pre>';
|
||||
if (needPwd) {
|
||||
html += '<label style="font-size:12px;color:var(--dim);display:block;margin-bottom:4px">⚠️ 金额≥1000债券,需输入管理员密码确认</label>';
|
||||
html += '<input id="cashierPwd" type="password" placeholder="管理员密码" style="width:100%;padding:9px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px;margin-bottom:12px" onkeydown="if(event.key===\'Enter\')cashierConfirmBuy()">';
|
||||
}
|
||||
html += '<div style="display:flex;gap:10px"><button class="btn btn-yellow" style="flex:1" onclick="document.getElementById(\'cashierConfirmOverlay\').remove()">取消</button><button class="btn btn-green" style="flex:2" id="cashierConfirmBtn" onclick="cashierConfirmBuy()">确认代购</button></div>';
|
||||
html += '</div>';
|
||||
overlay.innerHTML = html;
|
||||
document.body.appendChild(overlay);
|
||||
if (needPwd) { const p = document.getElementById('cashierPwd'); if (p) p.focus(); }
|
||||
}
|
||||
|
||||
async function cashierConfirmBuy() {
|
||||
const total = cashierTotal();
|
||||
const pwdEl = document.getElementById('cashierPwd');
|
||||
const password = pwdEl ? pwdEl.value : '';
|
||||
if (total > 1000 && !password) { toast('请输入管理员密码确认', 'err'); if (pwdEl) pwdEl.focus(); return; }
|
||||
const btn = document.getElementById('cashierConfirmBtn');
|
||||
if (btn) { btn.disabled = true; btn.textContent = '代购中...'; }
|
||||
await cashierDoBuy(total, password);
|
||||
const ov = document.getElementById('cashierConfirmOverlay');
|
||||
if (ov) ov.remove();
|
||||
}
|
||||
|
||||
async function cashierDoBuy(total, password) {
|
||||
try {
|
||||
// 1) 获取管理员代购令牌(all 权限,单次使用)
|
||||
const tR = await postApi('gen_token', {player: _cashierTarget, purpose: 'all'});
|
||||
if (!tR || !tR.success || !tR.data || !tR.data.token) {
|
||||
toast('获取代购令牌失败', 'err');
|
||||
return;
|
||||
}
|
||||
const token = tR.data.token;
|
||||
const items = _cashierCart.map(i => ({item_id: i.item_id, amount: i.amount}));
|
||||
const body = {
|
||||
token: token,
|
||||
items: JSON.stringify(items),
|
||||
settlement: _cashierSettlement,
|
||||
player: _cashierTarget,
|
||||
password: password || undefined
|
||||
};
|
||||
if (_cashierSettlement === 'shulker') body.shulker_color = _cashierColor;
|
||||
const res = await fetch('api/shop.php?action=buy_cart', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data && data.need_password) {
|
||||
toast('请重新输入密码确认', 'err');
|
||||
cashierShowConfirm(total, '目标玩家: ' + _cashierTarget + '\n(需密码确认)');
|
||||
return;
|
||||
}
|
||||
if (data && data.success) {
|
||||
toast('代购成功:' + _cashierTarget + ' 已获得商品', 'ok');
|
||||
_cashierCart = [];
|
||||
renderCashierCart();
|
||||
cashierCheckPlayer();
|
||||
} else {
|
||||
toast('代购失败: ' + (data ? data.message : '未知错误'), 'err');
|
||||
}
|
||||
} catch (e) {
|
||||
toast('代购异常: ' + e.message, 'err');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user