01-inline-table.html 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>01 可编辑表格 · 登记未用完 Demo</title>
  7. <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
  8. <link rel="stylesheet" href="_shared.css" />
  9. <style>
  10. .table-scroll {
  11. max-height: 480px;
  12. }
  13. .data-table .col-qty {
  14. min-width: 88px;
  15. }
  16. .data-table .col-holder {
  17. min-width: 96px;
  18. }
  19. .data-table .col-remark {
  20. min-width: 120px;
  21. }
  22. .source-tag {
  23. font-size: 10px;
  24. padding: 1px 5px;
  25. border-radius: 4px;
  26. margin-left: 4px;
  27. vertical-align: middle;
  28. }
  29. .source-tag--batch {
  30. background: var(--primary-bg);
  31. color: var(--primary-dark);
  32. }
  33. .source-tag--manual {
  34. background: var(--warning-bg);
  35. color: var(--warning);
  36. }
  37. .footer-summary {
  38. margin-top: 12px;
  39. padding: 10px 14px;
  40. background: #f8fafc;
  41. border: 1px solid var(--border);
  42. border-radius: var(--radius-sm);
  43. font-size: 13px;
  44. color: var(--text-muted);
  45. display: flex;
  46. gap: 24px;
  47. flex-wrap: wrap;
  48. }
  49. .footer-summary strong {
  50. color: var(--text);
  51. }
  52. </style>
  53. </head>
  54. <body>
  55. <header class="demo-banner">
  56. <div>
  57. <h1><span class="demo-badge">01 可编辑表格</span> 登记未用完物料</h1>
  58. <p>固定表头 · 横向滚动 · 核心字段 inline 编辑 · 10 条模拟数据</p>
  59. </div>
  60. <div class="demo-banner__actions">
  61. <a href="index.html">← 选型索引</a>
  62. <a href="02-split-panel.html">02 分栏</a>
  63. <a href="03-collapsible.html">03 折叠</a>
  64. <a class="is-current" href="01-inline-table.html">01 表格</a>
  65. </div>
  66. </header>
  67. <div class="page-wrap">
  68. <div class="dialog-backdrop">
  69. <div class="dialog dialog--wide" role="dialog" aria-labelledby="dlg-title">
  70. <div class="dialog__head">
  71. <h2 class="dialog__title" id="dlg-title">登记未用完物料</h2>
  72. <button type="button" class="dialog__close" aria-label="关闭">×</button>
  73. </div>
  74. <div class="dialog__body">
  75. <div class="section-head">
  76. <div class="section-head__left">
  77. <div class="section-title">未用完明细</div>
  78. <div class="section-hint">
  79. 批次关联行自动带出只读信息;仅需填写<strong>未用完数量、持有人、备注</strong>。剩余金额 = 数量 × 单价。
  80. </div>
  81. </div>
  82. <div class="section-tools">
  83. <span class="stat-pill">共 <strong id="line-count">0</strong> 项</span>
  84. <input type="search" class="input search-box" placeholder="过滤物料/批号/入库编号…" id="filter" />
  85. <button type="button" class="btn btn-sm">+ 新增一行</button>
  86. <button type="button" class="btn btn-sm">+ 手工登记</button>
  87. </div>
  88. </div>
  89. <div class="table-scroll">
  90. <table class="data-table" id="table">
  91. <thead>
  92. <tr>
  93. <th class="col-sticky-left" style="min-width: 44px">#</th>
  94. <th class="col-sticky-left col-name" style="left: 44px; min-width: 160px">物料名称</th>
  95. <th style="min-width: 100px">入库编号</th>
  96. <th style="min-width: 96px">批号</th>
  97. <th style="min-width: 96px">货号</th>
  98. <th style="min-width: 88px">规格</th>
  99. <th style="min-width: 72px">单位</th>
  100. <th style="min-width: 100px">厂家</th>
  101. <th style="min-width: 88px">单价</th>
  102. <th style="min-width: 96px">剩余金额</th>
  103. <th class="col-qty">未用完数量</th>
  104. <th class="col-holder">持有人</th>
  105. <th class="col-remark">备注</th>
  106. <th class="col-actions" style="min-width: 52px">操作</th>
  107. </tr>
  108. </thead>
  109. <tbody id="tbody"></tbody>
  110. </table>
  111. </div>
  112. <div class="footer-summary">
  113. <span>合计未用完数量:<strong id="sum-qty">0</strong></span>
  114. <span>合计剩余金额:<strong id="sum-amt">0.00</strong> 元</span>
  115. <span>已填持有人:<strong id="sum-holder">0</strong> / <strong id="sum-total">0</strong></span>
  116. </div>
  117. </div>
  118. <div class="dialog__foot">
  119. <button type="button" class="btn btn-ghost">取消</button>
  120. <button type="button" class="btn btn-primary">保存</button>
  121. </div>
  122. </div>
  123. </div>
  124. <p class="style-note">
  125. <strong>适用场景:</strong>一次登记 10~50 条,用户主要改「数量 / 持有人」。
  126. <ul>
  127. <li>批次检索可在「新增一行」时弹出,选中后插入表格行(本 Demo 省略交互)</li>
  128. <li>只读列灰底或纯文本,减少视觉噪音;Tab 键可在可编辑列间跳转</li>
  129. <li>与查看态「领用未用完登记」表格列对齐,心智一致</li>
  130. </ul>
  131. </p>
  132. </div>
  133. <script src="_demo-data.js"></script>
  134. <script>
  135. const tbody = document.getElementById('tbody')
  136. let rows = [...DEMO_UNUSED_LINES]
  137. function render() {
  138. const kw = (document.getElementById('filter').value || '').trim().toLowerCase()
  139. const filtered = rows.filter((r) => {
  140. if (!kw) return true
  141. return [r.materialName, r.batchNo, r.stockInNo, r.holder, r.materialCode]
  142. .join(' ')
  143. .toLowerCase()
  144. .includes(kw)
  145. })
  146. tbody.innerHTML = filtered
  147. .map((row, i) => {
  148. const amt = calcAmount(row.unitPrice, row.unusedQty)
  149. const srcTag = row.fromBatch
  150. ? '<span class="source-tag source-tag--batch">批次</span>'
  151. : '<span class="source-tag source-tag--manual">手工</span>'
  152. return `<tr data-id="${row.id}">
  153. <td class="col-sticky-left"><span class="row-index">${i + 1}</span></td>
  154. <td class="col-sticky-left col-name" style="left:44px" title="${row.materialName}">${row.materialName}${srcTag}</td>
  155. <td class="money--muted">${row.stockInNo || '—'}</td>
  156. <td>${row.batchNo || '—'}</td>
  157. <td class="money--muted">${row.catalogNo || '—'}</td>
  158. <td class="money--muted">${row.specModel || '—'}</td>
  159. <td>${row.unit || '—'}</td>
  160. <td class="money--muted">${row.manufacturer || '—'}</td>
  161. <td class="money">${formatMoney(row.unitPrice)}</td>
  162. <td class="money">${formatMoney(amt)}</td>
  163. <td><input class="input input-num qty-input" type="number" min="0.01" step="0.01" value="${row.unusedQty}" data-id="${row.id}" /></td>
  164. <td><input class="input holder-input" type="text" value="${row.holder || ''}" data-id="${row.id}" placeholder="持有人" /></td>
  165. <td><input class="input input--wide remark-input" type="text" value="${row.remark || ''}" data-id="${row.id}" placeholder="可选" /></td>
  166. <td class="col-actions"><button type="button" class="btn btn-danger-link btn-sm del-btn" data-id="${row.id}">删</button></td>
  167. </tr>`
  168. })
  169. .join('')
  170. document.getElementById('line-count').textContent = rows.length
  171. document.getElementById('sum-total').textContent = rows.length
  172. document.getElementById('sum-qty').textContent = rows.reduce((s, r) => s + Number(r.unusedQty), 0)
  173. document.getElementById('sum-amt').textContent = formatMoney(
  174. rows.reduce((s, r) => s + calcAmount(r.unitPrice, r.unusedQty), 0)
  175. )
  176. document.getElementById('sum-holder').textContent = rows.filter((r) => r.holder?.trim()).length
  177. }
  178. tbody.addEventListener('input', (e) => {
  179. const id = Number(e.target.dataset.id)
  180. const row = rows.find((r) => r.id === id)
  181. if (!row) return
  182. if (e.target.classList.contains('qty-input')) {
  183. row.unusedQty = Number(e.target.value) || 0
  184. render()
  185. } else if (e.target.classList.contains('holder-input')) {
  186. row.holder = e.target.value
  187. } else if (e.target.classList.contains('remark-input')) {
  188. row.remark = e.target.value
  189. }
  190. })
  191. tbody.addEventListener('click', (e) => {
  192. if (e.target.classList.contains('del-btn')) {
  193. const id = Number(e.target.dataset.id)
  194. rows = rows.filter((r) => r.id !== id)
  195. render()
  196. }
  197. })
  198. document.getElementById('filter').addEventListener('input', render)
  199. render()
  200. </script>
  201. </body>
  202. </html>