storage-facility-batch-outbound-demo.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  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" />
  6. <title>库位管理 · 批量出库 Demo</title>
  7. <style>
  8. :root {
  9. --primary: #10b981;
  10. --primary-dark: #059669;
  11. --primary-bg: #ecfdf5;
  12. --text: #1e293b;
  13. --muted: #64748b;
  14. --border: #e2e8f0;
  15. --danger: #ef4444;
  16. --danger-bg: #fef2f2;
  17. --danger-border: #fecaca;
  18. --orange: #ea580c;
  19. --orange-border: #fed7aa;
  20. --orange-soft: #fff7ed;
  21. }
  22. * { box-sizing: border-box; }
  23. body {
  24. margin: 0;
  25. font-family: Inter, "PingFang SC", "Helvetica Neue", Arial, sans-serif;
  26. background: #f1f5f9;
  27. color: var(--text);
  28. font-size: 14px;
  29. line-height: 1.5;
  30. }
  31. .page { max-width: 1100px; margin: 0 auto; padding: 20px 16px 80px; }
  32. .intro {
  33. background: #fff;
  34. border: 1px solid var(--border);
  35. border-radius: 10px;
  36. padding: 16px 18px;
  37. margin-bottom: 16px;
  38. }
  39. .intro h1 { margin: 0 0 6px; font-size: 18px; }
  40. .intro p { margin: 0; color: var(--muted); font-size: 13px; }
  41. .approaches {
  42. display: grid;
  43. grid-template-columns: repeat(3, 1fr);
  44. gap: 12px;
  45. margin-bottom: 16px;
  46. }
  47. @media (max-width: 900px) {
  48. .approaches { grid-template-columns: 1fr; }
  49. }
  50. .approach {
  51. background: #fff;
  52. border: 2px solid var(--border);
  53. border-radius: 10px;
  54. padding: 14px;
  55. cursor: pointer;
  56. transition: border-color .15s, box-shadow .15s;
  57. }
  58. .approach:hover { border-color: #a7f3d0; }
  59. .approach.active {
  60. border-color: var(--primary);
  61. box-shadow: 0 0 0 3px rgba(16,185,129,.15);
  62. }
  63. .approach .badge {
  64. display: inline-block;
  65. font-size: 11px;
  66. font-weight: 600;
  67. padding: 2px 8px;
  68. border-radius: 999px;
  69. background: var(--primary-bg);
  70. color: var(--primary-dark);
  71. margin-bottom: 8px;
  72. }
  73. .approach .badge.rec { background: #d1fae5; }
  74. .approach h3 { margin: 0 0 6px; font-size: 15px; }
  75. .approach p { margin: 0; font-size: 12px; color: var(--muted); }
  76. .panel {
  77. background: #fff;
  78. border: 1px solid var(--border);
  79. border-radius: 10px;
  80. padding: 16px 18px 20px;
  81. display: flex;
  82. gap: 16px;
  83. min-height: 520px;
  84. }
  85. .aside {
  86. width: 220px;
  87. flex-shrink: 0;
  88. border-right: 1px solid var(--border);
  89. padding-right: 14px;
  90. }
  91. .aside h2 {
  92. margin: 0 0 12px;
  93. font-size: 15px;
  94. display: flex;
  95. align-items: center;
  96. gap: 8px;
  97. }
  98. .aside h2::before {
  99. content: "";
  100. width: 3px;
  101. height: 14px;
  102. background: var(--primary);
  103. border-radius: 2px;
  104. }
  105. .tree-node {
  106. padding: 8px 10px;
  107. border-radius: 6px;
  108. font-size: 13px;
  109. color: var(--muted);
  110. }
  111. .tree-node.active {
  112. background: var(--primary-bg);
  113. color: var(--primary-dark);
  114. font-weight: 600;
  115. }
  116. .tree-node .leaf {
  117. float: right;
  118. font-size: 11px;
  119. color: var(--primary);
  120. background: #d1fae5;
  121. padding: 0 6px;
  122. border-radius: 4px;
  123. }
  124. .main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  125. .header { margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid #f0f0f0; }
  126. .header h3 { margin: 0; font-size: 15px; }
  127. .stats { font-size: 12px; color: var(--muted); margin-top: 4px; }
  128. .stats strong { color: var(--text); }
  129. .filter {
  130. display: flex;
  131. justify-content: center;
  132. margin-bottom: 12px;
  133. }
  134. .filter input {
  135. width: 100%;
  136. max-width: 360px;
  137. height: 36px;
  138. border: 1px solid var(--border);
  139. border-radius: 8px;
  140. padding: 0 12px;
  141. font-size: 13px;
  142. }
  143. .toolbar {
  144. display: flex;
  145. align-items: center;
  146. gap: 8px;
  147. margin-bottom: 14px;
  148. flex-wrap: wrap;
  149. min-height: 36px;
  150. }
  151. .toolbar .selected-hint {
  152. font-size: 13px;
  153. color: var(--muted);
  154. margin-right: 4px;
  155. }
  156. .toolbar .selected-hint em {
  157. font-style: normal;
  158. color: var(--primary-dark);
  159. font-weight: 600;
  160. }
  161. .btn {
  162. height: 32px;
  163. padding: 0 14px;
  164. border-radius: 8px;
  165. border: 1px solid var(--border);
  166. background: #fff;
  167. color: var(--text);
  168. font-size: 13px;
  169. cursor: pointer;
  170. display: inline-flex;
  171. align-items: center;
  172. gap: 6px;
  173. }
  174. .btn:disabled { opacity: .45; cursor: not-allowed; }
  175. .btn-primary {
  176. background: var(--primary);
  177. border-color: var(--primary);
  178. color: #fff;
  179. }
  180. .btn-primary:hover:not(:disabled) { background: var(--primary-dark); }
  181. .btn-danger {
  182. background: #fff;
  183. border-color: #fca5a5;
  184. color: #dc2626;
  185. }
  186. .btn-danger:hover:not(:disabled) { background: var(--danger-bg); }
  187. .btn-ghost:hover:not(:disabled) { background: #f8fafc; }
  188. .mode-banner {
  189. display: none;
  190. align-items: center;
  191. gap: 10px;
  192. background: #fff7ed;
  193. border: 1px solid #fdba74;
  194. color: #9a3412;
  195. border-radius: 8px;
  196. padding: 8px 12px;
  197. font-size: 13px;
  198. margin-bottom: 12px;
  199. }
  200. .mode-banner.show { display: flex; }
  201. .mode-banner strong { color: #c2410c; }
  202. .shelf { display: flex; flex-direction: column; gap: 10px; }
  203. .row { display: flex; align-items: stretch; }
  204. .row-label {
  205. width: 36px;
  206. flex-shrink: 0;
  207. background: var(--orange);
  208. color: #fff;
  209. display: flex;
  210. align-items: center;
  211. justify-content: center;
  212. font-weight: 600;
  213. border-radius: 4px 0 0 4px;
  214. font-size: 14px;
  215. }
  216. .cells { display: flex; flex-wrap: wrap; gap: 6px; }
  217. .cell {
  218. width: 80px;
  219. height: 80px;
  220. border: 1px solid var(--orange-border);
  221. border-radius: 4px;
  222. display: flex;
  223. flex-direction: column;
  224. align-items: center;
  225. justify-content: center;
  226. cursor: pointer;
  227. position: relative;
  228. padding: 4px 2px;
  229. gap: 1px;
  230. background: #fff;
  231. transition: all .15s;
  232. user-select: none;
  233. }
  234. .cell:hover { background: var(--orange-soft); }
  235. .cell.occupied {
  236. background: var(--danger-bg);
  237. border-color: var(--danger-border);
  238. }
  239. .cell.occupied:hover { background: #fee2e2; }
  240. .cell .name {
  241. font-size: 11px;
  242. font-weight: 600;
  243. color: #991b1b;
  244. text-align: center;
  245. line-height: 1.2;
  246. max-width: 72px;
  247. overflow: hidden;
  248. text-overflow: ellipsis;
  249. white-space: nowrap;
  250. }
  251. .cell .type {
  252. font-size: 10px;
  253. color: #b91c1c;
  254. max-width: 72px;
  255. overflow: hidden;
  256. text-overflow: ellipsis;
  257. white-space: nowrap;
  258. }
  259. .cell .empty { font-size: 12px; font-weight: 600; color: #303133; }
  260. .cell .check {
  261. display: none;
  262. position: absolute;
  263. top: 3px;
  264. left: 3px;
  265. width: 18px;
  266. height: 18px;
  267. border-radius: 4px;
  268. border: 1.5px solid #cbd5e1;
  269. background: #fff;
  270. align-items: center;
  271. justify-content: center;
  272. font-size: 11px;
  273. color: transparent;
  274. z-index: 2;
  275. }
  276. body.select-mode .cell.occupied .check { display: flex; }
  277. body.select-mode .cell.empty-slot {
  278. opacity: .4;
  279. cursor: not-allowed;
  280. }
  281. body.select-mode .cell.occupied.selected {
  282. outline: 2px solid var(--primary);
  283. outline-offset: 1px;
  284. background: #ecfdf5;
  285. border-color: #a7f3d0;
  286. }
  287. body.select-mode .cell.occupied.selected .name,
  288. body.select-mode .cell.occupied.selected .type { color: var(--primary-dark); }
  289. body.select-mode .cell.occupied.selected .check {
  290. background: var(--primary);
  291. border-color: var(--primary);
  292. color: #fff;
  293. }
  294. body.select-mode .cell.occupied:not(.selected):hover .check {
  295. border-color: var(--primary);
  296. }
  297. .toast {
  298. position: fixed;
  299. top: 20px;
  300. left: 50%;
  301. transform: translateX(-50%) translateY(-20px);
  302. background: #065f46;
  303. color: #fff;
  304. padding: 10px 18px;
  305. border-radius: 8px;
  306. font-size: 13px;
  307. opacity: 0;
  308. pointer-events: none;
  309. transition: all .25s;
  310. z-index: 100;
  311. }
  312. .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
  313. .mask {
  314. display: none;
  315. position: fixed;
  316. inset: 0;
  317. background: rgba(15, 23, 42, .35);
  318. z-index: 90;
  319. align-items: center;
  320. justify-content: center;
  321. }
  322. .mask.show { display: flex; }
  323. .dialog {
  324. background: #fff;
  325. border-radius: 10px;
  326. width: 420px;
  327. max-width: 92vw;
  328. padding: 20px;
  329. box-shadow: 0 20px 40px rgba(0,0,0,.12);
  330. }
  331. .dialog h4 { margin: 0 0 8px; font-size: 16px; }
  332. .dialog p { margin: 0 0 14px; color: var(--muted); font-size: 13px; }
  333. .dialog .list {
  334. max-height: 160px;
  335. overflow: auto;
  336. background: #f8fafc;
  337. border-radius: 8px;
  338. padding: 8px 10px;
  339. margin-bottom: 16px;
  340. font-size: 12px;
  341. }
  342. .dialog .list div { padding: 3px 0; }
  343. .dialog-actions { display: flex; justify-content: flex-end; gap: 8px; }
  344. .hint-bar {
  345. margin-top: 14px;
  346. font-size: 12px;
  347. color: var(--muted);
  348. background: #f8fafc;
  349. border-radius: 8px;
  350. padding: 10px 12px;
  351. }
  352. .hint-bar code {
  353. background: #e2e8f0;
  354. padding: 1px 5px;
  355. border-radius: 4px;
  356. font-size: 11px;
  357. }
  358. </style>
  359. </head>
  360. <body>
  361. <div class="page">
  362. <div class="intro">
  363. <h1>库位管理 · 批量出库交互 Demo</h1>
  364. <p>先点下方方案切换预览,再在货架上试操作。推荐方案 A(工具栏进入多选模式)。</p>
  365. </div>
  366. <div class="approaches">
  367. <div class="approach active" data-choice="a" onclick="pickApproach(this)">
  368. <span class="badge rec">推荐 · A</span>
  369. <h3>工具栏「批量出库」→ 多选格子</h3>
  370. <p>点按钮进入选择模式,只可选占用格;确认后一次性出库。空格不可选。</p>
  371. </div>
  372. <div class="approach" data-choice="b" onclick="pickApproach(this)">
  373. <span class="badge">B</span>
  374. <h3>格子常驻勾选框</h3>
  375. <p>占用格左上角始终显示勾选框,选完后点工具栏「出库」。更直接,但日常浏览稍吵。</p>
  376. </div>
  377. <div class="approach" data-choice="c" onclick="pickApproach(this)">
  378. <span class="badge">C</span>
  379. <h3>框选 / 起止区间</h3>
  380. <p>拖拽或选起止格批量勾选同盒连续占用格。适合密集出库,交互更重。</p>
  381. </div>
  382. </div>
  383. <div class="panel">
  384. <aside class="aside">
  385. <h2>库位</h2>
  386. <div class="tree-node">A区冷库</div>
  387. <div class="tree-node" style="padding-left:22px">1层</div>
  388. <div class="tree-node active" style="padding-left:34px">
  389. 盒-01 <span class="leaf">存</span>
  390. </div>
  391. </aside>
  392. <div class="main">
  393. <div class="header">
  394. <h3>A区冷库 / 1层 / 盒-01</h3>
  395. <div class="stats">
  396. 总数 <strong id="statTotal">12</strong>
  397. · 空闲 <strong id="statEmpty">4</strong>
  398. · 占用 <strong id="statOcc">8</strong>
  399. </div>
  400. </div>
  401. <div class="filter">
  402. <input type="text" placeholder="物品名称 / 产品类型 / 批次号" disabled />
  403. </div>
  404. <div class="mode-banner" id="modeBanner">
  405. <strong>批量出库模式</strong>
  406. <span>请勾选要出库的占用储物格,空格不可选。</span>
  407. </div>
  408. <div class="toolbar" id="toolbar"></div>
  409. <div class="shelf" id="shelf"></div>
  410. <div class="hint-bar" id="hint">
  411. 当前方案 A:先点「批量出库」进入选择模式,再点占用格勾选,最后确认出库。
  412. </div>
  413. </div>
  414. </div>
  415. </div>
  416. <div class="toast" id="toast"></div>
  417. <div class="mask" id="mask">
  418. <div class="dialog">
  419. <h4>批量出库确认</h4>
  420. <p id="confirmText">确认将选中储物格中的物品出库?</p>
  421. <div class="list" id="confirmList"></div>
  422. <div class="dialog-actions">
  423. <button class="btn btn-ghost" onclick="closeConfirm()">取消</button>
  424. <button class="btn btn-primary" onclick="doOutbound()">确认出库</button>
  425. </div>
  426. </div>
  427. </div>
  428. <script>
  429. const HINTS = {
  430. a: '当前方案 A:先点「批量出库」进入选择模式,再点占用格勾选,最后确认出库。',
  431. b: '当前方案 B:占用格左上角常驻勾选框,勾选后点工具栏「出库已选」。',
  432. c: '当前方案 C:演示简化为「按行全选占用格」+ 手动点选;正式版可再做拖拽框选/起止区间。'
  433. }
  434. let approach = 'a'
  435. let selectMode = false
  436. let selected = new Set()
  437. const initialCells = [
  438. // row A
  439. { id: 'A1', row: 'A', name: 'CP-1001', type: 'MSC', occupied: true },
  440. { id: 'A2', row: 'A', name: 'CP-1002', type: 'MSC', occupied: true },
  441. { id: 'A3', row: 'A', name: '', type: '', occupied: false },
  442. { id: 'A4', row: 'A', name: 'CP-1004', type: 'NK', occupied: true },
  443. // row B
  444. { id: 'B1', row: 'B', name: 'CP-2001', type: 'CAR-T', occupied: true },
  445. { id: 'B2', row: 'B', name: '', type: '', occupied: false },
  446. { id: 'B3', row: 'B', name: 'CP-2003', type: 'MSC', occupied: true },
  447. { id: 'B4', row: 'B', name: 'CP-2004', type: 'MSC', occupied: true },
  448. // row C
  449. { id: 'C1', row: 'C', name: '', type: '', occupied: false },
  450. { id: 'C2', row: 'C', name: 'CP-3002', type: 'NK', occupied: true },
  451. { id: 'C3', row: 'C', name: 'CP-3003', type: 'NK', occupied: true },
  452. { id: 'C4', row: 'C', name: '', type: '', occupied: false }
  453. ]
  454. let cells = JSON.parse(JSON.stringify(initialCells))
  455. function pickApproach(el) {
  456. document.querySelectorAll('.approach').forEach(a => a.classList.remove('active'))
  457. el.classList.add('active')
  458. approach = el.dataset.choice
  459. selectMode = approach === 'b' // B always selectable
  460. selected.clear()
  461. document.getElementById('hint').textContent = HINTS[approach]
  462. render()
  463. if (window.toggleSelect) {
  464. // companion helper if present
  465. try { window.toggleSelect(el) } catch (_) {}
  466. }
  467. }
  468. function updateStats() {
  469. const total = cells.length
  470. const occ = cells.filter(c => c.occupied).length
  471. document.getElementById('statTotal').textContent = total
  472. document.getElementById('statEmpty').textContent = total - occ
  473. document.getElementById('statOcc').textContent = occ
  474. }
  475. function renderToolbar() {
  476. const tb = document.getElementById('toolbar')
  477. const n = selected.size
  478. if (approach === 'a') {
  479. if (!selectMode) {
  480. tb.innerHTML = `
  481. <button class="btn btn-primary" onclick="enterSelectMode()">⬆ 批量出库</button>
  482. <button class="btn btn-ghost" disabled>批量生成格子</button>
  483. <button class="btn btn-ghost" disabled>清空格子</button>
  484. `
  485. } else {
  486. tb.innerHTML = `
  487. <span class="selected-hint">已选 <em>${n}</em> 项</span>
  488. <button class="btn btn-primary" ${n ? '' : 'disabled'} onclick="openConfirm()">确认出库</button>
  489. <button class="btn btn-ghost" onclick="selectAllOccupied()">全选占用</button>
  490. <button class="btn btn-ghost" onclick="clearSelection()">清空选择</button>
  491. <button class="btn btn-ghost" onclick="exitSelectMode()">取消</button>
  492. `
  493. }
  494. } else if (approach === 'b') {
  495. tb.innerHTML = `
  496. <span class="selected-hint">已选 <em>${n}</em> 项</span>
  497. <button class="btn btn-primary" ${n ? '' : 'disabled'} onclick="openConfirm()">⬆ 出库已选</button>
  498. <button class="btn btn-ghost" onclick="selectAllOccupied()">全选占用</button>
  499. <button class="btn btn-ghost" onclick="clearSelection()">清空选择</button>
  500. `
  501. } else {
  502. tb.innerHTML = `
  503. <span class="selected-hint">已选 <em>${n}</em> 项</span>
  504. <button class="btn btn-primary" ${n ? '' : 'disabled'} onclick="openConfirm()">⬆ 确认出库</button>
  505. <button class="btn btn-ghost" onclick="selectRow('A')">全选 A 行</button>
  506. <button class="btn btn-ghost" onclick="selectRow('B')">全选 B 行</button>
  507. <button class="btn btn-ghost" onclick="selectRow('C')">全选 C 行</button>
  508. <button class="btn btn-ghost" onclick="clearSelection()">清空选择</button>
  509. `
  510. }
  511. }
  512. function renderShelf() {
  513. const shelf = document.getElementById('shelf')
  514. const rows = ['A', 'B', 'C']
  515. shelf.innerHTML = rows.map(row => {
  516. const rowCells = cells.filter(c => c.row === row)
  517. return `
  518. <div class="row">
  519. <div class="row-label">${row}</div>
  520. <div class="cells">
  521. ${rowCells.map(c => {
  522. const occ = c.occupied
  523. const sel = selected.has(c.id)
  524. return `
  525. <div class="cell ${occ ? 'occupied' : 'empty-slot'} ${sel ? 'selected' : ''}"
  526. data-id="${c.id}"
  527. onclick="onCellClick('${c.id}')"
  528. title="${occ ? c.name + ' · 点击选择/取消' : '空格不可出库'}">
  529. <span class="check">✓</span>
  530. ${occ
  531. ? `<span class="name">${c.name}</span><span class="type">${c.type}</span>`
  532. : `<span class="empty">—</span>`}
  533. </div>
  534. `
  535. }).join('')}
  536. </div>
  537. </div>
  538. `
  539. }).join('')
  540. }
  541. function render() {
  542. const showBanner = approach === 'a' && selectMode
  543. document.getElementById('modeBanner').classList.toggle('show', showBanner)
  544. document.body.classList.toggle('select-mode', approach === 'a' ? selectMode : true)
  545. updateStats()
  546. renderToolbar()
  547. renderShelf()
  548. }
  549. function enterSelectMode() {
  550. selectMode = true
  551. selected.clear()
  552. render()
  553. toast('已进入批量出库模式')
  554. }
  555. function exitSelectMode() {
  556. selectMode = false
  557. selected.clear()
  558. render()
  559. }
  560. function onCellClick(id) {
  561. const cell = cells.find(c => c.id === id)
  562. if (!cell || !cell.occupied) {
  563. if (approach === 'a' && selectMode) toast('空格不可出库')
  564. else if (approach !== 'a') toast('空格不可出库')
  565. return
  566. }
  567. // 方案 A:非选择模式 → 模拟打开详情(提示)
  568. if (approach === 'a' && !selectMode) {
  569. toast(`打开储物格明细 — ${cell.name}(现有行为)`)
  570. return
  571. }
  572. if (selected.has(id)) selected.delete(id)
  573. else selected.add(id)
  574. render()
  575. }
  576. function selectAllOccupied() {
  577. cells.filter(c => c.occupied).forEach(c => selected.add(c.id))
  578. render()
  579. }
  580. function selectRow(row) {
  581. cells.filter(c => c.row === row && c.occupied).forEach(c => selected.add(c.id))
  582. render()
  583. }
  584. function clearSelection() {
  585. selected.clear()
  586. render()
  587. }
  588. function openConfirm() {
  589. if (!selected.size) return
  590. const list = [...selected].map(id => {
  591. const c = cells.find(x => x.id === id)
  592. return `<div>${id} ${c.name} ${c.type}</div>`
  593. }).join('')
  594. document.getElementById('confirmText').textContent =
  595. `确认将选中的 ${selected.size} 个储物格物品出库?`
  596. document.getElementById('confirmList').innerHTML = list
  597. document.getElementById('mask').classList.add('show')
  598. }
  599. function closeConfirm() {
  600. document.getElementById('mask').classList.remove('show')
  601. }
  602. function doOutbound() {
  603. const ids = [...selected]
  604. cells.forEach(c => {
  605. if (ids.includes(c.id)) {
  606. c.occupied = false
  607. c.name = ''
  608. c.type = ''
  609. }
  610. })
  611. selected.clear()
  612. if (approach === 'a') selectMode = false
  613. closeConfirm()
  614. render()
  615. toast(`出库成功,共 ${ids.length} 项`)
  616. }
  617. function toast(msg) {
  618. const el = document.getElementById('toast')
  619. el.textContent = msg
  620. el.classList.add('show')
  621. clearTimeout(toast._t)
  622. toast._t = setTimeout(() => el.classList.remove('show'), 1800)
  623. }
  624. // reset helper for demo
  625. window.resetDemo = function () {
  626. cells = JSON.parse(JSON.stringify(initialCells))
  627. selected.clear()
  628. selectMode = approach === 'b'
  629. render()
  630. toast('已重置货架数据')
  631. }
  632. render()
  633. </script>
  634. </body>
  635. </html>