pl-plan-flat-columns-no-merge-demo.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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. --muted: #64748b;
  17. --border: #d9e8e2;
  18. --head: #f8fafc;
  19. --page: #eef2f6;
  20. --panel: #fff;
  21. --warn: #d97706;
  22. --danger: #dc2626;
  23. --alt: #fafafa;
  24. --load-bg: #ecfdf5;
  25. --delivery-bg: #f0f9ff;
  26. --load-border: #a7f3d0;
  27. --delivery-border: #bae6fd;
  28. }
  29. * { box-sizing: border-box; }
  30. body {
  31. margin: 0;
  32. padding: 24px 20px 80px;
  33. font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
  34. font-size: 14px;
  35. line-height: 1.5;
  36. color: var(--text);
  37. background: var(--page);
  38. }
  39. .page { max-width: 1760px; margin: 0 auto; }
  40. .hero {
  41. background: linear-gradient(135deg, #ecfdf5 0%, #fff 55%, #f0f9ff 100%);
  42. border: 1px solid var(--border);
  43. border-radius: 10px;
  44. padding: 20px 22px;
  45. margin-bottom: 16px;
  46. }
  47. .hero h1 { margin: 0 0 8px; font-size: 22px; font-weight: 700; }
  48. .hero h1 em { font-style: normal; color: var(--primary-dark); }
  49. .hero p { margin: 0; color: var(--muted); max-width: 1080px; }
  50. .pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
  51. .pill {
  52. font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: 999px;
  53. background: var(--primary-bg); color: var(--primary-dark); border: 1px solid #a7f3d0;
  54. }
  55. .pill.muted { background: #f1f5f9; color: var(--muted); border-color: #e2e8f0; }
  56. .pill.warn { background: #fffbeb; color: var(--warn); border-color: #fde68a; }
  57. .tabs {
  58. display: flex; flex-wrap: wrap; gap: 6px;
  59. background: var(--panel); border: 1px solid var(--border);
  60. border-radius: 10px 10px 0 0; padding: 8px 10px 0;
  61. }
  62. .tab {
  63. border: none; background: transparent; cursor: pointer; font-family: inherit;
  64. padding: 10px 14px; font-size: 13px; font-weight: 600; color: var(--muted);
  65. border-bottom: 2px solid transparent;
  66. }
  67. .tab:hover { color: var(--primary-dark); }
  68. .tab.active { color: var(--primary-dark); border-bottom-color: var(--primary); }
  69. .tab .rec {
  70. margin-left: 6px; font-size: 10px; padding: 1px 6px; border-radius: 999px;
  71. background: var(--primary-bg); color: var(--primary-dark); border: 1px solid #a7f3d0;
  72. }
  73. .panel {
  74. display: none;
  75. background: var(--panel);
  76. border: 1px solid var(--border);
  77. border-top: none;
  78. border-radius: 0 0 10px 10px;
  79. padding: 16px 18px 22px;
  80. }
  81. .panel.active { display: block; }
  82. .desc {
  83. margin: 0 0 12px; padding: 12px 14px; border-radius: 8px;
  84. background: #f0f9ff; border: 1px solid #bae6fd; color: #0369a1; font-size: 13px;
  85. }
  86. .desc strong { color: var(--text); }
  87. .ok {
  88. margin: 0 0 14px; padding: 10px 12px; border-radius: 8px;
  89. background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46; font-size: 12.5px;
  90. }
  91. .pain {
  92. margin: 0 0 14px; padding: 10px 12px; border-radius: 8px;
  93. background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; font-size: 12.5px;
  94. }
  95. .table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 8px; }
  96. table.list {
  97. width: 100%;
  98. border-collapse: collapse;
  99. min-width: 1480px;
  100. background: #fff;
  101. }
  102. table.list th,
  103. table.list td {
  104. border-bottom: 1px solid var(--border);
  105. border-right: 1px solid #eef2f0;
  106. padding: 10px 12px;
  107. vertical-align: top;
  108. font-size: 13px;
  109. }
  110. table.list th:last-child,
  111. table.list td:last-child { border-right: none; }
  112. table.list thead th {
  113. position: sticky; top: 0; z-index: 1;
  114. background: var(--head);
  115. color: var(--muted);
  116. font-size: 12px;
  117. font-weight: 600;
  118. white-space: nowrap;
  119. text-align: left;
  120. }
  121. table.list thead th.num,
  122. table.list td.num { text-align: right; }
  123. table.list thead th.center,
  124. table.list td.center { text-align: center; }
  125. table.list tbody tr:nth-child(even) { background: var(--alt); }
  126. table.list tbody tr:hover { background: #f1f5f9; }
  127. table.list a { color: var(--primary-dark); font-weight: 500; text-decoration: none; }
  128. table.list a:hover { text-decoration: underline; }
  129. .tag {
  130. display: inline-flex; align-items: center;
  131. padding: 1px 8px; border-radius: 6px;
  132. font-size: 12px; font-weight: 600; line-height: 1.45; white-space: nowrap;
  133. }
  134. .tag.success { background: #059669; color: #fff; }
  135. .tag.warn { background: #d97706; color: #fff; }
  136. .tag.info {
  137. background: transparent; color: var(--text);
  138. border: 1px solid #cbd5e1; font-weight: 500;
  139. }
  140. .tag.side-load {
  141. background: var(--load-bg); color: var(--primary-dark);
  142. border: 1px solid var(--load-border);
  143. }
  144. .tag.side-need {
  145. background: var(--delivery-bg); color: #0369a1;
  146. border: 1px solid var(--delivery-border);
  147. }
  148. .muted { color: var(--muted); }
  149. .stack { display: flex; flex-direction: column; gap: 6px; }
  150. .stack .sub { color: var(--muted); font-size: 12px; }
  151. .name-with-mark {
  152. display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px;
  153. }
  154. .th-group-load { background: #ecfdf5 !important; color: var(--primary-dark) !important; }
  155. .th-group-need { background: #e0f2fe !important; color: #0369a1 !important; }
  156. .cell-load { background: rgba(236, 253, 245, 0.45); }
  157. .cell-need { background: rgba(240, 249, 255, 0.55); }
  158. .meta {
  159. margin-top: 14px; font-size: 12px; color: var(--muted);
  160. }
  161. .meta code {
  162. font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  163. background: #f1f5f9; padding: 1px 6px; border-radius: 4px;
  164. }
  165. </style>
  166. </head>
  167. <body>
  168. <div class="page">
  169. <header class="hero">
  170. <h1>运输计划列表 · <em>扁平多列</em>(取消合并单元格)</h1>
  171. <p>
  172. 案例数据来自测试库运输计划 <strong>PO20261009022</strong>:
  173. 装货客户「安庆市山峡化工有限公司」、送货客户「桐城运城制版有限公司」、
  174. 3 个产品(硫酸 / 聚合氯化铝 / 液碱);需方合同 <code>SX/X-JT20261008006</code>,供方合同为空。
  175. 目标:去掉供/需纵向拆行 + rowspan,改为扁平分列,每一物理行独立完整。
  176. </p>
  177. <div class="pills">
  178. <span class="pill">PO20261009022</span>
  179. <span class="pill">皖H21522</span>
  180. <span class="pill">程欢欢</span>
  181. <span class="pill muted">黄天宝(押)、沈卫斌(驾)</span>
  182. <span class="pill warn">待安排</span>
  183. </div>
  184. </header>
  185. <div class="tabs" role="tablist">
  186. <button class="tab active" data-tab="a" type="button">
  187. 方案 A · 一产品一行 + 供/需分列<span class="rec">推荐</span>
  188. </button>
  189. <button class="tab" data-tab="b" type="button">方案 B · 一计划一行(格内多行)</button>
  190. <button class="tab" data-tab="c" type="button">对照 · 现状(供/需拆行 + 合并)</button>
  191. </div>
  192. <!-- 方案 A -->
  193. <section class="panel active" id="panel-a">
  194. <p class="desc">
  195. <strong>方案 A(推荐):</strong>按计划产品对齐成行(本单 3 行)。
  196. 「合同」拆成 <strong>供合同 / 需合同</strong> 两列;计划量若两侧一致可只保留一列,也可再拆供/需计划量。
  197. <strong>无 rowspan / colspan</strong>,计划级字段在每行重复展示(或仅首行填、其余留空——本 demo 采用重复,便于导出/筛选)。
  198. </p>
  199. <p class="ok">
  200. 能解决:去掉合并单元格后,前端不再依赖 <code>span-method</code>,导出 Excel 也是标准扁平行;
  201. 筛选「需合同」「产品」时行语义清晰。代价是列变宽、计划级信息有重复。
  202. </p>
  203. <div class="table-wrap">
  204. <table class="list">
  205. <thead>
  206. <tr>
  207. <th>计划编号</th>
  208. <th class="center">跟进状态</th>
  209. <th>车牌号</th>
  210. <th>人员</th>
  211. <th>跟进人</th>
  212. <th>装货客户</th>
  213. <th>送货客户</th>
  214. <th>装货地址/时间</th>
  215. <th>送货地址/时间</th>
  216. <th class="th-group-load">供合同</th>
  217. <th class="th-group-need">需合同</th>
  218. <th>产品名称</th>
  219. <th class="center">型号/规格</th>
  220. <th class="center">包装规格</th>
  221. <th class="center">单位</th>
  222. <th class="num">计划量</th>
  223. <th class="num">实际量</th>
  224. </tr>
  225. </thead>
  226. <tbody>
  227. <tr>
  228. <td><a href="#">PO20261009022</a></td>
  229. <td class="center"><span class="tag warn">待安排</span></td>
  230. <td>皖H21522</td>
  231. <td>黄天宝(押)、沈卫斌(驾)</td>
  232. <td>程欢欢</td>
  233. <td>安庆市山峡化工有限公司</td>
  234. <td>桐城运城制版有限公司</td>
  235. <td>
  236. <div class="stack">
  237. <span class="muted">—</span>
  238. <span class="sub">2026-10-12</span>
  239. </div>
  240. </td>
  241. <td>
  242. <div class="stack">
  243. <span class="muted">—</span>
  244. <span class="sub">2026-10-12</span>
  245. </div>
  246. </td>
  247. <td class="cell-load"><span class="muted">—</span></td>
  248. <td class="cell-need"><a href="#">SX/X-JT20261008006</a></td>
  249. <td>
  250. <span class="name-with-mark">
  251. 98% 工业硫酸
  252. <span class="tag warn">毒</span>
  253. </span>
  254. </td>
  255. <td class="center">98%</td>
  256. <td class="center">25kg/桶</td>
  257. <td class="center">吨</td>
  258. <td class="num">1</td>
  259. <td class="num">0</td>
  260. </tr>
  261. <tr>
  262. <td><a href="#">PO20261009022</a></td>
  263. <td class="center"><span class="tag warn">待安排</span></td>
  264. <td>皖H21522</td>
  265. <td>黄天宝(押)、沈卫斌(驾)</td>
  266. <td>程欢欢</td>
  267. <td>安庆市山峡化工有限公司</td>
  268. <td>桐城运城制版有限公司</td>
  269. <td>
  270. <div class="stack">
  271. <span class="muted">—</span>
  272. <span class="sub">2026-10-12</span>
  273. </div>
  274. </td>
  275. <td>
  276. <div class="stack">
  277. <span class="muted">—</span>
  278. <span class="sub">2026-10-12</span>
  279. </div>
  280. </td>
  281. <td class="cell-load"><span class="muted">—</span></td>
  282. <td class="cell-need"><a href="#">SX/X-JT20261008006</a></td>
  283. <td>聚合氯化铝</td>
  284. <td class="center"><span class="muted">—</span></td>
  285. <td class="center">25Kg/袋</td>
  286. <td class="center">吨</td>
  287. <td class="num">1</td>
  288. <td class="num">0</td>
  289. </tr>
  290. <tr>
  291. <td><a href="#">PO20261009022</a></td>
  292. <td class="center"><span class="tag warn">待安排</span></td>
  293. <td>皖H21522</td>
  294. <td>黄天宝(押)、沈卫斌(驾)</td>
  295. <td>程欢欢</td>
  296. <td>安庆市山峡化工有限公司</td>
  297. <td>桐城运城制版有限公司</td>
  298. <td>
  299. <div class="stack">
  300. <span class="muted">—</span>
  301. <span class="sub">2026-10-12</span>
  302. </div>
  303. </td>
  304. <td>
  305. <div class="stack">
  306. <span class="muted">—</span>
  307. <span class="sub">2026-10-12</span>
  308. </div>
  309. </td>
  310. <td class="cell-load"><span class="muted">—</span></td>
  311. <td class="cell-need"><a href="#">SX/X-JT20261008006</a></td>
  312. <td>32%液碱</td>
  313. <td class="center">32%</td>
  314. <td class="center">25kg/桶</td>
  315. <td class="center">吨</td>
  316. <td class="num">0.25</td>
  317. <td class="num">0</td>
  318. </tr>
  319. </tbody>
  320. </table>
  321. </div>
  322. <p class="meta">
  323. 行粒度:计划产品(装/送两侧按 <code>plan_product_id</code> 对齐)。本单 load×3 + delivery×3 收敛为 <strong>3 行</strong>,不再拆成 6 行供/需。
  324. </p>
  325. </section>
  326. <!-- 方案 B -->
  327. <section class="panel" id="panel-b">
  328. <p class="desc">
  329. <strong>方案 B:</strong>整份计划只占 <strong>1 行</strong>。供/需合同、产品明细在单元格内用多行文本堆叠,
  330. 同样 <strong>无合并单元格</strong>。适合「一眼看完整单」;不适合按产品行勾选/导出明细。
  331. </p>
  332. <p class="ok">实现成本最低(去掉 flatten + span-method 即可),但表格行高随产品数膨胀,筛选产品时仍是计划粒度。</p>
  333. <div class="table-wrap">
  334. <table class="list" style="min-width: 1320px;">
  335. <thead>
  336. <tr>
  337. <th>计划编号</th>
  338. <th class="center">跟进状态</th>
  339. <th>车牌号</th>
  340. <th>人员</th>
  341. <th>跟进人</th>
  342. <th>装货客户</th>
  343. <th>送货客户</th>
  344. <th>装货地址/时间</th>
  345. <th>送货地址/时间</th>
  346. <th class="th-group-load">供合同</th>
  347. <th class="th-group-need">需合同</th>
  348. <th>产品明细</th>
  349. </tr>
  350. </thead>
  351. <tbody>
  352. <tr>
  353. <td><a href="#">PO20261009022</a></td>
  354. <td class="center"><span class="tag warn">待安排</span></td>
  355. <td>皖H21522</td>
  356. <td>黄天宝(押)、沈卫斌(驾)</td>
  357. <td>程欢欢</td>
  358. <td>安庆市山峡化工有限公司</td>
  359. <td>桐城运城制版有限公司</td>
  360. <td>
  361. <div class="stack">
  362. <span class="muted">—</span>
  363. <span class="sub">2026-10-12</span>
  364. </div>
  365. </td>
  366. <td>
  367. <div class="stack">
  368. <span class="muted">—</span>
  369. <span class="sub">2026-10-12</span>
  370. </div>
  371. </td>
  372. <td class="cell-load"><span class="muted">—</span></td>
  373. <td class="cell-need"><a href="#">SX/X-JT20261008006</a></td>
  374. <td>
  375. <div class="stack">
  376. <span class="name-with-mark">98% 工业硫酸 <span class="tag warn">毒</span> · 98% · 25kg/桶 · 1 吨</span>
  377. <span>聚合氯化铝 · 25Kg/袋 · 1 吨</span>
  378. <span>32%液碱 · 32% · 25kg/桶 · 0.25 吨</span>
  379. </div>
  380. </td>
  381. </tr>
  382. </tbody>
  383. </table>
  384. </div>
  385. </section>
  386. <!-- 对照现状 -->
  387. <section class="panel" id="panel-c">
  388. <p class="pain">
  389. <strong>现状对照:</strong>一份计划拆成供行 + 需行(本单 6 行),「合同 / 产品…」共用列头;
  390. 计划编号、装货客户等靠 <code>rowspan</code> 合并。视觉上「从合同起供/需多行」,也是导出与勾选复杂的根源。
  391. </p>
  392. <div class="table-wrap">
  393. <table class="list" style="min-width: 1200px;">
  394. <thead>
  395. <tr>
  396. <th>计划编号</th>
  397. <th>装货客户</th>
  398. <th>送货客户</th>
  399. <th>合同</th>
  400. <th>产品名称</th>
  401. <th class="center">型号/规格</th>
  402. <th class="center">包装规格</th>
  403. <th class="center">单位</th>
  404. <th class="num">计划量</th>
  405. </tr>
  406. </thead>
  407. <tbody>
  408. <tr>
  409. <td rowspan="6"><a href="#">PO20261009022</a></td>
  410. <td rowspan="6">安庆市山峡化工有限公司</td>
  411. <td rowspan="6">桐城运城制版有限公司</td>
  412. <td rowspan="3" class="cell-load"><span class="tag side-load">供</span> <span class="muted">—</span></td>
  413. <td class="cell-load"><span class="name-with-mark">98% 工业硫酸 <span class="tag warn">毒</span></span></td>
  414. <td class="center cell-load">98%</td>
  415. <td class="center cell-load">25kg/桶</td>
  416. <td class="center cell-load">吨</td>
  417. <td class="num cell-load">1</td>
  418. </tr>
  419. <tr>
  420. <td class="cell-load">聚合氯化铝</td>
  421. <td class="center cell-load"><span class="muted">—</span></td>
  422. <td class="center cell-load">25Kg/袋</td>
  423. <td class="center cell-load">吨</td>
  424. <td class="num cell-load">1</td>
  425. </tr>
  426. <tr>
  427. <td class="cell-load">32%液碱</td>
  428. <td class="center cell-load">32%</td>
  429. <td class="center cell-load">25kg/桶</td>
  430. <td class="center cell-load">吨</td>
  431. <td class="num cell-load">0.25</td>
  432. </tr>
  433. <tr>
  434. <td rowspan="3" class="cell-need"><span class="tag side-need">需</span> <a href="#">SX/X-JT20261008006</a></td>
  435. <td class="cell-need"><span class="name-with-mark">98% 工业硫酸 <span class="tag warn">毒</span></span></td>
  436. <td class="center cell-need">98%</td>
  437. <td class="center cell-need">25kg/桶</td>
  438. <td class="center cell-need">吨</td>
  439. <td class="num cell-need">1</td>
  440. </tr>
  441. <tr>
  442. <td class="cell-need">聚合氯化铝</td>
  443. <td class="center cell-need"><span class="muted">—</span></td>
  444. <td class="center cell-need">25Kg/袋</td>
  445. <td class="center cell-need">吨</td>
  446. <td class="num cell-need">1</td>
  447. </tr>
  448. <tr>
  449. <td class="cell-need">32%液碱</td>
  450. <td class="center cell-need">32%</td>
  451. <td class="center cell-need">25kg/桶</td>
  452. <td class="center cell-need">吨</td>
  453. <td class="num cell-need">0.25</td>
  454. </tr>
  455. </tbody>
  456. </table>
  457. </div>
  458. <p class="meta">本对照刻意保留 rowspan,仅用于对比;方案 A/B 均不使用合并单元格。</p>
  459. </section>
  460. </div>
  461. <script>
  462. const tabs = document.querySelectorAll('.tab')
  463. const panels = {
  464. a: document.getElementById('panel-a'),
  465. b: document.getElementById('panel-b'),
  466. c: document.getElementById('panel-c')
  467. }
  468. tabs.forEach((tab) => {
  469. tab.addEventListener('click', () => {
  470. const key = tab.getAttribute('data-tab')
  471. tabs.forEach((t) => t.classList.toggle('active', t === tab))
  472. Object.entries(panels).forEach(([k, el]) => el.classList.toggle('active', k === key))
  473. })
  474. })
  475. </script>
  476. </body>
  477. </html>