Files
Sdf1_login/orders.php
T
ypsdf1 7a4aad46c6 功能: 订单记录独立页+小票书+packmoney/green修复(??替代||)
1. 新增 orders.php 独立订单记录页面(搜索/筛选/详情弹窗)
2. 收银台底部订单区改为跳转链接(→orders.php)
3. buy_cart 成功后推送 give_receipt_book 到 Java 队列(发放 Written Book)
4. 修复 packmoney/green_discount 前端 JS || falsy 回退 bug(改用 ??)
5. 费率从后端 shop_config 动态读取(不再硬编码 RATES)
6. cashierGetConfig 扩展读取 cart_backpack_rate/cart_shulker_rate
2026-07-07 12:28:26 +08:00

219 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 独立订单记录页面
* 从收银台拆分出来,支持搜索、筛选、完整订单详情查看。
*/
require_once __DIR__ . '/core.php';
if (session_status() === PHP_SESSION_NONE) session_start();
$loggedIn = isAdminLoggedIn() || isCashierLoggedIn();
$role = 'guest';
$name = '';
if (isAdminLoggedIn()) { $role = 'admin'; $name = 'admin'; }
elseif (isCashierLoggedIn()) { $c = getCurrentCashier(); $role = 'cashier'; $name = $c['username']; }
$initialState = json_encode([
'logged_in' => $loggedIn,
'role' => $role,
'username' => $name,
], JSON_UNESCAPED_UNICODE);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate">
<title>SDF1 - 订单记录</title>
<style>
:root { --bg:#0d1117; --card:#161b22; --border:#30363d; --text:#e6edf3; --dim:#8b949e; --accent:#58a6ff; --green:#3fb950; --red:#f85149; --yellow:#d29922; --purple:#bc8cff; }
* { margin:0; padding:0; box-sizing:border-box; }
body { background:var(--bg); color:var(--text); font-family:'Segoe UI',system-ui,sans-serif; min-height:100vh; }
.header { background:linear-gradient(135deg,#1a1e2e,#0d1117); border-bottom:1px solid var(--border); padding:14px 24px; display:flex; justify-content:space-between; align-items:center; position:sticky; top:0; z-index:50; }
.header h1 { font-size:18px; color:var(--accent); }
.btn { padding:8px 16px; border:none; border-radius:6px; cursor:pointer; font-size:13px; font-weight:600; transition:all .2s cubic-bezier(.16,1,.3,1); }
.btn-blue { background:var(--accent); color:#fff; }
.btn-green { background:var(--green); color:#fff; }
.btn-red { background:var(--red); color:#fff; }
.btn-ghost { background:transparent; border:1px solid var(--border); color:var(--dim); }
.btn:hover { opacity:.88; transform:translateY(-1px); }
.container { max-width:1100px; margin:20px auto; padding:0 16px; }
.panel { background:var(--card); border:1px solid var(--border); border-radius:12px; padding:18px; margin-bottom:16px; }
.panel h3 { font-size:15px; color:var(--accent); margin-bottom:12px; display:flex; align-items:center; gap:8px; }
/* 搜索栏 */
.search-bar { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:16px; }
.search-bar input, .search-bar select { padding:9px 12px; background:var(--bg); border:1px solid var(--border); border-radius:7px; color:var(--text); font-size:13px; outline:none; flex:1; min-width:160px; }
.search-bar input:focus { border-color:var(--accent); }
/* 订单列表 */
.order-row { font-size:13px; padding:14px; background:var(--bg); border:1px solid var(--border); border-radius:10px; margin-bottom:10px; transition:border-color .2s; cursor:pointer; }
.order-row:hover { border-color:var(--accent); }
.order-row .ot { display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; }
.order-row .ono { color:var(--accent); font-weight:700; font-size:14px; }
.order-row .od { color:var(--dim); font-size:12px; margin-top:4px; line-height:1.5; }
.order-row .od .disc { color:var(--yellow); }
.order-row .op { font-size:11px; color:var(--purple); }
/* 订单详情弹窗 */
#detailOverlay { display:none; position:fixed; top:0;left:0;right:0;bottom:0; background:rgba(0,0,0,.55); z-index:6000; justify-content:center; align-items:flex-start; padding:24px 12px; overflow-y:auto; }
#detailOverlay.show { display:flex; }
.detail-card { background:var(--card); border:1px solid var(--border); border-radius:14px; padding:24px; width:520px; max-width:100%; box-shadow:0 16px 48px rgba(0,0,0,.5); }
.detail-card h2 { color:var(--accent); font-size:17px; margin-bottom:14px; }
.detail-card .d-line { display:flex; justify-content:space-between; padding:7px 0; border-bottom:1px solid var(--border); font-size:13px; }
.detail-card .d-line.total { border-bottom:none; font-size:17px; font-weight:700; margin-top:10px; padding-top:12px; border-top:2px solid var(--accent); }
.detail-card .d-total-val { color:var(--green); }
.detail-close { margin-top:16px; text-align:right; }
.tag { display:inline-block; padding:2px 8px; border-radius:999px; font-size:11px; font-weight:600; }
.tag-admin { background:rgba(188,140,255,.15); color:var(--purple); }
.tag-cashier { background:rgba(88,166,255,.15); color:var(--accent); }
.empty-state { text-align:center; color:var(--dim); padding:40px; font-size:14px; }
/* 滚动条 */
*::-webkit-scrollbar { width:8px; }
*::-webkit-scrollbar-track { background:transparent; }
*::-webkit-scrollbar-thumb { background:var(--border); border-radius:4px; }
@media(max-width:600px){ .detail-card { width:100%; padding:16px; } .container{margin:10px auto;padding:0 10px;} }
</style>
</head>
<body>
<div class="header">
<div style="display:flex;align-items:center;gap:12px">
<h1>📜 订单记录</h1>
<a href="cashier.php" class="btn btn-ghost" style="text-decoration:none;font-size:12px">← 返回收银台</a>
</div>
<div style="display:flex;align-items:center;gap:10px">
<?php if ($loggedIn): ?>
<span class="tag <?= $role==='admin'?'tag-admin':'tag-cashier' ?>"><?= $role==='admin'?'管理员':'收银员' ?> · <?= esc($name) ?></span>
<a href="cashier.php?logout=1" class="btn btn-red" style="text-decoration:none;font-size:12px">退出</a>
<?php else: ?>
<a href="cashier.php" class="btn btn-blue" style="text-decoration:none;font-size:12px">登录收银台</a>
<?php endif; ?>
</div>
</div>
<div class="container">
<!-- 搜索/筛选 -->
<div class="panel">
<h3>🔍 筛选与搜索</h3>
<div class="search-bar">
<input type="text" id="searchPlayer" placeholder="玩家名..." oninput="loadOrders()">
<input type="text" id="searchOrderNo" placeholder="订单号..." oninput="loadOrders()">
<select id="filterPayMode" onchange="loadOrders()">
<option value="">全部收款方式</option>
<option value="bond">债券扣款</option>
<option value="cash">现金记账</option>
</select>
<select id="limit" onchange="loadOrders()">
<option value="30">最近 30 条</option>
<option value="50" selected>最近 50 条</option>
<option value="100">最近 100 条</option>
<option value="200">最近 200 条</option>
</select>
<button class="btn btn-blue" onclick="loadOrders()">刷新</button>
</div>
</div>
<!-- 订单列表 -->
<div class="panel">
<h3>📋 订单列表 <span id="orderCount" style="font-weight:normal;color:var(--dim);font-size:12px"></span></h3>
<div id="orderList"><div class="empty-state">加载中…</div></div>
</div>
</div>
<!-- 订单详情弹窗 -->
<div id="detailOverlay">
<div class="detail-card" id="detailBody"><!-- JS填充 --></div>
</div>
<script>
const STATE = <?php echo $initialState; ?>;
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;}[c])); }
async function api(url, opts) {
const r = await fetch(url, Object.assign({headers:{'X-Requested-With':'orders'}}, opts));
return await r.json();
}
async function loadOrders() {
try {
const params = new URLSearchParams();
params.set('action', 'order_list');
params.set('limit', document.getElementById('limit').value || '50');
const p = document.getElementById('searchPlayer').value.trim();
const o = document.getElementById('searchOrderNo').value.trim();
const pm = document.getElementById('filterPayMode').value;
if (p) params.set('player', p);
if (o) params.set('order_no', o);
if (pm) params.set('pay_mode', pm);
const d = await api('api/cashier.php?' + params.toString());
const el = document.getElementById('orderList');
document.getElementById('orderCount').textContent = '';
if (!d.success || !d.data || !d.data.length) {
el.innerHTML = '<div class="empty-state">暂无符合条件的订单</div>';
return;
}
document.getElementById('orderCount').textContent = '(共 ' + d.data.length + ' 条)';
el.innerHTML = d.data.map(o => {
const items = (o.items_detail && Array.isArray(o.items_detail)) ? o.items_detail.map(i=>esc(i.name)+'x'+i.amount).join('、') : '';
const disc = (parseInt(o.discount_percent)>0) ? ` <span class="disc">(${o.discount_percent}% off -${o.discount_amount})</span>` : '';
const pmBadge = o.payment_method === 'cash' ? '<span style="color:var(--yellow)">[现金]</span>' : '';
return `<div class="order-row" onclick="showDetail(${escapeHtmlAttr(JSON.stringify(o))})">
<div class="ot">
<span class="ono">${esc(o.order_no)} ${pmBadge}</span>
<span>${o.operator_type==='admin'?'管理员':'收银员'}:${esc(o.operator_name)}</span>
</div>
<div class="od">玩家 <b>${esc(o.player_name)}</b> · ${items}${disc}</div>
<div class="od">实收 <b style="color:var(--green)">${o.total_price}</b> 债券 · ${new Date((o.created_at||0)*1000).toLocaleString('zh-CN')}</div>
</div>`;
}).join('');
} catch(e) { document.getElementById('orderList').innerHTML = '<div class="empty-state">加载失败</div>'; }
}
function escapeHtmlAttr(str) {
// 安全转义JSON用于HTML属性中的onclick
return String(str).replace(/'/g, "&#39;").replace(/"/g, "&quot;");
}
function showDetail(orderData) {
const items = (orderData.items_detail && Array.isArray(orderData.items_detail)) ? orderData.items_detail : [];
const itemsHtml = items.map(it =>
`<div class="d-line"><span>${esc(it.name)} × ${it.amount}</span><span>${it.line_total} 债券</span></div>`
).join('');
const discLine = parseInt(orderData.discount_percent)>0
? `<div class="d-line" style="color:var(--yellow)"><span>手动折扣 ${orderData.discount_percent}%</span><span>-${orderData.discount_amount} 债券</span></div>` : '';
const payModeText = orderData.payment_method === 'cash' ? '现金(仅记账)' : '债券扣款';
const settlementName = orderData.settlement === 'shulker' ? '潜影盒打包' : '塞背包(环保单)';
const card = document.getElementById('detailBody');
card.innerHTML = `
<h2>📋 订单详情</h2>
<div class="d-line"><span>订单号</span><span style="color:var(--accent);font-weight:700">${esc(orderData.order_no)}</span></div>
<div class="d-line"><span>时间</span><span>${new Date((orderData.created_at||0)*1000).toLocaleString('zh-CN')}</span></div>
<div class="d-line"><span>玩家</span><span><b>${esc(orderData.player_name)}</b></span></div>
<div class="d-line"><span>操作员</span><span>${orderData.operator_type==='admin'?'管理员':esc(orderData.operator_name)}</span></div>
<div class="d-line"><span>结算方式</span><span>${settlementName}</span></div>
<div class="d-line"><span>收款方式</span><span>${payModeText}</span></div>
<div class="d-line" style="border-top:1px dashed var(--border);margin:8px 0"></div>
${itemsHtml}
<div class="d-line" style="border-top:1px dashed var(--border);margin:8px 0"><span>商品小计</span><span>${orderData.subtotal || 0} 债券</span></div>
${discLine}
<div class="d-line total"><span>实收合计</span><span class="d-total-val">${orderData.total_price} 债券</span></div>
<div class="detail-close">
<button class="btn btn-ghost" onclick="closeDetail()">关闭</button>
</div>`;
document.getElementById('detailOverlay').classList.add('show');
}
function closeDetail() { document.getElementById('detailOverlay').classList.remove('show'); }
document.getElementById('detailOverlay').addEventListener('click', e => { if(e.target.id === 'detailOverlay') closeDetail(); });
// 启动
if (STATE.logged_in) loadOrders();
else document.getElementById('orderList').innerHTML = '<div class="empty-state">请先<a href="cashier.php" style="color:var(--accent)">登录收银台</a>后查看订单</div>';
</script>
</body>
</html>