pl-plan-pair-stack-structure.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  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>运输计划 · 装/送结构重设计</title>
  7. <style>
  8. :root {
  9. --load: #059669;
  10. --delivery: #0284c7;
  11. --text: #1e293b;
  12. --muted: #64748b;
  13. --border: #e2e8f0;
  14. --page: #eef2f6;
  15. --panel: #fff;
  16. --primary: #10b981;
  17. }
  18. * { box-sizing: border-box; }
  19. body {
  20. margin: 0;
  21. padding: 28px 20px 80px;
  22. font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
  23. font-size: 14px;
  24. color: var(--text);
  25. background: var(--page);
  26. }
  27. h1 { margin: 0 0 6px; font-size: 20px; font-weight: 700; }
  28. .lead { margin: 0 0 10px; max-width: 860px; color: var(--muted); font-size: 13.5px; line-height: 1.55; }
  29. .shift {
  30. margin: 0 0 22px; padding: 12px 14px; max-width: 860px;
  31. border-radius: 8px; background: #ecfdf5; border: 1px solid #a7f3d0;
  32. color: #065f46; font-size: 13px; line-height: 1.5;
  33. }
  34. .scheme {
  35. margin-bottom: 22px;
  36. background: var(--panel);
  37. border: 1px solid var(--border);
  38. border-radius: 12px;
  39. overflow: hidden;
  40. }
  41. .scheme.is-rec { border-color: #6ee7b7; box-shadow: 0 0 0 1px #a7f3d0; }
  42. .scheme__head {
  43. display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  44. padding: 14px 16px; border-bottom: 1px solid var(--border);
  45. background: #fafbfc;
  46. }
  47. .scheme__title { margin: 0; font-size: 15px; font-weight: 700; }
  48. .scheme__why { margin: 4px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.45; }
  49. .badge {
  50. flex-shrink: 0; padding: 2px 9px; border-radius: 999px;
  51. font-size: 11px; font-weight: 600;
  52. }
  53. .badge--rec { color: #065f46; background: #ecfdf5; border: 1px solid #a7f3d0; }
  54. .badge--alt { color: #475569; background: #f8fafc; border: 1px solid #e2e8f0; }
  55. .scheme__body { padding: 16px; }
  56. .scheme__note { margin: 12px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
  57. table.t {
  58. width: 100%;
  59. border-collapse: collapse;
  60. font-size: 13px;
  61. border: 1px solid var(--border);
  62. border-radius: 8px;
  63. overflow: hidden;
  64. }
  65. table.t th, table.t td {
  66. padding: 8px 10px;
  67. border-bottom: 1px solid var(--border);
  68. border-right: 1px solid var(--border);
  69. vertical-align: middle;
  70. }
  71. table.t th:last-child, table.t td:last-child { border-right: none; }
  72. table.t tr:last-child td { border-bottom: none; }
  73. table.t thead th {
  74. background: #f8fafc;
  75. color: var(--muted);
  76. font-size: 12px;
  77. font-weight: 600;
  78. text-align: center;
  79. white-space: nowrap;
  80. }
  81. table.t .left { text-align: left; }
  82. table.t .num { text-align: center; font-variant-numeric: tabular-nums; font-weight: 600; }
  83. table.t .muted { color: var(--muted); font-weight: 500; }
  84. .addr { font-weight: 600; line-height: 1.4; }
  85. .time { margin-top: 2px; font-size: 12px; color: var(--muted); font-weight: 400; }
  86. /* S1 二级表头 */
  87. .s1 thead .group { background: #f1f5f9; color: var(--text); font-weight: 700; }
  88. .s1 thead .sub-load { color: var(--load); }
  89. .s1 thead .sub-delivery { color: var(--delivery); }
  90. .s1 tbody td { background: #fff; }
  91. /* S2 两行 */
  92. .s2 .kind {
  93. width: 36px; text-align: center; font-size: 11px; font-weight: 800;
  94. }
  95. .s2 .kind--load { color: var(--load); }
  96. .s2 .kind--delivery { color: var(--delivery); }
  97. .s2 tr.load-row td { background: #fff; }
  98. .s2 tr.delivery-row td { background: #fafbfc; }
  99. .s2 tr.pair-end td { border-bottom: 2px solid #94a3b8; }
  100. /* S3 视图切换 */
  101. .tabs {
  102. display: inline-flex; gap: 0; margin-bottom: 10px;
  103. border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
  104. }
  105. .tabs button {
  106. appearance: none; border: none; background: #fff;
  107. padding: 7px 14px; font-size: 13px; color: var(--muted); cursor: pointer;
  108. font-family: inherit;
  109. }
  110. .tabs button + button { border-left: 1px solid var(--border); }
  111. .tabs button.active {
  112. background: var(--primary); color: #fff; font-weight: 600;
  113. }
  114. .view-panel { display: none; }
  115. .view-panel.active { display: block; }
  116. /* S4 左侧类型轨(整行共享) */
  117. .s4 .rail-cell {
  118. width: 28px; padding: 0 !important; vertical-align: stretch !important;
  119. }
  120. .s4 .rail {
  121. display: flex; flex-direction: column; height: 100%; min-height: 64px;
  122. }
  123. .s4 .rail > span {
  124. flex: 1; display: flex; align-items: center; justify-content: center;
  125. writing-mode: vertical-rl; text-orientation: mixed;
  126. font-size: 11px; font-weight: 800; letter-spacing: 0.12em;
  127. color: #fff;
  128. }
  129. .s4 .rail > span.load { background: var(--load); }
  130. .s4 .rail > span.delivery { background: var(--delivery); }
  131. .s4 .stack { display: flex; flex-direction: column; gap: 0; }
  132. .s4 .stack > div { padding: 5px 0; }
  133. .s4 .stack > div + div {
  134. border-top: 1px dashed var(--border); margin-top: 5px; padding-top: 5px;
  135. }
  136. /* S5 卡片对照 */
  137. .cards { display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: stretch; }
  138. .stop-card {
  139. border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
  140. background: #fff;
  141. }
  142. .stop-card--load { border-top: 3px solid var(--load); }
  143. .stop-card--delivery { border-top: 3px solid var(--delivery); }
  144. .stop-card__role {
  145. font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
  146. margin-bottom: 8px;
  147. }
  148. .stop-card--load .stop-card__role { color: var(--load); }
  149. .stop-card--delivery .stop-card__role { color: var(--delivery); }
  150. .stop-card__addr { font-weight: 600; margin-bottom: 4px; }
  151. .stop-card__meta { font-size: 12px; color: var(--muted); }
  152. .stop-card__kvs {
  153. display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px;
  154. margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
  155. font-size: 12px;
  156. }
  157. .stop-card__kvs span { color: var(--muted); }
  158. .stop-card__kvs b { display: block; margin-top: 2px; font-size: 14px; color: var(--text); font-variant-numeric: tabular-nums; }
  159. .cards__arrow {
  160. display: flex; align-items: center; justify-content: center;
  161. color: #94a3b8; font-size: 18px;
  162. }
  163. .plan-meta {
  164. margin-bottom: 10px; font-size: 12px; color: var(--muted);
  165. }
  166. .plan-meta strong { color: var(--text); }
  167. .footer-pick {
  168. margin-top: 8px; padding: 16px 18px; max-width: 860px;
  169. background: #fff; border: 1px solid var(--border); border-radius: 10px;
  170. }
  171. .footer-pick h2 { margin: 0 0 8px; font-size: 14px; }
  172. .footer-pick ol { margin: 0; padding-left: 18px; color: var(--muted); font-size: 13px; }
  173. .footer-pick li { margin: 6px 0; }
  174. .footer-pick strong { color: var(--text); }
  175. </style>
  176. </head>
  177. <body>
  178. <h1>装 / 送 · 结构重设计(不再靠格子里贴标记)</h1>
  179. <p class="lead">
  180. 之前方案的问题:无论汉字、色条还是字色,都是<strong>在每个单元格里重复表达一次</strong>,列一多就吵。
  181. 这轮改从<strong>表格结构</strong>下手——让「装/送」只出现在表头、首列或视图切换里,正文格子尽量只放数据。
  182. </p>
  183. <p class="shift">
  184. 设计原则:标记出现次数 ≈ 1(表头 / 首列 / 工具栏),而不是 ≈ 列数 × 行数。
  185. </p>
  186. <!-- S1 -->
  187. <section class="scheme is-rec">
  188. <div class="scheme__head">
  189. <div>
  190. <h2 class="scheme__title">S1 · 二级表头拆列</h2>
  191. <p class="scheme__why">「计划数量」拆成子列「装 / 送」,单元格里只有数字,汉字只出现在表头各一次。</p>
  192. </div>
  193. <span class="badge badge--rec">列表推荐</span>
  194. </div>
  195. <div class="scheme__body">
  196. <table class="t s1">
  197. <thead>
  198. <tr>
  199. <th class="left" rowspan="2">装货地址</th>
  200. <th class="left" rowspan="2">送货地址</th>
  201. <th class="group" colspan="2">计划数量</th>
  202. <th class="group" colspan="2">单价</th>
  203. <th class="group" colspan="2">金额</th>
  204. </tr>
  205. <tr>
  206. <th class="sub-load">装</th>
  207. <th class="sub-delivery">送</th>
  208. <th class="sub-load">装</th>
  209. <th class="sub-delivery">送</th>
  210. <th class="sub-load">装</th>
  211. <th class="sub-delivery">送</th>
  212. </tr>
  213. </thead>
  214. <tbody>
  215. <tr>
  216. <td class="left">
  217. <div class="addr">宜昌西陵化工园 A12</div>
  218. <div class="time">09-16 08:30</div>
  219. </td>
  220. <td class="left">
  221. <div class="addr">武汉东西湖物流大道 88</div>
  222. <div class="time">09-16 16:00</div>
  223. </td>
  224. <td class="num">0.01</td>
  225. <td class="num">0.01</td>
  226. <td class="num">10</td>
  227. <td class="num muted">—</td>
  228. <td class="num">33</td>
  229. <td class="num muted">—</td>
  230. </tr>
  231. <tr>
  232. <td class="left">
  233. <div class="addr">荆州某仓库</div>
  234. <div class="time">09-17 09:00</div>
  235. </td>
  236. <td class="left">
  237. <div class="addr">长沙物流园 B3</div>
  238. <div class="time">09-17 18:00</div>
  239. </td>
  240. <td class="num">12</td>
  241. <td class="num">12</td>
  242. <td class="num">8.5</td>
  243. <td class="num">9.0</td>
  244. <td class="num">102</td>
  245. <td class="num">108</td>
  246. </tr>
  247. </tbody>
  248. </table>
  249. <p class="scheme__note">
  250. 正文零「装/送」重复。代价:列变多(可用列设置隐藏次要子列)。地址也拆成两列,上下对齐更清晰。
  251. Element Plus 原生支持多级表头,和现有列设置能力兼容。
  252. </p>
  253. </div>
  254. </section>
  255. <!-- S2 -->
  256. <section class="scheme is-rec">
  257. <div class="scheme__head">
  258. <div>
  259. <h2 class="scheme__title">S2 · 拆成两行(装一行、送一行)</h2>
  260. <p class="scheme__why">一条配对变成上下两行;「装/送」只在最左类型列出现一次,其余列全是普通单元格。</p>
  261. </div>
  262. <span class="badge badge--rec">列表推荐</span>
  263. </div>
  264. <div class="scheme__body">
  265. <table class="t s2">
  266. <thead>
  267. <tr>
  268. <th></th>
  269. <th class="left">地址 / 时间</th>
  270. <th>计划数量</th>
  271. <th>实际数量</th>
  272. <th>单价</th>
  273. <th>金额</th>
  274. </tr>
  275. </thead>
  276. <tbody>
  277. <tr class="load-row">
  278. <td class="kind kind--load">装</td>
  279. <td class="left">
  280. <div class="addr">宜昌西陵化工园 A12</div>
  281. <div class="time">09-16 08:30</div>
  282. </td>
  283. <td class="num">0.01</td>
  284. <td class="num">0.01</td>
  285. <td class="num">10</td>
  286. <td class="num">33</td>
  287. </tr>
  288. <tr class="delivery-row pair-end">
  289. <td class="kind kind--delivery">送</td>
  290. <td class="left">
  291. <div class="addr">武汉东西湖物流大道 88</div>
  292. <div class="time">09-16 16:00</div>
  293. </td>
  294. <td class="num">0.01</td>
  295. <td class="num">0.01</td>
  296. <td class="num muted">—</td>
  297. <td class="num muted">—</td>
  298. </tr>
  299. <tr class="load-row">
  300. <td class="kind kind--load">装</td>
  301. <td class="left">
  302. <div class="addr">荆州某仓库</div>
  303. <div class="time">09-17 09:00</div>
  304. </td>
  305. <td class="num">12</td>
  306. <td class="num">12</td>
  307. <td class="num">8.5</td>
  308. <td class="num">102</td>
  309. </tr>
  310. <tr class="delivery-row pair-end">
  311. <td class="kind kind--delivery">送</td>
  312. <td class="left">
  313. <div class="addr">长沙物流园 B3</div>
  314. <div class="time">09-17 18:00</div>
  315. </td>
  316. <td class="num">12</td>
  317. <td class="num">11.8</td>
  318. <td class="num">9.0</td>
  319. <td class="num">106.2</td>
  320. </tr>
  321. </tbody>
  322. </table>
  323. <p class="scheme__note">
  324. 这是物流/仓储系统里最常见的读法:横扫一行全是「装」的数据,再扫下一行全是「送」。
  325. 合并单元格(车牌、司机等计划级字段)可跨两行合并。改动比换皮肤大,但噪音下降最彻底。
  326. </p>
  327. </div>
  328. </section>
  329. <!-- S3 -->
  330. <section class="scheme">
  331. <div class="scheme__head">
  332. <div>
  333. <h2 class="scheme__title">S3 · 视图切换(一次只看一层)</h2>
  334. <p class="scheme__why">默认看「装货」或「送货」单层列表;需要对照时再进对照模式。</p>
  335. </div>
  336. <span class="badge badge--alt">备选</span>
  337. </div>
  338. <div class="scheme__body">
  339. <div class="tabs" id="s3-tabs">
  340. <button type="button" class="active" data-view="load">装货</button>
  341. <button type="button" data-view="delivery">送货</button>
  342. <button type="button" data-view="both">对照</button>
  343. </div>
  344. <div class="view-panel active" data-view="load">
  345. <table class="t">
  346. <thead>
  347. <tr>
  348. <th class="left">装货地址</th>
  349. <th>计划数量</th>
  350. <th>单价</th>
  351. <th>金额</th>
  352. </tr>
  353. </thead>
  354. <tbody>
  355. <tr>
  356. <td class="left">
  357. <div class="addr">宜昌西陵化工园 A12</div>
  358. <div class="time">09-16 08:30</div>
  359. </td>
  360. <td class="num">0.01</td>
  361. <td class="num">10</td>
  362. <td class="num">33</td>
  363. </tr>
  364. </tbody>
  365. </table>
  366. </div>
  367. <div class="view-panel" data-view="delivery">
  368. <table class="t">
  369. <thead>
  370. <tr>
  371. <th class="left">送货地址</th>
  372. <th>计划数量</th>
  373. <th>单价</th>
  374. <th>金额</th>
  375. </tr>
  376. </thead>
  377. <tbody>
  378. <tr>
  379. <td class="left">
  380. <div class="addr">武汉东西湖物流大道 88</div>
  381. <div class="time">09-16 16:00</div>
  382. </td>
  383. <td class="num">0.01</td>
  384. <td class="num muted">—</td>
  385. <td class="num muted">—</td>
  386. </tr>
  387. </tbody>
  388. </table>
  389. </div>
  390. <div class="view-panel" data-view="both">
  391. <p style="margin:0 0 8px;font-size:12px;color:var(--muted)">对照模式可用 S1 或 S2,此处示意用 S1 子列。</p>
  392. <table class="t s1">
  393. <thead>
  394. <tr>
  395. <th class="left" rowspan="2">装货地址</th>
  396. <th class="left" rowspan="2">送货地址</th>
  397. <th class="group" colspan="2">计划数量</th>
  398. </tr>
  399. <tr>
  400. <th class="sub-load">装</th>
  401. <th class="sub-delivery">送</th>
  402. </tr>
  403. </thead>
  404. <tbody>
  405. <tr>
  406. <td class="left"><div class="addr">宜昌西陵化工园 A12</div></td>
  407. <td class="left"><div class="addr">武汉东西湖物流大道 88</div></td>
  408. <td class="num">0.01</td>
  409. <td class="num">0.01</td>
  410. </tr>
  411. </tbody>
  412. </table>
  413. </div>
  414. <p class="scheme__note">日常调度若多数时间只关心一侧(如只盯装货),噪音为零。需要装送对齐核对时切到对照。</p>
  415. </div>
  416. </section>
  417. <!-- S4 -->
  418. <section class="scheme">
  419. <div class="scheme__head">
  420. <div>
  421. <h2 class="scheme__title">S4 · 整行共享左侧类型轨</h2>
  422. <p class="scheme__why">保留「一格里上下叠」的数据结构,但类型只在表格最左侧出现一次(半格装、半格送)。</p>
  423. </div>
  424. <span class="badge badge--alt">小改动</span>
  425. </div>
  426. <div class="scheme__body">
  427. <table class="t s4">
  428. <thead>
  429. <tr>
  430. <th style="width:28px"></th>
  431. <th class="left">地址</th>
  432. <th>计划数量</th>
  433. <th>单价</th>
  434. <th>金额</th>
  435. </tr>
  436. </thead>
  437. <tbody>
  438. <tr>
  439. <td class="rail-cell">
  440. <div class="rail">
  441. <span class="load">装</span>
  442. <span class="delivery">送</span>
  443. </div>
  444. </td>
  445. <td class="left">
  446. <div class="stack">
  447. <div>
  448. <div class="addr">宜昌西陵化工园 A12</div>
  449. <div class="time">09-16 08:30</div>
  450. </div>
  451. <div>
  452. <div class="addr">武汉东西湖物流大道 88</div>
  453. <div class="time">09-16 16:00</div>
  454. </div>
  455. </div>
  456. </td>
  457. <td>
  458. <div class="stack">
  459. <div class="num">0.01</div>
  460. <div class="num">0.01</div>
  461. </div>
  462. </td>
  463. <td>
  464. <div class="stack">
  465. <div class="num">10</div>
  466. <div class="num muted">—</div>
  467. </div>
  468. </td>
  469. <td>
  470. <div class="stack">
  471. <div class="num">33</div>
  472. <div class="num muted">—</div>
  473. </div>
  474. </td>
  475. </tr>
  476. </tbody>
  477. </table>
  478. <p class="scheme__note">
  479. 对现有「配对叠放」改动最小:删掉每个 cell 里的 label,最左侧加一列类型轨。
  480. 数量列变干净,但行内仍要上下对齐扫读。
  481. </p>
  482. </div>
  483. </section>
  484. <!-- S5 -->
  485. <section class="scheme">
  486. <div class="scheme__head">
  487. <div>
  488. <h2 class="scheme__title">S5 · 左装右送卡片对照</h2>
  489. <p class="scheme__why">不做密表叠字;每条计划一张对照卡,装/送各写一次角色名。</p>
  490. </div>
  491. <span class="badge badge--alt">备选</span>
  492. </div>
  493. <div class="scheme__body">
  494. <div class="plan-meta">计划号 <strong>JH20260916001</strong> · 车牌 鄂A·12X34 · 司机 张三</div>
  495. <div class="cards">
  496. <div class="stop-card stop-card--load">
  497. <div class="stop-card__role">装货</div>
  498. <div class="stop-card__addr">宜昌西陵化工园 A12</div>
  499. <div class="stop-card__meta">09-16 08:30</div>
  500. <div class="stop-card__kvs">
  501. <div><span>计划数量</span><b>0.01</b></div>
  502. <div><span>单价</span><b>10</b></div>
  503. <div><span>入库</span><b>0.01</b></div>
  504. <div><span>金额</span><b>33</b></div>
  505. </div>
  506. </div>
  507. <div class="cards__arrow">→</div>
  508. <div class="stop-card stop-card--delivery">
  509. <div class="stop-card__role">送货</div>
  510. <div class="stop-card__addr">武汉东西湖物流大道 88</div>
  511. <div class="stop-card__meta">09-16 16:00</div>
  512. <div class="stop-card__kvs">
  513. <div><span>计划数量</span><b>0.01</b></div>
  514. <div><span>单价</span><b>—</b></div>
  515. <div><span>出库</span><b>0.01</b></div>
  516. <div><span>金额</span><b>—</b></div>
  517. </div>
  518. </div>
  519. </div>
  520. <p class="scheme__note">适合详情/移动端/低密度审核。桌面高密度调度列表通常仍应以 S1/S2 为主。</p>
  521. </div>
  522. </section>
  523. <div class="footer-pick">
  524. <h2>建议</h2>
  525. <ol>
  526. <li><strong>S1 二级表头</strong>:继续「一行一条配对」,但数量类字段左右对照,最适合你截图里的数量/单价/金额列。</li>
  527. <li><strong>S2 拆两行</strong>:横扫读法最直觉,计划级字段用行合并;适合愿意改列表数据模型的情况。</li>
  528. <li><strong>S4</strong>:想最小改动、先去掉满屏「装/送」时的过渡方案。</li>
  529. </ol>
  530. <p style="margin:12px 0 0;font-size:13px;color:#64748b">回复 S1 / S2 / S4(或组合,如「数量列用 S1,地址先保持叠放」),再落到正式页面。</p>
  531. </div>
  532. <script>
  533. const tabs = document.getElementById('s3-tabs')
  534. if (tabs) {
  535. tabs.addEventListener('click', (e) => {
  536. const btn = e.target.closest('button[data-view]')
  537. if (!btn) return
  538. const view = btn.getAttribute('data-view')
  539. tabs.querySelectorAll('button').forEach((b) => b.classList.toggle('active', b === btn))
  540. document.querySelectorAll('.view-panel').forEach((p) => {
  541. p.classList.toggle('active', p.getAttribute('data-view') === view)
  542. })
  543. })
  544. }
  545. </script>
  546. </body>
  547. </html>