feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏

- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服
  pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验;
  登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段,
  已记 CONTRACT,screens.mjs 留存根)
- ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/
  DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态
- 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、
  h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、
  BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast)
- 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后
  失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览
- 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/
  SelectProductDialog/tabStateProvider
- golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打;
  修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
wangjia
2026-07-03 09:58:14 +08:00
parent ca7595b113
commit 6238b86dcb
286 changed files with 16831 additions and 11136 deletions
+3
View File
@@ -13,6 +13,9 @@ svg{fill:none; stroke:currentColor;}
.btn.primary{background:var(--primary); color:var(--on-primary);}
.btn.primary:hover{background:var(--primary-dark);}
.btn.danger{background:var(--danger); color:#fff;}
/* 浅蓝软变体:突出但不抢主操作(详细搜索等次强调入口);经典蓝主题 = 浅蓝底蓝字,其余主题自适应 */
.btn.soft{background:var(--brand50); border-color:var(--brand400); color:var(--primary);}
.btn.soft:hover{border-color:var(--primary); background:var(--info-soft);}
.btn.sm{height:32px; padding:0 12px; font-size:var(--fs-sm);}
.btn.lg{height:44px; padding:0 22px; font-size:14px;}
+2 -1
View File
@@ -313,8 +313,9 @@
<section class="section" id="comp">
<h2>公用组件 Components</h2><p class="desc">来自 <code>atoms.css</code> 的原子,<b>桌面与移动共用</b>(按钮 / 输入 / 徽章 / 分段 / 菜单 / 弹窗等)。随主题换色。移动专属原子见下一节。</p>
<div class="pcard"><div class="pcard-h"><h3>按钮</h3><span class="tk">.btn .primary/.ghost/.danger/.sm</span></div><div class="pcard-b">
<div class="pcard"><div class="pcard-h"><h3>按钮</h3><span class="tk">.btn .primary/.soft/.ghost/.danger/.sm</span></div><div class="pcard-b">
<div class="btn primary"><svg viewBox="0 0 24 24"><use href="#i-plus"/></svg>新增入库</div>
<div class="btn soft sm"><svg viewBox="0 0 24 24"><use href="#i-ic21"/></svg>详细搜索</div>
<div class="btn ghost">导出</div><div class="btn ghost sm">列设置</div><div class="btn danger">删除</div>
</div></div>
<div class="pcard"><div class="pcard-h"><h3>输入 / 搜索</h3><span class="tk">.input .searchbox .ctrl</span></div><div class="pcard-b">
+49
View File
@@ -53,6 +53,54 @@
menuEl.style.top = (below + h > window.innerHeight && above > 6 ? above : below) + 'px';
}
// 可搜索菜单:视觉承袭 .combo-pop(顶部 .cp-search 显式搜索框 + 边打边过滤 + 结果统计),
// 供列表工具栏「供应商 / 客户」等长名录筛选复用(复用 atoms.css .combo-pop 族,零新增样式)。
// items: [{ v, label, sel, small? }]opts: { placeholder, minW, cls }。
function openSearchMenu(t, items, onPick, opts) {
closeMenus();
opts = opts || {};
const esc = s => String(s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
const highlight = (name, kw) => {
const q = kw.trim(); if (!q) return esc(name);
const i = String(name).toLowerCase().indexOf(q.toLowerCase());
if (i < 0) return esc(name);
return esc(name.slice(0, i)) + '<span class="hl">' + esc(name.slice(i, i + q.length)) + '</span>' + esc(name.slice(i + q.length));
};
const r = t.getBoundingClientRect();
const w = Math.max(r.width, opts.minW || 220);
menuEl = document.createElement('div');
menuEl.className = 'combo-pop' + (opts.cls ? ' ' + opts.cls : '');
menuEl.style.left = Math.max(8, Math.min(r.left, window.innerWidth - w - 8)) + 'px';
menuEl.style.width = w + 'px';
menuEl.innerHTML =
'<div class="cp-search"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>' +
`<input class="cps-input" placeholder="${esc(opts.placeholder || '搜索…')}"></div>` +
'<div class="cp-body"><div class="cp-list"></div></div><div class="cp-foot"></div>';
const listEl = menuEl.querySelector('.cp-list');
const footEl = menuEl.querySelector('.cp-foot');
function draw(kw) {
const q = kw.trim().toLowerCase();
const hits = items.filter(it => !q || String(it.label).toLowerCase().includes(q));
listEl.innerHTML = hits.length
? hits.map(it => {
const small = it.small ? `<small>${esc(it.small)}</small>` : '';
const ck = it.sel ? '<svg class="ck" viewBox="0 0 24 24" stroke-width="2"><path d="M5 12l5 5 9-9"/></svg>' : '';
return `<div class="cp-item ${it.sel ? 'sel' : ''}" data-v="${esc(it.v)}"><span class="nm">${highlight(it.label, kw)}</span>${small}${ck}</div>`;
}).join('')
: '<div class="cp-empty">无匹配结果</div>';
footEl.innerHTML = q ? `匹配 <b>${hits.length}</b> 条 · 共 <b>${items.length}</b> 条` : `共 <b>${items.length}</b> 条`;
listEl.querySelectorAll('.cp-item').forEach(el => el.onclick = e => { e.stopPropagation(); onPick(el.dataset.v); });
}
draw('');
document.body.appendChild(menuEl);
const h = menuEl.offsetHeight;
const below = r.bottom + 6, above = r.top - h - 6;
menuEl.style.top = (below + h > window.innerHeight && above > 6 ? above : below) + 'px';
const input = menuEl.querySelector('.cps-input');
input.oninput = () => draw(input.value);
setTimeout(() => input.focus(), 0);
}
// ---- toast(若页面无 #toast 自动注入) ----
let toastT;
function toast(m) {
@@ -101,6 +149,7 @@
// 暴露给页面脚本复用
window.openMenu = openMenu;
window.openSearchMenu = openSearchMenu;
window.closeMenus = closeMenus;
window.toast = window.toast || toast;
window.setTheme = setTheme;
+168 -46
View File
@@ -28,8 +28,11 @@
/* ---- 状态徽章(新增变体,待提升至 atoms.css ---- */
.badge.b-草稿{background:var(--info-soft); color:var(--muted);}
.badge.b-待审核{background:var(--warn-bg); color:var(--warn);}
.badge.b-已通过{background:var(--success-bg); color:var(--success);}
.badge.b-已驳回{background:var(--danger-bg); color:var(--danger);}
.badge.b-已审核{background:var(--success-bg); color:var(--success);}
.badge.b-已拒绝{background:var(--danger-bg); color:var(--danger);}
/* 退单状态(叠加在已审核单上,对齐后端 return_state: partial/full */
.badge.b-部分退单{background:var(--warn-bg); color:var(--warn);}
.badge.b-已退单{background:var(--danger-bg); color:var(--danger);}
/* 设计期返回悬浮球(右下角,非侵入) */
.ds-fab{position:fixed; right:22px; bottom:22px; width:44px; height:44px; border-radius:50%; background:var(--surface); color:var(--muted); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; box-shadow:var(--sh-2); z-index:var(--z-fab); opacity:.6; transition:opacity .18s,color .18s,border-color .18s,transform .18s,box-shadow .18s; text-decoration:none;}
.ds-fab:hover{opacity:1; color:var(--primary); border-color:var(--primary); box-shadow:var(--sh-3); transform:translateY(-2px);}
@@ -62,6 +65,7 @@
<div class="chip" id="chipWh" onclick="openWhFilter(this)">仓库 <span class="cv" id="whVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipStatus" onclick="openStatusFilter(this)">状态 <span class="cv" id="statusVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipDate" onclick="openDateFilter(this)"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg>入库时间 <span class="cv" id="dateVal"></span></div>
<div class="btn soft sm" id="advBtn" onclick="openAdvSearch()"><svg viewBox="0 0 24 24"><use href="#i-ic21"/></svg>详细搜索<span class="cv" id="advCount"></span></div>
<div class="sp"></div>
<div class="btn ghost sm" id="resetBtn" onclick="clearFilters()"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>重置</div>
</div>
@@ -74,6 +78,14 @@
<div class="drawer-mask" id="drawerMask" onclick="closeDrawer()"></div>
<div class="drawer wide" id="drawer"><div class="drawer-head"><h3 id="dTitle">入库单详情</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeDrawer()"><use href="#i-close"/></svg></div><div class="drawer-body" id="drawerBody"></div></div>
<div class="overlay" id="ovReturn"><div class="modal">
<div class="modal-head"><h3>退单</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeOverlay('ovReturn')"><use href="#i-close"/></svg></div>
<div class="modal-body">
<div class="notice info" style="margin-bottom:14px"><svg viewBox="0 0 24 24"><use href="#i-ic42"/></svg><div id="returnNoticeText"></div></div>
<div id="returnRows"></div>
</div>
<div class="modal-foot"><div class="btn ghost sm" style="margin-right:auto" onclick="stageAllReturn()">全部退单</div><span id="returnSummary" style="align-self:center;font-size:var(--fs-sm);color:var(--muted);margin-right:12px">未选择退单明细</span><div class="btn ghost" onclick="closeOverlay('ovReturn')">取消</div><div class="btn danger" onclick="submitReturn()">提交退单</div></div>
</div></div>
<div class="overlay" id="ovConfirm"><div class="modal">
<div class="modal-head"><h3>确认进价</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeOverlay('ovConfirm')"><use href="#i-close"/></svg></div>
<div class="modal-body">
@@ -82,28 +94,58 @@
</div>
<div class="modal-foot"><div class="btn ghost" onclick="closeOverlay('ovConfirm')">取消</div><div class="btn primary" onclick="saveConfirmCost()">确认并补差额</div></div>
</div></div>
<div class="overlay" id="ovAdv"><div class="modal">
<div class="modal-head"><h3>详细搜索</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeOverlay('ovAdv')"><use href="#i-close"/></svg></div>
<div class="modal-body">
<div class="frow">
<div class="field"><label>单据编号</label><div class="ctrl input-like"><input id="advNo" placeholder="输入单据编号"></div></div>
<div class="field"><label>供应商</label><div class="combo" onclick="openAdvParty(this)"><input id="advParty" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>商品信息</label><div class="ctrl input-like"><input id="advProduct" placeholder="酒名 / 商品编码"></div></div>
<div class="field"><label>分类</label><div class="combo" onclick="openAdvCategory(this)"><input id="advCategory" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>系列</label><div class="combo" onclick="openAdvSeries(this)"><input id="advSeries" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
<div class="field"><label>规格</label><div class="combo" onclick="openAdvSpec(this)"><input id="advSpec" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>批次 · 编号 · 物流码</label><div class="ctrl input-like"><input id="advBatch" placeholder="批次 / 商品编码 / 物流码"></div></div>
<div class="field"><label>状态</label><div class="combo" onclick="openAdvStatus(this)"><input id="advStatus" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>入库员</label><div class="combo" onclick="openAdvOp(this)"><input id="advOp" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
<div class="field"><label>审核人</label><div class="combo" onclick="openAdvReviewer(this)"><input id="advReviewer" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>单据日期</label><div class="combo" onclick="openAdvDate(this)"><input id="advDate" readonly placeholder="全部时间"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg></span></div></div>
</div>
</div>
<div class="modal-foot"><div class="btn ghost" style="margin-right:auto" onclick="resetAdv()">重置</div><div class="btn ghost" onclick="closeOverlay('ovAdv')">取消</div><div class="btn primary" onclick="applyAdv()">确认</div></div>
</div></div>
<div class="toast" id="toast"></div>
<script src="shell.js"></script>
<script>
const ORDERS=[
{no:'RK-20260620-009',date:'2026-06-20',party:'鼎晟调货',wh:'主仓',count:2,amount:5360,status:'已通过',op:'王经理',auditor:'张主管',lines:[{name:'茅台 飞天 53°',qty:2,price:0},{name:'五粮液 普五 52°',qty:5,price:1072}]},
{no:'RK-20260620-009',date:'2026-06-20',party:'鼎晟调货',wh:'主仓',count:2,amount:5360,status:'已审核',ret:'部分退单',op:'王经理',auditor:'张主管',lines:[{name:'茅台 飞天 53°',qty:2,price:0},{name:'五粮液 普五 52°',qty:5,price:1072}]},
{no:'RK-20260620-001',date:'2026-06-20',party:'茅台华东总代',wh:'名酒仓',count:3,amount:48360,status:'待审核',op:'王经理',auditor:'',lines:[{name:'茅台 飞天 53°',qty:12,price:2680},{name:'茅台 1935',qty:8,price:1180},{name:'茅台 王子酒',qty:20,price:328}]},
{no:'RK-20260619-003',date:'2026-06-19',party:'五粮液经销',wh:'主仓',count:2,amount:33600,status:'已通过',op:'李采购',auditor:'张主管',lines:[{name:'五粮液 普五 52°',qty:24,price:1050},{name:'五粮液 1618',qty:12,price:680}]},
{no:'RK-20260619-001',date:'2026-06-19',party:'剑南春西南仓',wh:'主仓',count:2,amount:12640,status:'已通过',op:'王经理',auditor:'张主管',lines:[{name:'剑南春 水晶剑',qty:24,price:438},{name:'剑南春 珍藏级',qty:4,price:520}]},
{no:'RK-20260619-003',date:'2026-06-19',party:'五粮液经销',wh:'主仓',count:2,amount:33600,status:'已审核',ret:'已退单',op:'李采购',auditor:'张主管',lines:[{name:'五粮液 普五 52°',qty:24,price:1050},{name:'五粮液 1618',qty:12,price:680}]},
{no:'RK-20260619-001',date:'2026-06-19',party:'剑南春西南仓',wh:'主仓',count:2,amount:12640,status:'已审核',op:'王经理',auditor:'张主管',lines:[{name:'剑南春 水晶剑',qty:24,price:438},{name:'剑南春 珍藏级',qty:4,price:520}]},
{no:'RK-20260618-002',date:'2026-06-18',party:'国窖直营',wh:'名酒仓',count:1,amount:23520,status:'草稿',op:'李采购',auditor:'',lines:[{name:'国窖 1573',qty:24,price:980}]},
{no:'RK-20260617-004',date:'2026-06-17',party:'洋河股份华东',wh:'一号仓',count:3,amount:29760,status:'已通过',op:'张主管',auditor:'王经理',lines:[{name:'洋河 梦之蓝 M6',qty:24,price:620},{name:'洋河 海之蓝',qty:36,price:188},{name:'洋河 天之蓝',qty:24,price:328}]},
{no:'RK-20260617-004',date:'2026-06-17',party:'洋河股份华东',wh:'一号仓',count:3,amount:29760,status:'已审核',op:'张主管',auditor:'王经理',lines:[{name:'洋河 梦之蓝 M6',qty:24,price:620},{name:'洋河 海之蓝',qty:36,price:188},{name:'洋河 天之蓝',qty:24,price:328}]},
{no:'RK-20260617-001',date:'2026-06-17',party:'泸州老窖经销',wh:'主仓',count:2,amount:18720,status:'待审核',op:'王经理',auditor:'',lines:[{name:'泸州老窖 特曲',qty:48,price:320},{name:'泸州老窖 头曲',qty:24,price:140}]},
{no:'RK-20260616-002',date:'2026-06-16',party:'习酒贵州仓',wh:'一号仓',count:1,amount:18240,status:'已驳回',op:'李采购',auditor:'张主管',lines:[{name:'习酒 窖藏 1988',qty:24,price:760}]},
{no:'RK-20260615-003',date:'2026-06-15',party:'汾酒山西总代',wh:'主仓',count:2,amount:15120,status:'已通过',op:'张主管',auditor:'王经理',lines:[{name:'汾酒 青花 20',qty:24,price:420},{name:'汾酒 老白汾',qty:36,price:140}]},
{no:'RK-20260616-002',date:'2026-06-16',party:'习酒贵州仓',wh:'一号仓',count:1,amount:18240,status:'已拒绝',op:'李采购',auditor:'张主管',lines:[{name:'习酒 窖藏 1988',qty:24,price:760}]},
{no:'RK-20260615-003',date:'2026-06-15',party:'汾酒山西总代',wh:'主仓',count:2,amount:15120,status:'已审核',op:'张主管',auditor:'王经理',lines:[{name:'汾酒 青花 20',qty:24,price:420},{name:'汾酒 老白汾',qty:36,price:140}]},
{no:'RK-20260615-001',date:'2026-06-15',party:'郎酒集团华东',wh:'名酒仓',count:1,amount:13440,status:'待审核',op:'王经理',auditor:'',lines:[{name:'郎酒 红花郎 15',qty:24,price:560}]},
{no:'RK-20260614-002',date:'2026-06-14',party:'古井贡安徽仓',wh:'一号仓',count:2,amount:11400,status:'已通过',op:'李采购',auditor:'张主管',lines:[{name:'古井贡 年份原浆',qty:24,price:390},{name:'古井贡 古5',qty:24,price:85}]},
{no:'RK-20260614-002',date:'2026-06-14',party:'古井贡安徽仓',wh:'一号仓',count:2,amount:11400,status:'已审核',op:'李采购',auditor:'张主管',lines:[{name:'古井贡 年份原浆',qty:24,price:390},{name:'古井贡 古5',qty:24,price:85}]},
{no:'RK-20260613-001',date:'2026-06-13',party:'西凤酒陕西总代',wh:'主仓',count:1,amount:6720,status:'草稿',op:'张主管',auditor:'',lines:[{name:'西凤酒 旗舰版',qty:24,price:280}]},
{no:'RK-20260612-003',date:'2026-06-12',party:'水井坊川渝仓',wh:'名酒仓',count:1,amount:16320,status:'待审核',op:'王经理',auditor:'',lines:[{name:'水井坊 井台',qty:24,price:680}]},
];
const STATUS=['全部','草稿','待审核','已通过','已驳回'];
const COLS=[{key:'no',label:'单号'},{key:'party',label:'供应商'},{key:'wh',label:'仓库'},{key:'amount',label:'合计金额',num:true},{key:'status',label:'状态',filter:true},{key:'op',label:'入库员'},{key:'auditor',label:'审核员'},{key:'act',label:'操作',fixed:true}];
const state={q:'',party:'全部',wh:'全部',status:'全部',dateFrom:'',dateTo:'',page:1,perPage:10};
const STATUS=['全部','草稿','待审核','已审核','已拒绝','部分退单','已退单'];
const COLS=[{key:'no',label:'单号'},{key:'party',label:'供应商'},{key:'wh',label:'仓库'},{key:'amount',label:'合计金额',num:true},{key:'status',label:'状态'},{key:'op',label:'入库员'},{key:'auditor',label:'审核员'},{key:'act',label:'操作',fixed:true}];
const state={q:'',party:'全部',wh:'全部',status:'全部',dateFrom:'',dateTo:'',page:1,perPage:10,orderNo:'',productInfo:'',batch:'',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部'};
const PARTIES=['全部',...Array.from(new Set(ORDERS.map(o=>o.party)))];
const WAREHOUSES=['全部',...Array.from(new Set(ORDERS.map(o=>o.wh)))];
@@ -112,12 +154,22 @@ const PARTY_LABEL='供应商';
function money(n){ return '¥'+n.toLocaleString(); }
function filtered(){ return ORDERS.filter(o=>{
if(state.status!=='全部'&&o.status!==state.status)return false;
if(state.status!=='全部'&&o.status!==state.status&&o.ret!==state.status)return false;
if(state.party!=='全部'&&o.party!==state.party)return false;
if(state.wh!=='全部'&&o.wh!==state.wh)return false;
if(state.dateFrom&&o.date<state.dateFrom)return false;
if(state.dateTo&&o.date>state.dateTo)return false;
if(state.q){const q=state.q.toLowerCase(); if(!(o.no.toLowerCase().includes(q)||o.party.toLowerCase().includes(q)||(o.lines||[]).some(l=>l.name.toLowerCase().includes(q))))return false;}
// ---- 详细搜索条件 ----
if(state.orderNo&&!o.no.toLowerCase().includes(state.orderNo.toLowerCase()))return false;
if(state.op&&state.op!=='全部'&&o.op!==state.op)return false;
if(state.reviewer&&state.reviewer!=='全部'&&o.auditor!==state.reviewer)return false;
const L=o.lines||[];
if(state.productInfo){const p=state.productInfo.toLowerCase(); if(!L.some(l=>{const pi=PINFO[l.name]||{}; return l.name.toLowerCase().includes(p)||(pi.code||'').toLowerCase().includes(p);}))return false;}
if(state.series&&state.series!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).series===state.series))return false;
if(state.spec&&state.spec!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).spec===state.spec))return false;
if(state.category&&state.category!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).cat===state.category))return false;
if(state.batch){const b=state.batch.toLowerCase(); if(!L.some(l=>{const pi=PINFO[l.name]||{}; return (pi.batch||'').toLowerCase().includes(b)||(pi.code||'').toLowerCase().includes(b);}))return false;}
return true; }); }
function syncChips(){
const set=(id,chipId,val)=>{ document.getElementById(id).textContent=val||''; document.getElementById(chipId).classList.toggle('on',!!val); };
@@ -125,7 +177,17 @@ function syncChips(){
set('whVal','chipWh',state.wh==='全部'?'':state.wh);
set('statusVal','chipStatus',state.status==='全部'?'':state.status);
set('dateVal','chipDate',state.dateLabel||'');
const any=state.q||state.party!=='全部'||state.wh!=='全部'||state.status!=='全部'||state.dateFrom||state.dateTo;
const advCount=[state.orderNo,state.productInfo,state.batch,
(state.series&&state.series!=='全部')?1:0,
(state.spec&&state.spec!=='全部')?1:0,
(state.category&&state.category!=='全部')?1:0,
(state.op&&state.op!=='全部')?1:0,
(state.reviewer&&state.reviewer!=='全部')?1:0].filter(Boolean).length;
const advBtnEl=document.getElementById('advBtn');
advBtnEl.classList.toggle('primary',advCount>0);
advBtnEl.classList.toggle('soft',advCount===0);
document.getElementById('advCount').textContent=advCount?' · '+advCount:'';
const any=state.q||state.party!=='全部'||state.wh!=='全部'||state.status!=='全部'||state.dateFrom||state.dateTo||advCount>0;
document.getElementById('resetBtn').classList.toggle('primary',!!any);
}
function render(){
@@ -134,7 +196,7 @@ function render(){
document.getElementById('thead').innerHTML='<tr>'+cols.map(c=>{ const cls=(c.num?'num ':'')+(c.key==='act'?'act ':''); if(c.filter){const on=state.status!=='全部'; return `<th class="${cls}"><span class="fh ${on?'filtered':''}" onclick="openFilter(this)">${c.label} <svg class="funnel" viewBox="0 0 24 24"><use href="#i-ic22"/></svg></span></th>`;} return `<th class="${cls}">${c.label}</th>`; }).join('')+'</tr>';
const rows=filtered(); const start=(state.page-1)*state.perPage; const pageRows=rows.slice(start,start+state.perPage); const tb=document.getElementById('tbody');
if(rows.length===0){ tb.innerHTML=`<tr><td colspan="${cols.length}"><div class="empty">没有匹配的入库单 · 试试调整筛选或搜索</div></td></tr>`; }
else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+cols.map(c=>{ if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-body)">${o.no}</span></td>`; if(c.key==='party')return `<td class="pname">${o.party}</td>`; if(c.key==='wh')return `<td>${o.wh}</td>`; if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`; if(c.key==='status')return `<td><span class="badge b-${o.status}">${o.status}</span></td>`; if(c.key==='op')return `<td>${o.op}</td>`; if(c.key==='auditor')return `<td>${o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>'}</td>`; if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`; return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); }
else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+cols.map(c=>{ if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-body)">${o.no}</span></td>`; if(c.key==='party')return `<td class="pname">${o.party}</td>`; if(c.key==='wh')return `<td>${o.wh}</td>`; if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`; if(c.key==='status')return `<td><span class="badge b-${o.status}">${o.status}</span>${o.ret?` <span class="badge b-${o.ret}">${o.ret}</span>`:''}</td>`; if(c.key==='op')return `<td>${o.op}</td>`; if(c.key==='auditor')return `<td>${o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>'}</td>`; if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`; return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); }
document.getElementById('pagerInfo').textContent=`显示 ${rows.length?start+1:0}${Math.min(start+state.perPage,rows.length)},共 ${rows.length}`;
const pages=Math.max(1,Math.ceil(rows.length/state.perPage)); let pg=`<button ${state.page<=1?'disabled':''} onclick="go(${state.page-1})"></button>`; for(let p=1;p<=pages;p++)pg+=`<button class="${p===state.page?'cur':''}" onclick="go(${p})">${p}</button>`; pg+=`<button ${state.page>=pages?'disabled':''} onclick="go(${state.page+1})"></button>`; document.getElementById('pager').innerHTML=pg;
document.getElementById('subInfo').textContent=`本月 ${rows.length} 笔入库单`;
@@ -144,11 +206,10 @@ const PAGE_SIZES=[10,20,50,100];
function openPageSize(e){ openMenu(e.currentTarget, PAGE_SIZES.map(n=>({v:String(n),label:String(n),sel:n===state.perPage})), v=>{ closeMenus(); setPerPage(v); }, 'pgsize-menu', 88); }
function setPerPage(v){ state.perPage=+v; state.page=1; const el=document.getElementById('pageSizeVal'); if(el)el.textContent=v; render(); }
function filterStatus(s){ state.status=s; state.page=1; render(); }
function clearFilters(){ state.q='';state.party='全部';state.wh='全部';state.status='全部';state.dateFrom='';state.dateTo='';state.dateLabel='';state.page=1; document.getElementById('searchInput').value=''; render(); }
function clearFilters(){ state.q='';state.party='全部';state.wh='全部';state.status='全部';state.dateFrom='';state.dateTo='';state.dateLabel='';state.datePreset='all';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';state.page=1; document.getElementById('searchInput').value=''; render(); }
function openFilter(t){ openStatusFilter(t); }
function openStatusFilter(t){ openMenu(t,STATUS.map(v=>({v,label:v,sel:v===state.status})),v=>{ state.status=v; state.page=1; closeMenus(); render(); }); }
function openPartyFilter(t){ openMenu(t,PARTIES.map(v=>({v,label:v,sel:v===state.party})),v=>{ state.party=v; state.page=1; closeMenus(); render(); }); }
function openPartyFilter(t){ openSearchMenu(t,PARTIES.map(v=>({v,label:v,sel:v===state.party})),v=>{ state.party=v; state.page=1; closeMenus(); render(); },{placeholder:'搜索供应商…'}); }
function openWhFilter(t){ openMenu(t,WAREHOUSES.map(v=>({v,label:v,sel:v===state.wh})),v=>{ state.wh=v; state.page=1; closeMenus(); render(); }); }
const TODAY='2026-06-22';
function dateMinus(days){ const d=new Date(TODAY); d.setDate(d.getDate()-days); return d.toISOString().slice(0,10); }
@@ -165,33 +226,77 @@ function applyDatePreset(v){ state.datePreset=v; if(v==='all'){state.dateFrom=''
const PINFO={
'茅台 飞天 53°':{code:'MT-FT-53-500',series:'飞天',spec:'53° 500ml×6'},
'茅台 1935':{code:'MT-1935-53-500',series:'1935',spec:'53° 500ml×6'},
'茅台 王子酒':{code:'MT-WZ-53-500',series:'王子',spec:'53° 500ml×6'},
'五粮液 普五 52°':{code:'WLY-P8-52-500',series:'第八代',spec:'52° 500ml×6'},
'五粮液 1618':{code:'WLY-1618-52-500',series:'1618',spec:'52° 500ml×6'},
'剑南春 水晶剑':{code:'JNC-SJ-52-500',series:'水晶剑',spec:'52° 500ml×6'},
'剑南春 珍藏级':{code:'JNC-ZC-52-500',series:'珍藏',spec:'52° 500ml×6'},
'国窖 1573':{code:'GJ-1573-52-500',series:'经典装',spec:'52° 500ml×6'},
'洋河 梦之蓝 M6':{code:'YH-M6-45-500',series:'梦之蓝',spec:'45° 500ml×6'},
'洋河 海之蓝':{code:'YH-HZL-42-480',series:'海之蓝',spec:'42° 480ml×6'},
'洋河 天之蓝':{code:'YH-TZL-42-480',series:'天之蓝',spec:'42° 480ml×6'},
'泸州老窖 特曲':{code:'LZLJ-TQ-52-500',series:'特曲',spec:'52° 500ml×6'},
'泸州老窖 头曲':{code:'LZLJ-TOUQ-52-500',series:'头曲',spec:'52° 500ml×6'},
'习酒 窖藏 1988':{code:'XJ-JC-53-500',series:'窖藏',spec:'53° 500ml×6'},
'汾酒 青花 20':{code:'FJ-QH20-53-500',series:'青花',spec:'53° 500ml×6'},
'汾酒 老白汾':{code:'FJ-LBF-53-475',series:'老白汾',spec:'53° 475ml×6'},
'郎酒 红花郎 15':{code:'LJ-HHL15-53-500',series:'红花郎',spec:'53° 500ml×6'},
'古井贡 年份原浆':{code:'GJG-NF-50-500',series:'年份原浆',spec:'50° 500ml×6'},
'古井贡 古5':{code:'GJG-G5-50-500',series:'古5',spec:'50° 500ml×6'},
'西凤酒 旗舰版':{code:'XF-QJ-52-500',series:'旗舰',spec:'52° 500ml×6'},
'水井坊 井台':{code:'SJF-JT-52-500',series:'井台',spec:'52° 500ml×6'},
'茅台 飞天 53°':{code:'MT-FT-53-500',series:'飞天',spec:'53° 500ml×6',cat:'酱香型',batch:'20260518'},
'茅台 1935':{code:'MT-1935-53-500',series:'1935',spec:'53° 500ml×6',cat:'酱香型',batch:'20260420'},
'茅台 王子酒':{code:'MT-WZ-53-500',series:'王子',spec:'53° 500ml×6',cat:'酱香型',batch:'20260512'},
'五粮液 普五 52°':{code:'WLY-P8-52-500',series:'第八代',spec:'52° 500ml×6',cat:'浓香型',batch:'20260408'},
'五粮液 1618':{code:'WLY-1618-52-500',series:'1618',spec:'52° 500ml×6',cat:'浓香型',batch:'20260316'},
'剑南春 水晶剑':{code:'JNC-SJ-52-500',series:'水晶剑',spec:'52° 500ml×6',cat:'浓香型',batch:'20260502'},
'剑南春 珍藏级':{code:'JNC-ZC-52-500',series:'珍藏',spec:'52° 500ml×6',cat:'浓香型',batch:'20260228'},
'国窖 1573':{code:'GJ-1573-52-500',series:'经典装',spec:'52° 500ml×6',cat:'浓香型',batch:'20260410'},
'洋河 梦之蓝 M6':{code:'YH-M6-45-500',series:'梦之蓝',spec:'45° 500ml×6',cat:'浓香型',batch:'20260322'},
'洋河 海之蓝':{code:'YH-HZL-42-480',series:'海之蓝',spec:'42° 480ml×6',cat:'浓香型',batch:'20260305'},
'洋河 天之蓝':{code:'YH-TZL-42-480',series:'天之蓝',spec:'42° 480ml×6',cat:'浓香型',batch:'20260318'},
'泸州老窖 特曲':{code:'LZLJ-TQ-52-500',series:'特曲',spec:'52° 500ml×6',cat:'浓香型',batch:'20260415'},
'泸州老窖 头曲':{code:'LZLJ-TOUQ-52-500',series:'头曲',spec:'52° 500ml×6',cat:'浓香型',batch:'20260401'},
'习酒 窖藏 1988':{code:'XJ-JC-53-500',series:'窖藏',spec:'53° 500ml×6',cat:'酱香型',batch:'20260210'},
'汾酒 青花 20':{code:'FJ-QH20-53-500',series:'青花',spec:'53° 500ml×6',cat:'清香型',batch:'20260506'},
'汾酒 老白汾':{code:'FJ-LBF-53-475',series:'老白汾',spec:'53° 475ml×6',cat:'清香型',batch:'20260429'},
'郎酒 红花郎 15':{code:'LJ-HHL15-53-500',series:'红花郎',spec:'53° 500ml×6',cat:'酱香型',batch:'20260118'},
'古井贡 年份原浆':{code:'GJG-NF-50-500',series:'年份原浆',spec:'50° 500ml×6',cat:'浓香型',batch:'20260512'},
'古井贡 古5':{code:'GJG-G5-50-500',series:'古5',spec:'50° 500ml×6',cat:'浓香型',batch:'20260508'},
'西凤酒 旗舰版':{code:'XF-QJ-52-500',series:'旗舰',spec:'52° 500ml×6',cat:'凤香型',batch:'20260325'},
'水井坊 井台':{code:'SJF-JT-52-500',series:'井台',spec:'52° 500ml×6',cat:'浓香型',batch:'20260430'},
};
function auditBtns(o){
if(o.status==='草稿') return '<div class="btn primary sm" onclick="toast(\'已提交审核 ✓\');closeDrawer()">提交审核</div>';
if(o.status==='待审核') return '<div class="btn primary sm" onclick="toast(\'已审核通过 ✓\');closeDrawer()">审核通过</div><div class="btn danger sm" onclick="toast(\'已驳回\');closeDrawer()">驳回</div>';
if(o.status==='已通过') return '<div class="btn ghost sm" onclick="toast(\'已结清 ✓\');closeDrawer()">结清</div><div class="btn danger sm" onclick="toast(\'已退单\');closeDrawer()">退单</div>';
if(o.status==='已驳回') return '<div class="btn primary sm" onclick="toast(\'已重新提交 ✓\');closeDrawer()">重新提交</div>';
// ---- 详细搜索模态框 ----
const SERIES_OPTS=['全部',...Array.from(new Set(Object.values(PINFO).map(p=>p.series)))];
const SPEC_OPTS=['全部',...Array.from(new Set(Object.values(PINFO).map(p=>p.spec)))];
const CAT_OPTS=['全部',...Array.from(new Set(Object.values(PINFO).map(p=>p.cat)))];
const OP_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.op)))];
const REVIEWER_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.auditor).filter(Boolean)))];
let adv={};
function advSet(id,val){ const el=document.getElementById(id); if(el)el.value=(!val||val==='全部')?'':val; }
function dateLabelOf(v){ return v==='month'?'本月':(!v||v==='all'?'':'近'+v+'天'); }
function openAdvSearch(){
adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:state.status,datePreset:state.datePreset||'all'};
advSet('advParty',adv.party); advSet('advSeries',adv.series); advSet('advSpec',adv.spec); advSet('advCategory',adv.category);
advSet('advOp',adv.op); advSet('advReviewer',adv.reviewer); advSet('advStatus',adv.status);
document.getElementById('advNo').value=state.orderNo||'';
document.getElementById('advProduct').value=state.productInfo||'';
document.getElementById('advBatch').value=state.batch||'';
document.getElementById('advDate').value=dateLabelOf(adv.datePreset);
document.getElementById('ovAdv').classList.add('show');
}
function advPick(el,id,opts,key,ph){ openSearchMenu(el,opts.map(v=>({v,label:v,sel:v===adv[key]})),v=>{ adv[key]=v; advSet(id,v); closeMenus(); },{placeholder:ph}); }
function advMenu(el,id,opts,key){ openMenu(el,opts.map(v=>({v,label:v,sel:v===adv[key]})),v=>{ adv[key]=v; advSet(id,v); closeMenus(); }); }
function openAdvParty(el){ advPick(el,'advParty',PARTIES,'party','搜索'+PARTY_LABEL+'…'); }
function openAdvSeries(el){ advPick(el,'advSeries',SERIES_OPTS,'series','搜索系列…'); }
function openAdvSpec(el){ advPick(el,'advSpec',SPEC_OPTS,'spec','搜索规格…'); }
function openAdvCategory(el){ advPick(el,'advCategory',CAT_OPTS,'category','搜索分类…'); }
function openAdvOp(el){ advMenu(el,'advOp',OP_OPTS,'op'); }
function openAdvReviewer(el){ advMenu(el,'advReviewer',REVIEWER_OPTS,'reviewer'); }
function openAdvStatus(el){ advMenu(el,'advStatus',STATUS,'status'); }
function openAdvDate(el){ openMenu(el,DATE_PRESETS.map(p=>({v:p.v,label:p.label,sel:p.v===adv.datePreset})),v=>{ adv.datePreset=v; document.getElementById('advDate').value=dateLabelOf(v); closeMenus(); }); }
function applyAdv(){
state.orderNo=document.getElementById('advNo').value.trim();
state.productInfo=document.getElementById('advProduct').value.trim();
state.batch=document.getElementById('advBatch').value.trim();
state.party=adv.party; state.series=adv.series; state.spec=adv.spec; state.category=adv.category;
state.op=adv.op; state.reviewer=adv.reviewer; state.status=adv.status;
applyDatePreset(adv.datePreset);
state.page=1; closeOverlay('ovAdv'); render();
}
function resetAdv(){
adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',datePreset:'all'};
['advNo','advProduct','advBatch','advParty','advSeries','advSpec','advCategory','advOp','advReviewer','advStatus','advDate'].forEach(id=>{const el=document.getElementById(id); if(el)el.value='';});
state.party='全部';state.status='全部';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';
applyDatePreset('all'); state.page=1; render();
}
function auditBtns(o,idx){
if(o.status==='草稿') return `<div class="btn ghost sm" onclick="toast('打开修改(原型)')">修改</div><div class="btn danger sm" onclick="toast('已删除');closeDrawer()">删除</div><div class="btn primary sm" onclick="toast('已提交审核 ✓');closeDrawer()">提交</div>`;
if(o.status==='待审核') return `<div class="btn primary sm" onclick="toast('已审核通过 ✓');closeDrawer()">通过</div><div class="btn danger sm" onclick="toast('已拒绝');closeDrawer()">拒绝</div><div class="btn ghost sm" onclick="toast('已撤回为草稿');closeDrawer()">撤回</div>`;
if(o.status==='已审核') return `<div class="btn ghost sm" onclick="toast('已结清 ✓');closeDrawer()">结清</div><div class="btn danger sm" onclick="openReturn(${idx})">退单</div>`;
return '';
}
function openDetail(idx){ const o=ORDERS[idx]; document.getElementById('dTitle').textContent=o.no;
@@ -199,10 +304,27 @@ function openDetail(idx){ const o=ORDERS[idx]; document.getElementById('dTitle')
o.lines.forEach(l=>{ const pi=PINFO[l.name]||{}; const pend=(l.price||0)<=0; const priceC=pend?'<span class="pending-price">待定价</span>':money(l.price); const amtC=pend?'<span class="pending-price">待定价</span>':money(l.qty*l.price); lh+=`<div class="lr" style="grid-template-columns:1fr 124px 40px 64px 78px;align-items:start"><span class="lcell"><span class="ln">${l.name}</span><span class="lcode">${pi.code||'—'}</span></span><span class="lcell"><span class="lser">${pi.series||'—'}</span><span class="lspec">${pi.spec||''}</span></span><span style="text-align:right;font-family:var(--font-mono)">${l.qty}</span><span style="text-align:right;font-family:var(--font-mono)">${priceC}</span><span class="amt">${amtC}</span></div>`; }); lh+='</div>';
const pendCount=o.lines.filter(l=>(l.price||0)<=0).length;
const auditor=o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>';
document.getElementById('drawerBody').innerHTML=`<div class="drow"><span>单据日期</span><b>${o.date}</b></div><div class="drow"><span>${PARTY_LABEL}</span><b>${o.party}</b></div><div class="drow"><span>仓库</span><b>${o.wh}</b></div><div class="drow"><span>商品数</span><b>${o.count}</b></div><div class="drow"><span>入库员</span><b>${o.op}</b></div><div class="drow"><span>审核员</span><b>${auditor}</b></div><div class="drow"><span>状态</span><b><span class="badge b-${o.status}">${o.status}</span></b></div><label style="display:block;font-size:var(--fs-sm);color:var(--muted);margin:16px 0 8px;">入库明细</label>${lh}<div class="ltotal">合计金额 <b>${money(o.amount)}</b></div>`+
`<div class="dactions"><div class="dact-group"><span class="dact-label">单据审核</span><div class="dact-row">${auditBtns(o)}</div></div>${(o.status==='已通过'&&pendCount>0)?`<div class="dact-group"><span class="dact-label">成本 · ${pendCount} 项待定价</span><div class="dact-row"><div class="btn primary sm" onclick="openConfirmCost(${idx})"><svg viewBox="0 0 24 24"><use href="#i-edit"/></svg>确认进价</div></div></div>`:''}<div class="dact-group"><span class="dact-label">打印</span><div class="dact-row"><div class="btn ghost sm" onclick="toast('打印标签(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic49"/></svg>打印标签</div><div class="btn ghost sm" onclick="toast('打印单据(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic50"/></svg>打印单据</div></div></div></div>`;
document.getElementById('drawerBody').innerHTML=`<div class="drow"><span>单据日期</span><b>${o.date}</b></div><div class="drow"><span>${PARTY_LABEL}</span><b>${o.party}</b></div><div class="drow"><span>仓库</span><b>${o.wh}</b></div><div class="drow"><span>商品数</span><b>${o.count}</b></div><div class="drow"><span>入库员</span><b>${o.op}</b></div><div class="drow"><span>审核员</span><b>${auditor}</b></div><div class="drow"><span>状态</span><b><span class="badge b-${o.status}">${o.status}</span>${o.ret?` <span class="badge b-${o.ret}">${o.ret}</span>`:''}</b></div><label style="display:block;font-size:var(--fs-sm);color:var(--muted);margin:16px 0 8px;">入库明细</label>${lh}<div class="ltotal">合计金额 <b>${money(o.amount)}</b></div>`+
`<div class="dactions"><div class="dact-group"><span class="dact-label">单据审核</span><div class="dact-row">${auditBtns(o,idx)}</div></div>${(o.status==='已审核'&&pendCount>0)?`<div class="dact-group"><span class="dact-label">成本 · ${pendCount} 项待定价</span><div class="dact-row"><div class="btn primary sm" onclick="openConfirmCost(${idx})"><svg viewBox="0 0 24 24"><use href="#i-edit"/></svg>确认进价</div></div></div>`:''}<div class="dact-group"><span class="dact-label">打印</span><div class="dact-row"><div class="btn ghost sm" onclick="toast('打印标签(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic49"/></svg>打印标签</div><div class="btn ghost sm" onclick="toast('打印单据(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic50"/></svg>打印单据</div></div></div></div>`;
document.getElementById('drawer').classList.add('show'); document.getElementById('drawerMask').classList.add('show'); }
function closeDrawer(){ document.getElementById('drawer').classList.remove('show'); document.getElementById('drawerMask').classList.remove('show'); }
// ---- 退单(入库:从库存删除 −) ----
const RETURN_IS_OUT=false;
let returnOrder=-1, returnStage={};
function openReturn(idx){ returnOrder=idx; returnStage={};
document.getElementById('returnNoticeText').textContent = RETURN_IS_OUT
? '提交后,退单明细的数量将「加回库存」。退单明细不会删除,以红色「已退单」标注,且不可撤销。'
: '提交后,退单明细对应的库存将「从库存删除」。退单明细不会删除,以红色「已退单」标注,且不可撤销。';
drawReturnRows(); document.getElementById('ovReturn').classList.add('show'); }
function drawReturnRows(){ const o=ORDERS[returnOrder];
document.getElementById('returnRows').innerHTML=o.lines.map((l,i)=>{ const st=returnStage[i]!=null; const pi=PINFO[l.name]||{};
return `<div class="frow" style="align-items:center;gap:12px;margin-bottom:10px"><label style="display:flex;align-items:center;gap:9px;flex:1;cursor:pointer"><input type="checkbox" ${st?'checked':''} onchange="toggleReturn(${i},${l.qty})"><span><div class="pname">${l.name}</div><div class="pcode">${pi.code||'—'} · 数量 ${l.qty}</div></span></label><div class="ctrl input-like" style="width:120px"><input type="number" min="1" max="${l.qty}" value="${st?returnStage[i]:''}" ${st?'':'disabled'} placeholder="退货数量" oninput="setReturnQty(${i},this.value,${l.qty})"></div></div>`; }).join('');
updateReturnSummary(); }
function toggleReturn(i,max){ if(returnStage[i]!=null) delete returnStage[i]; else returnStage[i]=max; drawReturnRows(); }
function setReturnQty(i,v,max){ returnStage[i]=Math.min(Math.max(1,Math.floor(+v||0)),max); updateReturnSummary(); }
function stageAllReturn(){ ORDERS[returnOrder].lines.forEach((l,i)=>returnStage[i]=l.qty); drawReturnRows(); }
function updateReturnSummary(){ const keys=Object.keys(returnStage); const qty=keys.reduce((s,k)=>s+(+returnStage[k]||0),0); document.getElementById('returnSummary').textContent=keys.length?`已退单 ${keys.length} 项 · ${RETURN_IS_OUT?'退回库存 +':'从库存移除 '}${qty}`:'未选择退单明细'; }
function submitReturn(){ const keys=Object.keys(returnStage); if(!keys.length){toast('请先选择要退的明细');return;} const o=ORDERS[returnOrder]; o.ret = keys.length<o.lines.length?'部分退单':'已退单'; closeOverlay('ovReturn'); closeDrawer(); render(); toast('退单成功 ✓'); }
let confirmOrder=-1;
function openConfirmCost(idx){ confirmOrder=idx; const o=ORDERS[idx]; const pend=o.lines.map((l,i)=>({l,i})).filter(({l})=>(l.price||0)<=0);
document.getElementById('confirmRows').innerHTML=pend.map(({l,i})=>`<div class="frow" style="align-items:center;gap:12px;margin-bottom:10px"><div style="flex:1"><div class="pname">${l.name}</div><div class="pcode">数量 ${l.qty}</div></div><div class="ctrl input-like" style="width:150px"><input type="number" inputmode="decimal" data-li="${i}" placeholder="真实进价 0.00"></div></div>`).join('');
@@ -128,10 +128,6 @@
<a class="back" href="../index.html" style="border:none;padding:0" title="返回"><svg width="20" height="20" viewBox="0 0 24 24" stroke-width="2" style="color:var(--muted)"><use href="#i-chevron-left"/></svg></a>
<h1 id="pageTitle">新建入库单</h1>
<span class="badge" id="statusBadge" style="display:none"></span>
<div class="seg" id="modeSeg">
<button data-mode="in" class="on">入库</button>
<button data-mode="out">出库</button>
</div>
<div class="ph-actions" id="pageActions"></div>
</div>
-1
View File
@@ -492,7 +492,6 @@ document.addEventListener('datechange',e=>{ const el=e.target;
});
document.getElementById('remark').addEventListener('input',e=>{ state.doc.remark=e.target.value; draftSave(); });
document.getElementById('docCollapse').onclick=()=>{ const dh=document.getElementById('dochead'); dh.classList.toggle('folded'); document.getElementById('collapseTxt').textContent=dh.classList.contains('folded')?'展开':'收起'; document.querySelector('#docCollapse svg').style.transform=dh.classList.contains('folded')?'rotate(180deg)':''; };
document.querySelectorAll('#modeSeg button').forEach(b=>b.onclick=()=>{ switchMode(b.dataset.mode); buildNav(); });
document.addEventListener('keydown',e=>{
if((e.metaKey||e.ctrlKey)&&e.key==='Enter'){ e.preventDefault(); if(!readonlyMode())doSubmit(false); return; }
@@ -28,8 +28,11 @@
/* ---- 状态徽章(新增变体,待提升至 atoms.css ---- */
.badge.b-草稿{background:var(--info-soft); color:var(--muted);}
.badge.b-待审核{background:var(--warn-bg); color:var(--warn);}
.badge.b-已通过{background:var(--success-bg); color:var(--success);}
.badge.b-已驳回{background:var(--danger-bg); color:var(--danger);}
.badge.b-已审核{background:var(--success-bg); color:var(--success);}
.badge.b-已拒绝{background:var(--danger-bg); color:var(--danger);}
/* 退单状态(叠加在已审核单上,对齐后端 return_state: partial/full */
.badge.b-部分退单{background:var(--warn-bg); color:var(--warn);}
.badge.b-已退单{background:var(--danger-bg); color:var(--danger);}
/* 设计期返回悬浮球(右下角,非侵入) */
.ds-fab{position:fixed; right:22px; bottom:22px; width:44px; height:44px; border-radius:50%; background:var(--surface); color:var(--muted); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; box-shadow:var(--sh-2); z-index:var(--z-fab); opacity:.6; transition:opacity .18s,color .18s,border-color .18s,transform .18s,box-shadow .18s; text-decoration:none;}
.ds-fab:hover{opacity:1; color:var(--primary); border-color:var(--primary); box-shadow:var(--sh-3); transform:translateY(-2px);}
@@ -62,6 +65,7 @@
<div class="chip" id="chipWh" onclick="openWhFilter(this)">仓库 <span class="cv" id="whVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipStatus" onclick="openStatusFilter(this)">状态 <span class="cv" id="statusVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipDate" onclick="openDateFilter(this)"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg>出库时间 <span class="cv" id="dateVal"></span></div>
<div class="btn soft sm" id="advBtn" onclick="openAdvSearch()"><svg viewBox="0 0 24 24"><use href="#i-ic21"/></svg>详细搜索<span class="cv" id="advCount"></span></div>
<div class="sp"></div>
<div class="btn ghost sm" id="resetBtn" onclick="clearFilters()"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>重置</div>
</div>
@@ -73,27 +77,74 @@
<div class="drawer-mask" id="drawerMask" onclick="closeDrawer()"></div>
<div class="drawer wide" id="drawer"><div class="drawer-head"><h3 id="dTitle">出库单详情</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeDrawer()"><use href="#i-close"/></svg></div><div class="drawer-body" id="drawerBody"></div></div>
<div class="overlay" id="ovAdv"><div class="modal">
<div class="modal-head"><h3>详细搜索</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeOverlay('ovAdv')"><use href="#i-close"/></svg></div>
<div class="modal-body">
<div class="frow">
<div class="field"><label>单据编号</label><div class="ctrl input-like"><input id="advNo" placeholder="输入单据编号"></div></div>
<div class="field"><label>客户</label><div class="combo" onclick="openAdvParty(this)"><input id="advParty" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>商品信息</label><div class="ctrl input-like"><input id="advProduct" placeholder="酒名 / 商品编码"></div></div>
<div class="field"><label>分类</label><div class="combo" onclick="openAdvCategory(this)"><input id="advCategory" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>系列</label><div class="combo" onclick="openAdvSeries(this)"><input id="advSeries" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
<div class="field"><label>规格</label><div class="combo" onclick="openAdvSpec(this)"><input id="advSpec" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>批次 · 编号 · 物流码</label><div class="ctrl input-like"><input id="advBatch" placeholder="批次 / 商品编码 / 物流码"></div></div>
<div class="field"><label>状态</label><div class="combo" onclick="openAdvStatus(this)"><input id="advStatus" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>出库员</label><div class="combo" onclick="openAdvOp(this)"><input id="advOp" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
<div class="field"><label>审核人</label><div class="combo" onclick="openAdvReviewer(this)"><input id="advReviewer" readonly placeholder="全部"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></span></div></div>
</div>
<div class="frow">
<div class="field"><label>单据日期</label><div class="combo" onclick="openAdvDate(this)"><input id="advDate" readonly placeholder="全部时间"><span class="caf"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg></span></div></div>
</div>
</div>
<div class="modal-foot"><div class="btn ghost" style="margin-right:auto" onclick="resetAdv()">重置</div><div class="btn ghost" onclick="closeOverlay('ovAdv')">取消</div><div class="btn primary" onclick="applyAdv()">确认</div></div>
</div></div>
<div class="overlay" id="ovReturn"><div class="modal">
<div class="modal-head"><h3>退单</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeOverlay('ovReturn')"><use href="#i-close"/></svg></div>
<div class="modal-body">
<div class="notice info" style="margin-bottom:14px"><svg viewBox="0 0 24 24"><use href="#i-ic42"/></svg><div id="returnNoticeText"></div></div>
<div id="returnRows"></div>
</div>
<div class="modal-foot"><div class="btn ghost sm" style="margin-right:auto" onclick="stageAllReturn()">全部退单</div><span id="returnSummary" style="align-self:center;font-size:var(--fs-sm);color:var(--muted);margin-right:12px">未选择退单明细</span><div class="btn ghost" onclick="closeOverlay('ovReturn')">取消</div><div class="btn danger" onclick="submitReturn()">提交退单</div></div>
</div></div>
<div class="overlay" id="ovConfirmSale"><div class="modal">
<div class="modal-head"><h3>确认售价</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeOverlay('ovConfirmSale')"><use href="#i-close"/></svg></div>
<div class="modal-body">
<div class="notice info" style="margin-bottom:14px"><svg viewBox="0 0 24 24"><use href="#i-ic42"/></svg><div>「先出后定价」:填写真实售价后,系统据此结算应收(售价 × 数量)并补一条应收流水——不影响成本 / 库存,不反审核。</div></div>
<div id="confirmSaleRows"></div>
</div>
<div class="modal-foot"><div class="btn ghost" onclick="closeOverlay('ovConfirmSale')">取消</div><div class="btn primary" onclick="saveConfirmSale()">确认并补应收</div></div>
</div></div>
<div class="toast" id="toast"></div>
<script src="shell.js"></script>
<script>
const ORDERS=[
{no:'CK-20260621-002',date:'2026-06-21',party:'金悦大酒店',wh:'名酒仓',count:3,amount:39600,status:'待审核',op:'王经理',auditor:'',lines:[{name:'茅台 飞天 53°',qty:10,price:2880},{name:'五粮液 普五 52°',qty:8,price:1180},{name:'国窖 1573',qty:6,price:1080}]},
{no:'CK-20260620-004',date:'2026-06-20',party:'万达广场名烟名酒',wh:'主仓',count:2,amount:21120,status:'已通过',op:'李销售',auditor:'张主管',lines:[{name:'剑南春 水晶剑',qty:24,price:528},{name:'洋河 海之蓝',qty:36,price:228}]},
{no:'CK-20260620-001',date:'2026-06-20',party:'喜来登宴会厅',wh:'名酒仓',count:2,amount:35400,status:'已通过',op:'王经理',auditor:'张主管',lines:[{name:'茅台 1935',qty:12,price:1480},{name:'习酒 窖藏 1988',qty:24,price:740}]},
{no:'CK-20260620-004',date:'2026-06-20',party:'万达广场名烟名酒',wh:'主仓',count:2,amount:21120,status:'已审核',ret:'部分退单',op:'李销售',auditor:'张主管',lines:[{name:'剑南春 水晶剑',qty:24,price:528},{name:'洋河 海之蓝',qty:36,price:228}]},
{no:'CK-20260620-001',date:'2026-06-20',party:'喜来登宴会厅',wh:'名酒仓',count:2,amount:35400,status:'已审核',ret:'已退单',op:'王经理',auditor:'张主管',lines:[{name:'茅台 1935',qty:12,price:1480},{name:'习酒 窖藏 1988',qty:24,price:740}]},
{no:'CK-20260619-008',date:'2026-06-19',party:'私人客户 · 陈总',wh:'主仓',count:1,amount:0,status:'已审核',op:'李销售',auditor:'张主管',lines:[{name:'茅台 飞天 53°',qty:6,price:0}]},
{no:'CK-20260619-003',date:'2026-06-19',party:'城东烟酒批发部',wh:'主仓',count:1,amount:14400,status:'草稿',op:'李销售',auditor:'',lines:[{name:'泸州老窖 特曲',qty:36,price:400}]},
{no:'CK-20260618-005',date:'2026-06-18',party:'锦江饭店采购',wh:'一号仓',count:3,amount:27840,status:'已通过',op:'张主管',auditor:'王经理',lines:[{name:'汾酒 青花 20',qty:24,price:480},{name:'郎酒 红花郎 15',qty:18,price:640},{name:'西凤酒 旗舰版',qty:24,price:200}]},
{no:'CK-20260618-005',date:'2026-06-18',party:'锦江饭店采购',wh:'一号仓',count:3,amount:27840,status:'已审核',op:'张主管',auditor:'王经理',lines:[{name:'汾酒 青花 20',qty:24,price:480},{name:'郎酒 红花郎 15',qty:18,price:640},{name:'西凤酒 旗舰版',qty:24,price:200}]},
{no:'CK-20260618-001',date:'2026-06-18',party:'东方明珠酒楼',wh:'主仓',count:2,amount:19680,status:'待审核',op:'王经理',auditor:'',lines:[{name:'洋河 梦之蓝 M6',qty:24,price:720},{name:'古井贡 年份原浆',qty:24,price:100}]},
{no:'CK-20260617-002',date:'2026-06-17',party:'连锁超市配送中心',wh:'一号仓',count:1,amount:13680,status:'已驳回',op:'李销售',auditor:'张主管',lines:[{name:'水井坊 井台',qty:18,price:760}]},
{no:'CK-20260616-004',date:'2026-06-16',party:'婚庆礼仪公司',wh:'名酒仓',count:2,amount:24000,status:'已通过',op:'张主管',auditor:'王经理',lines:[{name:'五粮液 1618',qty:24,price:780},{name:'剑南春 珍藏级',qty:12,price:520}]},
{no:'CK-20260617-002',date:'2026-06-17',party:'连锁超市配送中心',wh:'一号仓',count:1,amount:13680,status:'已拒绝',op:'李销售',auditor:'张主管',lines:[{name:'水井坊 井台',qty:18,price:760}]},
{no:'CK-20260616-004',date:'2026-06-16',party:'婚庆礼仪公司',wh:'名酒仓',count:2,amount:24000,status:'已审核',op:'张主管',auditor:'王经理',lines:[{name:'五粮液 1618',qty:24,price:780},{name:'剑南春 珍藏级',qty:12,price:520}]},
{no:'CK-20260616-001',date:'2026-06-16',party:'高新区企业团购',wh:'主仓',count:1,amount:18720,status:'待审核',op:'王经理',auditor:'',lines:[{name:'国窖 1573',qty:18,price:1040}]},
{no:'CK-20260615-003',date:'2026-06-15',party:'老街餐饮连锁',wh:'一号仓',count:2,amount:12960,status:'已通过',op:'李销售',auditor:'张主管',lines:[{name:'洋河 天之蓝',qty:24,price:380},{name:'汾酒 老白汾',qty:24,price:160}]},
{no:'CK-20260615-003',date:'2026-06-15',party:'老街餐饮连锁',wh:'一号仓',count:2,amount:12960,status:'已审核',op:'李销售',auditor:'张主管',lines:[{name:'洋河 天之蓝',qty:24,price:380},{name:'汾酒 老白汾',qty:24,price:160}]},
{no:'CK-20260614-002',date:'2026-06-14',party:'国际会展中心',wh:'主仓',count:1,amount:8400,status:'草稿',op:'张主管',auditor:'',lines:[{name:'西凤酒 旗舰版',qty:24,price:350}]},
{no:'CK-20260613-005',date:'2026-06-13',party:'凯悦酒店宴会',wh:'名酒仓',count:1,amount:32400,status:'已通过',op:'王经理',auditor:'张主管',lines:[{name:'茅台 飞天 53°',qty:12,price:2700}]},
{no:'CK-20260613-005',date:'2026-06-13',party:'凯悦酒店宴会',wh:'名酒仓',count:1,amount:32400,status:'已审核',op:'王经理',auditor:'张主管',lines:[{name:'茅台 飞天 53°',qty:12,price:2700}]},
];
const STATUS=['全部','草稿','待审核','已通过','已驳回'];
const COLS=[{key:'no',label:'单号'},{key:'party',label:'客户'},{key:'wh',label:'仓库'},{key:'amount',label:'合计金额',num:true},{key:'status',label:'状态',filter:true},{key:'op',label:'出库员'},{key:'auditor',label:'审核员'},{key:'act',label:'操作',fixed:true}];
const state={q:'',party:'全部',wh:'全部',status:'全部',dateFrom:'',dateTo:'',page:1,perPage:10};
const STATUS=['全部','草稿','待审核','已审核','已拒绝','部分退单','已退单'];
const COLS=[{key:'no',label:'单号'},{key:'party',label:'客户'},{key:'wh',label:'仓库'},{key:'amount',label:'合计金额',num:true},{key:'status',label:'状态'},{key:'op',label:'出库员'},{key:'auditor',label:'审核员'},{key:'act',label:'操作',fixed:true}];
const state={q:'',party:'全部',wh:'全部',status:'全部',dateFrom:'',dateTo:'',page:1,perPage:10,orderNo:'',productInfo:'',batch:'',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部'};
const PARTIES=['全部',...Array.from(new Set(ORDERS.map(o=>o.party)))];
const WAREHOUSES=['全部',...Array.from(new Set(ORDERS.map(o=>o.wh)))];
@@ -102,12 +153,22 @@ const PARTY_LABEL='客户';
function money(n){ return '¥'+n.toLocaleString(); }
function filtered(){ return ORDERS.filter(o=>{
if(state.status!=='全部'&&o.status!==state.status)return false;
if(state.status!=='全部'&&o.status!==state.status&&o.ret!==state.status)return false;
if(state.party!=='全部'&&o.party!==state.party)return false;
if(state.wh!=='全部'&&o.wh!==state.wh)return false;
if(state.dateFrom&&o.date<state.dateFrom)return false;
if(state.dateTo&&o.date>state.dateTo)return false;
if(state.q){const q=state.q.toLowerCase(); if(!(o.no.toLowerCase().includes(q)||o.party.toLowerCase().includes(q)||(o.lines||[]).some(l=>l.name.toLowerCase().includes(q))))return false;}
// ---- 详细搜索条件 ----
if(state.orderNo&&!o.no.toLowerCase().includes(state.orderNo.toLowerCase()))return false;
if(state.op&&state.op!=='全部'&&o.op!==state.op)return false;
if(state.reviewer&&state.reviewer!=='全部'&&o.auditor!==state.reviewer)return false;
const L=o.lines||[];
if(state.productInfo){const p=state.productInfo.toLowerCase(); if(!L.some(l=>{const pi=PINFO[l.name]||{}; return l.name.toLowerCase().includes(p)||(pi.code||'').toLowerCase().includes(p);}))return false;}
if(state.series&&state.series!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).series===state.series))return false;
if(state.spec&&state.spec!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).spec===state.spec))return false;
if(state.category&&state.category!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).cat===state.category))return false;
if(state.batch){const b=state.batch.toLowerCase(); if(!L.some(l=>{const pi=PINFO[l.name]||{}; return (pi.batch||'').toLowerCase().includes(b)||(pi.code||'').toLowerCase().includes(b);}))return false;}
return true; }); }
function syncChips(){
const set=(id,chipId,val)=>{ document.getElementById(id).textContent=val||''; document.getElementById(chipId).classList.toggle('on',!!val); };
@@ -115,7 +176,17 @@ function syncChips(){
set('whVal','chipWh',state.wh==='全部'?'':state.wh);
set('statusVal','chipStatus',state.status==='全部'?'':state.status);
set('dateVal','chipDate',state.dateLabel||'');
const any=state.q||state.party!=='全部'||state.wh!=='全部'||state.status!=='全部'||state.dateFrom||state.dateTo;
const advCount=[state.orderNo,state.productInfo,state.batch,
(state.series&&state.series!=='全部')?1:0,
(state.spec&&state.spec!=='全部')?1:0,
(state.category&&state.category!=='全部')?1:0,
(state.op&&state.op!=='全部')?1:0,
(state.reviewer&&state.reviewer!=='全部')?1:0].filter(Boolean).length;
const advBtnEl=document.getElementById('advBtn');
advBtnEl.classList.toggle('primary',advCount>0);
advBtnEl.classList.toggle('soft',advCount===0);
document.getElementById('advCount').textContent=advCount?' · '+advCount:'';
const any=state.q||state.party!=='全部'||state.wh!=='全部'||state.status!=='全部'||state.dateFrom||state.dateTo||advCount>0;
document.getElementById('resetBtn').classList.toggle('primary',!!any);
}
function render(){
@@ -124,7 +195,7 @@ function render(){
document.getElementById('thead').innerHTML='<tr>'+cols.map(c=>{ const cls=(c.num?'num ':'')+(c.key==='act'?'act ':''); if(c.filter){const on=state.status!=='全部'; return `<th class="${cls}"><span class="fh ${on?'filtered':''}" onclick="openFilter(this)">${c.label} <svg class="funnel" viewBox="0 0 24 24"><use href="#i-ic22"/></svg></span></th>`;} return `<th class="${cls}">${c.label}</th>`; }).join('')+'</tr>';
const rows=filtered(); const start=(state.page-1)*state.perPage; const pageRows=rows.slice(start,start+state.perPage); const tb=document.getElementById('tbody');
if(rows.length===0){ tb.innerHTML=`<tr><td colspan="${cols.length}"><div class="empty">没有匹配的出库单 · 试试调整筛选或搜索</div></td></tr>`; }
else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+cols.map(c=>{ if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-body)">${o.no}</span></td>`; if(c.key==='party')return `<td class="pname">${o.party}</td>`; if(c.key==='wh')return `<td>${o.wh}</td>`; if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`; if(c.key==='status')return `<td><span class="badge b-${o.status}">${o.status}</span></td>`; if(c.key==='op')return `<td>${o.op}</td>`; if(c.key==='auditor')return `<td>${o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>'}</td>`; if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`; return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); }
else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+cols.map(c=>{ if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-body)">${o.no}</span></td>`; if(c.key==='party')return `<td class="pname">${o.party}</td>`; if(c.key==='wh')return `<td>${o.wh}</td>`; if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`; if(c.key==='status')return `<td><span class="badge b-${o.status}">${o.status}</span>${o.ret?` <span class="badge b-${o.ret}">${o.ret}</span>`:''}</td>`; if(c.key==='op')return `<td>${o.op}</td>`; if(c.key==='auditor')return `<td>${o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>'}</td>`; if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`; return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); }
document.getElementById('pagerInfo').textContent=`显示 ${rows.length?start+1:0}${Math.min(start+state.perPage,rows.length)},共 ${rows.length}`;
const pages=Math.max(1,Math.ceil(rows.length/state.perPage)); let pg=`<button ${state.page<=1?'disabled':''} onclick="go(${state.page-1})"></button>`; for(let p=1;p<=pages;p++)pg+=`<button class="${p===state.page?'cur':''}" onclick="go(${p})">${p}</button>`; pg+=`<button ${state.page>=pages?'disabled':''} onclick="go(${state.page+1})"></button>`; document.getElementById('pager').innerHTML=pg;
document.getElementById('subInfo').textContent=`本月 ${rows.length} 笔出库单`;
@@ -134,11 +205,10 @@ const PAGE_SIZES=[10,20,50,100];
function openPageSize(e){ openMenu(e.currentTarget, PAGE_SIZES.map(n=>({v:String(n),label:String(n),sel:n===state.perPage})), v=>{ closeMenus(); setPerPage(v); }, 'pgsize-menu', 88); }
function setPerPage(v){ state.perPage=+v; state.page=1; const el=document.getElementById('pageSizeVal'); if(el)el.textContent=v; render(); }
function filterStatus(s){ state.status=s; state.page=1; render(); }
function clearFilters(){ state.q='';state.party='全部';state.wh='全部';state.status='全部';state.dateFrom='';state.dateTo='';state.dateLabel='';state.page=1; document.getElementById('searchInput').value=''; render(); }
function clearFilters(){ state.q='';state.party='全部';state.wh='全部';state.status='全部';state.dateFrom='';state.dateTo='';state.dateLabel='';state.datePreset='all';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';state.page=1; document.getElementById('searchInput').value=''; render(); }
function openFilter(t){ openStatusFilter(t); }
function openStatusFilter(t){ openMenu(t,STATUS.map(v=>({v,label:v,sel:v===state.status})),v=>{ state.status=v; state.page=1; closeMenus(); render(); }); }
function openPartyFilter(t){ openMenu(t,PARTIES.map(v=>({v,label:v,sel:v===state.party})),v=>{ state.party=v; state.page=1; closeMenus(); render(); }); }
function openPartyFilter(t){ openSearchMenu(t,PARTIES.map(v=>({v,label:v,sel:v===state.party})),v=>{ state.party=v; state.page=1; closeMenus(); render(); },{placeholder:'搜索客户…'}); }
function openWhFilter(t){ openMenu(t,WAREHOUSES.map(v=>({v,label:v,sel:v===state.wh})),v=>{ state.wh=v; state.page=1; closeMenus(); render(); }); }
const TODAY='2026-06-22';
function dateMinus(days){ const d=new Date(TODAY); d.setDate(d.getDate()-days); return d.toISOString().slice(0,10); }
@@ -150,44 +220,112 @@ function applyDatePreset(v){ state.datePreset=v; if(v==='all'){state.dateFrom=''
const PINFO={
'茅台 飞天 53°':{code:'MT-FT-53-500',series:'飞天',spec:'53° 500ml×6'},
'茅台 1935':{code:'MT-1935-53-500',series:'1935',spec:'53° 500ml×6'},
'茅台 王子酒':{code:'MT-WZ-53-500',series:'王子',spec:'53° 500ml×6'},
'五粮液 普五 52°':{code:'WLY-P8-52-500',series:'第八代',spec:'52° 500ml×6'},
'五粮液 1618':{code:'WLY-1618-52-500',series:'1618',spec:'52° 500ml×6'},
'剑南春 水晶剑':{code:'JNC-SJ-52-500',series:'水晶剑',spec:'52° 500ml×6'},
'剑南春 珍藏级':{code:'JNC-ZC-52-500',series:'珍藏',spec:'52° 500ml×6'},
'国窖 1573':{code:'GJ-1573-52-500',series:'经典装',spec:'52° 500ml×6'},
'洋河 梦之蓝 M6':{code:'YH-M6-45-500',series:'梦之蓝',spec:'45° 500ml×6'},
'洋河 海之蓝':{code:'YH-HZL-42-480',series:'海之蓝',spec:'42° 480ml×6'},
'洋河 天之蓝':{code:'YH-TZL-42-480',series:'天之蓝',spec:'42° 480ml×6'},
'泸州老窖 特曲':{code:'LZLJ-TQ-52-500',series:'特曲',spec:'52° 500ml×6'},
'泸州老窖 头曲':{code:'LZLJ-TOUQ-52-500',series:'头曲',spec:'52° 500ml×6'},
'习酒 窖藏 1988':{code:'XJ-JC-53-500',series:'窖藏',spec:'53° 500ml×6'},
'汾酒 青花 20':{code:'FJ-QH20-53-500',series:'青花',spec:'53° 500ml×6'},
'汾酒 老白汾':{code:'FJ-LBF-53-475',series:'老白汾',spec:'53° 475ml×6'},
'郎酒 红花郎 15':{code:'LJ-HHL15-53-500',series:'红花郎',spec:'53° 500ml×6'},
'古井贡 年份原浆':{code:'GJG-NF-50-500',series:'年份原浆',spec:'50° 500ml×6'},
'古井贡 古5':{code:'GJG-G5-50-500',series:'古5',spec:'50° 500ml×6'},
'西凤酒 旗舰版':{code:'XF-QJ-52-500',series:'旗舰',spec:'52° 500ml×6'},
'水井坊 井台':{code:'SJF-JT-52-500',series:'井台',spec:'52° 500ml×6'},
'茅台 飞天 53°':{code:'MT-FT-53-500',series:'飞天',spec:'53° 500ml×6',cat:'酱香型',batch:'20260518'},
'茅台 1935':{code:'MT-1935-53-500',series:'1935',spec:'53° 500ml×6',cat:'酱香型',batch:'20260420'},
'茅台 王子酒':{code:'MT-WZ-53-500',series:'王子',spec:'53° 500ml×6',cat:'酱香型',batch:'20260512'},
'五粮液 普五 52°':{code:'WLY-P8-52-500',series:'第八代',spec:'52° 500ml×6',cat:'浓香型',batch:'20260408'},
'五粮液 1618':{code:'WLY-1618-52-500',series:'1618',spec:'52° 500ml×6',cat:'浓香型',batch:'20260316'},
'剑南春 水晶剑':{code:'JNC-SJ-52-500',series:'水晶剑',spec:'52° 500ml×6',cat:'浓香型',batch:'20260502'},
'剑南春 珍藏级':{code:'JNC-ZC-52-500',series:'珍藏',spec:'52° 500ml×6',cat:'浓香型',batch:'20260228'},
'国窖 1573':{code:'GJ-1573-52-500',series:'经典装',spec:'52° 500ml×6',cat:'浓香型',batch:'20260410'},
'洋河 梦之蓝 M6':{code:'YH-M6-45-500',series:'梦之蓝',spec:'45° 500ml×6',cat:'浓香型',batch:'20260322'},
'洋河 海之蓝':{code:'YH-HZL-42-480',series:'海之蓝',spec:'42° 480ml×6',cat:'浓香型',batch:'20260305'},
'洋河 天之蓝':{code:'YH-TZL-42-480',series:'天之蓝',spec:'42° 480ml×6',cat:'浓香型',batch:'20260318'},
'泸州老窖 特曲':{code:'LZLJ-TQ-52-500',series:'特曲',spec:'52° 500ml×6',cat:'浓香型',batch:'20260415'},
'泸州老窖 头曲':{code:'LZLJ-TOUQ-52-500',series:'头曲',spec:'52° 500ml×6',cat:'浓香型',batch:'20260401'},
'习酒 窖藏 1988':{code:'XJ-JC-53-500',series:'窖藏',spec:'53° 500ml×6',cat:'酱香型',batch:'20260210'},
'汾酒 青花 20':{code:'FJ-QH20-53-500',series:'青花',spec:'53° 500ml×6',cat:'清香型',batch:'20260506'},
'汾酒 老白汾':{code:'FJ-LBF-53-475',series:'老白汾',spec:'53° 475ml×6',cat:'清香型',batch:'20260429'},
'郎酒 红花郎 15':{code:'LJ-HHL15-53-500',series:'红花郎',spec:'53° 500ml×6',cat:'酱香型',batch:'20260118'},
'古井贡 年份原浆':{code:'GJG-NF-50-500',series:'年份原浆',spec:'50° 500ml×6',cat:'浓香型',batch:'20260512'},
'古井贡 古5':{code:'GJG-G5-50-500',series:'古5',spec:'50° 500ml×6',cat:'浓香型',batch:'20260508'},
'西凤酒 旗舰版':{code:'XF-QJ-52-500',series:'旗舰',spec:'52° 500ml×6',cat:'凤香型',batch:'20260325'},
'水井坊 井台':{code:'SJF-JT-52-500',series:'井台',spec:'52° 500ml×6',cat:'浓香型',batch:'20260430'},
};
function auditBtns(o){
if(o.status==='草稿') return '<div class="btn primary sm" onclick="toast(\'已提交审核 ✓\');closeDrawer()">提交审核</div>';
if(o.status==='待审核') return '<div class="btn primary sm" onclick="toast(\'已审核通过 ✓\');closeDrawer()">审核通过</div><div class="btn danger sm" onclick="toast(\'已驳回\');closeDrawer()">驳回</div>';
if(o.status==='已通过') return '<div class="btn ghost sm" onclick="toast(\'已结清 ✓\');closeDrawer()">结清</div><div class="btn danger sm" onclick="toast(\'已退单\');closeDrawer()">退单</div>';
if(o.status==='已驳回') return '<div class="btn primary sm" onclick="toast(\'已重新提交 ✓\');closeDrawer()">重新提交</div>';
// ---- 详细搜索模态框 ----
function closeOverlay(id){ document.getElementById(id).classList.remove('show'); }
const SERIES_OPTS=['全部',...Array.from(new Set(Object.values(PINFO).map(p=>p.series)))];
const SPEC_OPTS=['全部',...Array.from(new Set(Object.values(PINFO).map(p=>p.spec)))];
const CAT_OPTS=['全部',...Array.from(new Set(Object.values(PINFO).map(p=>p.cat)))];
const OP_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.op)))];
const REVIEWER_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.auditor).filter(Boolean)))];
let adv={};
function advSet(id,val){ const el=document.getElementById(id); if(el)el.value=(!val||val==='全部')?'':val; }
function dateLabelOf(v){ return v==='month'?'本月':(!v||v==='all'?'':'近'+v+'天'); }
function openAdvSearch(){
adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:state.status,datePreset:state.datePreset||'all'};
advSet('advParty',adv.party); advSet('advSeries',adv.series); advSet('advSpec',adv.spec); advSet('advCategory',adv.category);
advSet('advOp',adv.op); advSet('advReviewer',adv.reviewer); advSet('advStatus',adv.status);
document.getElementById('advNo').value=state.orderNo||'';
document.getElementById('advProduct').value=state.productInfo||'';
document.getElementById('advBatch').value=state.batch||'';
document.getElementById('advDate').value=dateLabelOf(adv.datePreset);
document.getElementById('ovAdv').classList.add('show');
}
function advPick(el,id,opts,key,ph){ openSearchMenu(el,opts.map(v=>({v,label:v,sel:v===adv[key]})),v=>{ adv[key]=v; advSet(id,v); closeMenus(); },{placeholder:ph}); }
function advMenu(el,id,opts,key){ openMenu(el,opts.map(v=>({v,label:v,sel:v===adv[key]})),v=>{ adv[key]=v; advSet(id,v); closeMenus(); }); }
function openAdvParty(el){ advPick(el,'advParty',PARTIES,'party','搜索'+PARTY_LABEL+'…'); }
function openAdvSeries(el){ advPick(el,'advSeries',SERIES_OPTS,'series','搜索系列…'); }
function openAdvSpec(el){ advPick(el,'advSpec',SPEC_OPTS,'spec','搜索规格…'); }
function openAdvCategory(el){ advPick(el,'advCategory',CAT_OPTS,'category','搜索分类…'); }
function openAdvOp(el){ advMenu(el,'advOp',OP_OPTS,'op'); }
function openAdvReviewer(el){ advMenu(el,'advReviewer',REVIEWER_OPTS,'reviewer'); }
function openAdvStatus(el){ advMenu(el,'advStatus',STATUS,'status'); }
function openAdvDate(el){ openMenu(el,DATE_PRESETS.map(p=>({v:p.v,label:p.label,sel:p.v===adv.datePreset})),v=>{ adv.datePreset=v; document.getElementById('advDate').value=dateLabelOf(v); closeMenus(); }); }
function applyAdv(){
state.orderNo=document.getElementById('advNo').value.trim();
state.productInfo=document.getElementById('advProduct').value.trim();
state.batch=document.getElementById('advBatch').value.trim();
state.party=adv.party; state.series=adv.series; state.spec=adv.spec; state.category=adv.category;
state.op=adv.op; state.reviewer=adv.reviewer; state.status=adv.status;
applyDatePreset(adv.datePreset);
state.page=1; closeOverlay('ovAdv'); render();
}
function resetAdv(){
adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',datePreset:'all'};
['advNo','advProduct','advBatch','advParty','advSeries','advSpec','advCategory','advOp','advReviewer','advStatus','advDate'].forEach(id=>{const el=document.getElementById(id); if(el)el.value='';});
state.party='全部';state.status='全部';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';
applyDatePreset('all'); state.page=1; render();
}
function auditBtns(o,idx){
if(o.status==='草稿') return `<div class="btn ghost sm" onclick="toast('打开修改(原型)')">修改</div><div class="btn danger sm" onclick="toast('已删除');closeDrawer()">删除</div><div class="btn primary sm" onclick="toast('已提交审核 ✓');closeDrawer()">提交</div>`;
if(o.status==='待审核') return `<div class="btn primary sm" onclick="toast('已审核通过 ✓');closeDrawer()">通过</div><div class="btn danger sm" onclick="toast('已拒绝');closeDrawer()">拒绝</div><div class="btn ghost sm" onclick="toast('已撤回为草稿');closeDrawer()">撤回</div>`;
if(o.status==='已审核') return `<div class="btn ghost sm" onclick="toast('已结清 ✓');closeDrawer()">结清</div><div class="btn danger sm" onclick="openReturn(${idx})">退单</div>`;
return '';
}
function openDetail(idx){ const o=ORDERS[idx]; document.getElementById('dTitle').textContent=o.no;
let lh=`<div class="lines"><div class="lh" style="grid-template-columns:1fr 124px 40px 64px 78px"><span>商品 · 编码</span><span>系列 / 规格</span><span style="text-align:right">数量</span><span style="text-align:right">单价</span><span style="text-align:right">金额</span></div>`;
o.lines.forEach(l=>{ const pi=PINFO[l.name]||{}; lh+=`<div class="lr" style="grid-template-columns:1fr 124px 40px 64px 78px;align-items:start"><span class="lcell"><span class="ln">${l.name}</span><span class="lcode">${pi.code||'—'}</span></span><span class="lcell"><span class="lser">${pi.series||'—'}</span><span class="lspec">${pi.spec||''}</span></span><span style="text-align:right;font-family:var(--font-mono)">${l.qty}</span><span style="text-align:right;font-family:var(--font-mono)">${money(l.price)}</span><span class="amt">${money(l.qty*l.price)}</span></div>`; }); lh+='</div>';
o.lines.forEach(l=>{ const pi=PINFO[l.name]||{}; const pend=(l.price||0)<=0; const priceC=pend?'<span class="pending-price">待定价</span>':money(l.price); const amtC=pend?'<span class="pending-price">待定价</span>':money(l.qty*l.price); lh+=`<div class="lr" style="grid-template-columns:1fr 124px 40px 64px 78px;align-items:start"><span class="lcell"><span class="ln">${l.name}</span><span class="lcode">${pi.code||'—'}</span></span><span class="lcell"><span class="lser">${pi.series||'—'}</span><span class="lspec">${pi.spec||''}</span></span><span style="text-align:right;font-family:var(--font-mono)">${l.qty}</span><span style="text-align:right;font-family:var(--font-mono)">${priceC}</span><span class="amt">${amtC}</span></div>`; }); lh+='</div>';
const pendCount=o.lines.filter(l=>(l.price||0)<=0).length;
const auditor=o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>';
document.getElementById('drawerBody').innerHTML=`<div class="drow"><span>单据日期</span><b>${o.date}</b></div><div class="drow"><span>${PARTY_LABEL}</span><b>${o.party}</b></div><div class="drow"><span>仓库</span><b>${o.wh}</b></div><div class="drow"><span>商品数</span><b>${o.count}</b></div><div class="drow"><span>出库员</span><b>${o.op}</b></div><div class="drow"><span>审核员</span><b>${auditor}</b></div><div class="drow"><span>状态</span><b><span class="badge b-${o.status}">${o.status}</span></b></div><label style="display:block;font-size:var(--fs-sm);color:var(--muted);margin:16px 0 8px;">出库明细</label>${lh}<div class="ltotal">合计金额 <b>${money(o.amount)}</b></div>`+
`<div class="dactions"><div class="dact-group"><span class="dact-label">单据审核</span><div class="dact-row">${auditBtns(o)}</div></div><div class="dact-group"><span class="dact-label">打印</span><div class="dact-row"><div class="btn ghost sm" onclick="toast('打印标签(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic49"/></svg>打印标签</div><div class="btn ghost sm" onclick="toast('打印单据(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic50"/></svg>打印单据</div></div></div></div>`;
document.getElementById('drawerBody').innerHTML=`<div class="drow"><span>单据日期</span><b>${o.date}</b></div><div class="drow"><span>${PARTY_LABEL}</span><b>${o.party}</b></div><div class="drow"><span>仓库</span><b>${o.wh}</b></div><div class="drow"><span>商品数</span><b>${o.count}</b></div><div class="drow"><span>出库员</span><b>${o.op}</b></div><div class="drow"><span>审核员</span><b>${auditor}</b></div><div class="drow"><span>状态</span><b><span class="badge b-${o.status}">${o.status}</span>${o.ret?` <span class="badge b-${o.ret}">${o.ret}</span>`:''}</b></div><label style="display:block;font-size:var(--fs-sm);color:var(--muted);margin:16px 0 8px;">出库明细</label>${lh}<div class="ltotal">合计金额 <b>${money(o.amount)}</b></div>`+
`<div class="dactions"><div class="dact-group"><span class="dact-label">单据审核</span><div class="dact-row">${auditBtns(o,idx)}</div></div>${(o.status==='已审核'&&pendCount>0)?`<div class="dact-group"><span class="dact-label">售价 · ${pendCount} 项待定价</span><div class="dact-row"><div class="btn primary sm" onclick="openConfirmSale(${idx})"><svg viewBox="0 0 24 24"><use href="#i-edit"/></svg>确认售价</div></div></div>`:''}<div class="dact-group"><span class="dact-label">打印</span><div class="dact-row"><div class="btn ghost sm" onclick="toast('打印标签(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic49"/></svg>打印标签</div><div class="btn ghost sm" onclick="toast('打印单据(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic50"/></svg>打印单据</div></div></div></div>`;
document.getElementById('drawer').classList.add('show'); document.getElementById('drawerMask').classList.add('show'); }
function closeDrawer(){ document.getElementById('drawer').classList.remove('show'); document.getElementById('drawerMask').classList.remove('show'); }
document.addEventListener('keydown',e=>{ if(e.key==='Escape'){closeMenus();closeDrawer();} });
// ---- 退单(出库:退回库存 + ----
const RETURN_IS_OUT=true;
let returnOrder=-1, returnStage={};
function openReturn(idx){ returnOrder=idx; returnStage={};
document.getElementById('returnNoticeText').textContent = RETURN_IS_OUT
? '提交后,退单明细的数量将「加回库存」。退单明细不会删除,以红色「已退单」标注,且不可撤销。'
: '提交后,退单明细对应的库存将「从库存删除」。退单明细不会删除,以红色「已退单」标注,且不可撤销。';
drawReturnRows(); document.getElementById('ovReturn').classList.add('show'); }
function drawReturnRows(){ const o=ORDERS[returnOrder];
document.getElementById('returnRows').innerHTML=o.lines.map((l,i)=>{ const st=returnStage[i]!=null; const pi=PINFO[l.name]||{};
return `<div class="frow" style="align-items:center;gap:12px;margin-bottom:10px"><label style="display:flex;align-items:center;gap:9px;flex:1;cursor:pointer"><input type="checkbox" ${st?'checked':''} onchange="toggleReturn(${i},${l.qty})"><span><div class="pname">${l.name}</div><div class="pcode">${pi.code||'—'} · 数量 ${l.qty}</div></span></label><div class="ctrl input-like" style="width:120px"><input type="number" min="1" max="${l.qty}" value="${st?returnStage[i]:''}" ${st?'':'disabled'} placeholder="退货数量" oninput="setReturnQty(${i},this.value,${l.qty})"></div></div>`; }).join('');
updateReturnSummary(); }
function toggleReturn(i,max){ if(returnStage[i]!=null) delete returnStage[i]; else returnStage[i]=max; drawReturnRows(); }
function setReturnQty(i,v,max){ returnStage[i]=Math.min(Math.max(1,Math.floor(+v||0)),max); updateReturnSummary(); }
function stageAllReturn(){ ORDERS[returnOrder].lines.forEach((l,i)=>returnStage[i]=l.qty); drawReturnRows(); }
function updateReturnSummary(){ const keys=Object.keys(returnStage); const qty=keys.reduce((s,k)=>s+(+returnStage[k]||0),0); document.getElementById('returnSummary').textContent=keys.length?`已退单 ${keys.length} 项 · ${RETURN_IS_OUT?'退回库存 +':'从库存移除 '}${qty}`:'未选择退单明细'; }
function submitReturn(){ const keys=Object.keys(returnStage); if(!keys.length){toast('请先选择要退的明细');return;} const o=ORDERS[returnOrder]; o.ret = keys.length<o.lines.length?'部分退单':'已退单'; closeOverlay('ovReturn'); closeDrawer(); render(); toast('退单成功 ✓'); }
let confirmSaleOrder=-1;
function openConfirmSale(idx){ confirmSaleOrder=idx; const o=ORDERS[idx]; const pend=o.lines.map((l,i)=>({l,i})).filter(({l})=>(l.price||0)<=0);
document.getElementById('confirmSaleRows').innerHTML=pend.map(({l,i})=>`<div class="frow" style="align-items:center;gap:12px;margin-bottom:10px"><div style="flex:1"><div class="pname">${l.name}</div><div class="pcode">数量 ${l.qty}</div></div><div class="ctrl input-like" style="width:150px"><input type="number" inputmode="decimal" data-li="${i}" placeholder="真实售价 0.00"></div></div>`).join('');
document.getElementById('ovConfirmSale').classList.add('show'); }
function saveConfirmSale(){ closeOverlay('ovConfirmSale'); closeDrawer(); toast('售价已确认,已按售价×数量结算应收并补应收流水 ✓'); }
document.addEventListener('keydown',e=>{ if(e.key==='Escape'){closeMenus();document.querySelectorAll('.overlay.show').forEach(o=>o.classList.remove('show'));closeDrawer();} });
render();
</script>