fresh-mint-pl-plan-list-layout-demo.html 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  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>运输计划列表 · 布局方案对比 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. --text: #1e293b;
  16. --text-muted: #64748b;
  17. --border: #d9e8e2;
  18. --surface: #ffffff;
  19. --table-head: #f8fafc;
  20. --row-alt: #fafafa;
  21. --row-hover: #f1f5f9;
  22. --bg-page: #f1f5f9;
  23. --shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  24. --radius: 10px;
  25. --warn: #d97706;
  26. --warn-bg: #fffbeb;
  27. --danger: #dc2626;
  28. --danger-bg: #fef2f2;
  29. --info: #0284c7;
  30. --load-accent: #059669;
  31. --load-bg: #ecfdf5;
  32. --delivery-accent: #0284c7;
  33. --delivery-bg: #f0f9ff;
  34. }
  35. * { box-sizing: border-box; }
  36. body {
  37. margin: 0;
  38. min-height: 100vh;
  39. font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  40. font-size: 14px;
  41. line-height: 1.5;
  42. color: var(--text);
  43. background: var(--bg-page);
  44. }
  45. .page { max-width: 1440px; margin: 0 auto; padding: 24px 20px 80px; }
  46. .hero {
  47. background: linear-gradient(135deg, #ecfdf5 0%, #fff 60%, #f0f9ff 100%);
  48. border: 1px solid var(--border);
  49. border-radius: var(--radius);
  50. padding: 22px 24px;
  51. margin-bottom: 16px;
  52. box-shadow: var(--shadow);
  53. }
  54. .hero h1 { margin: 0 0 8px; font-size: 22px; font-weight: 700; }
  55. .hero h1 em { font-style: normal; color: var(--primary-dark); }
  56. .hero p { margin: 0; color: var(--text-muted); max-width: 960px; }
  57. .compare-bar {
  58. display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  59. margin-top: 14px;
  60. }
  61. .compare-btn {
  62. border: 1px solid var(--border);
  63. background: #fff;
  64. color: var(--text-muted);
  65. border-radius: 999px;
  66. padding: 8px 16px;
  67. font-size: 13px;
  68. font-weight: 600;
  69. cursor: pointer;
  70. font-family: inherit;
  71. transition: all .15s ease;
  72. }
  73. .compare-btn:hover { border-color: #a7f3d0; color: var(--primary-dark); }
  74. .compare-btn.active {
  75. background: var(--primary);
  76. border-color: var(--primary);
  77. color: #fff;
  78. }
  79. .compare-btn .badge {
  80. display: inline-block;
  81. margin-left: 6px;
  82. padding: 1px 7px;
  83. border-radius: 999px;
  84. font-size: 11px;
  85. font-weight: 700;
  86. background: rgba(255,255,255,.22);
  87. }
  88. .compare-btn:not(.active) .badge {
  89. background: var(--primary-bg);
  90. color: var(--primary-dark);
  91. }
  92. .verdict {
  93. margin-left: auto;
  94. font-size: 12px;
  95. color: var(--text-muted);
  96. }
  97. .verdict strong { color: var(--primary-dark); }
  98. .analysis {
  99. display: grid;
  100. grid-template-columns: 1fr 1fr 1fr;
  101. gap: 12px;
  102. margin-bottom: 16px;
  103. }
  104. @media (max-width: 1100px) { .analysis { grid-template-columns: 1fr; } }
  105. .analysis-card {
  106. background: #fff;
  107. border: 1px solid var(--border);
  108. border-radius: var(--radius);
  109. padding: 14px 16px;
  110. box-shadow: var(--shadow);
  111. }
  112. .analysis-card h3 {
  113. margin: 0 0 8px;
  114. font-size: 13px;
  115. font-weight: 700;
  116. display: flex;
  117. align-items: center;
  118. gap: 8px;
  119. }
  120. .analysis-card ul {
  121. margin: 0;
  122. padding-left: 18px;
  123. color: var(--text-muted);
  124. font-size: 12px;
  125. }
  126. .analysis-card li { margin-bottom: 4px; }
  127. .analysis-card.recommended {
  128. border-color: #a7f3d0;
  129. background: linear-gradient(180deg, #fff 0%, #f8fffb 100%);
  130. }
  131. .tag-rec {
  132. font-size: 10px;
  133. font-weight: 700;
  134. letter-spacing: .04em;
  135. text-transform: uppercase;
  136. color: var(--primary-dark);
  137. background: var(--primary-bg);
  138. border: 1px solid #a7f3d0;
  139. border-radius: 4px;
  140. padding: 2px 6px;
  141. }
  142. .panel {
  143. background: var(--surface);
  144. border: 1px solid var(--border);
  145. border-radius: var(--radius);
  146. box-shadow: var(--shadow);
  147. overflow: hidden;
  148. }
  149. .panel-top {
  150. display: flex;
  151. align-items: center;
  152. gap: 10px;
  153. padding: 16px 20px;
  154. border-bottom: 1px solid var(--border);
  155. }
  156. .panel-top h2 {
  157. margin: 0;
  158. font-size: 16px;
  159. font-weight: 700;
  160. display: flex;
  161. align-items: center;
  162. gap: 8px;
  163. }
  164. .panel-top h2::before {
  165. content: "";
  166. width: 3px;
  167. height: 16px;
  168. border-radius: 2px;
  169. background: var(--primary);
  170. }
  171. .type-tabs {
  172. display: flex;
  173. gap: 4px;
  174. padding: 10px 16px 0;
  175. border-bottom: 1px solid var(--border);
  176. background: #fff;
  177. }
  178. .type-tab {
  179. border: none;
  180. background: transparent;
  181. padding: 10px 14px;
  182. font-size: 13px;
  183. font-weight: 600;
  184. color: var(--text-muted);
  185. cursor: pointer;
  186. border-bottom: 2px solid transparent;
  187. font-family: inherit;
  188. }
  189. .type-tab.active { color: var(--primary-dark); border-bottom-color: var(--primary); }
  190. .toolbar {
  191. display: flex;
  192. flex-wrap: wrap;
  193. gap: 8px;
  194. align-items: center;
  195. padding: 12px 16px;
  196. border-bottom: 1px solid var(--border);
  197. background: var(--table-head);
  198. }
  199. .btn {
  200. display: inline-flex;
  201. align-items: center;
  202. gap: 6px;
  203. height: 36px;
  204. padding: 0 14px;
  205. border-radius: 8px;
  206. font-size: 13px;
  207. font-weight: 600;
  208. border: 1px solid var(--border);
  209. background: #fff;
  210. color: var(--text);
  211. cursor: pointer;
  212. font-family: inherit;
  213. }
  214. .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
  215. .btn-ghost { background: #fff; }
  216. .search {
  217. height: 36px;
  218. border: 1px solid var(--border);
  219. border-radius: 8px;
  220. padding: 0 12px;
  221. min-width: 260px;
  222. font: inherit;
  223. }
  224. .sel-hint {
  225. font-size: 12px;
  226. color: var(--text-muted);
  227. margin-right: auto;
  228. }
  229. .sel-hint .num { color: var(--primary-dark); font-weight: 700; }
  230. .scheme { display: none; }
  231. .scheme.active { display: block; }
  232. table.list {
  233. width: 100%;
  234. border-collapse: collapse;
  235. font-size: 13px;
  236. }
  237. table.list th {
  238. background: var(--table-head);
  239. color: var(--text-muted);
  240. font-size: 12px;
  241. font-weight: 600;
  242. text-align: left;
  243. padding: 10px 12px;
  244. border-bottom: 1px solid var(--border);
  245. white-space: nowrap;
  246. }
  247. table.list td {
  248. padding: 10px 12px;
  249. border-bottom: 1px solid var(--border);
  250. vertical-align: top;
  251. }
  252. table.list tbody tr:hover td { background: var(--row-hover); }
  253. table.list tbody tr:nth-child(even) td { background: var(--row-alt); }
  254. table.list tbody tr:hover:nth-child(even) td { background: var(--row-hover); }
  255. .link { color: var(--primary-dark); font-weight: 600; cursor: pointer; text-decoration: none; }
  256. .tag {
  257. display: inline-flex;
  258. align-items: center;
  259. padding: 2px 8px;
  260. border-radius: 4px;
  261. font-size: 12px;
  262. font-weight: 600;
  263. white-space: nowrap;
  264. }
  265. .tag-success { background: var(--primary-dark); color: #fff; }
  266. .tag-danger { background: var(--danger); color: #fff; }
  267. .tag-warning { background: var(--warn); color: #fff; }
  268. .tag-info { background: transparent; color: var(--text); }
  269. .muted { color: var(--text-muted); font-size: 12px; }
  270. .stack > div + div { margin-top: 4px; }
  271. .product-line { line-height: 1.45; }
  272. .product-line .qty { color: var(--primary-dark); font-weight: 600; }
  273. /* 方案 B:主从分行 */
  274. .plan-group { border-bottom: 2px solid #e2e8f0; }
  275. .plan-group:last-child { border-bottom: none; }
  276. .plan-master td {
  277. background: #fff !important;
  278. padding-top: 14px;
  279. padding-bottom: 8px;
  280. border-bottom: none;
  281. }
  282. .plan-master .master-bar {
  283. display: flex;
  284. flex-wrap: wrap;
  285. align-items: center;
  286. gap: 10px 16px;
  287. }
  288. .plan-master .master-no {
  289. font-size: 15px;
  290. font-weight: 700;
  291. color: var(--primary-dark);
  292. }
  293. .plan-master .master-meta {
  294. display: flex;
  295. flex-wrap: wrap;
  296. gap: 8px 14px;
  297. font-size: 12px;
  298. color: var(--text-muted);
  299. }
  300. .plan-master .master-meta b { color: var(--text); font-weight: 600; }
  301. .plan-master .master-actions {
  302. margin-left: auto;
  303. display: flex;
  304. gap: 6px;
  305. }
  306. .plan-master .master-actions .btn { height: 32px; padding: 0 10px; font-size: 12px; }
  307. .stop-row td {
  308. padding-top: 6px;
  309. padding-bottom: 10px;
  310. border-bottom: 1px dashed #e2e8f0;
  311. }
  312. .stop-row:last-child td { border-bottom: none; padding-bottom: 14px; }
  313. .stop-badge {
  314. display: inline-flex;
  315. align-items: center;
  316. gap: 6px;
  317. padding: 3px 10px;
  318. border-radius: 999px;
  319. font-size: 11px;
  320. font-weight: 700;
  321. letter-spacing: .02em;
  322. }
  323. .stop-badge.load {
  324. background: var(--load-bg);
  325. color: var(--load-accent);
  326. border: 1px solid #a7f3d0;
  327. }
  328. .stop-badge.delivery {
  329. background: var(--delivery-bg);
  330. color: var(--delivery-accent);
  331. border: 1px solid #bae6fd;
  332. }
  333. .stop-indent { padding-left: 36px !important; }
  334. .stop-customer { font-weight: 600; color: var(--text); }
  335. .stop-addr { font-size: 12px; color: var(--text-muted); margin-top: 2px; max-width: 280px; }
  336. .internal-tag {
  337. font-size: 10px;
  338. font-weight: 600;
  339. color: var(--primary-dark);
  340. background: var(--primary-bg);
  341. border: 1px solid #a7f3d0;
  342. border-radius: 4px;
  343. padding: 1px 5px;
  344. margin-left: 6px;
  345. vertical-align: middle;
  346. }
  347. .merge-cell {
  348. border-left: 3px solid transparent;
  349. }
  350. .stop-row.load .merge-cell { border-left-color: var(--load-accent); }
  351. .stop-row.delivery .merge-cell { border-left-color: var(--delivery-accent); }
  352. /* 方案 C:树形展开 */
  353. .tree-toggle {
  354. width: 22px;
  355. height: 22px;
  356. border: 1px solid var(--border);
  357. border-radius: 6px;
  358. background: #fff;
  359. color: var(--text-muted);
  360. cursor: pointer;
  361. font-size: 14px;
  362. line-height: 1;
  363. vertical-align: middle;
  364. margin-right: 6px;
  365. }
  366. .tree-child { display: none; }
  367. .tree-child.open { display: table-row; }
  368. .tree-child td:first-child { padding-left: 42px; }
  369. .tree-child td { background: #fafcff !important; font-size: 12px; }
  370. .tree-label {
  371. display: inline-block;
  372. min-width: 52px;
  373. font-size: 11px;
  374. font-weight: 700;
  375. color: var(--text-muted);
  376. }
  377. .pager {
  378. display: flex;
  379. justify-content: space-between;
  380. align-items: center;
  381. padding: 12px 16px;
  382. border-top: 1px solid var(--border);
  383. font-size: 12px;
  384. color: var(--text-muted);
  385. }
  386. .note-box {
  387. margin: 16px 20px 20px;
  388. padding: 12px 14px;
  389. border-radius: 8px;
  390. background: var(--primary-bg);
  391. border: 1px solid #a7f3d0;
  392. font-size: 12px;
  393. color: #065f46;
  394. }
  395. .note-box.warn {
  396. background: var(--warn-bg);
  397. border-color: rgba(217,119,6,.25);
  398. color: #92400e;
  399. }
  400. .hidden-col { color: var(--text-muted); font-style: italic; }
  401. /* 方案 D:合并单元格(rowspan) */
  402. table.list-merge { table-layout: fixed; }
  403. table.list-merge th.col-plan,
  404. table.list-merge td.col-plan {
  405. background: #fff;
  406. border-right: 2px solid #cbd5e1;
  407. }
  408. table.list-merge th.col-stop,
  409. table.list-merge td.col-stop {
  410. background: #fafcff;
  411. }
  412. table.list-merge thead tr:last-child th.col-stop {
  413. background: #f0fdf9;
  414. color: var(--primary-dark);
  415. border-bottom: 2px solid #a7f3d0;
  416. }
  417. table.list-merge thead .th-group-plan {
  418. text-align: center;
  419. font-size: 11px;
  420. letter-spacing: .06em;
  421. color: var(--text-muted);
  422. border-bottom: none;
  423. padding-bottom: 4px;
  424. }
  425. table.list-merge thead .th-group-stop {
  426. text-align: center;
  427. font-size: 11px;
  428. letter-spacing: .06em;
  429. color: var(--primary-dark);
  430. border-bottom: none;
  431. padding-bottom: 4px;
  432. }
  433. table.list-merge td.cell-merge {
  434. vertical-align: middle;
  435. background: #fff !important;
  436. border-right: 2px solid #cbd5e1;
  437. }
  438. table.list-merge tbody tr.plan-block-last td.col-stop {
  439. border-bottom: 2px solid #cbd5e1;
  440. }
  441. table.list-merge tbody tr:hover td.col-stop {
  442. background: var(--row-hover) !important;
  443. }
  444. .merge-plan-no {
  445. display: block;
  446. font-size: 14px;
  447. font-weight: 700;
  448. margin-bottom: 6px;
  449. }
  450. .merge-plan-stack { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
  451. .merge-copy-stack { display: flex; flex-direction: column; gap: 4px; }
  452. .merge-copy-stack .btn { width: 100%; justify-content: center; font-size: 11px; height: 28px; padding: 0 6px; }
  453. .thead-sub th { font-size: 11px; padding-top: 0; }
  454. </style>
  455. </head>
  456. <body>
  457. <div class="page">
  458. <header class="hero">
  459. <h1>运输计划列表 · <em>布局方案对比</em></h1>
  460. <p>
  461. 左侧<strong>计划主信息合并单元格</strong>(勾选、编号、状态、车牌、复制等只出现一次),右侧按行展开<strong>装货 → 送货#1 → 送货#2 …</strong>,
  462. 段内客户、地址、产品、数量各占自己的列。既保留「一个主计划」的整体感,又看清每段明细。
  463. </p>
  464. <div class="compare-bar">
  465. <button type="button" class="compare-btn active" data-scheme="d">方案 D · 合并单元格<span class="badge">推荐</span></button>
  466. <button type="button" class="compare-btn" data-scheme="a">方案 A · 现状<span class="badge">扁平一行</span></button>
  467. <button type="button" class="compare-btn" data-scheme="b">方案 B · 主从分行</button>
  468. <button type="button" class="compare-btn" data-scheme="c">方案 C · 可展开树</button>
  469. <span class="verdict">推荐:<strong>方案 D</strong>(主信息 rowspan + 装送货分行)</span>
  470. </div>
  471. </header>
  472. <div class="analysis">
  473. <div class="analysis-card recommended">
  474. <h3>方案 D · 合并单元格 <span class="tag-rec">推荐</span></h3>
  475. <ul>
  476. <li>左侧列 <code>rowspan</code>:序号、计划编号、状态、日期、车牌、复制 — 视觉上仍是「一条计划」</li>
  477. <li>右侧列按段分行:装货 1 行 + 送货 N 行,客户/产品/数量段内对齐</li>
  478. <li>比 B 少一行「计划头横条」,比 A 清晰,比 C 不用点开</li>
  479. <li>Element Plus:<code>el-table :span-method</code> 即可实现</li>
  480. </ul>
  481. </div>
  482. <div class="analysis-card">
  483. <h3>方案 A · 现状(当前代码)</h3>
  484. <ul>
  485. <li>一行展示整单:装货客户一列、送货客户摘要一列</li>
  486. <li>产品列多行堆叠,看不出属于哪一段</li>
  487. <li>多段送货时「送货客户」只能是「A / B / C」摘要</li>
  488. <li>实现简单,分页/导出/勾选都按 plan 维度,无需改 API</li>
  489. </ul>
  490. </div>
  491. <div class="analysis-card">
  492. <h3>方案 B · 主从分行</h3>
  493. <ul>
  494. <li>计划头单独占一行,装送货在下方 — 主信息重复感略强</li>
  495. <li>段信息清晰,但与 D 相比多占一行高度</li>
  496. </ul>
  497. </div>
  498. <div class="analysis-card">
  499. <h3>方案 C · 树形可展开</h3>
  500. <ul>
  501. <li>默认只显示计划行,点击展开看装送货段</li>
  502. <li>行高最省,适合计划量特别大的场景</li>
  503. <li>多一段操作(展开)才能看到段信息</li>
  504. <li>Element Plus 可用 expand 行;批量操作仍按 plan 勾选</li>
  505. </ul>
  506. </div>
  507. </div>
  508. <section class="panel">
  509. <div class="panel-top"><h2>运输计划</h2></div>
  510. <div class="type-tabs">
  511. <button type="button" class="type-tab active">全部</button>
  512. <button type="button" class="type-tab">直销单</button>
  513. <button type="button" class="type-tab">入库单</button>
  514. <button type="button" class="type-tab">出库单</button>
  515. <button type="button" class="type-tab">待安排</button>
  516. </div>
  517. <div class="toolbar">
  518. <span class="sel-hint">已选 <span class="num">1</span> 项</span>
  519. <input class="search" placeholder="计划编号/供方/需方/产品名称/车牌号" />
  520. <button type="button" class="btn btn-primary">新增计划</button>
  521. <button type="button" class="btn btn-ghost">安排运输</button>
  522. <button type="button" class="btn btn-ghost">打印单据</button>
  523. </div>
  524. <!-- ========== 方案 D:合并单元格(推荐) ========== -->
  525. <div id="scheme-d" class="scheme active">
  526. <table class="list list-merge">
  527. <colgroup>
  528. <col style="width:40px" />
  529. <col style="width:52px" />
  530. <col style="width:148px" />
  531. <col style="width:88px" />
  532. <col style="width:96px" />
  533. <col style="width:80px" />
  534. <col style="width:108px" />
  535. <col style="width:108px" />
  536. <col style="width:72px" />
  537. <col style="width:140px" />
  538. <col style="width:88px" />
  539. <col style="width:160px" />
  540. <col style="width:72px" />
  541. <col style="width:72px" />
  542. </colgroup>
  543. <thead>
  544. <tr>
  545. <th class="col-plan th-group-plan" colspan="8">计划主信息(合并单元格)</th>
  546. <th class="col-stop th-group-stop" colspan="6">装货 / 送货段信息(按行展开)</th>
  547. </tr>
  548. <tr class="thead-sub">
  549. <th class="col-plan"><input type="checkbox" checked /></th>
  550. <th class="col-plan">序号</th>
  551. <th class="col-plan">计划编号</th>
  552. <th class="col-plan">跟进状态</th>
  553. <th class="col-plan">计划日期</th>
  554. <th class="col-plan">单据类型</th>
  555. <th class="col-plan">车牌号</th>
  556. <th class="col-plan">复制</th>
  557. <th class="col-stop">段次</th>
  558. <th class="col-stop">客户</th>
  559. <th class="col-stop">地址 / 时间</th>
  560. <th class="col-stop">段内产品</th>
  561. <th class="col-stop">计划数量</th>
  562. <th class="col-stop">地磅</th>
  563. </tr>
  564. </thead>
  565. <tbody>
  566. <!-- 计划 1:1装 + 2送 = 3 行,左侧 rowspan=3 -->
  567. <tr>
  568. <td class="cell-merge col-plan" rowspan="3"><input type="checkbox" checked /></td>
  569. <td class="cell-merge col-plan" rowspan="3">1</td>
  570. <td class="cell-merge col-plan" rowspan="3">
  571. <a class="link merge-plan-no" href="#">PL20250826001</a>
  572. <span class="muted" style="font-size:11px">跟进:张三</span>
  573. </td>
  574. <td class="cell-merge col-plan" rowspan="3"><span class="tag tag-success">已安排</span></td>
  575. <td class="cell-merge col-plan" rowspan="3">2025-08-26</td>
  576. <td class="cell-merge col-plan" rowspan="3">出库单</td>
  577. <td class="cell-merge col-plan" rowspan="3">皖A·12345</td>
  578. <td class="cell-merge col-plan" rowspan="3">
  579. <div class="merge-copy-stack">
  580. <button type="button" class="btn btn-ghost">复制给司机</button>
  581. <button type="button" class="btn btn-ghost">复制给现场</button>
  582. </div>
  583. </td>
  584. <td class="col-stop"><span class="stop-badge load">装货</span></td>
  585. <td class="col-stop">
  586. <span class="stop-customer">安庆市山峡化工有限公司</span>
  587. <span class="internal-tag">内部</span>
  588. </td>
  589. <td class="col-stop"><span class="stop-addr">安庆市 … · 08-26 08:00</span></td>
  590. <td class="col-stop">
  591. <div class="product-line">硫酸 <span class="muted">32%</span></div>
  592. <div class="product-line">盐酸 <span class="muted">31%</span></div>
  593. </td>
  594. <td class="col-stop">
  595. <div class="product-line"><span class="qty">28 吨</span></div>
  596. <div class="product-line"><span class="qty">12 吨</span></div>
  597. </td>
  598. <td class="col-stop"><span class="tag tag-success" style="font-size:11px">出库</span></td>
  599. </tr>
  600. <tr>
  601. <td class="col-stop"><span class="stop-badge delivery">送货 #1</span></td>
  602. <td class="col-stop"><span class="stop-customer">合肥某某贸易有限公司</span></td>
  603. <td class="col-stop"><span class="stop-addr">合肥市 … · 08-26 14:00</span></td>
  604. <td class="col-stop"><div class="product-line">硫酸 <span class="muted">32%</span></div></td>
  605. <td class="col-stop"><span class="qty">28 吨</span></td>
  606. <td class="col-stop muted">—</td>
  607. </tr>
  608. <tr class="plan-block-last">
  609. <td class="col-stop"><span class="stop-badge delivery">送货 #2</span></td>
  610. <td class="col-stop"><span class="stop-customer">芜湖某某物流有限公司</span></td>
  611. <td class="col-stop"><span class="stop-addr">芜湖市 … · 08-26 17:30</span></td>
  612. <td class="col-stop"><div class="product-line">盐酸 <span class="muted">31%</span></div></td>
  613. <td class="col-stop"><span class="qty">12 吨</span></td>
  614. <td class="col-stop muted">—</td>
  615. </tr>
  616. <!-- 计划 2:1装 + 1送 = 2 行 -->
  617. <tr>
  618. <td class="cell-merge col-plan" rowspan="2"><input type="checkbox" /></td>
  619. <td class="cell-merge col-plan" rowspan="2">2</td>
  620. <td class="cell-merge col-plan" rowspan="2">
  621. <a class="link merge-plan-no" href="#">PL20250826002</a>
  622. </td>
  623. <td class="cell-merge col-plan" rowspan="2"><span class="tag tag-danger">待安排</span></td>
  624. <td class="cell-merge col-plan" rowspan="2">2025-08-26</td>
  625. <td class="cell-merge col-plan" rowspan="2">入库单</td>
  626. <td class="cell-merge col-plan muted" rowspan="2">—</td>
  627. <td class="cell-merge col-plan hidden-col" rowspan="2">—</td>
  628. <td class="col-stop"><span class="stop-badge load">装货</span></td>
  629. <td class="col-stop"><span class="stop-customer">江苏某某供应商</span></td>
  630. <td class="col-stop"><span class="stop-addr">南京市 … · 08-26 09:00</span></td>
  631. <td class="col-stop"><div class="product-line">甲醇</div></td>
  632. <td class="col-stop"><span class="qty">20 吨</span></td>
  633. <td class="col-stop muted">—</td>
  634. </tr>
  635. <tr class="plan-block-last">
  636. <td class="col-stop"><span class="stop-badge delivery">送货 #1</span></td>
  637. <td class="col-stop">
  638. <span class="stop-customer">安徽云栋化工有限公司</span>
  639. <span class="internal-tag">内部</span>
  640. </td>
  641. <td class="col-stop"><span class="stop-addr">安庆市 … · 08-26 15:00</span></td>
  642. <td class="col-stop"><div class="product-line">甲醇</div></td>
  643. <td class="col-stop"><span class="qty">20 吨</span></td>
  644. <td class="col-stop"><span class="tag tag-success" style="font-size:11px">入库</span></td>
  645. </tr>
  646. <!-- 计划 3:直销 1装+1送 -->
  647. <tr>
  648. <td class="cell-merge col-plan" rowspan="2"><input type="checkbox" /></td>
  649. <td class="cell-merge col-plan" rowspan="2">3</td>
  650. <td class="cell-merge col-plan" rowspan="2">
  651. <a class="link merge-plan-no" href="#">PL20250825018</a>
  652. </td>
  653. <td class="cell-merge col-plan" rowspan="2"><span class="tag tag-warning">等通知</span></td>
  654. <td class="cell-merge col-plan" rowspan="2">2025-08-25</td>
  655. <td class="cell-merge col-plan" rowspan="2">直销单</td>
  656. <td class="cell-merge col-plan muted" rowspan="2">—</td>
  657. <td class="cell-merge col-plan hidden-col" rowspan="2">—</td>
  658. <td class="col-stop"><span class="stop-badge load">装货</span></td>
  659. <td class="col-stop"><span class="stop-customer">山东某某工厂</span></td>
  660. <td class="col-stop"><span class="stop-addr">济南市 … · 08-25 10:00</span></td>
  661. <td class="col-stop"><div class="product-line">双氧水</div></td>
  662. <td class="col-stop"><span class="qty">15 吨</span></td>
  663. <td class="col-stop muted">—</td>
  664. </tr>
  665. <tr class="plan-block-last">
  666. <td class="col-stop"><span class="stop-badge delivery">送货 #1</span></td>
  667. <td class="col-stop"><span class="stop-customer">南京某某客户</span></td>
  668. <td class="col-stop"><span class="stop-addr">南京市 … · 08-25 16:00</span></td>
  669. <td class="col-stop"><div class="product-line">双氧水</div></td>
  670. <td class="col-stop"><span class="qty">15 吨</span></td>
  671. <td class="col-stop muted">—</td>
  672. </tr>
  673. </tbody>
  674. </table>
  675. <div class="note-box">
  676. <strong>方案 D 结构:</strong>
  677. 表头分两区 — 左「计划主信息」、右「装货/送货段信息」。
  678. 每个计划在左侧占一块合并单元格(高度 = 装货 1 行 + 送货 N 行),右侧逐段展开。
  679. 扫列表时先看左块认计划,再看右块认路线与货量,符合调度习惯。
  680. </div>
  681. </div>
  682. <!-- ========== 方案 A:现状 ========== -->
  683. <div id="scheme-a" class="scheme">
  684. <table class="list">
  685. <thead>
  686. <tr>
  687. <th style="width:40px"><input type="checkbox" checked /></th>
  688. <th style="width:52px">序号</th>
  689. <th>计划编号</th>
  690. <th>跟进状态</th>
  691. <th>计划日期</th>
  692. <th>单据类型</th>
  693. <th>装货客户</th>
  694. <th>送货客户</th>
  695. <th>车牌号</th>
  696. <th>产品名称</th>
  697. <th>计划数量</th>
  698. <th>复制</th>
  699. </tr>
  700. </thead>
  701. <tbody>
  702. <tr>
  703. <td><input type="checkbox" checked /></td>
  704. <td>1</td>
  705. <td><a class="link" href="#">PL20250826001</a></td>
  706. <td><span class="tag tag-success">已安排</span></td>
  707. <td>2025-08-26</td>
  708. <td>出库单</td>
  709. <td>安庆市山峡化工有限公司</td>
  710. <td class="muted">合肥某某贸易 / 芜湖某某物流</td>
  711. <td>皖A·12345</td>
  712. <td class="stack">
  713. <div class="product-line">硫酸 <span class="qty">28 吨</span></div>
  714. <div class="product-line">盐酸 <span class="qty">12 吨</span></div>
  715. </td>
  716. <td class="stack">
  717. <div>28 吨</div>
  718. <div>12 吨</div>
  719. </td>
  720. <td><button type="button" class="btn btn-ghost" style="height:30px;padding:0 8px;font-size:12px">复制给司机</button></td>
  721. </tr>
  722. <tr>
  723. <td><input type="checkbox" /></td>
  724. <td>2</td>
  725. <td><a class="link" href="#">PL20250826002</a></td>
  726. <td><span class="tag tag-danger">待安排</span></td>
  727. <td>2025-08-26</td>
  728. <td>入库单</td>
  729. <td>江苏某某供应商</td>
  730. <td>安徽云栋化工有限公司</td>
  731. <td class="muted">—</td>
  732. <td class="stack">
  733. <div class="product-line">甲醇 <span class="qty">20 吨</span></div>
  734. </td>
  735. <td>20 吨</td>
  736. <td class="hidden-col">—</td>
  737. </tr>
  738. <tr>
  739. <td><input type="checkbox" /></td>
  740. <td>3</td>
  741. <td><a class="link" href="#">PL20250825018</a></td>
  742. <td><span class="tag tag-warning">等通知</span></td>
  743. <td>2025-08-25</td>
  744. <td>直销单</td>
  745. <td>山东某某工厂</td>
  746. <td>南京某某客户</td>
  747. <td class="muted">—</td>
  748. <td class="stack">
  749. <div class="product-line">双氧水 <span class="qty">15 吨</span></div>
  750. </td>
  751. <td>15 吨</td>
  752. <td class="hidden-col">—</td>
  753. </tr>
  754. </tbody>
  755. </table>
  756. <div class="note-box warn">
  757. <strong>现状痛点(样例 PL20250826001):</strong>
  758. 两个送货客户挤在「送货客户」一列;两种产品堆在「产品名称」列,无法看出 28 吨硫酸去合肥、12 吨盐酸去芜湖。
  759. 多段计划越多,这一行越难扫读。
  760. </div>
  761. </div>
  762. <!-- ========== 方案 B:主从分行 ========== -->
  763. <div id="scheme-b" class="scheme">
  764. <table class="list">
  765. <thead>
  766. <tr>
  767. <th style="width:40px"><input type="checkbox" checked /></th>
  768. <th>段次 / 客户</th>
  769. <th>地址 / 时间</th>
  770. <th>段内产品</th>
  771. <th>计划数量</th>
  772. <th>地磅</th>
  773. </tr>
  774. </thead>
  775. <tbody>
  776. <!-- 计划 1 -->
  777. <tr class="plan-group plan-master">
  778. <td><input type="checkbox" checked /></td>
  779. <td colspan="5">
  780. <div class="master-bar">
  781. <a class="link master-no" href="#">PL20250826001</a>
  782. <span class="tag tag-success">已安排</span>
  783. <span class="tag tag-info">出库单</span>
  784. <div class="master-meta">
  785. <span>计划日期 <b>2025-08-26</b></span>
  786. <span>车牌 <b>皖A·12345</b></span>
  787. <span>跟进人 <b>张三</b></span>
  788. </div>
  789. <div class="master-actions">
  790. <button type="button" class="btn btn-ghost">查看</button>
  791. <button type="button" class="btn btn-ghost">复制给司机</button>
  792. <button type="button" class="btn btn-ghost">调整运输</button>
  793. </div>
  794. </div>
  795. </td>
  796. </tr>
  797. <tr class="plan-group stop-row load">
  798. <td></td>
  799. <td class="merge-cell stop-indent">
  800. <span class="stop-badge load">装货</span>
  801. <span class="stop-customer">安庆市山峡化工有限公司</span>
  802. <span class="internal-tag">内部</span>
  803. <div class="stop-addr">安徽省安庆市 … · 08-26 08:00</div>
  804. </td>
  805. <td class="muted">出库 · 已勾地磅</td>
  806. <td>
  807. <div class="product-line">硫酸 <span class="muted">32%工业级</span></div>
  808. <div class="product-line">盐酸 <span class="muted">31%</span></div>
  809. </td>
  810. <td>
  811. <div class="product-line"><span class="qty">28 吨</span></div>
  812. <div class="product-line"><span class="qty">12 吨</span></div>
  813. </td>
  814. <td><span class="tag tag-success" style="font-size:11px">出库单</span></td>
  815. </tr>
  816. <tr class="plan-group stop-row delivery">
  817. <td></td>
  818. <td class="merge-cell stop-indent">
  819. <span class="stop-badge delivery">送货 #1</span>
  820. <span class="stop-customer">合肥某某贸易有限公司</span>
  821. <div class="stop-addr">合肥市蜀山区 … · 08-26 14:00</div>
  822. </td>
  823. <td class="muted">—</td>
  824. <td><div class="product-line">硫酸 <span class="muted">32%工业级</span></div></td>
  825. <td><span class="qty">28 吨</span></td>
  826. <td class="muted">—</td>
  827. </tr>
  828. <tr class="plan-group stop-row delivery">
  829. <td></td>
  830. <td class="merge-cell stop-indent">
  831. <span class="stop-badge delivery">送货 #2</span>
  832. <span class="stop-customer">芜湖某某物流有限公司</span>
  833. <div class="stop-addr">芜湖市镜湖区 … · 08-26 17:30</div>
  834. </td>
  835. <td class="muted">—</td>
  836. <td><div class="product-line">盐酸 <span class="muted">31%</span></div></td>
  837. <td><span class="qty">12 吨</span></td>
  838. <td class="muted">—</td>
  839. </tr>
  840. <!-- 计划 2 -->
  841. <tr class="plan-group plan-master">
  842. <td><input type="checkbox" /></td>
  843. <td colspan="5">
  844. <div class="master-bar">
  845. <a class="link master-no" href="#">PL20250826002</a>
  846. <span class="tag tag-danger">待安排</span>
  847. <span class="tag tag-info">入库单</span>
  848. <div class="master-meta">
  849. <span>计划日期 <b>2025-08-26</b></span>
  850. <span>车牌 <b class="muted">未安排</b></span>
  851. </div>
  852. <div class="master-actions">
  853. <button type="button" class="btn btn-primary" style="height:32px;padding:0 10px;font-size:12px">安排运输</button>
  854. </div>
  855. </div>
  856. </td>
  857. </tr>
  858. <tr class="plan-group stop-row load">
  859. <td></td>
  860. <td class="merge-cell stop-indent">
  861. <span class="stop-badge load">装货</span>
  862. <span class="stop-customer">江苏某某供应商</span>
  863. <div class="stop-addr">南京市 … · 08-26 09:00</div>
  864. </td>
  865. <td class="muted">—</td>
  866. <td><div class="product-line">甲醇</div></td>
  867. <td><span class="qty">20 吨</span></td>
  868. <td class="muted">—</td>
  869. </tr>
  870. <tr class="plan-group stop-row delivery">
  871. <td></td>
  872. <td class="merge-cell stop-indent">
  873. <span class="stop-badge delivery">送货 #1</span>
  874. <span class="stop-customer">安徽云栋化工有限公司</span>
  875. <span class="internal-tag">内部</span>
  876. <div class="stop-addr">安庆市 … · 08-26 15:00</div>
  877. </td>
  878. <td class="muted">入库 · 已勾地磅</td>
  879. <td><div class="product-line">甲醇</div></td>
  880. <td><span class="qty">20 吨</span></td>
  881. <td><span class="tag tag-success" style="font-size:11px">入库单</span></td>
  882. </tr>
  883. </tbody>
  884. </table>
  885. <div class="note-box">
  886. <strong>方案 B 优点:</strong>
  887. 计划头只出现一次(编号/状态/车辆/操作);装货、送货 #1、#2 各占一行,段内产品数量清晰。
  888. 勾选仍在计划头,与现有工具栏「已选 N 项 / 安排运输 / 作废」兼容。
  889. 列可收敛为:段次·客户、地址·时间、段内产品、数量、地磅/现场标记。
  890. </div>
  891. </div>
  892. <!-- ========== 方案 C:树形展开 ========== -->
  893. <div id="scheme-c" class="scheme">
  894. <table class="list">
  895. <thead>
  896. <tr>
  897. <th style="width:40px"><input type="checkbox" checked /></th>
  898. <th style="width:52px">序号</th>
  899. <th>计划编号</th>
  900. <th>跟进状态</th>
  901. <th>计划日期</th>
  902. <th>装货 → 送货摘要</th>
  903. <th>车牌号</th>
  904. <th>产品概要</th>
  905. <th>操作</th>
  906. </tr>
  907. </thead>
  908. <tbody>
  909. <tr data-tree="p1">
  910. <td><input type="checkbox" checked /></td>
  911. <td>1</td>
  912. <td>
  913. <button type="button" class="tree-toggle" data-target="p1" aria-label="展开">−</button>
  914. <a class="link" href="#">PL20250826001</a>
  915. </td>
  916. <td><span class="tag tag-success">已安排</span></td>
  917. <td>2025-08-26</td>
  918. <td class="muted">山峡化工 → 合肥贸易 / 芜湖物流(2 段送货)</td>
  919. <td>皖A·12345</td>
  920. <td>硫酸 28t + 盐酸 12t</td>
  921. <td><button type="button" class="btn btn-ghost" style="height:30px;padding:0 8px;font-size:12px">查看</button></td>
  922. </tr>
  923. <tr class="tree-child open" data-tree-child="p1">
  924. <td></td>
  925. <td></td>
  926. <td colspan="7">
  927. <span class="tree-label">装货</span>
  928. <strong>安庆市山峡化工有限公司</strong>
  929. <span class="muted"> · 硫酸 28t、盐酸 12t · 出库地磅</span>
  930. </td>
  931. </tr>
  932. <tr class="tree-child open" data-tree-child="p1">
  933. <td></td>
  934. <td></td>
  935. <td colspan="7">
  936. <span class="tree-label">送货#1</span>
  937. <strong>合肥某某贸易有限公司</strong>
  938. <span class="muted"> · 硫酸 28t</span>
  939. </td>
  940. </tr>
  941. <tr class="tree-child open" data-tree-child="p1">
  942. <td></td>
  943. <td></td>
  944. <td colspan="7">
  945. <span class="tree-label">送货#2</span>
  946. <strong>芜湖某某物流有限公司</strong>
  947. <span class="muted"> · 盐酸 12t</span>
  948. </td>
  949. </tr>
  950. <tr data-tree="p2">
  951. <td><input type="checkbox" /></td>
  952. <td>2</td>
  953. <td>
  954. <button type="button" class="tree-toggle" data-target="p2" aria-label="展开">+</button>
  955. <a class="link" href="#">PL20250826002</a>
  956. </td>
  957. <td><span class="tag tag-danger">待安排</span></td>
  958. <td>2025-08-26</td>
  959. <td class="muted">江苏供应商 → 安徽云栋(1 段)</td>
  960. <td class="muted">—</td>
  961. <td>甲醇 20t</td>
  962. <td><button type="button" class="btn btn-primary" style="height:30px;padding:0 8px;font-size:12px">安排</button></td>
  963. </tr>
  964. <tr class="tree-child" data-tree-child="p2">
  965. <td></td>
  966. <td></td>
  967. <td colspan="7">
  968. <span class="tree-label">装货</span>
  969. <strong>江苏某某供应商</strong>
  970. <span class="muted"> · 甲醇 20t</span>
  971. </td>
  972. </tr>
  973. <tr class="tree-child" data-tree-child="p2">
  974. <td></td>
  975. <td></td>
  976. <td colspan="7">
  977. <span class="tree-label">送货#1</span>
  978. <strong>安徽云栋化工有限公司</strong>
  979. <span class="muted"> · 甲醇 20t · 入库地磅</span>
  980. </td>
  981. </tr>
  982. </tbody>
  983. </table>
  984. <div class="note-box warn">
  985. <strong>方案 C 权衡:</strong>
  986. 默认折叠时与现状行高接近;展开后信息等价于方案 B,但多一次点击。
  987. 适合「列表以扫计划号/状态为主,偶尔下钻看段」的场景。
  988. </div>
  989. </div>
  990. <div class="pager">
  991. <span>共 128 条计划 · 第 1 / 7 页</span>
  992. <span>分页仍按<strong>计划</strong>计数,不按段行计数</span>
  993. </div>
  994. </section>
  995. <section class="analysis" style="margin-top:16px">
  996. <div class="analysis-card recommended" style="grid-column: 1 / -1">
  997. <h3>实施建议(方案 D · 合并单元格)</h3>
  998. <ul>
  999. <li><strong>渲染:</strong>将 <code>planList</code> 展平为 <code>displayRows[]</code>(每段一行,带 <code>planId</code>、<code>isFirstStop</code>、<code>planRowspan</code>),<code>el-table :span-method</code> 合并左侧计划列。</li>
  1000. <li><strong>数据:</strong>列表 API 返回 <code>load</code> + <code>deliveries[]</code> 及段内 <code>products</code>。</li>
  1001. <li><strong>交互:</strong>勾选/双击/工具栏仍按 <strong>planId</strong>;仅首段行渲染 checkbox;段行 hover 时整组高亮(可选)。</li>
  1002. <li><strong>列分区:</strong>计划主信息 8 列 + 段信息 6 列;列设置可对两侧分组配置。</li>
  1003. <li><strong>单段计划:</strong>rowspan=2(1 装 + 1 送),与多段结构一致。</li>
  1004. <li><strong>导出:</strong>Excel 仍可扁平「一行一计划」,与 UI 展示解耦。</li>
  1005. </ul>
  1006. </div>
  1007. </section>
  1008. </div>
  1009. <script>
  1010. document.querySelectorAll('.compare-btn').forEach(btn => {
  1011. btn.addEventListener('click', () => {
  1012. const id = btn.dataset.scheme
  1013. document.querySelectorAll('.compare-btn').forEach(b => b.classList.remove('active'))
  1014. document.querySelectorAll('.scheme').forEach(s => s.classList.remove('active'))
  1015. btn.classList.add('active')
  1016. document.getElementById('scheme-' + id).classList.add('active')
  1017. })
  1018. })
  1019. document.querySelectorAll('.tree-toggle').forEach(btn => {
  1020. btn.addEventListener('click', () => {
  1021. const id = btn.dataset.target
  1022. const rows = document.querySelectorAll('[data-tree-child="' + id + '"]')
  1023. const open = btn.textContent.trim() === '+'
  1024. rows.forEach(r => r.classList.toggle('open', open))
  1025. btn.textContent = open ? '−' : '+'
  1026. })
  1027. })
  1028. </script>
  1029. </body>
  1030. </html>