Files
shanxia2.0/docs/fresh-mint-pl-purchase-cert-link-compare-demo.html
T
wuzilongandCursor d22b06dbe7 feat(pl): 易制毒先安排待确认与购买证管理
含易制毒计划安排后进入已安排待确认,确认通过后再占合同并生成链接/地磅;新增购买证/运输证主数据、产品行关联、余量校验与 OCR 配置,并停止跟踪 api_key/app.pid。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-09 18:41:10 +08:00

667 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fresh Mint · 购买证关联运输计划 · 校验与比对 Demo</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #1e293b;
--text-muted: #64748b;
--border: #d9e8e2;
--surface: #ffffff;
--bg-page: #f1f5f9;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 12px 40px rgba(15, 23, 42, 0.14);
--radius: 10px;
--warn: #d97706;
--warn-bg: #fffbeb;
--warn-border: #fde68a;
--danger: #dc2626;
--danger-bg: #fef2f2;
--danger-border: #fecaca;
--ok: #059669;
--ok-bg: #ecfdf5;
--ok-border: #a7f3d0;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: Inter, -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: var(--bg-page);
}
.demo-page { max-width: 1100px; margin: 0 auto; padding: 28px 24px 80px; }
.demo-head { margin-bottom: 18px; }
.demo-head h1 { margin: 0 0 8px; font-size: 22px; font-weight: 700; letter-spacing: -0.02em; }
.demo-head h1 span { color: var(--primary); }
.demo-head p { margin: 0; color: var(--text-muted); max-width: 920px; }
.meta { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.pill {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 500;
background: var(--primary-bg); color: var(--primary-dark); border: 1px solid #a7f3d0;
}
.pill.muted { background: #f1f5f9; color: var(--text-muted); border-color: #e2e8f0; }
.pill.warn { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-border); }
.pill.danger { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-border); }
.brief {
display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px;
}
@media (max-width: 900px) { .brief { grid-template-columns: 1fr; } }
.card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 14px 16px; box-shadow: var(--shadow);
}
.card h2 {
margin: 0 0 8px; font-size: 14px; font-weight: 600;
padding-left: 10px; border-left: 3px solid var(--primary);
color: var(--primary-dark);
}
.card.danger-card h2 { border-left-color: var(--danger); color: var(--danger); }
.card.warn-card h2 { border-left-color: var(--warn); color: var(--warn); }
.card ul { margin: 0; padding-left: 18px; color: var(--text-muted); font-size: 13px; }
.card li { margin: 4px 0; }
.card li strong { color: var(--text); }
.formula {
margin-top: 8px; padding: 8px 10px; border-radius: 8px;
background: var(--primary-bg); color: var(--primary-dark);
font-size: 12px; font-weight: 500;
}
.formula.warn { background: var(--warn-bg); color: #92400e; }
.formula.danger { background: var(--danger-bg); color: #991b1b; }
.scenario-tabs {
display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap;
}
.scenario-tab {
appearance: none; border: 1px solid var(--border); background: #fff;
height: 36px; padding: 0 14px; border-radius: 8px; cursor: pointer;
font-size: 13px; font-weight: 500; color: var(--text);
}
.scenario-tab:hover { border-color: #a7f3d0; color: var(--primary-dark); }
.scenario-tab.active {
background: var(--primary-bg); border-color: #a7f3d0; color: var(--primary-dark);
}
/* Dialog shell */
.stage {
background: rgba(15, 23, 42, 0.35);
border-radius: 14px;
padding: 28px 20px 36px;
min-height: 640px;
display: flex;
align-items: flex-start;
justify-content: center;
}
.dialog {
width: min(920px, 100%);
background: var(--surface);
border-radius: 12px;
box-shadow: var(--shadow-lg);
overflow: hidden;
display: flex;
flex-direction: column;
max-height: calc(100vh - 120px);
}
.dialog-hd {
display: flex; align-items: center; justify-content: space-between;
padding: 16px 20px; border-bottom: 1px solid var(--border);
}
.dialog-hd h3 { margin: 0; font-size: 16px; font-weight: 600; }
.dialog-hd .x {
width: 28px; height: 28px; border: none; background: transparent;
color: var(--text-muted); font-size: 18px; cursor: pointer; border-radius: 6px;
}
.dialog-hd .x:hover { background: #f1f5f9; }
.dialog-bd {
padding: 16px 20px;
overflow-y: auto;
flex: 1;
}
.dialog-ft {
display: flex; justify-content: flex-end; gap: 10px;
padding: 12px 20px; border-top: 1px solid var(--border);
background: #fafafa;
}
.btn {
display: inline-flex; align-items: center; gap: 6px;
height: 36px; padding: 0 14px; border-radius: 8px;
font-size: 14px; font-weight: 500; cursor: pointer;
border: 1px solid var(--border); background: #fff; color: var(--text);
}
.btn:hover { border-color: #a7f3d0; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary {
background: var(--primary); border-color: var(--primary); color: #fff;
}
.btn-primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.btn-primary:disabled { background: #94a3b8; border-color: #94a3b8; }
.field-label {
display: block; font-size: 11px; color: var(--text-muted);
margin-bottom: 6px; font-weight: 500;
}
.select-like {
height: 36px; border: 1px solid var(--border); border-radius: 8px;
padding: 0 12px; display: flex; align-items: center; justify-content: space-between;
background: #fff; color: var(--text); font-size: 14px;
}
.select-like .chev { color: var(--text-muted); font-size: 12px; }
.plan-meta {
margin: 12px 0 14px; padding: 10px 12px; border-radius: 8px;
background: #f8fafc; border: 1px solid #e2e8f0;
font-size: 13px; color: var(--text-muted);
}
.plan-meta strong { color: var(--text); }
.link-row {
border: 1px solid var(--border);
border-radius: 10px;
padding: 12px 14px;
margin-bottom: 12px;
background: #fff;
}
.link-row-title {
display: flex; align-items: center; justify-content: space-between;
gap: 10px; margin-bottom: 10px; flex-wrap: wrap;
}
.link-row-title .name { font-weight: 600; }
.tag {
display: inline-flex; align-items: center;
height: 22px; padding: 0 8px; border-radius: 4px;
font-size: 12px; font-weight: 500;
background: transparent; color: var(--text); border: 1px solid #e2e8f0;
}
.tag.ok { background: var(--ok); color: #fff; border-color: var(--ok); }
.tag.warn { background: var(--warn); color: #fff; border-color: var(--warn); }
.tag.danger { background: var(--danger); color: #fff; border-color: var(--danger); }
.link-row-fields {
display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
}
@media (max-width: 700px) { .link-row-fields { grid-template-columns: 1fr; } }
/* Hard gate / soft compare panels */
.gate-panel {
margin-top: 12px;
border-radius: 10px;
border: 1px solid var(--border);
overflow: hidden;
}
.gate-panel.ok { border-color: var(--ok-border); }
.gate-panel.warn { border-color: var(--warn-border); }
.gate-panel.danger { border-color: var(--danger-border); }
.gate-hd {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 12px; font-weight: 600; font-size: 13px;
}
.gate-panel.ok .gate-hd { background: var(--ok-bg); color: var(--ok); }
.gate-panel.warn .gate-hd { background: var(--warn-bg); color: var(--warn); }
.gate-panel.danger .gate-hd { background: var(--danger-bg); color: var(--danger); }
.gate-bd { padding: 0; }
.gate-row {
display: grid;
grid-template-columns: 110px 1fr 1fr 88px;
gap: 8px;
padding: 10px 12px;
border-top: 1px solid #f1f5f9;
font-size: 13px;
align-items: start;
}
.gate-row.header {
background: #f8fafc;
color: var(--text-muted);
font-size: 11px;
font-weight: 600;
text-transform: none;
border-top: none;
}
@media (max-width: 700px) {
.gate-row { grid-template-columns: 1fr; gap: 4px; }
.gate-row.header { display: none; }
}
.gate-row .col-label { color: var(--text-muted); font-size: 12px; }
.gate-row .col-plan, .gate-row .col-cert { word-break: break-all; }
.gate-row.mismatch .col-plan,
.gate-row.mismatch .col-cert { color: var(--warn); font-weight: 500; }
.gate-row.block .col-plan,
.gate-row.block .col-cert { color: var(--danger); font-weight: 500; }
.gate-row.match .result { color: var(--ok); }
.gate-row.mismatch .result { color: var(--warn); }
.gate-row.block .result { color: var(--danger); }
.qty-breakdown {
margin: 10px 12px 12px;
padding: 10px 12px;
border-radius: 8px;
background: #f8fafc;
border: 1px solid #e2e8f0;
font-size: 12px;
color: var(--text-muted);
}
.qty-breakdown .line {
display: flex; justify-content: space-between; gap: 12px; margin: 3px 0;
}
.qty-breakdown .line.total {
margin-top: 8px; padding-top: 8px; border-top: 1px dashed #cbd5e1;
font-weight: 600; color: var(--text); font-size: 13px;
}
.qty-breakdown .line.over { color: var(--danger); }
.qty-breakdown .num { font-variant-numeric: tabular-nums; color: var(--text); font-weight: 500; }
.toast {
position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
background: #1e293b; color: #fff; padding: 10px 16px; border-radius: 8px;
font-size: 13px; box-shadow: var(--shadow-lg); opacity: 0; pointer-events: none;
transition: opacity 0.2s;
z-index: 50;
}
.toast.show { opacity: 1; }
.note {
margin-top: 16px; font-size: 12px; color: var(--text-muted);
}
</style>
</head>
<body>
<div class="demo-page">
<header class="demo-head">
<h1>购买证关联运输计划 · <span>校验与比对</span> Demo</h1>
<p>
关联「已安排待确认」计划时:有效期与数量为硬拦截;销售单位 / 购买单位 / 购买物品与计划字段比对后展示差异,
由用户确认或取消。切换下方场景查看不同结果态。
</p>
<div class="meta">
<span class="pill">Fresh Mint</span>
<span class="pill muted">关联弹窗</span>
<span class="pill warn">软比对 · 可确认</span>
<span class="pill danger">硬门禁 · 不可确认</span>
</div>
</header>
<div class="brief">
<div class="card danger-card">
<h2>硬门禁(不通过则禁用「确定」)</h2>
<ul>
<li><strong>有效期</strong>:本次计划送货/装货日期须落在购买证有效期内</li>
<li><strong>数量</strong>:已归档实际量 + 已确认未归档计划量 + 本次计划量 ≤ 购买证数量</li>
</ul>
<div class="formula danger">
占用合计 = Σ归档实际量 + Σ已安排已确认未归档计划量 + 本次计划量 ≤ 证面数量
</div>
</div>
<div class="card warn-card">
<h2>软比对(展示差异,点确定 / 取消)</h2>
<ul>
<li><strong>销售单位</strong> ↔ 计划装货客户(示意)</li>
<li><strong>购买单位或个人</strong> ↔ 计划送货客户(示意)</li>
<li><strong>购买物品</strong> ↔ 计划产品品名</li>
</ul>
<div class="formula warn">
有出入时仍可点「确定」完成关联(Demo 映射待业务确认)
</div>
</div>
</div>
<div class="scenario-tabs" role="tablist">
<button type="button" class="scenario-tab active" data-scene="ok">场景 A · 全部通过</button>
<button type="button" class="scenario-tab" data-scene="soft">场景 B · 单位/品名有出入</button>
<button type="button" class="scenario-tab" data-scene="qty">场景 C · 数量超限</button>
<button type="button" class="scenario-tab" data-scene="date">场景 D · 超出有效期</button>
</div>
<div class="stage">
<div class="dialog" id="dialog">
<div class="dialog-hd">
<h3>关联待确认计划</h3>
<button type="button" class="x" aria-label="关闭">×</button>
</div>
<div class="dialog-bd">
<label class="field-label">待确认计划</label>
<div class="select-like">
<span id="planLabel">YS-20261009-018 · 陕A·D8821</span>
<span class="chev">▾</span>
</div>
<div class="plan-meta" id="planMeta">
车牌 <strong>陕A·D8821</strong>;送货日期 <strong>2026-10-12</strong>;
每行易制毒产品须绑定 1 张购买证 + 1 张运输证。
</div>
<div class="link-row">
<div class="link-row-title">
<span class="name" id="prodName">丙酮</span>
<span class="tag" id="prodQtyTag">计划量 32 吨</span>
</div>
<div class="link-row-fields">
<div>
<label class="field-label">购买证</label>
<div class="select-like" id="purchaseSelect">
<span>购备〔2026〕0831 · 丙酮 · 余 118</span>
<span class="chev">▾</span>
</div>
</div>
<div>
<label class="field-label">运输证</label>
<div class="select-like" id="transportSelect">
<span>运备〔2026〕2104 · 车陕A·D8821 · 余 40</span>
<span class="chev">▾</span>
</div>
</div>
</div>
<!-- 硬门禁 -->
<div class="gate-panel" id="hardGate">
<div class="gate-hd">
<span id="hardGateTitle">硬门禁校验</span>
<span class="tag" id="hardGateTag">通过</span>
</div>
<div class="gate-bd">
<div class="gate-row header">
<div>校验项</div>
<div>计划侧</div>
<div>购买证侧</div>
<div>结果</div>
</div>
<div class="gate-row" id="rowDate">
<div class="col-label">有效期限</div>
<div class="col-plan" id="datePlan">送货日 2026-10-12</div>
<div class="col-cert" id="dateCert">2026-09-01 ~ 2026-12-31</div>
<div class="result" id="dateResult">通过</div>
</div>
<div class="gate-row" id="rowQty">
<div class="col-label">数量占用</div>
<div class="col-plan" id="qtyPlan">本次 32 吨</div>
<div class="col-cert" id="qtyCert">证面 170 吨</div>
<div class="result" id="qtyResult">通过</div>
</div>
<div class="qty-breakdown" id="qtyBreakdown"></div>
</div>
</div>
<!-- 软比对 -->
<div class="gate-panel" id="softGate">
<div class="gate-hd">
<span id="softGateTitle">单位 / 品名比对</span>
<span class="tag" id="softGateTag">一致</span>
</div>
<div class="gate-bd">
<div class="gate-row header">
<div>比对项</div>
<div>计划侧</div>
<div>购买证侧</div>
<div>结果</div>
</div>
<div class="gate-row" id="rowSeller">
<div class="col-label">销售单位</div>
<div class="col-plan" id="sellerPlan">—</div>
<div class="col-cert" id="sellerCert">—</div>
<div class="result" id="sellerResult">—</div>
</div>
<div class="gate-row" id="rowBuyer">
<div class="col-label">购买单位</div>
<div class="col-plan" id="buyerPlan">—</div>
<div class="col-cert" id="buyerCert">—</div>
<div class="result" id="buyerResult">—</div>
</div>
<div class="gate-row" id="rowProduct">
<div class="col-label">购买物品</div>
<div class="col-plan" id="productPlan">—</div>
<div class="col-cert" id="productCert">—</div>
<div class="result" id="productResult">—</div>
</div>
</div>
</div>
</div>
</div>
<div class="dialog-ft">
<button type="button" class="btn" id="btnCancel">取消</button>
<button type="button" class="btn btn-primary" id="btnOk">确定</button>
</div>
</div>
</div>
<p class="note">
Demo 说明:装货客户 ↔ 销售单位、送货客户 ↔ 购买单位为示意映射,落地前需业务确认。
文件路径:<code>docs/fresh-mint-pl-purchase-cert-link-compare-demo.html</code>
</p>
</div>
<div class="toast" id="toast"></div>
<script>
const SCENES = {
ok: {
label: '全部通过',
planNo: 'YS-20261009-018 · 陕A·D8821',
deliveryDate: '2026-10-12',
product: '丙酮',
planQty: 32,
purchaseLabel: '购备〔2026〕0831 · 丙酮 · 余 118',
transportLabel: '运备〔2026〕2104 · 车陕A·D8821 · 余 40',
validFrom: '2026-09-01',
validTo: '2026-12-31',
certTotal: 170,
archivedActual: 20,
occupiedPlan: 32,
loadCustomer: '陕西某某化工有限公司',
deliveryCustomer: '西安某某制药有限公司',
sellerName: '陕西某某化工有限公司',
buyerName: '西安某某制药有限公司',
certProduct: '丙酮',
},
soft: {
label: '单位/品名有出入',
planNo: 'YS-20261009-022 · 陕A·D8821',
deliveryDate: '2026-10-12',
product: '丙酮(工业级)',
planQty: 28,
purchaseLabel: '购备〔2026〕0831 · 丙酮 · 余 118',
transportLabel: '运备〔2026〕2104 · 车陕A·D8821 · 余 40',
validFrom: '2026-09-01',
validTo: '2026-12-31',
certTotal: 170,
archivedActual: 20,
occupiedPlan: 32,
loadCustomer: '陕西某某化工(销售)有限公司',
deliveryCustomer: '西安某某制药股份有限公司',
sellerName: '陕西某某化工有限公司',
buyerName: '西安某某制药有限公司',
certProduct: '丙酮',
},
qty: {
label: '数量超限',
planNo: 'YS-20261009-031 · 陕B·F1908',
deliveryDate: '2026-10-15',
product: '丙酮',
planQty: 90,
purchaseLabel: '购备〔2026〕0831 · 丙酮 · 余 118',
transportLabel: '运备〔2026〕2110 · 车陕B·F1908 · 余 100',
validFrom: '2026-09-01',
validTo: '2026-12-31',
certTotal: 170,
archivedActual: 52,
occupiedPlan: 40,
loadCustomer: '陕西某某化工有限公司',
deliveryCustomer: '西安某某制药有限公司',
sellerName: '陕西某某化工有限公司',
buyerName: '西安某某制药有限公司',
certProduct: '丙酮',
},
date: {
label: '超出有效期',
planNo: 'YS-20261009-040 · 陕A·D8821',
deliveryDate: '2027-01-05',
product: '丙酮',
planQty: 20,
purchaseLabel: '购备〔2026〕0831 · 丙酮 · 余 118',
transportLabel: '运备〔2026〕2104 · 车陕A·D8821 · 余 40',
validFrom: '2026-09-01',
validTo: '2026-12-31',
certTotal: 170,
archivedActual: 20,
occupiedPlan: 32,
loadCustomer: '陕西某某化工有限公司',
deliveryCustomer: '西安某某制药有限公司',
sellerName: '陕西某某化工有限公司',
buyerName: '西安某某制药有限公司',
certProduct: '丙酮',
},
};
function norm(s) {
return String(s || '')
.replace(/\s+/g, '')
.replace(/(/g, '(')
.replace(/)/g, ')')
.toLowerCase();
}
function softMatch(a, b) {
const na = norm(a);
const nb = norm(b);
if (!na || !nb) return false;
if (na === nb) return true;
return na.includes(nb) || nb.includes(na);
}
function setRow(el, state) {
el.classList.remove('match', 'mismatch', 'block');
if (state) el.classList.add(state);
}
function setGate(panel, tag, level, title) {
panel.classList.remove('ok', 'warn', 'danger');
panel.classList.add(level);
tag.className = 'tag ' + (level === 'ok' ? 'ok' : level === 'warn' ? 'warn' : 'danger');
tag.textContent = title;
}
function render(sceneKey) {
const s = SCENES[sceneKey];
const used = s.archivedActual + s.occupiedPlan + s.planQty;
const remainAfter = s.certTotal - used;
const dateOk = s.deliveryDate >= s.validFrom && s.deliveryDate <= s.validTo;
const qtyOk = used <= s.certTotal + 1e-9;
const hardOk = dateOk && qtyOk;
const sellerOk = softMatch(s.loadCustomer, s.sellerName);
const buyerOk = softMatch(s.deliveryCustomer, s.buyerName);
const productOk = softMatch(s.product, s.certProduct);
const softOk = sellerOk && buyerOk && productOk;
document.getElementById('planLabel').textContent = s.planNo;
document.getElementById('planMeta').innerHTML =
'车牌 <strong>' + s.planNo.split('·').slice(1).join('·').trim() + '</strong>;送货日期 <strong>' +
s.deliveryDate + '</strong>;每行易制毒产品须绑定 1 张购买证 + 1 张运输证。';
document.getElementById('prodName').textContent = s.product;
document.getElementById('prodQtyTag').textContent = '计划量 ' + s.planQty + ' 吨';
document.getElementById('purchaseSelect').querySelector('span').textContent = s.purchaseLabel;
document.getElementById('transportSelect').querySelector('span').textContent = s.transportLabel;
// hard
document.getElementById('datePlan').textContent = '送货日 ' + s.deliveryDate;
document.getElementById('dateCert').textContent = s.validFrom + ' ~ ' + s.validTo;
document.getElementById('dateResult').textContent = dateOk ? '通过' : '超出有效期';
setRow(document.getElementById('rowDate'), dateOk ? 'match' : 'block');
document.getElementById('qtyPlan').textContent = '本次 ' + s.planQty + ' 吨';
document.getElementById('qtyCert').textContent = '证面 ' + s.certTotal + ' 吨';
document.getElementById('qtyResult').textContent = qtyOk ? '通过' : '超限';
setRow(document.getElementById('rowQty'), qtyOk ? 'match' : 'block');
document.getElementById('qtyBreakdown').innerHTML =
'<div class="line"><span>已归档实际数量</span><span class="num">' + s.archivedActual + ' 吨</span></div>' +
'<div class="line"><span>已安排已确认未归档计划量</span><span class="num">' + s.occupiedPlan + ' 吨</span></div>' +
'<div class="line"><span>本次计划数量</span><span class="num">' + s.planQty + ' 吨</span></div>' +
'<div class="line total' + (qtyOk ? '' : ' over') + '"><span>合计占用 / 证面</span><span class="num">' +
used + ' / ' + s.certTotal + ' 吨' + (qtyOk ? '(剩余 ' + remainAfter + ')' : '(超 ' + Math.abs(remainAfter) + ')') +
'</span></div>';
setGate(
document.getElementById('hardGate'),
document.getElementById('hardGateTag'),
hardOk ? 'ok' : 'danger',
hardOk ? '通过' : '拦截'
);
document.getElementById('hardGateTitle').textContent = hardOk ? '硬门禁校验 · 通过' : '硬门禁校验 · 不可确认';
// soft
function fillSoft(rowId, planId, certId, resultId, planVal, certVal, ok) {
document.getElementById(planId).textContent = planVal;
document.getElementById(certId).textContent = certVal;
document.getElementById(resultId).textContent = ok ? '一致' : '有出入';
setRow(document.getElementById(rowId), ok ? 'match' : 'mismatch');
}
fillSoft('rowSeller', 'sellerPlan', 'sellerCert', 'sellerResult', s.loadCustomer, s.sellerName, sellerOk);
fillSoft('rowBuyer', 'buyerPlan', 'buyerCert', 'buyerResult', s.deliveryCustomer, s.buyerName, buyerOk);
fillSoft('rowProduct', 'productPlan', 'productCert', 'productResult', s.product, s.certProduct, productOk);
setGate(
document.getElementById('softGate'),
document.getElementById('softGateTag'),
softOk ? 'ok' : 'warn',
softOk ? '一致' : '有出入'
);
document.getElementById('softGateTitle').textContent = softOk
? '单位 / 品名比对 · 一致'
: '单位 / 品名比对 · 请确认后继续';
const btnOk = document.getElementById('btnOk');
btnOk.disabled = !hardOk;
btnOk.textContent = hardOk
? (softOk ? '确定' : '确认仍要关联')
: '确定(已拦截)';
btnOk.dataset.softWarn = softOk ? '0' : '1';
btnOk.dataset.hardOk = hardOk ? '1' : '0';
}
function toast(msg) {
const el = document.getElementById('toast');
el.textContent = msg;
el.classList.add('show');
clearTimeout(toast._t);
toast._t = setTimeout(() => el.classList.remove('show'), 2200);
}
document.querySelectorAll('.scenario-tab').forEach((btn) => {
btn.addEventListener('click', () => {
document.querySelectorAll('.scenario-tab').forEach((b) => b.classList.remove('active'));
btn.classList.add('active');
render(btn.dataset.scene);
});
});
document.getElementById('btnCancel').addEventListener('click', () => {
toast('已取消,未保存关联');
});
document.getElementById('btnOk').addEventListener('click', (e) => {
const btn = e.currentTarget;
if (btn.disabled) return;
if (btn.dataset.softWarn === '1') {
toast('已确认差异并保存关联(Demo)');
} else {
toast('关联已保存(Demo)');
}
});
render('ok');
</script>
</body>
</html>