| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764 |
- <!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 {
- --mint: #059669;
- --mint-soft: #ecfdf5;
- --mint-line: #a7f3d0;
- --slate: #64748b;
- --ink: #0f172a;
- --warn: #d97706;
- --danger: #dc2626;
- --bg: #f1f5f9;
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
- background: linear-gradient(165deg, #ecfdf5 0%, #f8fafc 42%, #e2e8f0 100%);
- color: var(--ink);
- min-height: 100vh;
- }
- .page {
- max-width: 1180px;
- margin: 0 auto;
- padding: 28px 20px 48px;
- }
- h1 {
- margin: 0 0 6px;
- font-size: 22px;
- font-weight: 700;
- }
- .lead {
- margin: 0 0 20px;
- color: var(--slate);
- font-size: 14px;
- line-height: 1.6;
- }
- .layout-picker {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 12px;
- margin-bottom: 24px;
- }
- .layout-opt {
- border: 2px solid #e2e8f0;
- background: #fff;
- border-radius: 12px;
- padding: 14px 14px 12px;
- cursor: pointer;
- text-align: left;
- transition: border-color .15s, box-shadow .15s;
- }
- .layout-opt:hover { border-color: var(--mint-line); }
- .layout-opt.active {
- border-color: var(--mint);
- box-shadow: 0 0 0 3px rgb(16 185 129 / 18%);
- }
- .layout-opt .tag {
- display: inline-block;
- font-size: 11px;
- font-weight: 700;
- color: var(--mint);
- background: var(--mint-soft);
- padding: 2px 8px;
- border-radius: 999px;
- margin-bottom: 8px;
- }
- .layout-opt h3 {
- margin: 0 0 4px;
- font-size: 15px;
- }
- .layout-opt p {
- margin: 0;
- font-size: 12px;
- color: var(--slate);
- line-height: 1.5;
- }
- .phones {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 28px;
- align-items: start;
- }
- .phone-wrap h2 {
- margin: 0 0 10px;
- font-size: 15px;
- color: var(--slate);
- font-weight: 600;
- }
- .phone {
- width: 100%;
- max-width: 390px;
- margin: 0 auto;
- background: #0f172a;
- border-radius: 36px;
- padding: 12px;
- box-shadow: 0 24px 48px rgb(15 23 42 / 18%);
- }
- .phone-screen {
- background: var(--bg);
- border-radius: 28px;
- overflow: hidden;
- min-height: 640px;
- display: flex;
- flex-direction: column;
- }
- .nav {
- background: var(--mint);
- color: #fff;
- text-align: center;
- font-weight: 600;
- font-size: 16px;
- padding: 14px 16px 16px;
- border-radius: 0 0 14px 14px;
- }
- .scroll {
- flex: 1;
- overflow: auto;
- padding: 12px 0 80px;
- }
- .card {
- margin: 0 12px 10px;
- background: #fff;
- border-radius: 12px;
- overflow: hidden;
- border: 1px solid #e2e8f0;
- }
- .card-title {
- display: flex;
- align-items: center;
- gap: 6px;
- padding: 6px 12px;
- font-size: 12px;
- font-weight: 600;
- color: #047857;
- background: linear-gradient(90deg, #ecfdf5 0%, transparent 72%);
- }
- .card-title::before {
- content: "";
- width: 3px;
- height: 12px;
- border-radius: 2px;
- background: var(--mint);
- }
- .cell {
- display: flex;
- justify-content: space-between;
- gap: 12px;
- padding: 10px 14px;
- font-size: 14px;
- border-top: 1px solid #f1f5f9;
- }
- .cell:first-child { border-top: 0; }
- .cell .k { color: var(--slate); }
- .cell .v { font-weight: 600; text-align: right; }
- .product {
- margin: 10px 12px;
- padding: 14px;
- border-radius: 12px;
- background: linear-gradient(180deg, #f0fdf4 0%, #fff 48%);
- border: 1px solid #d1fae5;
- }
- .product-head {
- display: flex;
- justify-content: space-between;
- gap: 8px;
- margin-bottom: 10px;
- }
- .product-name { font-size: 15px; font-weight: 700; }
- .badge {
- flex-shrink: 0;
- font-size: 10px;
- font-weight: 600;
- padding: 2px 8px;
- border-radius: 999px;
- background: #d1fae5;
- color: #047857;
- height: fit-content;
- }
- .badge.warn { background: #fef3c7; color: #b45309; }
- .meta {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 8px;
- margin-bottom: 12px;
- }
- .meta-item {
- background: rgb(255 255 255 / 85%);
- border: 1px solid #e2e8f0;
- border-radius: 8px;
- padding: 8px 10px;
- }
- .meta-item.span { grid-column: 1 / -1; }
- .meta-label { font-size: 11px; color: var(--slate); margin-bottom: 2px; }
- .meta-value { font-size: 13px; font-weight: 600; }
- .meta-value.plan { color: var(--mint); font-size: 16px; }
- .actual-box {
- background: #fff;
- border: 1px solid var(--mint-line);
- border-radius: 10px;
- padding: 10px 12px;
- }
- .actual-box__label {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
- gap: 8px;
- margin-bottom: 6px;
- }
- .actual-box__label strong { font-size: 13px; }
- .actual-box__label span { font-size: 11px; color: var(--slate); }
- .actual-row {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .actual-row input {
- flex: 1;
- border: 0;
- outline: none;
- font-size: 22px;
- font-weight: 700;
- color: var(--ink);
- width: 0;
- background: transparent;
- }
- .unit { color: var(--slate); font-size: 13px; font-weight: 600; }
- .brand-block {
- margin-top: 10px;
- border-top: 1px dashed #bbf7d0;
- padding-top: 10px;
- }
- .brand-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- margin-bottom: 8px;
- }
- .brand-head h4 {
- margin: 0;
- font-size: 13px;
- color: #047857;
- }
- .brand-sum {
- font-size: 11px;
- color: var(--slate);
- }
- .brand-sum.bad { color: var(--danger); font-weight: 600; }
- .brand-sum.ok { color: var(--mint); font-weight: 600; }
- .brand-row {
- display: grid;
- grid-template-columns: 1fr 88px 28px;
- gap: 6px;
- align-items: center;
- margin-bottom: 6px;
- }
- .brand-row select,
- .brand-row input {
- height: 36px;
- border: 1px solid #cbd5e1;
- border-radius: 8px;
- padding: 0 8px;
- font-size: 13px;
- background: #fff;
- width: 100%;
- }
- .brand-row input { text-align: right; font-weight: 600; }
- .icon-btn {
- width: 28px;
- height: 28px;
- border: 0;
- border-radius: 8px;
- background: #fee2e2;
- color: var(--danger);
- font-size: 16px;
- cursor: pointer;
- line-height: 1;
- }
- .add-brand {
- width: 100%;
- margin-top: 4px;
- height: 34px;
- border: 1px dashed var(--mint);
- border-radius: 8px;
- background: var(--mint-soft);
- color: var(--mint);
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- }
- .chips {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin-bottom: 8px;
- }
- .chip {
- border: 1px solid #cbd5e1;
- background: #fff;
- border-radius: 999px;
- padding: 6px 10px;
- font-size: 12px;
- cursor: pointer;
- }
- .chip.on {
- border-color: var(--mint);
- background: var(--mint-soft);
- color: #047857;
- font-weight: 600;
- }
- .chip .q {
- margin-left: 4px;
- color: var(--slate);
- font-weight: 500;
- }
- .footer {
- position: sticky;
- bottom: 0;
- padding: 12px 16px 16px;
- background: linear-gradient(180deg, transparent, #f1f5f9 28%);
- }
- .submit {
- width: 100%;
- height: 44px;
- border: 0;
- border-radius: 999px;
- background: var(--mint);
- color: #fff;
- font-size: 16px;
- font-weight: 600;
- cursor: pointer;
- }
- .note {
- margin-top: 20px;
- padding: 14px 16px;
- background: #fff;
- border-radius: 12px;
- border: 1px solid #e2e8f0;
- font-size: 13px;
- color: var(--slate);
- line-height: 1.6;
- }
- .note strong { color: var(--ink); }
- @media (max-width: 900px) {
- .phones, .layout-picker { grid-template-columns: 1fr; }
- }
- </style>
- </head>
- <body>
- <div class="page">
- <h1>运输计划回传 · 按品牌填量 Demo</h1>
- <p class="lead">
- 左侧模拟<strong>现场链接</strong>(入/出库单回执),右侧模拟<strong>司机链接</strong>。
- 有品牌的产品(烧碱)可填 1~N 条「品牌 + 数量」;无品牌产品(盐酸)只填总实际数量。
- 请先选择下方交互方案,再在手机框内试用。
- </p>
- <div class="layout-picker" id="layoutPicker">
- <button type="button" class="layout-opt active" data-layout="a" onclick="setLayout('a')">
- <span class="tag">方案 A · 推荐</span>
- <h3>下拉选品牌 + 数量行</h3>
- <p>可增减行;合计自动回写实际数量。与归档弹窗逻辑一致,适合现场快速录入。</p>
- </button>
- <button type="button" class="layout-opt" data-layout="b" onclick="setLayout('b')">
- <span class="tag">方案 B</span>
- <h3>品牌芯片 + 点选填量</h3>
- <p>先点选参与品牌,再为每个已选品牌填数量。品牌少时更直观。</p>
- </button>
- <button type="button" class="layout-opt" data-layout="c" onclick="setLayout('c')">
- <span class="tag">方案 C</span>
- <h3>仅现场填品牌;司机只填总量</h3>
- <p>司机链接保持现状(一个总实际数量);品牌明细只在现场链接填写。</p>
- </button>
- </div>
- <div class="phones">
- <!-- 现场 -->
- <div class="phone-wrap">
- <h2>现场链接 · 入库单回执</h2>
- <div class="phone">
- <div class="phone-screen">
- <div class="nav">入库单 · 现场回执</div>
- <div class="scroll">
- <div class="card">
- <div class="cell"><span class="k">业务类型</span><span class="v">采购入库</span></div>
- <div class="cell"><span class="k">往来单位</span><span class="v">君正化工</span></div>
- <div class="cell"><span class="k">车牌</span><span class="v">皖A·12X34</span></div>
- <div class="cell"><span class="k">计划号</span><span class="v">JH20260731001</span></div>
- </div>
- <div class="card">
- <div class="card-title">产品实际数量</div>
- </div>
- <!-- 有品牌:烧碱 -->
- <div class="product" id="siteBranded">
- <div class="product-head">
- <div class="product-name">烧碱</div>
- <span class="badge warn">不限品牌</span>
- </div>
- <div class="meta">
- <div class="meta-item">
- <div class="meta-label">规格型号</div>
- <div class="meta-value">32%</div>
- </div>
- <div class="meta-item">
- <div class="meta-label">包装规格</div>
- <div class="meta-value">散装</div>
- </div>
- <div class="meta-item span">
- <div class="meta-label">计划数量</div>
- <div class="meta-value plan">30 吨</div>
- </div>
- </div>
- <div class="actual-box">
- <div class="actual-box__label">
- <strong>实际数量</strong>
- <span class="layout-a-only layout-b-only">由品牌明细合计</span>
- <span class="layout-c-only" style="display:none">请按现场实装填写</span>
- </div>
- <div class="actual-row">
- <input id="siteActualCaustic" type="number" value="28" step="0.001" readonly />
- <span class="unit">吨</span>
- </div>
- </div>
- <div class="brand-block layout-a-only" id="siteBrandRowsA">
- <div class="brand-head">
- <h4>按品牌填量</h4>
- <span class="brand-sum ok" id="siteSumA">合计 28 = 实际数量 ✓</span>
- </div>
- <div id="siteRowsA"></div>
- <button type="button" class="add-brand" onclick="addSiteRowA()">+ 添加品牌</button>
- </div>
- <div class="brand-block layout-b-only" id="siteBrandRowsB" style="display:none">
- <div class="brand-head">
- <h4>选择品牌并填量</h4>
- <span class="brand-sum ok" id="siteSumB">合计 28 = 实际数量 ✓</span>
- </div>
- <div class="chips" id="siteChips"></div>
- <div id="siteRowsB"></div>
- </div>
- </div>
- <!-- 无品牌:盐酸 -->
- <div class="product">
- <div class="product-head">
- <div class="product-name">盐酸</div>
- <span class="badge">产品 2/2</span>
- </div>
- <div class="meta">
- <div class="meta-item">
- <div class="meta-label">规格型号</div>
- <div class="meta-value">31%</div>
- </div>
- <div class="meta-item">
- <div class="meta-label">包装规格</div>
- <div class="meta-value">吨袋</div>
- </div>
- <div class="meta-item span">
- <div class="meta-label">计划数量</div>
- <div class="meta-value plan">10 吨</div>
- </div>
- </div>
- <div class="actual-box">
- <div class="actual-box__label">
- <strong>实际数量</strong>
- <span>无品牌产品,直接填写总量</span>
- </div>
- <div class="actual-row">
- <input type="number" value="9.8" step="0.001" />
- <span class="unit">吨</span>
- </div>
- </div>
- </div>
- </div>
- <div class="footer">
- <button type="button" class="submit" onclick="alert('Demo:现场回执已提交(含品牌明细)')">提交</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 司机 -->
- <div class="phone-wrap">
- <h2>司机链接 · 运输回传</h2>
- <div class="phone">
- <div class="phone-screen">
- <div class="nav">运输计划</div>
- <div class="scroll">
- <div class="card">
- <div class="cell"><span class="k">计划编号</span><span class="v">JH20260731001</span></div>
- <div class="cell"><span class="k">车牌</span><span class="v">皖A·12X34</span></div>
- <div class="cell"><span class="k">司机</span><span class="v">张师傅 138****0001</span></div>
- </div>
- <div class="card">
- <div class="card-title">产品明细</div>
- <div class="cell"><span class="k">烧碱 32% · 散装</span><span class="v">计划 30 吨</span></div>
- <div class="cell"><span class="k">盐酸 31% · 吨袋</span><span class="v">计划 10 吨</span></div>
- </div>
- <div class="card layout-c-only" style="display:none">
- <div class="card-title">回传数量</div>
- <div style="padding:12px 14px 14px">
- <div class="actual-box">
- <div class="actual-box__label">
- <strong>实际数量</strong>
- <span>方案 C:司机只填总量</span>
- </div>
- <div class="actual-row">
- <input type="number" value="37.8" step="0.001" />
- <span class="unit">吨</span>
- </div>
- </div>
- </div>
- </div>
- <div class="layout-a-only layout-b-only" id="driverProducts">
- <div class="card"><div class="card-title">回传数量(按产品)</div></div>
- <div class="product">
- <div class="product-head">
- <div class="product-name">烧碱</div>
- <span class="badge warn">不限品牌</span>
- </div>
- <div class="actual-box">
- <div class="actual-box__label">
- <strong>实际数量</strong>
- <span>由品牌明细合计</span>
- </div>
- <div class="actual-row">
- <input id="driverActualCaustic" type="number" value="28" step="0.001" readonly />
- <span class="unit">吨</span>
- </div>
- </div>
- <div class="brand-block">
- <div class="brand-head">
- <h4>按品牌填量</h4>
- <span class="brand-sum ok" id="driverSum">合计 28 = 实际数量 ✓</span>
- </div>
- <div id="driverRows"></div>
- <button type="button" class="add-brand" onclick="addDriverRow()">+ 添加品牌</button>
- </div>
- </div>
- <div class="product">
- <div class="product-head">
- <div class="product-name">盐酸</div>
- <span class="badge">无品牌</span>
- </div>
- <div class="actual-box">
- <div class="actual-box__label">
- <strong>实际数量</strong>
- <span>无品牌,直接填总量</span>
- </div>
- <div class="actual-row">
- <input type="number" value="9.8" step="0.001" />
- <span class="unit">吨</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="footer">
- <button type="button" class="submit" onclick="alert('Demo:司机回执已保存')">保存回执</button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="note">
- <strong>规则预览:</strong>
- ① 产品有品牌概念(brandMode = any/required)时展示品牌明细;无品牌则隐藏。
- ② 可填 1 个或多个品牌行,数量 > 0 才计入。
- ③ 各品牌数量合计 = 该产品实际数量;不限品牌与指定品牌(偏好)均可多选品牌,指定品牌默认预选且可取消。
- ④ 现场回传写入后,归档弹窗可预填品牌明细(与现有归档按品牌库存衔接)。
- <br /><br />
- 点击上方方案卡片选择偏好(会写入选择事件)。本地文件也可直接打开:
- <code>.superpowers/brainstorm/.../content/site-driver-brand-qty-demo.html</code>
- </div>
- </div>
- <script>
- const BRANDS = [
- { id: 'b1', name: '君正' },
- { id: 'b2', name: '红四方' },
- { id: 'b3', name: '中盐' }
- ]
- let layout = 'a'
- let siteRows = [
- { brandId: 'b1', qty: 16 },
- { brandId: 'b2', qty: 12 }
- ]
- let driverRows = [
- { brandId: 'b1', qty: 16 },
- { brandId: 'b2', qty: 12 }
- ]
- let selectedChips = new Set(['b1', 'b2'])
- function setLayout(key) {
- layout = key
- document.querySelectorAll('.layout-opt').forEach(el => {
- el.classList.toggle('active', el.dataset.layout === key)
- })
- document.querySelectorAll('.layout-a-only').forEach(el => {
- el.style.display = key === 'a' || (el.classList.contains('layout-b-only') && key === 'b') ? '' : 'none'
- })
- document.querySelectorAll('.layout-b-only').forEach(el => {
- if (el.classList.contains('layout-a-only')) {
- el.style.display = (key === 'a' || key === 'b') ? '' : 'none'
- } else {
- el.style.display = key === 'b' ? '' : 'none'
- }
- })
- document.querySelectorAll('.layout-c-only').forEach(el => {
- el.style.display = key === 'c' ? '' : 'none'
- })
- // fix dual-class elements
- document.querySelectorAll('.layout-a-only.layout-b-only').forEach(el => {
- el.style.display = (key === 'a' || key === 'b') ? '' : 'none'
- })
- document.getElementById('siteBrandRowsA').style.display = key === 'a' ? '' : 'none'
- document.getElementById('siteBrandRowsB').style.display = key === 'b' ? '' : 'none'
- const actual = document.getElementById('siteActualCaustic')
- actual.readOnly = key !== 'c'
- if (key === 'c') actual.value = '28'
- renderAll()
- if (typeof toggleSelect === 'function') {
- const opt = document.querySelector(`.layout-opt[data-layout="${key}"]`)
- if (opt) toggleSelect(opt)
- }
- }
- function sumRows(rows) {
- return rows.reduce((s, r) => s + (Number(r.qty) || 0), 0)
- }
- function brandOptionsHtml(selectedId) {
- return BRANDS.map(b =>
- `<option value="${b.id}" ${b.id === selectedId ? 'selected' : ''}>${b.name}</option>`
- ).join('')
- }
- function updateSumLabel(el, total, actual) {
- const ok = Math.abs(total - actual) < 0.0005
- el.className = 'brand-sum ' + (ok ? 'ok' : 'bad')
- el.textContent = ok
- ? `合计 ${total} = 实际数量 ✓`
- : `合计 ${total} ≠ 实际数量 ${actual}`
- }
- function renderSiteA() {
- const box = document.getElementById('siteRowsA')
- box.innerHTML = siteRows.map((r, i) => `
- <div class="brand-row">
- <select onchange="siteRows[${i}].brandId=this.value; renderAll()">
- ${brandOptionsHtml(r.brandId)}
- </select>
- <input type="number" step="0.001" min="0" value="${r.qty}"
- oninput="siteRows[${i}].qty=Number(this.value)||0; syncSiteActual()" />
- <button type="button" class="icon-btn" onclick="siteRows.splice(${i},1); renderAll()" title="删除">×</button>
- </div>
- `).join('')
- syncSiteActual()
- }
- function syncSiteActual() {
- const total = Number(sumRows(siteRows).toFixed(3))
- document.getElementById('siteActualCaustic').value = total
- updateSumLabel(document.getElementById('siteSumA'), total, total)
- updateSumLabel(document.getElementById('siteSumB'), total, total)
- }
- function addSiteRowA() {
- const used = new Set(siteRows.map(r => r.brandId))
- const next = BRANDS.find(b => !used.has(b.id)) || BRANDS[0]
- siteRows.push({ brandId: next.id, qty: 0 })
- renderAll()
- }
- function renderSiteB() {
- const chips = document.getElementById('siteChips')
- chips.innerHTML = BRANDS.map(b => {
- const on = selectedChips.has(b.id)
- const row = siteRows.find(r => r.brandId === b.id)
- const q = row ? row.qty : 0
- return `<button type="button" class="chip ${on ? 'on' : ''}" onclick="toggleChip('${b.id}')">
- ${b.name}${on ? `<span class="q">${q || ''}</span>` : ''}
- </button>`
- }).join('')
- const box = document.getElementById('siteRowsB')
- const active = BRANDS.filter(b => selectedChips.has(b.id))
- box.innerHTML = active.map(b => {
- let row = siteRows.find(r => r.brandId === b.id)
- if (!row) {
- row = { brandId: b.id, qty: 0 }
- siteRows.push(row)
- }
- const i = siteRows.indexOf(row)
- return `<div class="brand-row" style="grid-template-columns: 72px 1fr">
- <div style="font-size:13px;font-weight:600;padding-left:4px">${b.name}</div>
- <input type="number" step="0.001" min="0" value="${row.qty}"
- oninput="siteRows[${i}].qty=Number(this.value)||0; syncSiteActual(); renderSiteB()" />
- </div>`
- }).join('')
- siteRows = siteRows.filter(r => selectedChips.has(r.brandId))
- syncSiteActual()
- }
- function toggleChip(id) {
- if (selectedChips.has(id)) selectedChips.delete(id)
- else selectedChips.add(id)
- renderAll()
- }
- function renderDriver() {
- const box = document.getElementById('driverRows')
- box.innerHTML = driverRows.map((r, i) => `
- <div class="brand-row">
- <select onchange="driverRows[${i}].brandId=this.value; renderAll()">
- ${brandOptionsHtml(r.brandId)}
- </select>
- <input type="number" step="0.001" min="0" value="${r.qty}"
- oninput="driverRows[${i}].qty=Number(this.value)||0; syncDriverActual()" />
- <button type="button" class="icon-btn" onclick="driverRows.splice(${i},1); renderAll()">×</button>
- </div>
- `).join('')
- syncDriverActual()
- }
- function syncDriverActual() {
- const total = Number(sumRows(driverRows).toFixed(3))
- document.getElementById('driverActualCaustic').value = total
- updateSumLabel(document.getElementById('driverSum'), total, total)
- }
- function addDriverRow() {
- const used = new Set(driverRows.map(r => r.brandId))
- const next = BRANDS.find(b => !used.has(b.id)) || BRANDS[0]
- driverRows.push({ brandId: next.id, qty: 0 })
- renderAll()
- }
- function renderAll() {
- renderSiteA()
- renderSiteB()
- renderDriver()
- }
- renderAll()
- setLayout('a')
- </script>
- </body>
- </html>
|