加密token

This commit is contained in:
Developer
2026-04-17 07:00:26 +08:00
parent 08dce0aed0
commit 20fb06cac9
176 changed files with 33424 additions and 3565 deletions

825
web_order/index.html Normal file
View File

@@ -0,0 +1,825 @@
<!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">
<title>🍽️ 点餐助手 - 小妈厨房</title>
<style>
:root {
--primary: #007AFF;
--primary-light: rgba(0,122,255,0.1);
--green: #34C759;
--green-light: rgba(52,199,89,0.1);
--orange: #FF9500;
--orange-light: rgba(255,149,0,0.1);
--red: #FF3B30;
--red-light: rgba(255,59,48,0.1);
--bg: #F2F2F7;
--card: rgba(255,255,255,0.72);
--text: #1C1C1E;
--text2: #3C3C43;
--text3: #8E8E93;
--border: rgba(60,60,67,0.08);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--shadow: 0 2px 12px rgba(0,0,0,0.06);
--font: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #000000;
--card: rgba(44,44,46,0.72);
--text: #FFFFFF;
--text2: #EBEBF5;
--text3: #8E8E93;
--border: rgba(84,84,88,0.65);
--shadow: 0 2px 12px rgba(0,0,0,0.3);
}
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font);
background: var(--bg);
color: var(--text);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 16px;
}
.header {
text-align: center;
padding: 24px 0 16px;
}
.header h1 {
font-size: 24px;
font-weight: 700;
letter-spacing: -0.5px;
}
.header .subtitle {
font-size: 14px;
color: var(--text3);
margin-top: 4px;
}
.card {
background: var(--card);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: var(--radius-lg);
border: 0.5px solid var(--border);
padding: 16px;
margin-bottom: 12px;
box-shadow: var(--shadow);
animation: fadeIn 0.3s ease-out;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.order-header-left {
display: flex;
align-items: center;
gap: 8px;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: var(--radius-sm);
font-size: 13px;
font-weight: 600;
}
.status-draft { background: var(--orange-light); color: var(--orange); }
.status-active { background: var(--green-light); color: var(--green); }
.status-completed { background: var(--primary-light); color: var(--primary); }
.status-cancelled { background: var(--red-light); color: var(--red); }
.order-no {
font-size: 12px;
color: var(--text3);
font-family: 'SF Mono', Menlo, monospace;
}
.order-time {
font-size: 12px;
color: var(--text3);
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-top: 12px;
}
.info-chip {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
background: rgba(142,142,147,0.06);
border-radius: var(--radius-sm);
font-size: 14px;
}
.info-chip-icon {
font-size: 16px;
}
.info-chip-label {
font-size: 11px;
color: var(--text3);
}
.info-chip-value {
font-weight: 600;
font-size: 14px;
}
.item-list { list-style: none; }
.item-row {
display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 0.5px solid var(--border);
}
.item-row:last-child { border-bottom: none; }
.item-source {
font-size: 11px;
padding: 2px 6px;
border-radius: 4px;
background: var(--primary-light);
color: var(--primary);
margin-right: 8px;
white-space: nowrap;
}
.item-name {
flex: 1;
font-size: 15px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-qty {
font-size: 14px;
color: var(--text2);
margin: 0 8px;
min-width: 30px;
text-align: center;
}
.item-price {
font-size: 15px;
font-weight: 600;
color: var(--primary);
}
.item-detail {
font-size: 12px;
color: var(--text3);
margin-top: 2px;
padding-left: 0;
}
.total-section {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
border-top: 1px solid var(--border);
margin-top: 8px;
}
.total-label {
font-size: 15px;
color: var(--text2);
}
.total-amount {
font-size: 22px;
font-weight: 700;
color: var(--primary);
}
.note-section {
margin-top: 8px;
padding: 10px;
background: rgba(142,142,147,0.06);
border-radius: var(--radius-sm);
}
.note-label {
font-size: 12px;
color: var(--text3);
margin-bottom: 4px;
}
.note-text {
font-size: 14px;
color: var(--text2);
}
.empty-state {
text-align: center;
padding: 60px 20px;
}
.empty-icon {
font-size: 56px;
margin-bottom: 16px;
}
.empty-text {
font-size: 16px;
color: var(--text3);
}
.action-bar {
display: flex;
gap: 8px;
margin-top: 12px;
}
.action-btn {
flex: 1;
padding: 14px;
border: none;
border-radius: var(--radius-md);
font-size: 15px;
font-weight: 600;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.action-btn:active { opacity: 0.8; }
.action-btn-primary {
background: var(--primary);
color: white;
}
.action-btn-secondary {
background: var(--primary-light);
color: var(--primary);
}
.action-btn-green {
background: var(--green);
color: white;
}
.meta-row {
display: flex;
justify-content: space-between;
font-size: 12px;
color: var(--text3);
margin-top: 8px;
}
.debug-panel {
margin-top: 16px;
padding: 12px;
background: rgba(142,142,147,0.06);
border-radius: var(--radius-md);
font-family: 'SF Mono', Menlo, monospace;
font-size: 11px;
color: var(--text3);
word-break: break-all;
}
.debug-panel summary {
cursor: pointer;
font-weight: 600;
margin-bottom: 8px;
}
.sse-indicator {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text3);
margin-top: 4px;
}
.sse-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--green);
animation: pulse 2s infinite;
}
.sse-dot.disconnected { background: var(--red); animation: none; }
.screenshot-area {
background: var(--bg);
padding: 16px;
border-radius: var(--radius-lg);
margin-bottom: 12px;
}
.screenshot-area .card {
margin-bottom: 8px;
}
.screenshot-area .card:last-child {
margin-bottom: 0;
}
.divider {
height: 0.5px;
background: var(--border);
margin: 8px 0;
}
.desc-text {
font-size: 13px;
color: var(--text3);
line-height: 1.5;
margin-top: 4px;
}
.people-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: var(--radius-sm);
background: var(--green-light);
color: var(--green);
font-size: 13px;
font-weight: 600;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.toast {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%) translateY(20px);
background: rgba(0,0,0,0.75);
color: white;
padding: 10px 20px;
border-radius: 20px;
font-size: 14px;
opacity: 0;
transition: all 0.3s ease;
z-index: 9999;
pointer-events: none;
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
@media print {
.action-bar, .debug-panel, .sse-indicator, .refresh-btn { display: none !important; }
body { background: white; }
.card { box-shadow: none; border: 1px solid #eee; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🍽️ 点餐助手</h1>
<div class="subtitle" id="subtitle">加载中...</div>
<div class="sse-indicator">
<span class="sse-dot" id="sseDot"></span>
<span id="sseStatus">连接中...</span>
</div>
</div>
<div id="screenshotArea" class="screenshot-area">
<div id="content">
<div class="empty-state">
<div class="empty-icon"></div>
<div class="empty-text">正在加载点单信息...</div>
</div>
</div>
</div>
<div class="action-bar">
<button class="action-btn action-btn-secondary" onclick="loadOrder()">🔄 刷新</button>
<button class="action-btn action-btn-primary" onclick="screenshotOrder()">📸 截图保存</button>
<button class="action-btn action-btn-green" onclick="shareOrder()">📤 分享</button>
</div>
<details class="debug-panel">
<summary>🔧 调试信息</summary>
<div id="debug-info">等待数据...</div>
</details>
</div>
<div class="toast" id="toast"></div>
<script>
var API_BASE = 'https://eat.wktyl.com/api/kitchen/kitchen.php';
var SSE_URL = 'https://eat.wktyl.com/api/kitchen/kitchen_sse.php';
var eventSource = null;
var pollTimer = null;
var currentOrder = null;
function showToast(msg) {
var t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
setTimeout(function() { t.classList.remove('show'); }, 2000);
}
function getQueryParam(name) {
var url = new URL(window.location.href);
return url.searchParams.get(name);
}
function formatTime(isoStr) {
if (!isoStr) return '';
try {
var d = new Date(isoStr);
var y = d.getFullYear();
var m = String(d.getMonth()+1).padStart(2,'0');
var day = String(d.getDate()).padStart(2,'0');
var h = String(d.getHours()).padStart(2,'0');
var min = String(d.getMinutes()).padStart(2,'0');
return y + '-' + m + '-' + day + ' ' + h + ':' + min;
} catch(e) { return isoStr; }
}
function formatRelativeTime(isoStr) {
if (!isoStr) return '';
try {
var d = new Date(isoStr);
var now = new Date();
var diff = now - d;
var mins = Math.floor(diff / 60000);
if (mins < 1) return '刚刚';
if (mins < 60) return mins + '分钟前';
var hours = Math.floor(mins / 60);
if (hours < 24) return hours + '小时前';
var days = Math.floor(hours / 24);
if (days === 1) return '昨天';
if (days < 7) return days + '天前';
return (d.getMonth()+1) + '月' + d.getDate() + '日';
} catch(e) { return isoStr; }
}
function renderOrder(order) {
currentOrder = order;
var statusMap = {
0: { label: '草稿', icon: '📝', cls: 'status-draft' },
1: { label: '进行中', icon: '🟢', cls: 'status-active' },
2: { label: '已完成', icon: '✅', cls: 'status-completed' },
3: { label: '已取消', icon: '❌', cls: 'status-cancelled' },
};
var typeMap = { 0: '🧑 用户点餐', 1: '🏪 商家推单' };
var sourceMap = {
0: { label: '浏览', icon: '📖' },
1: { label: '搜索', icon: '🔍' },
2: { label: '手动', icon: '✏️' },
3: { label: '推荐', icon: '⭐' },
};
var st = statusMap[order.status] || statusMap[0];
var items = order.items || [];
var total = items.reduce(function(s, i) { return s + (i.price || 0) * i.quantity; }, 0);
var totalQty = items.reduce(function(s, i) { return s + i.quantity; }, 0);
document.getElementById('subtitle').textContent =
typeMap[order.type] + ' · ' + formatRelativeTime(order.createdAt);
var html = '';
// 订单状态卡片
html += '<div class="card">';
html += '<div class="order-header">';
html += '<div class="order-header-left">';
html += '<span class="status-badge ' + st.cls + '">' + st.icon + ' ' + st.label + '</span>';
html += '<span class="order-no">' + escapeHtml(order.orderNo) + '</span>';
html += '</div>';
html += '<span class="order-time">' + formatTime(order.createdAt) + '</span>';
html += '</div>';
// 信息网格:桌号 + 人数
html += '<div class="info-grid">';
if (order.tableNo) {
html += '<div class="info-chip">';
html += '<span class="info-chip-icon">🪑</span>';
html += '<div><div class="info-chip-label">桌号</div>';
html += '<div class="info-chip-value">' + escapeHtml(order.tableNo) + '</div></div>';
html += '</div>';
}
if (order.peopleCount) {
html += '<div class="info-chip">';
html += '<span class="info-chip-icon">👥</span>';
html += '<div><div class="info-chip-label">用餐人数</div>';
html += '<div class="info-chip-value">' + order.peopleCount + ' 人</div></div>';
html += '</div>';
}
if (!order.tableNo && !order.peopleCount) {
html += '<div class="info-chip">';
html += '<span class="info-chip-icon">📋</span>';
html += '<div><div class="info-chip-label">记录</div>';
html += '<div class="info-chip-value">#' + (order.recordCount || 0) + '</div></div>';
html += '</div>';
}
if (order.tableNo && !order.peopleCount) {
html += '<div class="info-chip">';
html += '<span class="info-chip-icon">📋</span>';
html += '<div><div class="info-chip-label">记录</div>';
html += '<div class="info-chip-value">#' + (order.recordCount || 0) + '</div></div>';
html += '</div>';
}
html += '</div>';
html += '</div>';
// 菜品列表
if (items.length === 0) {
html += '<div class="card"><div class="empty-state">';
html += '<div class="empty-icon">📭</div>';
html += '<div class="empty-text">暂无菜品</div>';
html += '</div></div>';
} else {
html += '<div class="card">';
html += '<div style="font-size:15px;font-weight:600;margin-bottom:8px;">🥘 菜品明细</div>';
html += '<ul class="item-list">';
items.forEach(function(item, idx) {
var src = sourceMap[item.source] || sourceMap[2];
html += '<li class="item-row">';
html += '<span class="item-source">' + src.icon + ' ' + src.label + '</span>';
html += '<span class="item-name">' + escapeHtml(item.name) + '</span>';
html += '<span class="item-qty">×' + item.quantity + '</span>';
html += '<span class="item-price">' + (item.price != null ? '¥' + (item.price * item.quantity).toFixed(1) : '-') + '</span>';
html += '</li>';
if (item.ingredients || item.note) {
html += '<li class="item-detail">';
if (item.ingredients) html += '🥘 ' + escapeHtml(item.ingredients) + ' ';
if (item.note) html += '💬 ' + escapeHtml(item.note);
html += '</li>';
}
});
html += '</ul>';
html += '<div class="total-section">';
html += '<span class="total-label">共 ' + totalQty + ' 道菜' + (order.peopleCount ? ' · ' + order.peopleCount + '人用餐' : '') + '</span>';
html += '<span class="total-amount">¥' + total.toFixed(1) + '</span>';
html += '</div>';
html += '</div>';
}
// 备注
if (order.note) {
html += '<div class="card"><div class="note-section">';
html += '<div class="note-label">💬 备注</div>';
html += '<div class="note-text">' + escapeHtml(order.note) + '</div>';
html += '</div></div>';
}
// 底部描述
html += '<div class="card">';
html += '<div style="font-size:13px;color:var(--text3);line-height:1.6;">';
html += '📱 由「小妈厨房」点餐助手生成<br>';
html += '🕐 下单时间: ' + formatTime(order.createdAt);
if (order.updatedAt && order.updatedAt !== order.createdAt) {
html += '<br>🔄 更新时间: ' + formatTime(order.updatedAt);
}
html += '</div>';
html += '</div>';
document.getElementById('content').innerHTML = html;
document.getElementById('debug-info').textContent = JSON.stringify(order, null, 2);
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function screenshotOrder() {
showToast('📸 请使用系统截图功能保存');
// 尝试使用 Web Share API
if (navigator.share && currentOrder) {
var items = currentOrder.items || [];
var total = items.reduce(function(s, i) { return s + (i.price || 0) * i.quantity; }, 0);
var text = '🍽️ 点餐助手 - ' + (currentOrder.orderNo || '') + '\n';
if (currentOrder.tableNo) text += '🪑 桌号: ' + currentOrder.tableNo + '\n';
if (currentOrder.peopleCount) text += '👥 人数: ' + currentOrder.peopleCount + '\n';
text += '\n';
items.forEach(function(item) {
text += '· ' + item.name + ' ×' + item.quantity;
if (item.price) text += ' ¥' + (item.price * item.quantity).toFixed(1);
text += '\n';
});
text += '\n合计: ¥' + total.toFixed(1) + '\n';
if (currentOrder.note) text += '备注: ' + currentOrder.note + '\n';
navigator.share({
title: '点餐助手 - ' + currentOrder.orderNo,
text: text,
}).catch(function() {});
return;
}
// Fallback: 尝试打印
window.print();
}
function shareOrder() {
if (navigator.share && currentOrder) {
var items = currentOrder.items || [];
var total = items.reduce(function(s, i) { return s + (i.price || 0) * i.quantity; }, 0);
var text = '🍽️ 点餐助手 - ' + (currentOrder.orderNo || '') + '\n';
if (currentOrder.tableNo) text += '🪑 桌号: ' + currentOrder.tableNo + '\n';
if (currentOrder.peopleCount) text += '👥 人数: ' + currentOrder.peopleCount + '\n';
text += '\n';
items.forEach(function(item) {
text += '· ' + item.name + ' ×' + item.quantity;
if (item.price) text += ' ¥' + (item.price * item.quantity).toFixed(1);
text += '\n';
});
text += '\n合计: ¥' + total.toFixed(1) + '\n';
if (currentOrder.note) text += '备注: ' + currentOrder.note + '\n';
navigator.share({
title: '点餐助手 - ' + currentOrder.orderNo,
text: text,
url: window.location.href,
}).catch(function() {});
} else {
// Fallback: 复制链接
navigator.clipboard.writeText(window.location.href).then(function() {
showToast('✅ 链接已复制到剪贴板');
}).catch(function() {
showToast('❌ 复制失败,请手动复制链接');
});
}
}
async function loadOrder() {
var orderId = getQueryParam('id');
if (!orderId) {
document.getElementById('content').innerHTML =
'<div class="card"><div class="empty-state">' +
'<div class="empty-icon">🔍</div>' +
'<div class="empty-text">缺少订单ID参数<br><small>请从App端扫码打开</small></div>' +
'</div></div>';
document.getElementById('debug-info').textContent = 'URL: ' + window.location.href + '\n缺少 id 参数';
return;
}
document.getElementById('subtitle').textContent = '加载中...';
try {
var resp = await fetch(API_BASE + '?act=get&id=' + encodeURIComponent(orderId));
if (resp.ok) {
var result = await resp.json();
if (result.code === 200 && result.data) {
renderOrder(result.data);
} else {
throw new Error(result.message || '订单不存在');
}
} else {
throw new Error('HTTP ' + resp.status);
}
} catch(e) {
document.getElementById('debug-info').textContent = 'API请求失败: ' + e.message + '\n尝试本地参数...';
tryLocalFallback(orderId);
}
}
function tryLocalFallback(orderId) {
var dataStr = getQueryParam('data');
if (dataStr) {
try {
var order = JSON.parse(decodeURIComponent(dataStr));
renderOrder(order);
return;
} catch(e2) {
document.getElementById('debug-info').textContent += '\n本地参数解析失败: ' + e2.message;
}
}
var demoOrder = {
id: orderId,
orderNo: 'OD_DEMO_' + Date.now().toString().slice(-6),
type: 0,
status: 1,
items: [
{ id: '1', name: '红烧肉', source: 0, quantity: 1, price: 38, ingredients: '五花肉、酱油、冰糖', note: null },
{ id: '2', name: '番茄炒蛋', source: 2, quantity: 2, price: 18, ingredients: null, note: '少盐' },
{ id: '3', name: '紫菜蛋花汤', source: 3, quantity: 1, price: 12, ingredients: null, note: null },
],
note: '三人用餐',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
createdBy: 'demo',
tableNo: 'A3',
peopleCount: 3,
recordCount: 1,
};
renderOrder(demoOrder);
document.getElementById('debug-info').textContent += '\n使用Demo数据展示';
}
function initSSE() {
var orderId = getQueryParam('id');
if (!orderId) return;
try {
eventSource = new EventSource(SSE_URL + '?order_id=' + encodeURIComponent(orderId));
eventSource.addEventListener('connected', function(e) {
updateSSEStatus(true, 'SSE已连接');
});
eventSource.addEventListener('order_update', function(e) {
try {
var order = JSON.parse(e.data);
renderOrder(order);
} catch(err) {
console.error('SSE数据解析失败:', err);
}
});
eventSource.addEventListener('order_deleted', function(e) {
document.getElementById('content').innerHTML =
'<div class="card"><div class="empty-state">' +
'<div class="empty-icon">🗑️</div>' +
'<div class="empty-text">订单已被删除</div>' +
'</div></div>';
});
eventSource.addEventListener('heartbeat', function(e) {
updateSSEStatus(true, 'SSE连接中');
});
eventSource.addEventListener('close', function(e) {
updateSSEStatus(false, 'SSE超时重连中...');
setTimeout(initSSE, 3000);
});
eventSource.onerror = function() {
updateSSEStatus(false, 'SSE断开');
eventSource.close();
eventSource = null;
setTimeout(initSSE, 5000);
};
} catch(e) {
console.error('SSE初始化失败:', e);
updateSSEStatus(false, 'SSE不可用');
}
}
function updateSSEStatus(connected, text) {
var dot = document.getElementById('sseDot');
var status = document.getElementById('sseStatus');
if (dot) dot.className = 'sse-dot' + (connected ? '' : ' disconnected');
if (status) status.textContent = text;
}
loadOrder();
initSSE();
pollTimer = setInterval(function() {
if (!eventSource || eventSource.readyState === EventSource.CLOSED) {
loadOrder();
}
}, 30000);
</script>
</body>
</html>