docs: 退单功能交互原型(order-return-prototype.html)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZ4DskSRKsSiheQonFtQvx
This commit is contained in:
wangjia
2026-06-21 22:17:02 +08:00
parent e36de528b0
commit 2e941fdb5f
+218
View File
@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>退单 原型 — 酒库管理系统</title>
<style>
:root{
--primary:#2563AC; --primary-dark:#154072; --danger:#D14343; --danger-bg:#FDECEC;
--success:#2E8B57; --warn:#B45309; --accent:#8B2331;
--ink:#232934; --muted:#6E7888; --border:#DCE2EB; --paper:#F5F7FA; --head:#F0F4FF;
}
*{box-sizing:border-box;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;}
body{margin:0;background:var(--paper);color:var(--ink);padding:28px;}
h1{font-size:18px;margin:0 0 4px;}
.sub{color:var(--muted);font-size:13px;margin-bottom:20px;}
.toggle{display:inline-flex;border:1px solid var(--border);border-radius:8px;overflow:hidden;margin-bottom:18px;}
.toggle button{border:0;background:#fff;padding:8px 18px;font-size:13px;cursor:pointer;color:var(--muted);}
.toggle button.on{background:var(--primary);color:#fff;font-weight:600;}
/* 入口:已审核单据行 */
.listrow{background:#fff;border:1px solid var(--border);border-radius:8px;padding:12px 16px;display:flex;align-items:center;gap:14px;max-width:780px;margin-bottom:24px;}
.mono{font-family:ui-monospace,Menlo,monospace;font-size:14px;}
.badge{font-size:11px;padding:2px 8px;border-radius:10px;}
.badge.approved{background:#E6F3EC;color:var(--success);}
.badge.returned{background:var(--danger-bg);color:var(--danger);}
.badge.partial{background:#FFF4E5;color:var(--warn);}
.spacer{flex:1;}
.btn{border:0;border-radius:6px;padding:7px 14px;font-size:12px;cursor:pointer;}
.btn.link{background:transparent;color:var(--primary);padding:6px 8px;}
.btn.error{background:var(--danger);color:#fff;}
.btn.error-o{background:#fff;border:1px solid var(--danger);color:var(--danger);}
.btn.ghost{background:#fff;border:1px solid var(--border);color:var(--ink);}
.btn.primary{background:var(--primary);color:#fff;}
.btn:disabled{opacity:.45;cursor:not-allowed;}
/* 弹窗 */
.scrim{position:fixed;inset:0;background:rgba(20,24,32,.45);display:none;align-items:center;justify-content:center;z-index:10;}
.scrim.show{display:flex;}
.dialog{background:#fff;border-radius:10px;width:880px;max-width:94vw;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 12px 40px rgba(0,0,0,.25);}
.dhead{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border);}
.dhead h2{font-size:15px;margin:0;}
.dmeta{display:flex;gap:22px;padding:10px 18px;background:var(--paper);font-size:12px;color:var(--muted);flex-wrap:wrap;}
.dmeta b{color:var(--ink);font-weight:600;}
.dbody{overflow:auto;padding:0 18px;}
table{width:100%;border-collapse:collapse;font-size:13px;}
th{position:sticky;top:0;background:var(--head);color:var(--primary-dark);font-weight:600;font-size:12px;text-align:left;padding:9px 10px;}
td{padding:9px 10px;border-bottom:1px solid #EEF1F5;}
td.num,th.num{text-align:right;}
tr.returned td{color:var(--danger);text-decoration:line-through;text-decoration-color:rgba(209,67,67,.6);background:repeating-linear-gradient(45deg,#FFF6F6,#FFF6F6 8px,#FFECEC 8px,#FFECEC 16px);}
tr.returned td .keep{text-decoration:none;display:inline-block;}
.rbadge{font-size:10px;background:var(--danger);color:#fff;padding:1px 7px;border-radius:8px;text-decoration:none;}
.dfoot{display:flex;align-items:center;gap:12px;padding:14px 18px;border-top:1px solid var(--border);}
.foot-note{font-size:12px;color:var(--muted);}
.foot-note .hot{color:var(--danger);font-weight:600;}
.x{margin-left:auto;border:0;background:transparent;font-size:18px;cursor:pointer;color:var(--muted);}
.hint{font-size:12px;color:var(--muted);background:#F0F6FF;border-left:3px solid var(--primary);padding:8px 12px;border-radius:4px;margin:10px 18px 0;}
/* 确认小窗 */
.confirm{z-index:20;}
.cbox{background:#fff;border-radius:10px;width:380px;max-width:92vw;padding:18px;}
.cbox h3{margin:0 0 8px;font-size:15px;}
.cbox p{font-size:13px;color:#444;margin:0 0 16px;line-height:1.6;}
.cbox .row{display:flex;justify-content:flex-end;gap:8px;}
</style>
</head>
<body>
<h1>退单 原型(已审核单据)</h1>
<div class="sub">出库单退单 → 数量加回库存;入库单退单 → 从库存删除。点按钮体验流程(纯原型,不连后端)。</div>
<div class="toggle">
<button id="tOut" class="on" onclick="setType('out')">出库单退单</button>
<button id="tIn" onclick="setType('in')">入库单退单</button>
</div>
<!-- 入口行 -->
<div class="listrow">
<span class="mono" id="entryNo">CKD20260621000012</span>
<span class="badge approved" id="entryBadge">已审核</span>
<span class="spacer"></span>
<button class="btn link">详情</button>
<button class="btn link">打印</button>
<button class="btn link" style="color:var(--accent)">结清</button>
<!-- 退单:error 样式 -->
<button class="btn error" onclick="openConfirm('entry')">退单</button>
</div>
<!-- 退单弹窗 -->
<div class="scrim" id="scrim">
<div class="dialog">
<div class="dhead">
<h2 id="dTitle">出库单退单 — CKD20260621000012</h2>
<button class="btn error-o" style="margin-left:14px" onclick="openConfirm('all')">全部退单</button>
<button class="x" onclick="cancelAll()"></button>
</div>
<div class="dmeta">
<span id="mPartnerLbl">客户:<b id="mPartner">张三烟酒行</b></span>
<span>仓库:<b>主仓</b></span>
<span id="mDateLbl">出库日期:<b>2026-06-21</b></span>
<span>状态:<b style="color:var(--success)">已审核</b></span>
</div>
<div class="hint" id="behaviorHint">提交后,已退单明细的数量将 <b>加回库存</b>(新建库存批次)。退单的明细不会删除,以红色删除线标注「已退单」。</div>
<div class="dbody">
<table>
<thead><tr>
<th>#</th><th>编码</th><th>名称</th><th>系列</th><th>规格</th>
<th class="num">数量</th><th class="num">单价</th><th class="num">金额</th><th style="width:96px">操作</th>
</tr></thead>
<tbody id="rows"></tbody>
</table>
</div>
<div class="dfoot">
<span class="foot-note" id="footNote">未选择退单明细</span>
<span class="spacer" style="flex:1"></span>
<button class="btn ghost" onclick="cancelAll()">取消</button>
<button class="btn error" id="submitBtn" disabled onclick="openConfirm('submit')">提交退单</button>
</div>
</div>
</div>
<!-- 通用确认小窗 -->
<div class="scrim confirm" id="confirm">
<div class="cbox">
<h3 id="cTitle">确认</h3>
<p id="cMsg"></p>
<div class="row">
<button class="btn ghost" onclick="closeConfirm()">取消</button>
<button class="btn error" id="cOk">确认</button>
</div>
</div>
</div>
<script>
const data = [
{code:'ZXZ010508', name:'贵州茅台酒', series:'飞天53度', spec:'500ml', qty:2, price:1499},
{code:'ZXZ027882', name:'五粮液', series:'第八代', spec:'500ml', qty:1, price:1080},
{code:'ZXZ016161', name:'国窖1573', series:'经典', spec:'500ml', qty:3, price:980},
{code:'ZXZ018666', name:'剑南春', series:'水晶剑', spec:'500ml', qty:6, price:430},
];
let type='out';
let returned = new Set(); // 已暂存退单的行 index
function setType(t){
type=t; returned.clear();
document.getElementById('tOut').classList.toggle('on',t==='out');
document.getElementById('tIn').classList.toggle('on',t==='in');
const isOut = t==='out';
document.getElementById('entryNo').textContent = isOut?'CKD20260621000012':'RKD20260621000034';
document.getElementById('dTitle').textContent = (isOut?'出库单退单 — CKD20260621000012':'入库单退单 — RKD20260621000034');
document.getElementById('mPartnerLbl').innerHTML = isOut?'客户:<b>张三烟酒行</b>':'供应商:<b>鼎晟供应链</b>';
document.getElementById('mDateLbl').innerHTML = isOut?'出库日期:<b>2026-06-21</b>':'入库日期:<b>2026-06-21</b>';
document.getElementById('behaviorHint').innerHTML = isOut
? '提交后,已退单明细的数量将 <b>加回库存</b>(新建库存批次)。退单的明细不会删除,以红色删除线标注「已退单」。'
: '提交后,已退单明细对应的库存将 <b>从库存中删除</b>。退单的明细不会删除,以红色删除线标注「已退单」。';
render();
}
function render(){
const tb=document.getElementById('rows'); tb.innerHTML='';
data.forEach((it,i)=>{
const isR=returned.has(i);
const tr=document.createElement('tr');
if(isR) tr.className='returned';
tr.innerHTML=`
<td>${i+1}</td>
<td class="mono">${it.code}</td>
<td>${it.name} ${isR?'<span class="rbadge">已退单</span>':''}</td>
<td>${it.series}</td><td>${it.spec}</td>
<td class="num">${it.qty}</td>
<td class="num">¥${it.price}</td>
<td class="num">¥${it.qty*it.price}</td>
<td><span class="keep">${ isR
? `<button class="btn link" onclick="undo(${i})">撤销</button>`
: `<button class="btn error-o" onclick="openConfirm('item',${i})">退单</button>`}</span></td>`;
tb.appendChild(tr);
});
const n=returned.size;
let sumQty=0; returned.forEach(i=>sumQty+=data[i].qty);
const fn=document.getElementById('footNote');
fn.innerHTML = n===0 ? '未选择退单明细'
: `已退单 <span class="hot">${n}</span> 项 · ${type==='out'?'退回库存':'从库存移除'} <span class="hot">${type==='out'?'+':''}${sumQty}</span> 件`;
document.getElementById('submitBtn').disabled = n===0;
// 入口徽章随暂存变化(仅示意)
const eb=document.getElementById('entryBadge');
if(n===0){eb.className='badge approved';eb.textContent='已审核';}
else if(n<data.length){eb.className='badge partial';eb.textContent='部分退单';}
else {eb.className='badge returned';eb.textContent='已退单';}
}
function undo(i){ returned.delete(i); render(); }
// 确认窗
let pending=null;
function openConfirm(kind,idx){
pending={kind,idx};
const c=document.getElementById('confirm');
const t=document.getElementById('cTitle'), m=document.getElementById('cMsg');
if(kind==='entry'){ t.textContent='退单确认'; m.textContent='确认对该已审核单据发起退单?将打开退单明细窗口。'; }
else if(kind==='item'){ const it=data[idx]; t.textContent='退单确认'; m.innerHTML=`确认退掉以下明细?<br><b>${it.name}</b> · ${it.series} · ${it.spec}<br><span style="color:var(--muted)">编码 ${it.code} · 数量 ${it.qty}</span>`; }
else if(kind==='all'){ t.textContent='全部退单确认'; m.textContent='确认退掉本单全部明细?'; }
else if(kind==='submit'){ t.textContent='提交退单'; m.innerHTML=`确认提交退单?${type==='out'?'退回库存':'从库存删除'}将立即生效,且不可撤销。`; }
c.classList.add('show');
}
function closeConfirm(){ document.getElementById('confirm').classList.remove('show'); pending=null; }
document.getElementById('cOk').onclick=()=>{
if(!pending) return;
const {kind,idx}=pending;
if(kind==='entry'){ closeConfirm(); document.getElementById('scrim').classList.add('show'); render(); return; }
if(kind==='item'){ returned.add(idx); }
if(kind==='all'){ data.forEach((_,i)=>returned.add(i)); }
if(kind==='submit'){ closeConfirm(); alert('(原型)退单已提交成功,库存已更新。'); cancelAll(); return; }
closeConfirm(); render();
};
function cancelAll(){ returned.clear(); document.getElementById('scrim').classList.remove('show'); render(); }
render();
</script>
</body>
</html>