173 lines
7.6 KiB
HTML
173 lines
7.6 KiB
HTML
<!DOCTYPE html><html lang="zh-CN">
|
|
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
<title>__SITE_NAME__ · 我的订单</title><link rel="stylesheet" href="style.css">
|
|
<style>
|
|
.dashboard-header { text-align:center;padding:24px 0 16px; }
|
|
.dashboard-header h1 { font-size:20px;font-weight:500;margin:0; }
|
|
.dashboard-header p { color:#888780;font-size:13px;margin:6px 0 0; }
|
|
.order-info { background:#f1efe8;border-radius:8px;padding:14px;margin:12px 0;text-align:center; }
|
|
.order-info .oid { font-family:monospace;font-size:15px;color:#534ab7;font-weight:500; }
|
|
.progress-wrap { margin:12px 0; }
|
|
.progress-bar { height:8px;background:#d3d1c7;border-radius:4px;overflow:hidden; }
|
|
.progress-fill { height:100%;background:#534ab7;border-radius:4px;transition:width .5s; }
|
|
.progress-label { display:flex;justify-content:space-between;font-size:12px;color:#888780;margin-top:4px; }
|
|
.status-grid { display:grid;grid-template-columns:repeat(auto-fit,minmax(100px,1fr));gap:8px;margin:12px 0; }
|
|
.status-step { text-align:center;padding:8px;border-radius:6px;font-size:12px;background:#f1efe8;color:#b4b2a9; }
|
|
.status-step.done { background:#eaf3de;color:#3b6d11; }
|
|
.status-step.active { background:#534ab7;color:#fff; }
|
|
.contact-box { border:0.5px solid #d3d1c7;border-radius:8px;padding:14px;margin:12px 0;text-align:center; }
|
|
.contact-box p { font-size:13px;color:#5f5e5a;margin:0 0 8px; }
|
|
.search-area { border:0.5px solid #d3d1c7;border-radius:8px;padding:20px;margin-top:16px;text-align:center; }
|
|
.search-area h3 { font-size:14px;font-weight:500;margin:0 0 8px;color:#3c3489; }
|
|
.search-row { display:flex;gap:8px;max-width:400px;margin:0 auto; }
|
|
.search-row input { flex:1;font-family:monospace; }
|
|
</style></head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="nav">
|
|
<a href="/submit">提交需求</a>
|
|
<a href="/track" class="active">我的订单</a>
|
|
<a href="/admin">管理面板</a>
|
|
</div>
|
|
|
|
<!-- 查看已有订单 -->
|
|
<div id="search-view">
|
|
<div class="dashboard-header">
|
|
<h1>🔍 我的订单</h1>
|
|
<p>输入订单编号查看进度</p>
|
|
</div>
|
|
<div class="search-area">
|
|
<h3>查询订单</h3>
|
|
<div class="search-row">
|
|
<input id="track-q" placeholder="如: ZT-20260606-001" onkeydown="if(event.key==='Enter')trackOrder()">
|
|
<button onclick="trackOrder()">查询</button>
|
|
</div>
|
|
<p style="font-size:12px;color:#b4b2a9;margin-top:6px;">提交需求后会自动获得订单编号</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 订单详情面板 -->
|
|
<div id="dashboard-view" style="display:none;">
|
|
<div class="dashboard-header">
|
|
<h1>📋 订单详情</h1>
|
|
<button class="small secondary" onclick="showSearch()" style="margin-top:8px;">← 查询其他订单</button>
|
|
</div>
|
|
<div id="dashboard-content"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const STATUS_LABEL = { submitted:'已提交', reviewing:'审核中', accepted:'已接单', 'in-progress':'制作中', testing:'测试中', completed:'已完成', delivered:'已交付' };
|
|
const STATUS_STEPS = ['submitted','reviewing','accepted','in-progress','testing','completed','delivered'];
|
|
|
|
function showSearch() {
|
|
document.getElementById('search-view').style.display = 'block';
|
|
document.getElementById('dashboard-view').style.display = 'none';
|
|
}
|
|
|
|
async function trackOrder() {
|
|
const q = document.getElementById('track-q').value.trim();
|
|
if (!q) return;
|
|
await loadOrder(q);
|
|
}
|
|
|
|
async function loadOrder(id) {
|
|
const dv = document.getElementById('dashboard-view');
|
|
const dc = document.getElementById('dashboard-content');
|
|
dv.style.display = 'block';
|
|
document.getElementById('search-view').style.display = 'none';
|
|
dc.innerHTML = '<div class="loading"><p>查询中...</p></div>';
|
|
|
|
try {
|
|
const r = await fetch(`api/orders/search?q=${encodeURIComponent(id)}`);
|
|
const orders = await r.json();
|
|
const o = orders.find(x => x.id.toLowerCase() === id.toLowerCase());
|
|
if (!o) {
|
|
dc.innerHTML = `<div class="card"><p style="color:#e24b4a;text-align:center;">未找到订单 <strong>${id}</strong></p>
|
|
<p style="text-align:center;font-size:13px;color:#888780;margin-top:8px;"><a href="/submit">提交新需求</a></p></div>`;
|
|
return;
|
|
}
|
|
renderDashboard(o, dc);
|
|
} catch(e) {
|
|
dc.innerHTML = `<p class="msg error">${e.message}</p>`;
|
|
}
|
|
}
|
|
|
|
function renderDashboard(o, el) {
|
|
const idx = STATUS_STEPS.indexOf(o.status);
|
|
const pct = o.progress || (idx >= 0 ? Math.round((idx / (STATUS_STEPS.length-1)) * 100) : 0);
|
|
const exp = o.expectedDelivery ? new Date(o.expectedDelivery).toLocaleDateString() : '待确认';
|
|
|
|
let h = `<div class="card"><div class="order-info"><div class="oid">${o.id}</div>`;
|
|
h += `<span class="status status-${o.status}" style="margin-top:6px;display:inline-block;">${STATUS_LABEL[o.status]||o.status}</span></div>`;
|
|
|
|
// 进度条
|
|
h += `<div class="progress-wrap"><div class="progress-bar"><div class="progress-fill" style="width:${pct}%"></div></div>`;
|
|
h += `<div class="progress-label"><span>${o.progressNote||STATUS_LABEL[o.status]}</span><span>${pct}%</span></div></div>`;
|
|
|
|
// 状态步骤
|
|
h += `<div class="status-grid">`;
|
|
STATUS_STEPS.forEach((s, i) => {
|
|
const cls = i <= idx ? 'done' : (i === idx+1 && idx >= 0 ? 'active' : '');
|
|
h += `<div class="status-step ${cls}">${i <= idx ? '✅' : (i === idx+1 ? '⏳' : '○')}<br>${STATUS_LABEL[s]}</div>`;
|
|
});
|
|
h += `</div>`;
|
|
|
|
// 预期交付
|
|
h += `<p style="font-size:13px;color:#5f5e5a;text-align:center;">📅 预计交付: ${exp}</p>`;
|
|
|
|
// 需求
|
|
h += `<h3>📝 需求</h3><p style="font-size:13px;color:#5f5e5a;white-space:pre-wrap;">${o.requirements||''}</p>`;
|
|
if (o.budget) h += `<p style="font-size:12px;color:#888780;">💰 预算: ${o.budget}</p>`;
|
|
|
|
// 子任务
|
|
if (o.subs?.length) {
|
|
h += `<h3>📋 任务清单</h3><div class="sub-list">`;
|
|
o.subs.forEach(s => {
|
|
h += `<div class="sub-item"><span class="sid">${s.subId}</span><span class="sub-status sub-${s.status==='done'?'done':'pending'}">${s.status==='done'?'✅':'⏳'}</span><span>${s.title}</span></div>`;
|
|
});
|
|
h += `</div>`;
|
|
}
|
|
|
|
// 沟通记录
|
|
if (o.comments?.length) {
|
|
h += `<h3>💬 沟通记录</h3>`;
|
|
o.comments.slice().reverse().forEach(c => {
|
|
h += `<div class="comment ${c.side}"><span class="time">${new Date(c.createdAt).toLocaleString()}</span>`;
|
|
h += `<span class="author">${c.author} (${c.side==='dev'?'开发者':'你'}):</span><br>${c.content}</div>`;
|
|
});
|
|
}
|
|
|
|
// 主动联系入口
|
|
h += `<div class="contact-box"><p>有问题需要联系开发者?</p>`;
|
|
h += `<textarea id="client-msg" rows="2" placeholder="留言给开发者..." style="margin-bottom:6px;"></textarea>`;
|
|
h += `<button class="small" onclick="sendMsg('${o.id}')">📤 发送留言</button></div>`;
|
|
|
|
// 时间线
|
|
if (o.timeline?.length) {
|
|
h += `<h3>⏱ 更新记录</h3><div style="font-size:12px;color:#888780;">`;
|
|
o.timeline.slice().reverse().forEach(t => {
|
|
h += `<div style="padding:2px 0;">${new Date(t.at).toLocaleString()} · ${t.detail}</div>`;
|
|
});
|
|
h += `</div>`;
|
|
}
|
|
|
|
h += `</div>`;
|
|
el.innerHTML = h;
|
|
}
|
|
|
|
async function sendMsg(id) {
|
|
const content = document.getElementById('client-msg').value.trim();
|
|
if (!content) return;
|
|
await fetch(`api/orders/${id}`, { method:'PUT', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ action:'comment', author:'客户', content, side:'client' }) });
|
|
document.getElementById('client-msg').value = '';
|
|
loadOrder(id);
|
|
}
|
|
|
|
// URL 参数自动查询
|
|
const params = new URLSearchParams(location.search);
|
|
if (params.get('id')) loadOrder(params.get('id'));
|
|
else document.getElementById('track-q').focus();
|
|
</script>
|
|
</body></html>
|