// ===== 收银台(管理员/收银员为玩家代购)=====
// 与 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 = '
加载中...
';
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 = '';
_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 += '
' + escAdmHtml(cat) + ' (' + cnt + ')
';
});
catTabs += '
';
}
el.innerHTML = `
🧾 收银台 为玩家代购商品
`;
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 = '该分类暂无商品
'; return; }
let h = '';
items.forEach(i => {
const price = parseInt(i.buy_price) || 0;
const stock = parseInt(i.stock);
const stockTxt = stock > 0 ? ('库存 ' + stock) : '无限';
h += '';
h += '
' + cashierMatIcon(i.material) + '' + escAdmHtml(i.display_name || i.id) + '
';
h += '
' + price + ' 债券 · ' + stockTxt + '
';
h += '
';
h += '';
h += '';
h += '';
h += '';
h += '
';
});
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 '' + title + '' + total + ' 债券
' + desc + '
';
}
function renderCashierCart() {
const list = document.getElementById('cashierCartList');
const settleWrap = document.getElementById('cashierSettleWrap');
if (!list || !settleWrap) return;
if (_cashierCart.length === 0) {
list.innerHTML = '清单为空,请从左侧添加商品
';
} else {
let h = '';
_cashierCart.forEach(i => {
h += '';
h += '
' + cashierMatIcon(i.material) + '';
h += '
' + escAdmHtml(i.name) + '
';
h += '
' + i.price + ' 债券/个
';
h += '
';
h += '' + i.amount + '';
h += '
';
h += '
' + (i.price * i.amount) + '
';
h += '
';
});
list.innerHTML = h;
}
const sub = cashierSubtotal();
const total = cashierTotal();
let html = '';
html += '原价合计' + sub + ' 债券
';
html += '';
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 += '
';
if (_cashierSettlement === 'shulker') {
html += '';
html += '
🎨 潜影盒颜色 (紫色免费,其它+2元)
';
html += '
';
CASHIER_COLORS.forEach(c => {
const sel = c.id === _cashierColor;
const extra = c.price > 0 ? ' (+' + c.price + ')' : '';
html += '';
});
html += '
';
}
html += '预估总价' + total + ' 债券
';
html += '';
html += '';
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 ? ' ●在线' : ' ○离线';
info.innerHTML = '目标玩家: ' + escAdmHtml(r.data.player) + '' + online + ' 债券余额: ' + r.data.balance + '';
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 = '';
html += '
🧾 确认代购
';
html += '
' + escAdmHtml(summary) + '
';
if (needPwd) {
html += '
';
html += '
';
}
html += '
';
html += '
';
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');
}
}