fix: 子卷包装关系新增判断erp卷号是否存在
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>批量新增子卷包装关系 · 卡片式方案</title>
|
||||
<style>
|
||||
:root{--blue:#2f6bff;--blue2:#eef4ff;--ink:#172033;--muted:#737b8c;--line:#e7eaf0;--bg:#f4f6fa;--white:#fff;--red:#e5484d;--green:#21a366;--shadow:0 18px 60px rgba(20,36,72,.18)}
|
||||
*{box-sizing:border-box}html,body{height:100%;margin:0;font:14px/1.5 "Microsoft YaHei","Segoe UI",sans-serif;color:var(--ink);background:var(--bg)}button,input,select{font:inherit}button{cursor:pointer}.layout{display:grid;grid-template-columns:220px 1fr;height:100%;overflow:hidden}.side{background:#17233b;color:#c9d2e3}.logo{height:60px;display:flex;align-items:center;gap:11px;padding:0 20px;color:#fff;font-size:16px;font-weight:700}.logo-badge{width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#63a7ff,#2f6bff);display:grid;place-items:center;font-size:12px}.nav{padding:12px}.nav-label{font-size:12px;color:#697996;padding:12px 10px 7px}.nav-item{padding:11px 13px;border-radius:8px;margin:3px 0}.nav-item.active{background:#2f6bff;color:#fff}.workspace{min-width:0;display:flex;flex-direction:column}.top{height:60px;background:#fff;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 22px}.top-right{display:flex;align-items:center;gap:18px}.avatar{width:30px;height:30px;border-radius:50%;display:grid;place-items:center;background:#eaf1ff;color:var(--blue);font-weight:700}.page{padding:18px;overflow:auto}.crumb{font-size:13px;color:#8a91a1;margin-bottom:14px}.panel{background:#fff;border:1px solid var(--line);border-radius:10px;padding:18px}.filter-row{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}.field-line{display:flex;align-items:center;gap:9px}.field-line label{white-space:nowrap}.control{height:34px;width:100%;border:1px solid #d9dde7;border-radius:7px;padding:0 10px;background:#fff;outline:none}.control:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(47,107,255,.12)}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}.btn{border:1px solid #d9dde7;background:#fff;color:#344054;height:34px;border-radius:7px;padding:0 14px;display:inline-flex;align-items:center;justify-content:center;gap:6px}.btn:hover{border-color:var(--blue);color:var(--blue)}.btn.primary{background:var(--blue);border-color:var(--blue);color:#fff}.btn.ghost-blue{background:var(--blue2);border-color:#cfe0ff;color:var(--blue)}.btn.danger{color:var(--red)}.list-panel{margin-top:14px}.list-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:15px}.list-title{font-size:17px;font-weight:700}.fake-table{width:100%;border-collapse:collapse}.fake-table th{background:#f7f8fa;text-align:left;color:#667085;padding:11px 12px}.fake-table td{padding:13px 12px;border-bottom:1px solid #f0f1f4}.pill{display:inline-flex;padding:2px 9px;border-radius:20px;background:#eaf8f1;color:#198754;font-size:12px}
|
||||
.overlay{position:fixed;inset:0;background:rgba(18,25,38,.52);z-index:20;display:grid;place-items:center;padding:24px}.overlay.hidden{display:none}.modal{width:min(1180px,calc(100vw - 48px));height:min(790px,calc(100vh - 50px));background:#f7f9fc;border-radius:14px;box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden}.modal-head{height:68px;background:#fff;border-bottom:1px solid var(--line);padding:0 24px;display:flex;align-items:center;justify-content:space-between}.modal-title{display:flex;align-items:center;gap:12px}.title-icon{width:38px;height:38px;border-radius:10px;background:var(--blue2);color:var(--blue);display:grid;place-items:center;font-size:20px}.modal-title h2{font-size:18px;margin:0}.modal-title p{margin:2px 0 0;color:var(--muted);font-size:12px}.icon-btn{width:34px;height:34px;border:0;border-radius:8px;background:transparent;color:#8a91a1;font-size:22px}.icon-btn:hover{background:#f0f2f5}.command{height:60px;background:#fff;padding:12px 24px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line)}.command-left{display:flex;gap:9px}.counter{color:var(--muted)}.counter strong{font-size:18px;color:var(--blue);margin:0 3px}.modal-body{flex:1;min-height:0;overflow:auto;padding:16px 24px 22px}.notice{border-radius:9px;background:#fff7e8;border:1px solid #ffe0a3;color:#8a5a00;padding:10px 13px;margin-bottom:13px;display:none}.notice.show{display:block}.record{background:#fff;border:1px solid var(--line);border-radius:12px;margin-bottom:14px;box-shadow:0 2px 8px rgba(23,32,51,.04);overflow:hidden}.record.invalid{border-color:#f2a7aa}.record-head{height:48px;background:linear-gradient(90deg,#f8faff,#fff);border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 16px}.record-no{display:flex;align-items:center;gap:9px;font-weight:700}.number{width:26px;height:26px;border-radius:8px;background:var(--blue);color:#fff;display:grid;place-items:center;font-size:12px}.record-actions{display:flex;gap:3px}.text-btn{border:0;background:transparent;color:var(--blue);padding:6px 9px;border-radius:6px}.text-btn:hover{background:var(--blue2)}.text-btn.delete{color:var(--red)}.record-body{padding:15px 16px 17px}.section{display:grid;grid-template-columns:125px 1fr;gap:14px;align-items:start}.section+.section{border-top:1px dashed #e3e7ef;margin-top:14px;padding-top:14px}.section-label{display:flex;align-items:center;gap:7px;color:#576071;font-weight:700;padding-top:8px}.section-dot{width:7px;height:7px;border-radius:50%;background:#84a9ff}.grid{display:grid;gap:13px}.grid.identity{grid-template-columns:1.2fr 1.2fr .8fr}.grid.spec{grid-template-columns:1.15fr repeat(3,.75fr)}.grid.lifecycle{grid-template-columns:1fr 1fr 1fr}.form-item label{display:flex;gap:3px;color:#596275;font-size:12px;margin-bottom:5px}.req{color:var(--red)}.form-item input,.form-item select{height:36px;width:100%;border:1px solid #d9dde7;border-radius:7px;padding:0 10px;background:#fff;outline:none}.form-item input:focus,.form-item select:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(47,107,255,.1)}.form-item input[readonly]{background:#f2f4f7;color:#667085}.with-unit{position:relative}.with-unit input{padding-right:36px}.unit{position:absolute;right:10px;bottom:9px;color:#98a0af;font-size:12px}.form-item.error input,.form-item.error select{border-color:var(--red);background:#fff8f8}.error-msg{display:none;color:var(--red);font-size:11px;margin-top:3px}.form-item.error .error-msg{display:block}.modal-foot{height:68px;background:#fff;border-top:1px solid var(--line);padding:0 24px;display:flex;align-items:center;justify-content:space-between}.foot-note{font-size:12px;color:var(--muted)}.foot-actions{display:flex;gap:10px}.foot-actions .btn{height:38px;min-width:96px}.confirm-layer{position:absolute;inset:0;background:rgba(16,24,40,.28);display:grid;place-items:center;z-index:5}.confirm-layer.hidden{display:none}.confirm-box{width:390px;background:#fff;border-radius:12px;padding:22px;box-shadow:var(--shadow)}.confirm-box h3{margin:0 0 8px}.confirm-box p{margin:0;color:var(--muted)}.confirm-buttons{display:flex;justify-content:flex-end;gap:8px;margin-top:22px}.toast{position:fixed;top:22px;left:50%;transform:translateX(-50%) translateY(-10px);padding:11px 18px;background:#fff;border-radius:9px;box-shadow:0 8px 30px rgba(0,0,0,.18);z-index:50;opacity:0;transition:.2s;pointer-events:none}.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}.toast span{color:var(--green);font-weight:700;margin-right:7px}
|
||||
.table-modal{width:min(1360px,calc(100vw - 40px));height:min(740px,calc(100vh - 48px));background:#fff}.table-modal .modal-body{padding:14px 20px 18px;background:#f7f9fc}.table-tip{display:flex;align-items:center;justify-content:space-between;background:#eef5ff;border:1px solid #cfe0ff;color:#315b9d;border-radius:8px;padding:9px 12px;margin-bottom:12px}.edit-wrap{height:100%;background:#fff;border:1px solid #dfe3ea;border-radius:9px;overflow:auto}.edit-table{border-collapse:separate;border-spacing:0;min-width:1780px;width:100%;font-size:13px}.edit-table th,.edit-table td{border-right:1px solid #e5e8ee;border-bottom:1px solid #e5e8ee;padding:7px;background:#fff;vertical-align:top}.edit-table thead th{position:sticky;top:0;z-index:3;height:44px;background:#f3f6fb;color:#4b5870;text-align:left;vertical-align:middle}.edit-table .fix-left{position:sticky;left:0;z-index:2}.edit-table .fix-right{position:sticky;right:0;z-index:2;box-shadow:-5px 0 12px rgba(21,35,62,.06)}.edit-table thead .fix-left,.edit-table thead .fix-right{z-index:5}.edit-table td.fix-left,.edit-table td.fix-right{background:#fff}.edit-table .index{width:54px;text-align:center;color:#788196}.edit-cell{min-height:48px}.edit-cell input,.edit-cell select{width:100%;height:34px;border:1px solid #d9dde7;border-radius:6px;padding:0 9px;outline:none;background:#fff}.edit-cell input:focus,.edit-cell select:focus{border-color:var(--blue);box-shadow:0 0 0 2px rgba(47,107,255,.1)}.edit-cell input[readonly]{background:#f2f4f7;color:#667085}.edit-cell.bad input,.edit-cell.bad select{border-color:var(--red);background:#fff8f8}.cell-error{display:none;color:var(--red);font-size:10px;margin-top:2px}.edit-cell.bad .cell-error{display:block}.table-row-actions{height:34px;display:flex;align-items:center;justify-content:center;gap:2px;white-space:nowrap}.th-req:before{content:"*";color:var(--red);margin-right:3px}.table-notice{display:none;color:#b54708;background:#fff7e8;border:1px solid #ffdca0;border-radius:7px;padding:7px 10px;margin-bottom:10px}.table-notice.show{display:block}.view-switch{display:flex;background:#f0f2f5;border-radius:8px;padding:3px;gap:2px}.view-btn{height:30px;padding:0 14px;border:0;border-radius:6px;background:transparent;color:#667085}.view-btn.active{background:#fff;color:var(--blue);box-shadow:0 1px 4px rgba(30,45,75,.14);font-weight:700}.view-hidden{display:none!important}.hybrid-cards{height:100%;overflow:auto;padding-right:3px}.hybrid-cards .record{margin-bottom:12px}.hybrid-cards .record-body{padding-top:13px;padding-bottom:14px}@media(max-width:1000px){.layout{grid-template-columns:68px 1fr}.side .logo-name,.nav-item span,.nav-label{display:none}.logo{justify-content:center;padding:0}.nav-item{text-align:center}.filter-row{grid-template-columns:repeat(2,1fr)}.section{grid-template-columns:1fr}.grid.identity,.grid.spec,.grid.lifecycle{grid-template-columns:repeat(2,1fr)}}@media(prefers-reduced-motion:reduce){*{transition:none!important}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout"><aside class="side"><div class="logo"><div class="logo-badge">NL</div><span class="logo-name">华创智能制造</span></div><nav class="nav"><div class="nav-label">物流管理</div><div class="nav-item">▦ <span>包装点管理</span></div><div class="nav-item active">▣ <span>子卷包装关系</span></div><div class="nav-item">▤ <span>成品入库</span></div><div class="nav-label">系统管理</div><div class="nav-item">⚙ <span>基础设置</span></div></nav></aside><div class="workspace"><header class="top"><span>☰</span><div class="top-right"><span>⌕</span><span>消息</span><span>管理员</span><div class="avatar">管</div></div></header><main class="page"><div class="crumb">首页 / 物流管理 / 子卷包装关系</div><section class="panel"><div class="filter-row"><div class="field-line"><label>木箱唯一码</label><input class="control" placeholder="请输入木箱唯一码"></div><div class="field-line"><label>木箱类型</label><select class="control"><option>全部</option><option>BX-A</option><option>BX-B</option></select></div><div class="field-line"><label>子卷号</label><input class="control" placeholder="请输入子卷号"></div><div class="field-line"><label>状态</label><select class="control"><option>全部</option><option>待包装</option><option>已包装</option></select></div></div><div class="actions"><button class="btn">重置</button><button class="btn primary">搜索</button></div></section><section class="panel list-panel"><div class="list-head"><div class="list-title">子卷包装关系列表</div><div><button class="btn primary" id="openBtn">+ 批量新增</button> <button class="btn ghost-blue" id="openTableBtn">▦ 表格批量新增</button> <button class="btn ghost-blue" id="openHybridBtn">⇄ 自由切换</button> <button class="btn">导出</button></div></div><table class="fake-table"><thead><tr><th>木箱唯一码</th><th>木箱类型</th><th>尺寸(mm)</th><th>子卷号</th><th>入库日期</th><th>状态</th></tr></thead><tbody id="listBody"><tr><td>BXSN-260817-001</td><td>BX-A</td><td>1200 × 1000 × 900</td><td>JR-260817-01</td><td>2026-08-17</td><td><span class="pill">已入库</span></td></tr><tr><td>BXSN-260817-002</td><td>BX-B</td><td>1100 × 900 × 850</td><td>JR-260817-02</td><td>2026-08-17</td><td><span class="pill">已包装</span></td></tr></tbody></table></section></main></div></div>
|
||||
|
||||
<div class="overlay" id="modal" role="dialog" aria-modal="true" aria-labelledby="title"><section class="modal"><header class="modal-head"><div class="modal-title"><div class="title-icon">⊞</div><div><h2 id="title">批量新增子卷包装关系</h2><p>逐条录入木箱、子卷及入库信息,尺寸由木箱类型自动匹配</p></div></div><button class="icon-btn" id="closeBtn" aria-label="关闭">×</button></header><div class="command"><div class="command-left"><button class="btn primary" id="addBtn">+ 新增一行</button><button class="btn ghost-blue" id="clearBtn">⌫ 清空全部</button></div><div class="counter">已添加 <strong id="count">2</strong> 条记录</div></div><main class="modal-body" id="scroll"><div class="notice" id="notice">请检查标红字段后再提交。</div><div id="records"></div></main><footer class="modal-foot"><div class="foot-note"><span class="req">*</span> 为必填项;所有数据仅用于原型演示</div><div class="foot-actions"><button class="btn" id="cancelBtn">取消</button><button class="btn primary" id="submitBtn">确认新增(2 条)</button></div></footer></section><div class="confirm-layer hidden" id="confirmLayer"><div class="confirm-box"><h3 id="confirmTitle">确认操作</h3><p id="confirmText"></p><div class="confirm-buttons"><button class="btn" id="confirmCancel">取消</button><button class="btn primary" id="confirmOk">确认</button></div></div></div></div><div class="toast" id="toast"><span>✓</span><b id="toastText"></b></div>
|
||||
|
||||
<div class="overlay hidden" id="tableModal" role="dialog" aria-modal="true" aria-labelledby="tableTitle"><section class="modal table-modal"><header class="modal-head"><div class="modal-title"><div class="title-icon">⇄</div><div><h2 id="tableTitle">自由切换批量新增</h2><p>卡片适合逐条录入,表格适合批量核对;切换时数据自动保留</p></div></div><button class="icon-btn" id="tableCloseBtn" aria-label="关闭自由切换批量新增">×</button></header><div class="command"><div class="command-left"><button class="btn primary" id="tableAddBtn">+ 新增一行</button><button class="btn ghost-blue" id="tableClearBtn">⌫ 清空全部</button></div><div class="view-switch" aria-label="录入视图"><button class="view-btn" id="cardViewBtn">▤ 卡片</button><button class="view-btn active" id="tableViewBtn">▦ 表格</button></div><div class="counter">当前共 <strong id="tableCount">3</strong> 条记录</div></div><main class="modal-body"><div class="table-notice" id="tableNotice"></div><div class="table-tip"><span id="viewTip">选择木箱类型后自动带出长、宽、高;复制后请补充新的唯一码和子卷号。</span><span>尺寸单位:mm</span></div><div class="hybrid-cards view-hidden" id="hybridCards"></div><div class="edit-wrap" id="hybridTable"><table class="edit-table"><thead><tr><th class="index fix-left">序号</th><th class="th-req" style="min-width:190px">木箱唯一码</th><th class="th-req" style="min-width:180px">木箱类型</th><th style="min-width:120px">木箱长</th><th style="min-width:120px">木箱宽</th><th style="min-width:120px">木箱高</th><th class="th-req" style="min-width:140px">保质期(天)</th><th class="th-req" style="min-width:150px">入库日期</th><th class="th-req" style="min-width:190px">子卷号</th><th class="th-req" style="min-width:140px">状态</th><th class="fix-right" style="min-width:116px;text-align:center">操作</th></tr></thead><tbody id="tableBody"></tbody></table></div></main><footer class="modal-foot"><div class="foot-note"><span class="req">*</span> 为必填项;数据仅用于原型演示</div><div class="foot-actions"><button class="btn" id="tableCancelBtn">取消</button><button class="btn primary" id="tableSubmitBtn">确认新增(3 条)</button></div></footer></section><div class="confirm-layer hidden" id="tableConfirmLayer"><div class="confirm-box"><h3 id="tableConfirmTitle">确认操作</h3><p id="tableConfirmText"></p><div class="confirm-buttons"><button class="btn" id="tableConfirmCancel">取消</button><button class="btn primary" id="tableConfirmOk">确认</button></div></div></div></div>
|
||||
|
||||
<script>
|
||||
const boxTypes={'BX-A 标准出口箱':{length:1200,width:1000,high:900},'BX-B 加固木箱':{length:1100,width:900,high:850},'BX-C 小型木箱':{length:800,width:600,high:600}};
|
||||
const statusOptions=['待包装','已包装','已入库','已作废'];
|
||||
let seq=1,rows=[],errors=[],pending=null;
|
||||
const $=id=>document.getElementById(id),fields=['package_box_sn','box_type','quality_guaran_period','date_of_FG_inbound','container_name','status'];
|
||||
const empty=()=>({key:seq++,package_box_sn:'',box_type:'',box_length:'',box_width:'',box_high:'',quality_guaran_period:'',date_of_FG_inbound:'',container_name:'',status:'待包装'});
|
||||
const esc=v=>String(v??'').replace(/[&<>"]/g,x=>({'&':'&','<':'<','>':'>','"':'"'}[x]));
|
||||
const error=(key,field)=>errors.find(e=>e.key===key&&e.field===field);
|
||||
function item(r,field,label,control){const e=error(r.key,field);return `<div class="form-item ${e?'error':''}" data-item="${r.key}-${field}"><label><span class="req">*</span>${label}</label>${control}<div class="error-msg">${e?esc(e.msg):''}</div></div>`}
|
||||
function render(){
|
||||
$('records').innerHTML=rows.map((r,i)=>`<article class="record ${errors.some(e=>e.key===r.key)?'invalid':''}"><header class="record-head"><div class="record-no"><span class="number">${i+1}</span>包装关系 ${String(i+1).padStart(2,'0')}</div><div class="record-actions"><button class="text-btn" data-act="copy" data-key="${r.key}">⧉ 复制</button><button class="text-btn delete" data-act="delete" data-key="${r.key}">⌫ 删除</button></div></header><div class="record-body"><section class="section"><div class="section-label"><span class="section-dot"></span>识别信息</div><div class="grid identity">${item(r,'package_box_sn','木箱唯一码',`<input data-key="${r.key}" data-field="package_box_sn" value="${esc(r.package_box_sn)}" placeholder="扫描或输入木箱唯一码">`)}${item(r,'container_name','子卷号',`<input data-key="${r.key}" data-field="container_name" value="${esc(r.container_name)}" placeholder="扫描或输入子卷号">`)}${item(r,'status','状态',`<select data-key="${r.key}" data-field="status">${statusOptions.map(s=>`<option ${r.status===s?'selected':''}>${s}</option>`).join('')}</select>`)}</div></section><section class="section"><div class="section-label"><span class="section-dot"></span>木箱规格</div><div class="grid spec">${item(r,'box_type','木箱类型',`<select data-key="${r.key}" data-field="box_type"><option value="">请选择木箱类型</option>${Object.keys(boxTypes).map(s=>`<option ${r.box_type===s?'selected':''}>${s}</option>`).join('')}</select>`)}<div class="form-item"><label>木箱长</label><div class="with-unit"><input readonly value="${esc(r.box_length)}" placeholder="自动带出"><span class="unit">mm</span></div></div><div class="form-item"><label>木箱宽</label><div class="with-unit"><input readonly value="${esc(r.box_width)}" placeholder="自动带出"><span class="unit">mm</span></div></div><div class="form-item"><label>木箱高</label><div class="with-unit"><input readonly value="${esc(r.box_high)}" placeholder="自动带出"><span class="unit">mm</span></div></div></div></section><section class="section"><div class="section-label"><span class="section-dot"></span>时效信息</div><div class="grid lifecycle">${item(r,'quality_guaran_period','保质期',`<div class="with-unit"><input type="number" min="1" data-key="${r.key}" data-field="quality_guaran_period" value="${esc(r.quality_guaran_period)}" placeholder="例如:90"><span class="unit">天</span></div>`)}${item(r,'date_of_FG_inbound','入库日期',`<input type="date" data-key="${r.key}" data-field="date_of_FG_inbound" value="${esc(r.date_of_FG_inbound)}">`)}<div></div></div></section></div></article>`).join('');
|
||||
$('count').textContent=rows.length;$('submitBtn').textContent=`确认新增(${rows.length} 条)`;
|
||||
}
|
||||
function reset(){rows=[empty(),empty()];errors=[];$('notice').classList.remove('show');render()}
|
||||
$('records').addEventListener('input',e=>{const k=Number(e.target.dataset.key),f=e.target.dataset.field;if(!k||!f)return;rows.find(r=>r.key===k)[f]=e.target.value;errors=errors.filter(x=>!(x.key===k&&x.field===f));e.target.closest('.form-item')?.classList.remove('error')});
|
||||
$('records').addEventListener('change',e=>{const k=Number(e.target.dataset.key),f=e.target.dataset.field;if(f!=='box_type')return;const r=rows.find(x=>x.key===k),d=boxTypes[e.target.value];r.box_type=e.target.value;r.box_length=d?.length||'';r.box_width=d?.width||'';r.box_high=d?.high||'';render()});
|
||||
$('records').addEventListener('click',e=>{const b=e.target.closest('[data-act]');if(!b)return;const i=rows.findIndex(r=>r.key===Number(b.dataset.key));if(b.dataset.act==='copy'){const n={...rows[i],key:seq++,package_box_sn:'',container_name:''};rows.splice(i+1,0,n);errors=[];render();toast('已复制记录,请补充唯一码和子卷号')}else{rows.splice(i,1);if(!rows.length)rows=[empty()];errors=[];render()}});
|
||||
$('addBtn').onclick=()=>{rows.push(empty());render();$('records').lastElementChild.scrollIntoView({behavior:'smooth',block:'center'})};
|
||||
function ask(title,text,fn){$('confirmTitle').textContent=title;$('confirmText').textContent=text;pending=fn;$('confirmLayer').classList.remove('hidden')}
|
||||
$('clearBtn').onclick=()=>ask('清空全部记录','已输入的数据将被清空,并恢复为 2 条空白记录。',reset);$('confirmCancel').onclick=()=>{$('confirmLayer').classList.add('hidden');pending=null};$('confirmOk').onclick=()=>{const fn=pending;$('confirmLayer').classList.add('hidden');pending=null;fn?.()};
|
||||
const hasData=()=>rows.some(r=>fields.some(f=>f!=='status'&&String(r[f]||'').trim()));
|
||||
function close(){ $('modal').classList.add('hidden') }
|
||||
function requestClose(){hasData()?ask('放弃本次录入?','关闭后当前未提交内容不会保留。',()=>{close();reset()}):close()}
|
||||
$('cancelBtn').onclick=requestClose;$('closeBtn').onclick=requestClose;$('openBtn').onclick=()=>{reset();$('modal').classList.remove('hidden')};
|
||||
function validate(){errors=[];rows.forEach(r=>{fields.forEach(f=>{if(!String(r[f]??'').trim())errors.push({key:r.key,field:f,msg:'此项为必填'})});if(r.quality_guaran_period&&Number(r.quality_guaran_period)<=0)errors.push({key:r.key,field:'quality_guaran_period',msg:'必须大于 0 天'})});['package_box_sn','container_name'].forEach(f=>{const seen=new Map();rows.forEach((r,i)=>{const v=String(r[f]||'').trim();if(!v)return;if(seen.has(v)){const j=seen.get(v);errors.push({key:r.key,field:f,msg:`与第 ${j+1} 条重复`});errors.push({key:rows[j].key,field:f,msg:`与第 ${i+1} 条重复`})}else seen.set(v,i)})});render();if(errors.length){$('notice').textContent=`发现 ${errors.length} 处问题,请检查标红字段。`;$('notice').classList.add('show');document.querySelector(`[data-item="${errors[0].key}-${errors[0].field}"]`)?.scrollIntoView({behavior:'smooth',block:'center'});return false}$('notice').classList.remove('show');return true}
|
||||
$('submitBtn').onclick=()=>{if(!validate())return;const b=$('submitBtn'),n=rows.length;b.disabled=true;b.textContent='正在提交…';setTimeout(()=>{const r=rows[0];$('listBody').insertAdjacentHTML('afterbegin',`<tr><td>${esc(r.package_box_sn)}</td><td>${esc(r.box_type.split(' ')[0])}</td><td>${r.box_length} × ${r.box_width} × ${r.box_high}</td><td>${esc(r.container_name)}</td><td>${esc(r.date_of_FG_inbound)}</td><td><span class="pill">${esc(r.status)}</span></td></tr>`);b.disabled=false;close();toast(`成功新增 ${n} 条子卷包装关系`);reset()},600)};
|
||||
function toast(t){$('toastText').textContent=t;$('toast').classList.add('show');setTimeout(()=>$('toast').classList.remove('show'),2200)}
|
||||
|
||||
let tableSeq=1,tableRows=[],tableErrors=[],tablePending=null,hybridView='table';
|
||||
const tableRequired=['package_box_sn','box_type','quality_guaran_period','date_of_FG_inbound','container_name','status'];
|
||||
const tableEmpty=()=>({key:tableSeq++,package_box_sn:'',box_type:'',box_length:'',box_width:'',box_high:'',quality_guaran_period:'',date_of_FG_inbound:'',container_name:'',status:'待包装'});
|
||||
const tableError=(key,field)=>tableErrors.find(e=>e.key===key&&e.field===field);
|
||||
function tableCell(r,field,html){const e=tableError(r.key,field);return `<td><div class="edit-cell ${e?'bad':''}" data-table-cell="${r.key}-${field}">${html}<div class="cell-error">${e?esc(e.msg):''}</div></div></td>`}
|
||||
function hybridItem(r,field,label,control){const e=tableError(r.key,field);return `<div class="form-item ${e?'error':''}" data-hybrid-item="${r.key}-${field}"><label><span class="req">*</span>${label}</label>${control}<div class="error-msg">${e?esc(e.msg):''}</div></div>`}
|
||||
function renderHybridCards(){
|
||||
$('hybridCards').innerHTML=tableRows.map((r,i)=>`<article class="record ${tableErrors.some(e=>e.key===r.key)?'invalid':''}"><header class="record-head"><div class="record-no"><span class="number">${i+1}</span>包装关系 ${String(i+1).padStart(2,'0')}</div><div class="record-actions"><button class="text-btn" data-hact="copy" data-hkey="${r.key}">⧉ 复制</button><button class="text-btn delete" data-hact="delete" data-hkey="${r.key}">⌫ 删除</button></div></header><div class="record-body"><section class="section"><div class="section-label"><span class="section-dot"></span>识别信息</div><div class="grid identity">${hybridItem(r,'package_box_sn','木箱唯一码',`<input data-hkey="${r.key}" data-hfield="package_box_sn" value="${esc(r.package_box_sn)}" placeholder="扫描或输入木箱唯一码">`)}${hybridItem(r,'container_name','子卷号',`<input data-hkey="${r.key}" data-hfield="container_name" value="${esc(r.container_name)}" placeholder="扫描或输入子卷号">`)}${hybridItem(r,'status','状态',`<select data-hkey="${r.key}" data-hfield="status">${statusOptions.map(s=>`<option ${r.status===s?'selected':''}>${s}</option>`).join('')}</select>`)}</div></section><section class="section"><div class="section-label"><span class="section-dot"></span>木箱规格</div><div class="grid spec">${hybridItem(r,'box_type','木箱类型',`<select data-hkey="${r.key}" data-hfield="box_type"><option value="">请选择木箱类型</option>${Object.keys(boxTypes).map(s=>`<option ${r.box_type===s?'selected':''}>${s}</option>`).join('')}</select>`)}<div class="form-item"><label>木箱长</label><div class="with-unit"><input readonly value="${esc(r.box_length)}" placeholder="自动带出"><span class="unit">mm</span></div></div><div class="form-item"><label>木箱宽</label><div class="with-unit"><input readonly value="${esc(r.box_width)}" placeholder="自动带出"><span class="unit">mm</span></div></div><div class="form-item"><label>木箱高</label><div class="with-unit"><input readonly value="${esc(r.box_high)}" placeholder="自动带出"><span class="unit">mm</span></div></div></div></section><section class="section"><div class="section-label"><span class="section-dot"></span>时效信息</div><div class="grid lifecycle">${hybridItem(r,'quality_guaran_period','保质期',`<div class="with-unit"><input type="number" min="1" data-hkey="${r.key}" data-hfield="quality_guaran_period" value="${esc(r.quality_guaran_period)}" placeholder="例如:90"><span class="unit">天</span></div>`)}${hybridItem(r,'date_of_FG_inbound','入库日期',`<input type="date" data-hkey="${r.key}" data-hfield="date_of_FG_inbound" value="${esc(r.date_of_FG_inbound)}">`)}<div></div></div></section></div></article>`).join('');
|
||||
}
|
||||
function renderTableRows(){
|
||||
$('tableBody').innerHTML=tableRows.map((r,i)=>`<tr><td class="index fix-left">${i+1}</td>${tableCell(r,'package_box_sn',`<input data-tkey="${r.key}" data-tfield="package_box_sn" value="${esc(r.package_box_sn)}" placeholder="请输入木箱唯一码">`)}${tableCell(r,'box_type',`<select data-tkey="${r.key}" data-tfield="box_type"><option value="">请选择木箱类型</option>${Object.keys(boxTypes).map(s=>`<option ${r.box_type===s?'selected':''}>${s}</option>`).join('')}</select>`)}${tableCell(r,'box_length',`<input readonly value="${esc(r.box_length)}" placeholder="自动带出">`)}${tableCell(r,'box_width',`<input readonly value="${esc(r.box_width)}" placeholder="自动带出">`)}${tableCell(r,'box_high',`<input readonly value="${esc(r.box_high)}" placeholder="自动带出">`)}${tableCell(r,'quality_guaran_period',`<input type="number" min="1" data-tkey="${r.key}" data-tfield="quality_guaran_period" value="${esc(r.quality_guaran_period)}" placeholder="例如:90">`)}${tableCell(r,'date_of_FG_inbound',`<input type="date" data-tkey="${r.key}" data-tfield="date_of_FG_inbound" value="${esc(r.date_of_FG_inbound)}">`)}${tableCell(r,'container_name',`<input data-tkey="${r.key}" data-tfield="container_name" value="${esc(r.container_name)}" placeholder="请输入子卷号">`)}${tableCell(r,'status',`<select data-tkey="${r.key}" data-tfield="status">${statusOptions.map(s=>`<option ${r.status===s?'selected':''}>${s}</option>`).join('')}</select>`)}<td class="fix-right"><div class="table-row-actions"><button class="text-btn" data-tact="copy" data-tkey="${r.key}">复制</button><button class="text-btn delete" data-tact="delete" data-tkey="${r.key}">删除</button></div></td></tr>`).join('');
|
||||
renderHybridCards();$('tableCount').textContent=tableRows.length;$('tableSubmitBtn').textContent=`确认新增(${tableRows.length} 条)`;
|
||||
}
|
||||
function tableReset(){tableRows=[tableEmpty(),tableEmpty(),tableEmpty()];tableErrors=[];$('tableNotice').classList.remove('show');renderTableRows()}
|
||||
$('tableBody').addEventListener('input',e=>{const key=Number(e.target.dataset.tkey),field=e.target.dataset.tfield;if(!key||!field)return;tableRows.find(r=>r.key===key)[field]=e.target.value;tableErrors=tableErrors.filter(x=>!(x.key===key&&x.field===field));e.target.closest('.edit-cell')?.classList.remove('bad')});
|
||||
$('tableBody').addEventListener('change',e=>{const key=Number(e.target.dataset.tkey),field=e.target.dataset.tfield;if(field!=='box_type')return;const r=tableRows.find(x=>x.key===key),d=boxTypes[e.target.value];r.box_type=e.target.value;r.box_length=d?.length||'';r.box_width=d?.width||'';r.box_high=d?.high||'';renderTableRows()});
|
||||
$('tableBody').addEventListener('click',e=>{const b=e.target.closest('[data-tact]');if(!b)return;const i=tableRows.findIndex(r=>r.key===Number(b.dataset.tkey));if(b.dataset.tact==='copy'){const n={...tableRows[i],key:tableSeq++,package_box_sn:'',container_name:''};tableRows.splice(i+1,0,n);tableErrors=[];renderTableRows();toast('已复制该行,请补充唯一码和子卷号')}else{tableRows.splice(i,1);if(!tableRows.length)tableRows=[tableEmpty()];tableErrors=[];renderTableRows()}});
|
||||
$('hybridCards').addEventListener('input',e=>{const key=Number(e.target.dataset.hkey),field=e.target.dataset.hfield;if(!key||!field)return;tableRows.find(r=>r.key===key)[field]=e.target.value;tableErrors=tableErrors.filter(x=>!(x.key===key&&x.field===field));e.target.closest('.form-item')?.classList.remove('error')});
|
||||
$('hybridCards').addEventListener('change',e=>{const key=Number(e.target.dataset.hkey),field=e.target.dataset.hfield;if(field!=='box_type')return;const r=tableRows.find(x=>x.key===key),d=boxTypes[e.target.value];r.box_type=e.target.value;r.box_length=d?.length||'';r.box_width=d?.width||'';r.box_high=d?.high||'';renderTableRows()});
|
||||
$('hybridCards').addEventListener('click',e=>{const b=e.target.closest('[data-hact]');if(!b)return;const i=tableRows.findIndex(r=>r.key===Number(b.dataset.hkey));if(b.dataset.hact==='copy'){const n={...tableRows[i],key:tableSeq++,package_box_sn:'',container_name:''};tableRows.splice(i+1,0,n);tableErrors=[];renderTableRows();toast('已复制该卡片,请补充唯一码和子卷号')}else{tableRows.splice(i,1);if(!tableRows.length)tableRows=[tableEmpty()];tableErrors=[];renderTableRows()}});
|
||||
$('tableAddBtn').onclick=()=>{tableRows.push(tableEmpty());renderTableRows();$('tableBody').lastElementChild?.querySelector('input')?.focus()};
|
||||
function tableAsk(title,text,fn){$('tableConfirmTitle').textContent=title;$('tableConfirmText').textContent=text;tablePending=fn;$('tableConfirmLayer').classList.remove('hidden')}
|
||||
$('tableClearBtn').onclick=()=>tableAsk('清空全部记录','已录入的数据将被清空,并恢复为 3 条空白记录。',tableReset);
|
||||
$('tableConfirmCancel').onclick=()=>{$('tableConfirmLayer').classList.add('hidden');tablePending=null};
|
||||
$('tableConfirmOk').onclick=()=>{const fn=tablePending;$('tableConfirmLayer').classList.add('hidden');tablePending=null;fn?.()};
|
||||
const tableHasData=()=>tableRows.some(r=>tableRequired.some(f=>f!=='status'&&String(r[f]||'').trim()));
|
||||
function closeTable(){ $('tableModal').classList.add('hidden') }
|
||||
function requestTableClose(){tableHasData()?tableAsk('放弃表格录入?','关闭后当前未提交内容不会保留。',()=>{closeTable();tableReset()}):closeTable()}
|
||||
function setHybridView(view){hybridView=view;renderTableRows();const card=view==='card';$('hybridCards').classList.toggle('view-hidden',!card);$('hybridTable').classList.toggle('view-hidden',card);$('cardViewBtn').classList.toggle('active',card);$('tableViewBtn').classList.toggle('active',!card);$('viewTip').textContent=card?'卡片视图适合逐条录入和检查,切换到表格后数据保持不变。':'表格视图适合批量核对,可横向滚动查看全部字段。'}
|
||||
$('cardViewBtn').onclick=()=>setHybridView('card');$('tableViewBtn').onclick=()=>setHybridView('table');
|
||||
$('openTableBtn').onclick=()=>{tableReset();setHybridView('table');$('tableModal').classList.remove('hidden')};$('openHybridBtn').onclick=()=>{tableReset();setHybridView('card');$('tableModal').classList.remove('hidden')};$('tableCloseBtn').onclick=requestTableClose;$('tableCancelBtn').onclick=requestTableClose;
|
||||
function validateTable(){tableErrors=[];tableRows.forEach(r=>{tableRequired.forEach(f=>{if(!String(r[f]??'').trim())tableErrors.push({key:r.key,field:f,msg:'此项为必填'})});if(r.quality_guaran_period&&Number(r.quality_guaran_period)<=0)tableErrors.push({key:r.key,field:'quality_guaran_period',msg:'必须大于 0'})});['package_box_sn','container_name'].forEach(f=>{const seen=new Map();tableRows.forEach((r,i)=>{const v=String(r[f]||'').trim();if(!v)return;if(seen.has(v)){const j=seen.get(v);tableErrors.push({key:r.key,field:f,msg:`与第 ${j+1} 行重复`});tableErrors.push({key:tableRows[j].key,field:f,msg:`与第 ${i+1} 行重复`})}else seen.set(v,i)})});renderTableRows();if(tableErrors.length){$('tableNotice').textContent=`发现 ${tableErrors.length} 处问题,请检查标红字段。`;$('tableNotice').classList.add('show');const first=tableErrors[0];document.querySelector(hybridView==='card'?`[data-hybrid-item="${first.key}-${first.field}"]`:`[data-table-cell="${first.key}-${first.field}"]`)?.scrollIntoView({block:'center',inline:'center'});return false}$('tableNotice').classList.remove('show');return true}
|
||||
$('tableSubmitBtn').onclick=()=>{if(!validateTable())return;const b=$('tableSubmitBtn'),n=tableRows.length;b.disabled=true;b.textContent='正在提交…';setTimeout(()=>{const r=tableRows[0];$('listBody').insertAdjacentHTML('afterbegin',`<tr><td>${esc(r.package_box_sn)}</td><td>${esc(r.box_type.split(' ')[0])}</td><td>${r.box_length} × ${r.box_width} × ${r.box_high}</td><td>${esc(r.container_name)}</td><td>${esc(r.date_of_FG_inbound)}</td><td><span class="pill">${esc(r.status)}</span></td></tr>`);b.disabled=false;closeTable();toast(`表格批量新增成功,共 ${n} 条`);tableReset()},600)};
|
||||
reset();tableReset();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -31,5 +31,12 @@ public interface SlittingproductionplanMapper extends BaseMapperX<Slittingproduc
|
||||
.orderByDesc(SlittingproductionplanDO::getWorkorderId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ERP 子卷号集合查询分切生产计划
|
||||
*/
|
||||
default List<SlittingproductionplanDO> selectListByExtContainerNames(Collection<String> extContainerNames) {
|
||||
return selectList(SlittingproductionplanDO::getExtContainerName, extContainerNames);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.code.nl.module.lms.service.subpackagerelation;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.code.nl.module.lms.dal.dataobject.boxtype.BoxTypeDO;
|
||||
import cn.code.nl.module.lms.dal.mysql.boxtype.BoxTypeMapper;
|
||||
import cn.code.nl.module.lms.dal.mysql.slittingproductionplan.SlittingproductionplanMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -38,6 +39,8 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
private static final String DUPLICATE_CLIENT_KEY = "DUPLICATE_CLIENT_KEY";
|
||||
/** 子卷号重复错误码 */
|
||||
private static final String DUPLICATE_CONTAINER_NAME = "DUPLICATE_CONTAINER_NAME";
|
||||
/** ERP 卷号未同步错误码 */
|
||||
private static final String ERP_CONTAINER_NOT_SYNCED = "ERP_CONTAINER_NOT_SYNCED";
|
||||
/** 木箱类型不存在错误码 */
|
||||
private static final String BOX_TYPE_NOT_FOUND = "BOX_TYPE_NOT_FOUND";
|
||||
/** 木箱已绑定其他类型错误码 */
|
||||
@@ -55,6 +58,10 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
@Resource
|
||||
private BoxTypeMapper boxTypeMapper;
|
||||
|
||||
/** 分切生产计划数据访问对象 */
|
||||
@Resource
|
||||
private SlittingproductionplanMapper slittingproductionplanMapper;
|
||||
|
||||
/** 子卷包装关系分组独立事务保存服务 */
|
||||
@Resource
|
||||
private SubPackageRelationBatchSaveService batchSaveService;
|
||||
@@ -161,6 +168,34 @@ public class SubPackageRelationServiceImpl implements SubPackageRelationService
|
||||
.build();
|
||||
}
|
||||
|
||||
containerNames = validItems.stream()
|
||||
.map(context -> context.item().getContainerName().trim())
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
Set<String> syncedContainerNames = slittingproductionplanMapper
|
||||
.selectListByExtContainerNames(containerNames).stream()
|
||||
.map(item -> item.getExtContainerName())
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toSet());
|
||||
List<BatchItemContext> syncedItems = new ArrayList<>();
|
||||
for (BatchItemContext context : validItems) {
|
||||
String containerName = context.item().getContainerName().trim();
|
||||
if (!syncedContainerNames.contains(containerName)) {
|
||||
failures.add(buildFailure(context.item().getClientKey(), context.rowIndex(),
|
||||
ERP_CONTAINER_NOT_SYNCED, "ERP 卷号未同步:" + containerName));
|
||||
continue;
|
||||
}
|
||||
syncedItems.add(context);
|
||||
}
|
||||
validItems = syncedItems;
|
||||
if (CollUtil.isEmpty(validItems)) {
|
||||
return SubPackageRelationBatchCreateRespVO.builder()
|
||||
.successCount(0)
|
||||
.failureCount(failures.size())
|
||||
.failures(failures)
|
||||
.build();
|
||||
}
|
||||
|
||||
Set<String> boxTypes = validItems.stream()
|
||||
.map(context -> context.item().getBoxType())
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
message,
|
||||
Segmented,
|
||||
Select,
|
||||
Table,
|
||||
} from 'antdv-next';
|
||||
|
||||
import { batchCreateSubPackageRelation } from '#/api/lms/subpackagerelation';
|
||||
@@ -43,6 +44,31 @@ const viewOptions = [
|
||||
{ label: '▤ 卡片', value: 'card' },
|
||||
{ label: '▦ 表格', value: 'table' },
|
||||
];
|
||||
const batchTableColumns = [
|
||||
{ title: '序号', key: 'sequence', width: 70, fixed: 'left' as const },
|
||||
{ title: '木箱唯一码', key: 'packageBoxSn', width: 200 },
|
||||
{ title: '子卷号', key: 'containerName', width: 200 },
|
||||
{ title: '木箱类型', key: 'boxType', width: 190 },
|
||||
{ title: '木箱长', key: 'boxLength', width: 130 },
|
||||
{ title: '木箱宽', key: 'boxWidth', width: 130 },
|
||||
{ title: '木箱高', key: 'boxHigh', width: 130 },
|
||||
{
|
||||
title: '保质期(天)',
|
||||
key: 'qualityGuaranPeriod',
|
||||
width: 170,
|
||||
},
|
||||
{ title: '入库日期', key: 'dateOfFgInbound', width: 180 },
|
||||
{ title: '状态', key: 'status', width: 140 },
|
||||
{ title: '操作', key: 'operations', width: 150, fixed: 'right' as const },
|
||||
];
|
||||
const requiredColumnKeys = new Set([
|
||||
'boxType',
|
||||
'containerName',
|
||||
'dateOfFgInbound',
|
||||
'packageBoxSn',
|
||||
'qualityGuaranPeriod',
|
||||
'status',
|
||||
]);
|
||||
|
||||
const statusOptions = computed(() =>
|
||||
getDictOptions(DICT_TYPE.SUB_PACKAGE_STATUS).map((item) => ({
|
||||
@@ -50,6 +76,11 @@ const statusOptions = computed(() =>
|
||||
value: String(item.value),
|
||||
})),
|
||||
);
|
||||
const errorRowKeys = computed(() =>
|
||||
rows.value
|
||||
.filter((row) => rowErrors.value.has(row.clientKey))
|
||||
.map((row) => row.clientKey),
|
||||
);
|
||||
|
||||
function reset() {
|
||||
rows.value = [createBatchRow()];
|
||||
@@ -222,34 +253,37 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</Alert>
|
||||
|
||||
<div v-if="viewMode === 'table'" class="table-wrap">
|
||||
<table class="batch-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th><th><i>*</i> 木箱唯一码</th><th><i>*</i> 子卷号</th><th><i>*</i> 木箱类型</th>
|
||||
<th>木箱长</th><th>木箱宽</th><th>木箱高</th>
|
||||
<th><i>*</i> 保质期(天)</th><th><i>*</i> 入库日期</th>
|
||||
<th><i>*</i> 状态</th><th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(row, index) in rows" :key="row.clientKey">
|
||||
<tr>
|
||||
<td class="sequence">{{ index + 1 }}</td>
|
||||
<td><Input v-model:value="row.packageBoxSn" :class="{ invalid: fieldError(index, 'packageBoxSn') }" placeholder="请输入木箱唯一码" @update:value="editField(index, 'packageBoxSn')" /><small>{{ fieldError(index, 'packageBoxSn') }}</small></td>
|
||||
<td><Input v-model:value="row.containerName" :class="{ invalid: fieldError(index, 'containerName') }" placeholder="请输入子卷号" @update:value="editField(index, 'containerName')" /><small>{{ fieldError(index, 'containerName') }}</small></td>
|
||||
<td><Input :value="row.boxType" readonly role="button" tabindex="0" aria-label="选择木箱类型" :class="{ invalid: fieldError(index, 'boxType') }" placeholder="请选择木箱类型" @click="selectBoxType(row.clientKey)" @keydown.enter="selectBoxType(row.clientKey)" @keydown.space.prevent="selectBoxType(row.clientKey)" /><small>{{ fieldError(index, 'boxType') }}</small></td>
|
||||
<td><Input v-model:value="row.boxLength" readonly placeholder="自动带出" /></td>
|
||||
<td><Input v-model:value="row.boxWidth" readonly placeholder="自动带出" /></td>
|
||||
<td><Input v-model:value="row.boxHigh" readonly placeholder="自动带出" /></td>
|
||||
<td><Input v-model:value="row.qualityGuaranPeriod" type="number" :class="{ invalid: fieldError(index, 'qualityGuaranPeriod') }" placeholder="例如:90" @update:value="editField(index, 'qualityGuaranPeriod')"><template #suffix><span class="input-unit">天</span></template></Input><small>{{ fieldError(index, 'qualityGuaranPeriod') }}</small></td>
|
||||
<td><DatePicker v-model:value="row.dateOfFgInbound" value-format="YYYY-MM-DD" format="YYYY-MM-DD" class="w-full" :class="{ invalid: fieldError(index, 'dateOfFgInbound') }" placeholder="请选择入库日期" @change="editField(index, 'dateOfFgInbound')" /><small>{{ fieldError(index, 'dateOfFgInbound') }}</small></td>
|
||||
<td><Select v-model:value="row.status" :options="statusOptions" :class="{ invalid: fieldError(index, 'status') }" @change="editField(index, 'status')" /><small>{{ fieldError(index, 'status') }}</small></td>
|
||||
<td class="operations"><Button type="link" @click="duplicateRow(index)"><template #icon><IconifyIcon icon="ant-design:copy-outlined" /></template>复制</Button><Button type="link" danger @click="removeRow(index)"><template #icon><IconifyIcon icon="ant-design:delete-outlined" /></template>删除</Button></td>
|
||||
</tr>
|
||||
<tr v-if="rowErrors.get(row.clientKey)" class="row-error"><td colspan="11">第 {{ index + 1 }} 行:{{ rowErrors.get(row.clientKey) }}</td></tr>
|
||||
<Table
|
||||
class="batch-table"
|
||||
bordered
|
||||
:columns="batchTableColumns"
|
||||
:data-source="rows"
|
||||
:expandable="{ showExpandColumn: false }"
|
||||
:expanded-row-keys="errorRowKeys"
|
||||
:pagination="false"
|
||||
row-key="clientKey"
|
||||
:scroll="{ x: 1690, y: 430 }"
|
||||
>
|
||||
<template #headerCell="{ column }">
|
||||
<i v-if="requiredColumnKeys.has(String(column.key))">*</i> {{ column.title }}
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<template #bodyCell="{ column, record: row, index }">
|
||||
<span v-if="column.key === 'sequence'" class="sequence">{{ index + 1 }}</span>
|
||||
<template v-else-if="column.key === 'packageBoxSn'"><Input v-model:value="row.packageBoxSn" :class="{ invalid: fieldError(index, 'packageBoxSn') }" placeholder="请输入木箱唯一码" @update:value="editField(index, 'packageBoxSn')" /><small>{{ fieldError(index, 'packageBoxSn') }}</small></template>
|
||||
<template v-else-if="column.key === 'containerName'"><Input v-model:value="row.containerName" :class="{ invalid: fieldError(index, 'containerName') }" placeholder="请输入子卷号" @update:value="editField(index, 'containerName')" /><small>{{ fieldError(index, 'containerName') }}</small></template>
|
||||
<template v-else-if="column.key === 'boxType'"><Input :value="row.boxType" readonly role="button" tabindex="0" aria-label="选择木箱类型" :class="{ invalid: fieldError(index, 'boxType') }" placeholder="请选择木箱类型" @click="selectBoxType(row.clientKey)" @keydown.enter="selectBoxType(row.clientKey)" @keydown.space.prevent="selectBoxType(row.clientKey)" /><small>{{ fieldError(index, 'boxType') }}</small></template>
|
||||
<Input v-else-if="column.key === 'boxLength'" v-model:value="row.boxLength" readonly placeholder="自动带出" />
|
||||
<Input v-else-if="column.key === 'boxWidth'" v-model:value="row.boxWidth" readonly placeholder="自动带出" />
|
||||
<Input v-else-if="column.key === 'boxHigh'" v-model:value="row.boxHigh" readonly placeholder="自动带出" />
|
||||
<template v-else-if="column.key === 'qualityGuaranPeriod'"><Input v-model:value="row.qualityGuaranPeriod" type="number" :class="{ invalid: fieldError(index, 'qualityGuaranPeriod') }" placeholder="例如:90" @update:value="editField(index, 'qualityGuaranPeriod')"><template #suffix><span class="input-unit">天</span></template></Input><small>{{ fieldError(index, 'qualityGuaranPeriod') }}</small></template>
|
||||
<template v-else-if="column.key === 'dateOfFgInbound'"><DatePicker v-model:value="row.dateOfFgInbound" value-format="YYYY-MM-DD" format="YYYY-MM-DD" class="w-full" :class="{ invalid: fieldError(index, 'dateOfFgInbound') }" placeholder="请选择入库日期" @change="editField(index, 'dateOfFgInbound')" /><small>{{ fieldError(index, 'dateOfFgInbound') }}</small></template>
|
||||
<template v-else-if="column.key === 'status'"><Select v-model:value="row.status" class="w-full" :options="statusOptions" :class="{ invalid: fieldError(index, 'status') }" @change="editField(index, 'status')" /><small>{{ fieldError(index, 'status') }}</small></template>
|
||||
<div v-else-if="column.key === 'operations'" class="operations"><Button type="link" @click="duplicateRow(index)"><template #icon><IconifyIcon icon="ant-design:copy-outlined" /></template>复制</Button><Button type="link" danger @click="removeRow(index)"><template #icon><IconifyIcon icon="ant-design:delete-outlined" /></template>删除</Button></div>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record, index }">
|
||||
<div class="row-error">第 {{ index + 1 }} 行:{{ rowErrors.get(record.clientKey) }}</div>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div v-else class="card-list">
|
||||
@@ -359,7 +393,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
.table-wrap {
|
||||
flex: 1;
|
||||
min-height: 420px;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
background: hsl(var(--background));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 10px;
|
||||
@@ -367,42 +401,31 @@ const [Modal, modalApi] = useVbenModal({
|
||||
|
||||
.batch-table {
|
||||
width: 100%;
|
||||
min-width: 1780px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.batch-table th,
|
||||
.batch-table td {
|
||||
padding: 10px;
|
||||
vertical-align: top;
|
||||
background: hsl(var(--background));
|
||||
border-right: 1px solid hsl(var(--border));
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
.batch-table :deep(.ant-table) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.batch-table th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
.batch-table :deep(.ant-table-thead > tr > th) {
|
||||
height: 48px;
|
||||
color: #4b5870;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
background: #f3f6fb;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.batch-table th i {
|
||||
.batch-table :deep(.ant-table-tbody > tr > td) {
|
||||
padding: 10px;
|
||||
vertical-align: top;
|
||||
background: hsl(var(--background));
|
||||
}
|
||||
|
||||
.batch-table i {
|
||||
color: #ff4d4f;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.batch-table td {
|
||||
min-width: 145px;
|
||||
}
|
||||
|
||||
.batch-table :deep(.ant-input),
|
||||
.batch-table :deep(.ant-input-affix-wrapper),
|
||||
.batch-table :deep(.ant-picker),
|
||||
@@ -429,34 +452,16 @@ const [Modal, modalApi] = useVbenModal({
|
||||
}
|
||||
|
||||
.batch-table .sequence {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
min-width: 64px;
|
||||
display: block;
|
||||
color: #788196;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.batch-table th:first-child {
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
min-width: 64px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.batch-table th:last-child,
|
||||
.batch-table .operations {
|
||||
position: sticky;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
min-width: 128px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
box-shadow: -5px 0 12px rgb(21 35 62 / 6%);
|
||||
}
|
||||
|
||||
.batch-table th:last-child {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.batch-table small {
|
||||
@@ -474,7 +479,12 @@ const [Modal, modalApi] = useVbenModal({
|
||||
border-color: #ff4d4f;
|
||||
}
|
||||
|
||||
.row-error td {
|
||||
.batch-table :deep(.ant-table-expanded-row > td) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.row-error {
|
||||
padding: 9px 16px;
|
||||
color: #ff4d4f;
|
||||
background: #fff2f0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user