2026-10-08-sample-freight-menu-demo.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  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. <link rel="preconnect" href="https://fonts.googleapis.com" />
  8. <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
  9. <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
  10. <style>
  11. :root {
  12. --primary: #10b981;
  13. --primary-dark: #059669;
  14. --primary-bg: #ecfdf5;
  15. --bg-page: #f8fafc;
  16. --panel: #ffffff;
  17. --text: #0f172a;
  18. --text-secondary: #334155;
  19. --text-muted: #64748b;
  20. --border: #e2e8f0;
  21. --radius: 10px;
  22. --radius-sm: 8px;
  23. --danger: #ef4444;
  24. --warning: #d97706;
  25. --info: #64748b;
  26. --sidebar: 220px;
  27. --topbar: 56px;
  28. }
  29. * { box-sizing: border-box; }
  30. body {
  31. margin: 0;
  32. font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  33. font-size: 14px;
  34. line-height: 1.5;
  35. color: var(--text);
  36. background: var(--bg-page);
  37. }
  38. .app {
  39. display: grid;
  40. grid-template-columns: var(--sidebar) 1fr;
  41. min-height: 100vh;
  42. }
  43. .sidebar {
  44. background: #0f172a;
  45. color: #e2e8f0;
  46. padding: 16px 0;
  47. }
  48. .sidebar .brand {
  49. padding: 8px 20px 20px;
  50. font-weight: 700;
  51. font-size: 15px;
  52. color: #fff;
  53. }
  54. .sidebar .group-title {
  55. padding: 12px 20px 6px;
  56. font-size: 11px;
  57. color: #94a3b8;
  58. letter-spacing: 0.04em;
  59. }
  60. .sidebar a {
  61. display: block;
  62. padding: 9px 20px 9px 28px;
  63. color: #cbd5e1;
  64. text-decoration: none;
  65. font-size: 13px;
  66. }
  67. .sidebar a:hover { background: rgba(255,255,255,0.04); color: #fff; }
  68. .sidebar a.active {
  69. background: rgba(16, 185, 129, 0.18);
  70. color: #6ee7b7;
  71. border-right: 3px solid var(--primary);
  72. font-weight: 600;
  73. }
  74. .sidebar a.l1 {
  75. padding-left: 20px;
  76. font-weight: 600;
  77. color: #f1f5f9;
  78. }
  79. .main { display: flex; flex-direction: column; min-width: 0; }
  80. .topbar {
  81. height: var(--topbar);
  82. background: var(--panel);
  83. border-bottom: 1px solid var(--border);
  84. display: flex;
  85. align-items: center;
  86. padding: 0 24px;
  87. color: var(--text-muted);
  88. font-size: 13px;
  89. }
  90. .topbar strong { color: var(--text-secondary); font-weight: 600; }
  91. .content { padding: 24px; }
  92. .panel {
  93. background: var(--panel);
  94. border: 1px solid var(--border);
  95. border-radius: var(--radius);
  96. overflow: hidden;
  97. }
  98. .panel-top {
  99. padding: 16px 20px 12px;
  100. border-bottom: 1px solid var(--border);
  101. }
  102. .panel-top h2 {
  103. margin: 0;
  104. font-size: 16px;
  105. font-weight: 600;
  106. display: flex;
  107. align-items: center;
  108. gap: 10px;
  109. }
  110. .panel-top h2::before {
  111. content: "";
  112. width: 3px;
  113. height: 16px;
  114. border-radius: 2px;
  115. background: var(--primary);
  116. }
  117. .panel-top .hint {
  118. margin: 8px 0 0;
  119. font-size: 12px;
  120. color: var(--text-muted);
  121. }
  122. .filter-block {
  123. padding: 16px 20px;
  124. display: grid;
  125. grid-template-columns: repeat(4, minmax(0, 1fr));
  126. gap: 12px 16px;
  127. }
  128. .field label {
  129. display: block;
  130. font-size: 11px;
  131. color: var(--text-muted);
  132. margin-bottom: 4px;
  133. }
  134. .field input, .field select {
  135. width: 100%;
  136. height: 36px;
  137. border: 1px solid var(--border);
  138. border-radius: var(--radius-sm);
  139. padding: 0 10px;
  140. font: inherit;
  141. color: var(--text);
  142. background: #fff;
  143. }
  144. .field input::placeholder { color: #94a3b8; }
  145. .toolbar {
  146. padding: 12px 20px;
  147. display: flex;
  148. align-items: center;
  149. gap: 10px;
  150. flex-wrap: wrap;
  151. border-top: 1px solid var(--border);
  152. }
  153. .selected-tip {
  154. font-size: 13px;
  155. color: var(--text-muted);
  156. margin-right: 4px;
  157. }
  158. .btn {
  159. display: inline-flex;
  160. align-items: center;
  161. gap: 6px;
  162. height: 36px;
  163. padding: 0 14px;
  164. border-radius: var(--radius-sm);
  165. border: 1px solid var(--border);
  166. background: #fff;
  167. color: var(--text-secondary);
  168. font: inherit;
  169. font-weight: 500;
  170. cursor: pointer;
  171. }
  172. .btn:hover { border-color: #cbd5e1; }
  173. .btn-main {
  174. background: var(--primary);
  175. border-color: var(--primary);
  176. color: #fff;
  177. }
  178. .btn-main:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
  179. .btn:disabled {
  180. opacity: 0.45;
  181. cursor: not-allowed;
  182. }
  183. .btn-op {
  184. height: 28px;
  185. padding: 0 10px;
  186. font-size: 12px;
  187. border-radius: 6px;
  188. }
  189. .list-body { border-top: 1px solid var(--border); }
  190. table.list-table {
  191. width: 100%;
  192. border-collapse: collapse;
  193. table-layout: fixed;
  194. }
  195. .list-table th {
  196. background: #f8fafc;
  197. color: var(--text-muted);
  198. font-weight: 600;
  199. font-size: 12px;
  200. text-align: left;
  201. padding: 10px 12px;
  202. border-bottom: 1px solid var(--border);
  203. white-space: nowrap;
  204. }
  205. .list-table td {
  206. padding: 10px 12px;
  207. border-bottom: 1px solid var(--border);
  208. vertical-align: middle;
  209. color: var(--text-secondary);
  210. font-size: 13px;
  211. }
  212. .list-table tbody tr:nth-child(even) td:not(.merged) {
  213. background: #fafbfc;
  214. }
  215. .list-table td.merged {
  216. background: #fff;
  217. border-right: 1px solid var(--border);
  218. vertical-align: middle;
  219. }
  220. .list-table tr:hover td:not(.merged) { background: var(--primary-bg); }
  221. .list-table .mono { font-variant-numeric: tabular-nums; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
  222. .list-table .amount { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
  223. .list-table .center { text-align: center; }
  224. .tag {
  225. display: inline-flex;
  226. align-items: center;
  227. height: 22px;
  228. padding: 0 8px;
  229. border-radius: 6px;
  230. font-size: 12px;
  231. font-weight: 500;
  232. }
  233. .tag.success { background: var(--primary-bg); color: var(--primary-dark); }
  234. .tag.info { background: #f1f5f9; color: var(--info); }
  235. .tag.warn { background: #fffbeb; color: var(--warning); }
  236. .pager {
  237. display: flex;
  238. justify-content: flex-end;
  239. align-items: center;
  240. gap: 12px;
  241. padding: 12px 20px;
  242. border-top: 1px solid var(--border);
  243. color: var(--text-muted);
  244. font-size: 13px;
  245. }
  246. .legend {
  247. margin-top: 12px;
  248. padding: 12px 14px;
  249. background: var(--primary-bg);
  250. border: 1px solid #a7f3d0;
  251. border-radius: var(--radius-sm);
  252. color: var(--primary-dark);
  253. font-size: 12px;
  254. }
  255. .legend code {
  256. background: rgba(255,255,255,0.7);
  257. padding: 1px 6px;
  258. border-radius: 4px;
  259. }
  260. /* modal */
  261. .mask {
  262. position: fixed; inset: 0;
  263. background: rgba(15, 23, 42, 0.45);
  264. display: none;
  265. align-items: center;
  266. justify-content: center;
  267. z-index: 50;
  268. backdrop-filter: none;
  269. }
  270. .mask.open { display: flex; }
  271. .dialog {
  272. width: min(720px, calc(100vw - 32px));
  273. max-height: calc(100vh - 100px);
  274. background: #fff;
  275. border-radius: var(--radius);
  276. display: flex;
  277. flex-direction: column;
  278. overflow: hidden;
  279. box-shadow: 0 20px 40px rgba(15,23,42,0.18);
  280. }
  281. .dialog header, .dialog footer {
  282. padding: 14px 20px;
  283. border-bottom: 1px solid var(--border);
  284. display: flex;
  285. align-items: center;
  286. justify-content: space-between;
  287. }
  288. .dialog footer {
  289. border-bottom: 0;
  290. border-top: 1px solid var(--border);
  291. justify-content: flex-end;
  292. gap: 8px;
  293. }
  294. .dialog h3 { margin: 0; font-size: 16px; }
  295. .dialog .body { padding: 16px 20px; overflow: auto; }
  296. .form-grid-2 {
  297. display: grid;
  298. grid-template-columns: 1fr 1fr;
  299. gap: 12px;
  300. }
  301. .form-grid-2 .full { grid-column: 1 / -1; }
  302. .tabs {
  303. display: inline-flex;
  304. border: 1px solid var(--border);
  305. border-radius: var(--radius-sm);
  306. overflow: hidden;
  307. margin-bottom: 14px;
  308. }
  309. .tabs button {
  310. border: 0;
  311. background: #fff;
  312. padding: 8px 14px;
  313. cursor: pointer;
  314. font: inherit;
  315. color: var(--text-muted);
  316. }
  317. .tabs button.active {
  318. background: var(--primary-bg);
  319. color: var(--primary-dark);
  320. font-weight: 600;
  321. }
  322. .sample-pick {
  323. border: 1px solid var(--border);
  324. border-radius: var(--radius-sm);
  325. max-height: 220px;
  326. overflow: auto;
  327. }
  328. .sample-pick label {
  329. display: flex;
  330. gap: 10px;
  331. align-items: center;
  332. padding: 8px 12px;
  333. border-bottom: 1px solid var(--border);
  334. cursor: pointer;
  335. }
  336. .sample-pick label:last-child { border-bottom: 0; }
  337. .sample-pick label:hover { background: var(--primary-bg); }
  338. @media (max-width: 1100px) {
  339. .filter-block { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  340. .app { grid-template-columns: 1fr; }
  341. .sidebar { display: none; }
  342. }
  343. </style>
  344. </head>
  345. <body>
  346. <div class="app">
  347. <aside class="sidebar">
  348. <div class="brand">中科兴皖 · Demo</div>
  349. <div class="group-title">市场运营</div>
  350. <a class="l1" href="javascript:;">细胞存储</a>
  351. <a href="javascript:;">预签约订单</a>
  352. <a href="javascript:;">缴费信息</a>
  353. <a href="javascript:;">样本信息管理</a>
  354. <a class="active" href="javascript:;">运费信息</a>
  355. <a href="javascript:;">缴费财务确认及开票</a>
  356. <div class="group-title">说明</div>
  357. <a href="javascript:;">协议单内仍可快捷配置</a>
  358. </aside>
  359. <div class="main">
  360. <div class="topbar">
  361. 细胞存储 / <strong>运费信息</strong>
  362. <span style="margin-left:auto">静态 Demo · Fresh Mint</span>
  363. </div>
  364. <div class="content">
  365. <div class="panel">
  366. <div class="panel-top">
  367. <h2>运费信息</h2>
  368. <p class="hint">
  369. 一行逻辑对应一笔运费批次;左侧「运输方式 / 运费 / 关联数」合并单元格。
  370. 右侧按实际关联展示细胞存储与科研捐赠样本(一次专线车可混装)。
  371. </p>
  372. </div>
  373. <div class="filter-block">
  374. <div class="field">
  375. <label>运输方式</label>
  376. <select>
  377. <option value="">全部</option>
  378. <option>顺丰快递</option>
  379. <option selected>专线车</option>
  380. </select>
  381. </div>
  382. <div class="field">
  383. <label>来源</label>
  384. <select>
  385. <option selected>全部(含细胞存储 / 科研捐赠)</option>
  386. <option>仅细胞存储</option>
  387. <option>仅科研捐赠</option>
  388. </select>
  389. </div>
  390. <div class="field">
  391. <label>协议单号 / 样本编码</label>
  392. <input placeholder="输入检索" />
  393. </div>
  394. <div class="field">
  395. <label>创建时间</label>
  396. <input type="text" placeholder="起止日期" value="2026-10-01 ~ 2026-10-08" />
  397. </div>
  398. </div>
  399. <div class="toolbar">
  400. <span class="selected-tip" id="selTip">已选 0 项</span>
  401. <button class="btn btn-main" type="button" onclick="openDialog()">
  402. <span>+</span> 配置运费
  403. </button>
  404. <button class="btn" type="button" id="btnUnlink" disabled onclick="alert('Demo:取消所选样本的运费关联')">
  405. 取消运费关联
  406. </button>
  407. <button class="btn" type="button" onclick="alert('Demo:编辑批次运输方式/金额')">编辑运费</button>
  408. </div>
  409. <div class="list-body">
  410. <table class="list-table">
  411. <thead>
  412. <tr>
  413. <th style="width:44px" class="center"><input type="checkbox" title="demo" disabled /></th>
  414. <th style="width:100px">运输方式</th>
  415. <th style="width:100px">运费(元)</th>
  416. <th style="width:72px" class="center">关联数</th>
  417. <th style="width:96px">来源</th>
  418. <th style="width:140px">协议单号</th>
  419. <th style="width:160px">样本编码</th>
  420. <th style="width:120px">样本类型</th>
  421. <th style="width:150px">创建时间</th>
  422. <th style="width:160px">操作</th>
  423. </tr>
  424. </thead>
  425. <tbody>
  426. <!-- 批次 A:专线车 800,3 个样本(合并) -->
  427. <tr>
  428. <td class="center merged" rowspan="3"><input type="checkbox" class="row-check" data-batch="A" /></td>
  429. <td class="merged" rowspan="3"><span class="tag success">专线车</span></td>
  430. <td class="merged amount" rowspan="3">¥800.00</td>
  431. <td class="merged center" rowspan="3">3</td>
  432. <td><span class="tag success">细胞存储</span></td>
  433. <td class="mono">CP20261008001</td>
  434. <td class="mono">ZK-UC-20261008-01</td>
  435. <td>脐带血</td>
  436. <td class="merged" rowspan="3">2026-10-08 09:12:00</td>
  437. <td class="merged" rowspan="3">
  438. <button class="btn btn-op" type="button">编辑</button>
  439. <button class="btn btn-op" type="button">取消关联</button>
  440. </td>
  441. </tr>
  442. <tr>
  443. <td><span class="tag info">科研捐赠</span></td>
  444. <td class="mono">RD20261008012</td>
  445. <td class="mono">RD-PL-20261008-03</td>
  446. <td>胎盘</td>
  447. </tr>
  448. <tr>
  449. <td><span class="tag success">细胞存储</span></td>
  450. <td class="mono">CP20261008007</td>
  451. <td class="mono">ZK-UC-20261008-07</td>
  452. <td>脐带血</td>
  453. </tr>
  454. <!-- 批次 B:顺丰 120,1 个样本 -->
  455. <tr>
  456. <td class="center merged"><input type="checkbox" class="row-check" data-batch="B" /></td>
  457. <td class="merged"><span class="tag warn">顺丰快递</span></td>
  458. <td class="merged amount">¥120.00</td>
  459. <td class="merged center">1</td>
  460. <td><span class="tag success">细胞存储</span></td>
  461. <td class="mono">CP20261007022</td>
  462. <td class="mono">ZK-UC-20261007-22</td>
  463. <td>脐带血</td>
  464. <td class="merged">2026-10-07 16:40:11</td>
  465. <td class="merged">
  466. <button class="btn btn-op" type="button">编辑</button>
  467. <button class="btn btn-op" type="button">取消关联</button>
  468. </td>
  469. </tr>
  470. <!-- 批次 C:同城闪送 260,2 个捐赠样本 -->
  471. <tr>
  472. <td class="center merged" rowspan="2"><input type="checkbox" class="row-check" data-batch="C" /></td>
  473. <td class="merged" rowspan="2"><span class="tag info">同城闪送</span></td>
  474. <td class="merged amount" rowspan="2">¥260.00</td>
  475. <td class="merged center" rowspan="2">2</td>
  476. <td><span class="tag info">科研捐赠</span></td>
  477. <td class="mono">RD20261006003</td>
  478. <td class="mono">RD-CB-20261006-01</td>
  479. <td>脐带</td>
  480. <td class="merged" rowspan="2">2026-10-06 11:05:33</td>
  481. <td class="merged" rowspan="2">
  482. <button class="btn btn-op" type="button">编辑</button>
  483. <button class="btn btn-op" type="button">取消关联</button>
  484. </td>
  485. </tr>
  486. <tr>
  487. <td><span class="tag info">科研捐赠</span></td>
  488. <td class="mono">RD20261006003</td>
  489. <td class="mono">RD-CB-20261006-02</td>
  490. <td>脐带</td>
  491. </tr>
  492. <!-- 批次 D:空批次示意 -->
  493. <tr>
  494. <td class="center merged"><input type="checkbox" class="row-check" data-batch="D" /></td>
  495. <td class="merged"><span class="tag success">专线车</span></td>
  496. <td class="merged amount">¥500.00</td>
  497. <td class="merged center">0</td>
  498. <td colspan="3" style="color:var(--text-muted)">— 暂无关联样本 —</td>
  499. <td class="merged">2026-10-05 18:20:00</td>
  500. <td class="merged">
  501. <button class="btn btn-op" type="button">编辑</button>
  502. <button class="btn btn-op btn-main" type="button" style="color:#fff;background:var(--primary);border-color:var(--primary)">关联样本</button>
  503. </td>
  504. </tr>
  505. </tbody>
  506. </table>
  507. </div>
  508. <div class="pager">
  509. <span>共 4 笔运费 · 6 条样本明细</span>
  510. <button class="btn btn-op" type="button" disabled>上一页</button>
  511. <span>1 / 1</span>
  512. <button class="btn btn-op" type="button" disabled>下一页</button>
  513. </div>
  514. </div>
  515. <div class="legend">
  516. <strong>Demo 说明:</strong>
  517. 浅色竖条标题对齐 Fresh Mint;批次 A 展示「专线车一次拉 3 个样本(含细胞存储 + 科研捐赠)」的合并单元格效果。
  518. 正式实现走 Vue + Element Plus,文件路径:
  519. <code>docs/demos/2026-10-08-sample-freight-menu-demo.html</code>
  520. </div>
  521. </div>
  522. </div>
  523. </div>
  524. <div class="mask" id="mask" onclick="if(event.target===this)closeDialog()">
  525. <div class="dialog" role="dialog" aria-modal="true">
  526. <header>
  527. <h3>配置运费</h3>
  528. <button class="btn btn-op" type="button" onclick="closeDialog()">关闭</button>
  529. </header>
  530. <div class="body">
  531. <div class="tabs">
  532. <button type="button" class="active" data-tab="create" onclick="switchTab(this)">新建运费</button>
  533. <button type="button" data-tab="link" onclick="switchTab(this)">关联已有</button>
  534. </div>
  535. <div id="tab-create">
  536. <div class="form-grid-2">
  537. <div class="field">
  538. <label>运输方式</label>
  539. <input list="modes" placeholder="选择或输入(可自动入字典)" value="专线车" />
  540. <datalist id="modes">
  541. <option value="顺丰快递"></option>
  542. <option value="专线车"></option>
  543. <option value="同城闪送"></option>
  544. </datalist>
  545. </div>
  546. <div class="field">
  547. <label>运费(元)</label>
  548. <input type="number" min="0" step="0.01" value="800" />
  549. </div>
  550. <div class="field full">
  551. <label>勾选要挂上的样本(可混选细胞存储 / 科研捐赠)</label>
  552. <div class="sample-pick">
  553. <label><input type="checkbox" checked /> <span class="tag success">细胞存储</span> CP20261008001 · ZK-UC-20261008-01</label>
  554. <label><input type="checkbox" checked /> <span class="tag info">科研捐赠</span> RD20261008012 · RD-PL-20261008-03</label>
  555. <label><input type="checkbox" /> <span class="tag success">细胞存储</span> CP20261008009 · ZK-UC-20261008-09</label>
  556. <label><input type="checkbox" /> <span class="tag info">科研捐赠</span> RD20261008001 · RD-CB-20261008-01</label>
  557. </div>
  558. </div>
  559. </div>
  560. </div>
  561. <div id="tab-link" style="display:none;color:var(--text-muted);font-size:13px">
  562. Demo 占位:此处为近期运费批次单选列表(方式 / 金额 / 关联数 / 时间),确定后挂到已选样本。
  563. </div>
  564. </div>
  565. <footer>
  566. <button class="btn" type="button" onclick="closeDialog()">取消</button>
  567. <button class="btn btn-main" type="button" onclick="alert('Demo:调用 create/link API'); closeDialog()">确定应用</button>
  568. </footer>
  569. </div>
  570. </div>
  571. <script>
  572. function openDialog() { document.getElementById('mask').classList.add('open') }
  573. function closeDialog() { document.getElementById('mask').classList.remove('open') }
  574. function switchTab(btn) {
  575. document.querySelectorAll('.tabs button').forEach(b => b.classList.remove('active'))
  576. btn.classList.add('active')
  577. const create = btn.dataset.tab === 'create'
  578. document.getElementById('tab-create').style.display = create ? '' : 'none'
  579. document.getElementById('tab-link').style.display = create ? 'none' : ''
  580. }
  581. function refreshSel() {
  582. const n = document.querySelectorAll('.row-check:checked').length
  583. document.getElementById('selTip').textContent = '已选 ' + n + ' 项'
  584. document.getElementById('btnUnlink').disabled = n === 0
  585. }
  586. document.querySelectorAll('.row-check').forEach(el => el.addEventListener('change', refreshSel))
  587. </script>
  588. </body>
  589. </html>