| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>03 折叠摘要行 · 登记未用完 Demo</title>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
- <link rel="stylesheet" href="_shared.css" />
- <style>
- .accordion-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
- max-height: 520px;
- overflow-y: auto;
- padding-right: 2px;
- }
- .acc-item {
- border: 1px solid var(--border);
- border-radius: var(--radius-sm);
- background: var(--surface);
- overflow: hidden;
- }
- .acc-item.is-expanded {
- border-color: #a7f3d0;
- box-shadow: 0 2px 8px rgba(16, 185, 129, 0.08);
- }
- .acc-summary {
- display: grid;
- grid-template-columns: 32px 1fr auto auto auto 72px 32px;
- gap: 12px;
- align-items: center;
- padding: 10px 12px;
- cursor: pointer;
- user-select: none;
- }
- .acc-summary:hover {
- background: #fafafa;
- }
- .acc-item.is-expanded .acc-summary {
- background: var(--primary-bg);
- border-bottom: 1px solid #d1fae5;
- }
- .acc-chevron {
- width: 20px;
- height: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--text-muted);
- transition: transform 0.2s;
- font-size: 12px;
- }
- .acc-item.is-expanded .acc-chevron {
- transform: rotate(90deg);
- color: var(--primary-dark);
- }
- .acc-name {
- font-weight: 600;
- font-size: 13px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .acc-meta {
- font-size: 12px;
- color: var(--text-muted);
- }
- .acc-inline-qty {
- width: 72px;
- }
- .acc-body {
- display: none;
- padding: 12px 14px 14px;
- background: #fafafa;
- }
- .acc-item.is-expanded .acc-body {
- display: block;
- }
- .acc-grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 10px 14px;
- }
- .acc-col-span-2 {
- grid-column: span 2;
- }
- .acc-col-span-4 {
- grid-column: 1 / -1;
- }
- .bulk-bar {
- display: flex;
- gap: 8px;
- align-items: center;
- margin-bottom: 12px;
- flex-wrap: wrap;
- }
- .bulk-bar .input {
- width: 140px;
- }
- @media (max-width: 960px) {
- .acc-summary {
- grid-template-columns: 28px 1fr auto 72px 28px;
- }
- .acc-summary .acc-meta:nth-child(4),
- .acc-summary .acc-meta:nth-child(5) {
- display: none;
- }
- .acc-grid {
- grid-template-columns: repeat(2, 1fr);
- }
- }
- </style>
- </head>
- <body>
- <header class="demo-banner">
- <div>
- <h1><span class="demo-badge">03 折叠摘要行</span> 登记未用完物料</h1>
- <p>默认一行摘要 + 行内改数量 · 点击展开完整字段</p>
- </div>
- <div class="demo-banner__actions">
- <a href="index.html">← 选型索引</a>
- <a href="01-inline-table.html">01 表格</a>
- <a class="is-current" href="03-collapsible.html">03 折叠</a>
- <a href="04-quick-add.html">04 检索</a>
- </div>
- </header>
- <div class="page-wrap">
- <div class="dialog-backdrop">
- <div class="dialog dialog--wide" role="dialog">
- <div class="dialog__head">
- <h2 class="dialog__title">登记未用完物料</h2>
- <button type="button" class="dialog__close">×</button>
- </div>
- <div class="dialog__body">
- <div class="section-head">
- <div class="section-head__left">
- <div class="section-title">未用完明细</div>
- <div class="section-hint">折叠态可快速改数量;需改批次或手工字段时再展开。支持全部展开/收起。</div>
- </div>
- <div class="section-tools">
- <span class="stat-pill">共 <strong id="line-count">0</strong> 项</span>
- <button type="button" class="btn btn-sm" id="expand-all">全部展开</button>
- <button type="button" class="btn btn-sm" id="collapse-all">全部收起</button>
- <button type="button" class="btn btn-sm">+ 新增一行</button>
- </div>
- </div>
- <div class="bulk-bar">
- <span style="font-size: 12px; color: var(--text-muted)">批量填充持有人:</span>
- <input type="text" class="input" id="bulk-holder" placeholder="输入姓名" />
- <button type="button" class="btn btn-sm" id="apply-holder">应用到全部</button>
- <input type="search" class="input" style="width: 200px; margin-left: auto" placeholder="过滤…" id="filter" />
- </div>
- <div class="accordion-list" id="accordion"></div>
- </div>
- <div class="dialog__foot">
- <button type="button" class="btn btn-ghost">取消</button>
- <button type="button" class="btn btn-primary">保存</button>
- </div>
- </div>
- </div>
- <p class="style-note">
- <strong>适用场景:</strong>多数行只需改数量/持有人,少数行需展开核对批次。
- <ul>
- <li>比现状卡片省约 70% 纵向空间,10 条折叠态一屏可见</li>
- <li>摘要行右侧 inline 数量框,无需展开即可改</li>
- <li>展开区 4 列栅格,比现状 5~6 列略疏,控件 32px</li>
- </ul>
- </p>
- </div>
- <script src="_demo-data.js"></script>
- <script>
- let rows = [...DEMO_UNUSED_LINES]
- const expanded = new Set([rows[0]?.id])
- const accordion = document.getElementById('accordion')
- function render() {
- const kw = (document.getElementById('filter').value || '').trim().toLowerCase()
- const filtered = rows.filter((r) => !kw || materialSummary(r).toLowerCase().includes(kw))
- accordion.innerHTML = filtered
- .map((row, i) => {
- const isExp = expanded.has(row.id)
- const amt = formatMoney(calcAmount(row.unitPrice, row.unusedQty))
- return `<div class="acc-item ${isExp ? 'is-expanded' : ''}" data-id="${row.id}">
- <div class="acc-summary" data-toggle="${row.id}">
- <span class="acc-chevron">▸</span>
- <span class="row-index">${rows.indexOf(row) + 1}</span>
- <span class="acc-name" title="${row.materialName}">${row.materialName}</span>
- <span class="acc-meta">${row.batchNo || '—'}</span>
- <span class="acc-meta">${row.holder || '—'}</span>
- <span class="acc-meta money">${amt} 元</span>
- <input class="input acc-inline-qty qty-inline" type="number" min="0.01" step="0.01" value="${row.unusedQty}" data-id="${row.id}" onclick="event.stopPropagation()" />
- <button type="button" class="btn btn-danger-link btn-sm del-btn" data-id="${row.id}" onclick="event.stopPropagation()">×</button>
- </div>
- <div class="acc-body">
- <div class="acc-grid">
- <div class="acc-col-span-2">
- <label class="field-label">批次检索</label>
- <select class="select" ${row.fromBatch ? 'disabled' : ''}><option>${row.fromBatch ? row.stockInNo + ' · ' + row.materialName : '检索或手工'}</option></select>
- </div>
- <div><label class="field-label">入库编号</label><input class="input input--readonly" readonly value="${row.stockInNo || ''}" /></div>
- <div><label class="field-label">批号</label><input class="input input--readonly" readonly value="${row.batchNo || ''}" /></div>
- <div><label class="field-label">物料编码</label><input class="input input--readonly" readonly value="${row.materialCode || ''}" /></div>
- <div><label class="field-label">货号</label><input class="input input--readonly" readonly value="${row.catalogNo || ''}" /></div>
- <div><label class="field-label">规格</label><input class="input input--readonly" readonly value="${row.specModel || ''}" /></div>
- <div><label class="field-label">单位</label><input class="input input--readonly" readonly value="${row.unit || ''}" /></div>
- <div><label class="field-label">厂家</label><input class="input input--readonly" readonly value="${row.manufacturer || ''}" /></div>
- <div><label class="field-label">单价</label><input class="input input--readonly" readonly value="${formatMoney(row.unitPrice)}" /></div>
- <div><label class="field-label">持有人</label><input class="input holder-field" data-id="${row.id}" value="${row.holder || ''}" /></div>
- <div class="acc-col-span-4"><label class="field-label">备注</label><input class="input remark-field" data-id="${row.id}" value="${row.remark || ''}" /></div>
- </div>
- </div>
- </div>`
- })
- .join('')
- document.getElementById('line-count').textContent = rows.length
- }
- accordion.addEventListener('click', (e) => {
- if (e.target.classList.contains('del-btn')) {
- const id = Number(e.target.dataset.id)
- rows = rows.filter((r) => r.id !== id)
- expanded.delete(id)
- render()
- return
- }
- const toggle = e.target.closest('[data-toggle]')
- if (toggle && !e.target.classList.contains('qty-inline')) {
- const id = Number(toggle.dataset.toggle)
- if (expanded.has(id)) expanded.delete(id)
- else expanded.add(id)
- render()
- }
- })
- accordion.addEventListener('input', (e) => {
- const id = Number(e.target.dataset.id)
- const row = rows.find((r) => r.id === id)
- if (!row) return
- if (e.target.classList.contains('qty-inline')) row.unusedQty = Number(e.target.value) || 0
- if (e.target.classList.contains('holder-field')) row.holder = e.target.value
- if (e.target.classList.contains('remark-field')) row.remark = e.target.value
- if (e.target.classList.contains('qty-inline')) render()
- })
- document.getElementById('expand-all').addEventListener('click', () => {
- rows.forEach((r) => expanded.add(r.id))
- render()
- })
- document.getElementById('collapse-all').addEventListener('click', () => {
- expanded.clear()
- render()
- })
- document.getElementById('apply-holder').addEventListener('click', () => {
- const v = document.getElementById('bulk-holder').value.trim()
- if (!v) return
- rows.forEach((r) => (r.holder = v))
- render()
- })
- document.getElementById('filter').addEventListener('input', render)
- render()
- </script>
- </body>
- </html>
|