| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>运输计划 · 多选合打单据 Demo</title>
- <style>
- :root {
- --primary: #10b981;
- --primary-dark: #059669;
- --primary-bg: #ecfdf5;
- --text: #0f172a;
- --muted: #64748b;
- --border: #e2e8f0;
- --warn: #d97706;
- --danger: #dc2626;
- --card: #fff;
- --bg: #f1f5f9;
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- font: 14px/1.5 Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
- color: var(--text);
- background: var(--bg);
- }
- .wrap { max-width: 1180px; margin: 0 auto; padding: 24px; }
- h1 { font-size: 20px; margin: 0 0 6px; }
- .sub { color: var(--muted); margin-bottom: 20px; }
- .panel {
- background: var(--card);
- border: 1px solid var(--border);
- border-radius: 10px;
- padding: 16px 20px 20px;
- margin-bottom: 16px;
- }
- .panel h2 {
- margin: 0 0 12px;
- font-size: 15px;
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .panel h2::before {
- content: "";
- width: 3px;
- height: 14px;
- background: var(--primary);
- border-radius: 2px;
- }
- .rules {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 10px;
- margin-bottom: 8px;
- }
- .rule {
- border: 1px solid var(--border);
- border-radius: 8px;
- padding: 10px 12px;
- background: #fafafa;
- }
- .rule strong { display: block; margin-bottom: 4px; }
- .rule.ok { border-color: #a7f3d0; background: var(--primary-bg); }
- .rule.loose { border-color: #fde68a; background: #fffbeb; }
- .toolbar {
- display: flex;
- align-items: center;
- gap: 10px;
- flex-wrap: wrap;
- margin-bottom: 12px;
- padding-bottom: 12px;
- border-bottom: 1px solid var(--border);
- }
- .selected-tip { color: var(--muted); min-width: 72px; }
- .btn {
- height: 36px;
- padding: 0 14px;
- border-radius: 8px;
- border: 1px solid var(--border);
- background: #fff;
- cursor: pointer;
- font-size: 14px;
- display: inline-flex;
- align-items: center;
- gap: 6px;
- }
- .btn:disabled { opacity: 0.45; cursor: not-allowed; }
- .btn-main {
- background: var(--primary);
- border-color: var(--primary);
- color: #fff;
- }
- .dropdown { position: relative; }
- .menu {
- display: none;
- position: absolute;
- top: 40px;
- left: 0;
- z-index: 20;
- min-width: 180px;
- background: #fff;
- border: 1px solid var(--border);
- border-radius: 8px;
- box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
- padding: 6px;
- }
- .menu.open { display: block; }
- .menu button {
- width: 100%;
- text-align: left;
- border: 0;
- background: transparent;
- padding: 8px 10px;
- border-radius: 6px;
- cursor: pointer;
- font-size: 13px;
- }
- .menu button:hover:not(:disabled) { background: var(--primary-bg); color: var(--primary-dark); }
- .menu button:disabled { color: #94a3b8; cursor: not-allowed; }
- .menu .hint {
- font-size: 11px;
- color: var(--muted);
- padding: 0 10px 6px;
- line-height: 1.35;
- }
- table.list {
- width: 100%;
- border-collapse: collapse;
- }
- table.list th, table.list td {
- padding: 10px 8px;
- border-bottom: 1px solid var(--border);
- text-align: left;
- font-size: 13px;
- }
- table.list th { color: var(--muted); font-weight: 600; background: #f8fafc; }
- table.list tr:hover td { background: #f8fafc; }
- table.list tr.picked td { background: var(--primary-bg); }
- .tag {
- display: inline-block;
- padding: 1px 8px;
- border-radius: 999px;
- font-size: 12px;
- background: #e2e8f0;
- color: #334155;
- }
- .tag.in { background: #d1fae5; color: #065f46; }
- .tag.out { background: #dbeafe; color: #1e40af; }
- .msg {
- margin-top: 12px;
- padding: 10px 12px;
- border-radius: 8px;
- font-size: 13px;
- display: none;
- }
- .msg.show { display: block; }
- .msg.err { background: #fef2f2; color: var(--danger); border: 1px solid #fecaca; }
- .msg.warn { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
- .msg.ok { background: var(--primary-bg); color: #065f46; border: 1px solid #a7f3d0; }
- .grid2 {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 16px;
- }
- @media (max-width: 900px) {
- .rules, .grid2 { grid-template-columns: 1fr; }
- }
- .slip {
- border: 1px solid #cbd5e1;
- border-radius: 8px;
- padding: 14px;
- background: #fff;
- min-height: 280px;
- overflow: auto;
- }
- .slip .inbound-like,
- .delivery-like,
- .subpack-like {
- font-family: SimSun, "Songti SC", serif;
- color: #111;
- max-width: 960px;
- margin: 0 auto;
- padding: 8px 10px 24px;
- }
- .slip-title {
- position: relative;
- min-height: 56px;
- margin-bottom: 14px;
- text-align: center;
- padding: 4px 40px 0;
- }
- .slip-title .co {
- font-size: 22px;
- font-weight: 700;
- letter-spacing: 6px;
- line-height: 1.35;
- }
- .slip-title .doc {
- font-size: 22px;
- font-weight: 700;
- letter-spacing: 10px;
- margin-top: 2px;
- }
- .slip-info {
- width: 100%;
- border-collapse: collapse;
- margin-bottom: 10px;
- font-size: 14px;
- }
- .slip-info td { padding: 4px 0; vertical-align: middle; }
- .slip-info .lab { white-space: nowrap; }
- .type-checks {
- display: flex;
- gap: 12px;
- font-size: 14px;
- margin-bottom: 4px;
- justify-content: flex-start;
- }
- .type-checks label { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
- .slip-product {
- width: 100%;
- border-collapse: collapse;
- table-layout: fixed;
- font-size: 13px;
- }
- .slip-product th,
- .slip-product td {
- border: 1px solid #333 !important;
- padding: 8px 4px;
- text-align: center;
- word-break: break-all;
- }
- .slip-product th {
- font-weight: bold;
- background: #fff;
- }
- .slip-product .remark,
- .slip-product .terms {
- text-align: left;
- padding-left: 8px;
- font-size: 12px;
- line-height: 1.45;
- }
- .slip-sign {
- width: 100%;
- margin-top: 16px;
- font-size: 13px;
- border-collapse: collapse;
- }
- .slip-sign td {
- padding: 8px 4px;
- vertical-align: top;
- }
- .bucket-checks {
- display: inline-flex;
- gap: 14px;
- margin-left: 12px;
- flex-wrap: wrap;
- font-size: 13px;
- }
- .subpack-no {
- text-align: right;
- font-size: 13px;
- margin-bottom: 6px;
- color: #333;
- }
- .empty {
- color: var(--muted);
- text-align: center;
- padding: 48px 12px;
- }
- .legend { font-size: 12px; color: var(--muted); margin-top: 8px; }
- code { background: #f1f5f9; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
- </style>
- </head>
- <body>
- <div class="wrap">
- <h1>运输计划 · 多选合打单据 Demo</h1>
- <p class="sub">
- 勾选多条计划 →「打印单据」→ 按规则校验 → 右侧预览「产品合并到一张单据」。
- 本页仅演示交互与合打规则,非最终生产版式。
- </p>
- <section class="panel">
- <h2>合打规则(客户确认稿)</h2>
- <div class="rules">
- <div class="rule ok">
- <strong>入库单</strong>
- 勾选计划的<strong>客户名称一致</strong>且<strong>计划日期一致</strong>才可合打;产品行合并到一张入库单。
- </div>
- <div class="rule ok">
- <strong>送(提)货单</strong>
- 同入库单:客户名称 + 计划日期均须一致。
- </div>
- <div class="rule loose">
- <strong>分装单</strong>
- <strong>不校验</strong>客户名称、计划日期;勾选即可合打产品到一张分装单。
- </div>
- </div>
- <p class="legend">
- 合打策略:沿用现网三类单据模版,<strong>只合并物料表格行</strong>(分行列出,不合并数量);
- 入库/送货「单据编号」= 各计划号;分装「序号」递增。
- 表头:客户/日期取共有值(入库、送货须一致);<strong>备注</strong>= 各计划备注换行拼接(一行一计划);
- <strong>收货联系方式留空</strong>;不填车辆信息。
- </p>
- </section>
- <div class="grid2">
- <section class="panel">
- <h2>运输计划列表(多选)</h2>
- <div class="toolbar">
- <span class="selected-tip" id="selCount">已选 0 项</span>
- <div class="dropdown">
- <button class="btn" id="printBtn" type="button" disabled>
- 🖨 打印单据 ▾
- </button>
- <div class="menu" id="printMenu">
- <div class="hint" id="menuHint">请先勾选计划</div>
- <button type="button" data-cmd="inbound" disabled>入库单</button>
- <button type="button" data-cmd="delivery" disabled>送(提)货单</button>
- <button type="button" data-cmd="subpack" disabled>分装单</button>
- </div>
- </div>
- <button class="btn" type="button" id="clearBtn">清空选择</button>
- </div>
- <table class="list">
- <thead>
- <tr>
- <th style="width:36px"><input type="checkbox" id="checkAll" title="全选当前页" /></th>
- <th>计划号</th>
- <th>计划日期</th>
- <th>客户名称</th>
- <th>类型</th>
- <th>产品摘要</th>
- </tr>
- </thead>
- <tbody id="tbody"></tbody>
- </table>
- <div class="msg" id="toast"></div>
- </section>
- <section class="panel">
- <h2>合打预览</h2>
- <div class="slip" id="preview">
- <div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>
- </div>
- </section>
- </div>
- </div>
- <script>
- /** @typedef {{ id: string, planNo: string, planDate: string, customerName: string, planType: 'inbound'|'outbound'|'direct', remark?: string, products: {name:string, model:string, qty:number, unit:string}[] }} Plan */
- /** @type {Plan[]} */
- const PLANS = [
- {
- id: '1', planNo: 'PO20260727001', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
- planType: 'inbound', remark: '上午到厂,注意卸货安全',
- products: [
- { name: '亚硫酸钠', model: '液体', qty: 12, unit: '吨' },
- { name: '试剂硝酸', model: 'AR', qty: 2, unit: '吨' }
- ]
- },
- {
- id: '2', planNo: 'PO20260727002', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
- planType: 'inbound', remark: '附带质检报告',
- products: [
- { name: '次氯酸钠', model: '固体', qty: 8, unit: '吨' }
- ]
- },
- {
- id: '3', planNo: 'PO20260727003', planDate: '2026-07-27', customerName: '合肥市皖和化工有限公司',
- planType: 'inbound', remark: '',
- products: [
- { name: '98% 工业硫酸', model: 'AR', qty: 20, unit: '吨' }
- ]
- },
- {
- id: '4', planNo: 'PO20260728001', planDate: '2026-07-28', customerName: '安徽云栋化工有限公司',
- planType: 'inbound', remark: '改期后计划',
- products: [
- { name: '试剂氨水', model: 'AR', qty: 5, unit: '吨' }
- ]
- },
- {
- id: '5', planNo: 'PO20260727011', planDate: '2026-07-27', customerName: '中国长江实业',
- planType: 'outbound', remark: '客户自提点卸货',
- products: [
- { name: 'TEST液碱', model: '31%', qty: 30, unit: '吨' }
- ]
- },
- {
- id: '6', planNo: 'PO20260727012', planDate: '2026-07-27', customerName: '中国长江实业',
- planType: 'outbound', remark: '需带防护用品',
- products: [
- { name: 'TEST盐酸', model: '30%', qty: 15, unit: '吨' },
- { name: 'TEST液碱', model: '31%', qty: 10, unit: '吨' }
- ]
- },
- {
- id: '7', planNo: 'PO20260729001', planDate: '2026-07-29', customerName: '中国长江实业',
- planType: 'outbound', remark: '',
- products: [
- { name: 'TEST液碱', model: '31%', qty: 6, unit: '吨' }
- ]
- }
- ]
- const selected = new Set()
- let menuOpen = false
- function typeLabel(t) {
- return t === 'inbound' ? '入库单' : t === 'outbound' ? '出库单' : '直销单'
- }
- function typeClass(t) {
- return t === 'inbound' ? 'in' : t === 'outbound' ? 'out' : ''
- }
- function printableCommands(planType) {
- if (planType === 'inbound') return ['inbound']
- if (planType === 'outbound') return ['delivery', 'subpack']
- return []
- }
- function selectedPlans() {
- return PLANS.filter(p => selected.has(p.id))
- }
- function sameCustomerAndDate(plans) {
- if (!plans.length) return false
- const c = plans[0].customerName
- const d = plans[0].planDate
- return plans.every(p => p.customerName === c && p.planDate === d)
- }
- function canMerge(cmd, plans) {
- if (plans.length < 1) return { ok: false, reason: '请至少勾选 1 条运输计划' }
- if (plans.length === 1) {
- const allow = printableCommands(plans[0].planType)
- if (!allow.includes(cmd)) {
- return { ok: false, reason: `当前单据类型(${typeLabel(plans[0].planType)})不支持打印该单据` }
- }
- return { ok: true, reason: '' }
- }
- // multi
- if (cmd === 'subpack') {
- const bad = plans.filter(p => !printableCommands(p.planType).includes('subpack'))
- if (bad.length) {
- return { ok: false, reason: `分装单仅支持出库类计划;以下不可打:${bad.map(p => p.planNo).join('、')}` }
- }
- return { ok: true, reason: '' }
- }
- if (cmd === 'inbound') {
- const bad = plans.filter(p => p.planType !== 'inbound')
- if (bad.length) {
- return { ok: false, reason: `入库单仅支持入库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
- }
- if (!sameCustomerAndDate(plans)) {
- return { ok: false, reason: '合打入库单要求:客户名称一致 且 计划日期一致' }
- }
- return { ok: true, reason: '' }
- }
- if (cmd === 'delivery') {
- const bad = plans.filter(p => p.planType !== 'outbound')
- if (bad.length) {
- return { ok: false, reason: `送(提)货单仅支持出库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
- }
- if (!sameCustomerAndDate(plans)) {
- return { ok: false, reason: '合打送(提)货单要求:客户名称一致 且 计划日期一致' }
- }
- return { ok: true, reason: '' }
- }
- return { ok: false, reason: '未知单据类型' }
- }
- function renderList() {
- const tb = document.getElementById('tbody')
- tb.innerHTML = PLANS.map(p => {
- const checked = selected.has(p.id) ? 'checked' : ''
- const picked = selected.has(p.id) ? 'picked' : ''
- const prod = p.products.map(x => `${x.name}×${x.qty}`).join(',')
- return `<tr class="${picked}" data-id="${p.id}">
- <td><input type="checkbox" class="row-check" data-id="${p.id}" ${checked} /></td>
- <td>${p.planNo}</td>
- <td>${p.planDate}</td>
- <td>${p.customerName}</td>
- <td><span class="tag ${typeClass(p.planType)}">${typeLabel(p.planType)}</span></td>
- <td>${prod}</td>
- </tr>`
- }).join('')
- document.getElementById('selCount').textContent = `已选 ${selected.size} 项`
- document.getElementById('printBtn').disabled = selected.size === 0
- document.getElementById('checkAll').checked = selected.size === PLANS.length && PLANS.length > 0
- updateMenuState()
- }
- function updateMenuState() {
- const plans = selectedPlans()
- const hint = document.getElementById('menuHint')
- const cmds = [
- ['inbound', '入库单'],
- ['delivery', '送(提)货单'],
- ['subpack', '分装单']
- ]
- cmds.forEach(([cmd]) => {
- const btn = document.querySelector(`#printMenu button[data-cmd="${cmd}"]`)
- const r = canMerge(cmd, plans)
- btn.disabled = !r.ok
- btn.title = r.ok ? '可合打' : r.reason
- })
- if (!plans.length) {
- hint.textContent = '请先勾选计划'
- } else if (plans.length === 1) {
- hint.textContent = '单条:沿用原打印能力'
- } else {
- const okAny = cmds.some(([c]) => canMerge(c, plans).ok)
- hint.textContent = okAny
- ? `已选 ${plans.length} 条 · 灰色项表示不满足合打条件`
- : `已选 ${plans.length} 条 · 当前选择不满足任一合打条件`
- }
- }
- function showToast(text, type) {
- const el = document.getElementById('toast')
- el.className = 'msg show ' + type
- el.textContent = text
- }
- function formatPlanDateChinese(ymd) {
- if (!ymd) return ''
- const [y, m, d] = ymd.split('-')
- return `${Number(y)}年${Number(m)}月${Number(d)}日`
- }
- function productNoStub(name) {
- // demo only
- const map = {
- '亚硫酸钠': 'WL-001',
- '试剂硝酸': 'WL-002',
- '次氯酸钠': 'WL-003',
- '98% 工业硫酸': 'WL-004',
- '试剂氨水': 'WL-005',
- 'TEST液碱': 'WL-101',
- 'TEST盐酸': 'WL-102'
- }
- return map[name] || '—'
- }
- function mergePlanRemarks(plans) {
- // 一行一个计划备注;空备注跳过;前缀计划号便于对照
- return plans
- .map(p => {
- const text = (p.remark || '').trim()
- if (!text) return ''
- return `${p.planNo}:${text}`
- })
- .filter(Boolean)
- .join('<br />')
- }
- function collectProductRows(plans) {
- const rows = []
- plans.forEach(p => {
- p.products.forEach(prod => {
- rows.push({
- planNo: p.planNo,
- productNo: productNoStub(prod.name),
- name: prod.name,
- model: prod.model,
- qty: prod.qty,
- unit: prod.unit
- })
- })
- })
- return rows
- }
- function renderInboundSlip(plans) {
- const customer = plans[0].customerName
- const dateCn = formatPlanDateChinese(plans[0].planDate)
- const rows = collectProductRows(plans)
- const body = rows.map(r => `<tr>
- <td>${r.planNo}</td>
- <td>${r.productNo}</td>
- <td>${r.name}</td>
- <td>${r.model}</td>
- <td>${r.unit}</td>
- <td>${r.qty}</td>
- <td></td>
- <td></td>
- </tr>`).join('')
- document.getElementById('preview').innerHTML = `
- <div class="inbound-like">
- <div class="slip-title">
- <div class="co">安庆市山峡化工有限公司</div>
- <div class="doc">入 库 单</div>
- </div>
- <table class="slip-info">
- <tr>
- <td style="width:58%"><span class="lab">送货单位:</span>${customer}</td>
- <td style="width:42%;text-align:right"><span class="lab">日 期:</span>${dateCn}</td>
- </tr>
- </table>
- <table class="slip-product">
- <colgroup>
- <col style="width:14%" /><col style="width:14%" /><col style="width:20%" />
- <col style="width:8%" /><col style="width:6%" /><col style="width:10%" />
- <col style="width:14%" /><col style="width:14%" />
- </colgroup>
- <thead>
- <tr>
- <th>单据编号</th>
- <th>物料编号</th>
- <th>物料名称</th>
- <th>规格</th>
- <th>单位</th>
- <th>数量</th>
- <th>产品规格</th>
- <th>实收数量</th>
- </tr>
- </thead>
- <tbody>
- ${body}
- <tr><td colspan="8" class="remark">备注:${mergePlanRemarks(plans) || ''}</td></tr>
- </tbody>
- </table>
- <table class="slip-sign">
- <tr>
- <td>仓库:</td>
- <td>审核:</td>
- <td>客户签收:</td>
- <td>司机/送货人(附车牌):</td>
- </tr>
- </table>
- </div>
- `
- }
- function renderDeliverySlip(plans) {
- const customer = plans[0].customerName
- const dateCn = formatPlanDateChinese(plans[0].planDate)
- const rows = collectProductRows(plans)
- const remarkHtml = mergePlanRemarks(plans)
- const body = rows.map(r => `<tr>
- <td>${r.planNo}</td>
- <td>${r.productNo}</td>
- <td>${r.name}</td>
- <td>${r.model}</td>
- <td>${r.unit}</td>
- <td>${r.qty}</td>
- <td></td>
- <td></td>
- </tr>`).join('')
- document.getElementById('preview').innerHTML = `
- <div class="delivery-like">
- <div class="slip-title">
- <div class="co">安庆市山峡化工有限公司</div>
- <div class="doc">送 (提) 货 单</div>
- </div>
- <table class="slip-info">
- <tr>
- <td style="width:34%"><span class="lab">收货单位:</span>${customer}</td>
- <td style="width:33%"><span class="lab">收货联系方式:</span></td>
- <td style="width:33%;text-align:right;vertical-align:top;">
- <div class="type-checks">
- <label><input type="checkbox" />自提</label>
- <label><input type="checkbox" checked />出库</label>
- <label><input type="checkbox" />直销</label>
- </div>
- <div><span class="lab">日 期:</span>${dateCn}</div>
- </td>
- </tr>
- </table>
- <table class="slip-product">
- <colgroup>
- <col style="width:14%" /><col style="width:14%" /><col style="width:20%" />
- <col style="width:8%" /><col style="width:6%" /><col style="width:10%" />
- <col style="width:14%" /><col style="width:14%" />
- </colgroup>
- <thead>
- <tr>
- <th>单据编号</th>
- <th>物料编号</th>
- <th>物料名称</th>
- <th>规格</th>
- <th>单位</th>
- <th>数量</th>
- <th>产品规格</th>
- <th>实收数量</th>
- </tr>
- </thead>
- <tbody>
- ${body}
- <tr><td colspan="8" class="remark">备注:${remarkHtml || ''}</td></tr>
- <tr>
- <td colspan="5" class="terms">
- 1.本送货单如购销双方未签订合同,视为有效;<br />
- 2.本次送货含桶____个,回收____个,结存____个;
- </td>
- <td colspan="3" class="remark" style="font-size:11px;line-height:1.5;">
- 地址:安庆市高新区霞虹路5号<br />
- 送货单位固话:0556-5999710<br />
- 联系人电话:方总13395560066
- </td>
- </tr>
- </tbody>
- </table>
- <table class="slip-sign">
- <tr>
- <td>仓库:</td>
- <td>审核:</td>
- <td>客户签收:</td>
- <td>司机/送货人(附车牌):</td>
- </tr>
- </table>
- </div>
- `
- }
- function renderSubpackSlip(plans) {
- // 分装单允许跨客户/跨日期:沿用现网模版字段;仅扩展物料表行
- const first = plans[0]
- const orderDate = formatPlanDateChinese(first.planDate)
- const rows = collectProductRows(plans)
- const remarkHtml = mergePlanRemarks(plans)
- const body = rows.map((r, idx) => `<tr>
- <td>${idx + 1}</td>
- <td>${r.name}</td>
- <td>${r.model}</td>
- <td></td>
- <td>${r.unit}</td>
- <td>${r.qty}</td>
- <td><input type="checkbox" /></td>
- </tr>`).join('')
- const slipNo = `SXFZ${first.planDate.replace(/-/g, '')}-001`
- document.getElementById('preview').innerHTML = `
- <div class="subpack-like">
- <div class="slip-title">
- <div class="co">安庆市山峡化工有限公司</div>
- <div class="doc">分 装 单</div>
- </div>
- <div class="subpack-no">${slipNo}</div>
- <table class="slip-info">
- <tr>
- <td><span class="lab">下单日期:</span>${orderDate}</td>
- <td><span class="lab">交货日期:</span>${orderDate}</td>
- <td><span class="lab">下单人:</span></td>
- <td><span class="lab">经手人:</span></td>
- </tr>
- </table>
- <table class="slip-product">
- <colgroup>
- <col style="width:8%" /><col style="width:22%" /><col style="width:16%" />
- <col style="width:16%" /><col style="width:10%" /><col style="width:12%" />
- <col style="width:16%" />
- </colgroup>
- <thead>
- <tr>
- <th>序号</th>
- <th>产品名称</th>
- <th>产品型号</th>
- <th>包装规格</th>
- <th>单位</th>
- <th>数量</th>
- <th>是否完成</th>
- </tr>
- </thead>
- <tbody>
- ${body}
- <tr>
- <td colspan="7" class="remark">
- 备注:${remarkHtml || ''}
- <span class="bucket-checks">
- <label><input type="checkbox" />全新桶</label>
- <label><input type="checkbox" />是否回收桶</label>
- <label><input type="checkbox" />桶循环</label>
- </span>
- </td>
- </tr>
- </tbody>
- </table>
- <table class="slip-sign">
- <tr>
- <td>仓库:</td>
- <td>审核:</td>
- <td>客户签收:</td>
- <td>司机/送货人(附车牌):</td>
- </tr>
- </table>
- </div>
- `
- }
- function renderPreview(cmd, plans) {
- if (cmd === 'inbound') {
- renderInboundSlip(plans)
- return
- }
- if (cmd === 'delivery') {
- renderDeliverySlip(plans)
- return
- }
- renderSubpackSlip(plans)
- }
- function tryPrint(cmd) {
- const plans = selectedPlans()
- const r = canMerge(cmd, plans)
- menuOpen = false
- document.getElementById('printMenu').classList.remove('open')
- if (!r.ok) {
- showToast(r.reason, 'err')
- document.getElementById('preview').innerHTML = `<div class="empty">${r.reason}</div>`
- return
- }
- const label = cmd === 'inbound' ? '入库单' : cmd === 'delivery' ? '送(提)货单' : '分装单'
- showToast(
- plans.length === 1
- ? `将打开单计划「${label}」打印(与现网一致)`
- : `将合并 ${plans.length} 条计划的产品到一张「${label}」`,
- 'ok'
- )
- renderPreview(cmd, plans)
- }
- document.getElementById('tbody').addEventListener('change', (e) => {
- const t = e.target
- if (!(t instanceof HTMLInputElement) || !t.classList.contains('row-check')) return
- if (t.checked) selected.add(t.dataset.id)
- else selected.delete(t.dataset.id)
- renderList()
- document.getElementById('toast').classList.remove('show')
- })
- document.getElementById('checkAll').addEventListener('change', (e) => {
- const on = e.target.checked
- selected.clear()
- if (on) PLANS.forEach(p => selected.add(p.id))
- renderList()
- })
- document.getElementById('clearBtn').addEventListener('click', () => {
- selected.clear()
- renderList()
- document.getElementById('preview').innerHTML = '<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>'
- document.getElementById('toast').classList.remove('show')
- })
- document.getElementById('printBtn').addEventListener('click', (e) => {
- e.stopPropagation()
- menuOpen = !menuOpen
- document.getElementById('printMenu').classList.toggle('open', menuOpen)
- updateMenuState()
- })
- document.getElementById('printMenu').addEventListener('click', (e) => {
- const btn = e.target.closest('button[data-cmd]')
- if (!btn || btn.disabled) return
- tryPrint(btn.dataset.cmd)
- })
- document.addEventListener('click', () => {
- if (!menuOpen) return
- menuOpen = false
- document.getElementById('printMenu').classList.remove('open')
- })
- // 预设:勾选同客户同日的两条入库计划,方便演示合打
- selected.add('1')
- selected.add('2')
- renderList()
- tryPrint('inbound')
- showToast('已预勾选同客户同日两条入库计划,并按现网入库单版式展示合打预览(无「来源计划」「产品行数」)。', 'warn')
- </script>
- </body>
- </html>
|