| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>运输计划列表 · 布局方案对比 Demo</title>
- <link rel="preconnect" href="https://fonts.googleapis.com" />
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
- <style>
- :root {
- --primary: #10b981;
- --primary-dark: #059669;
- --primary-bg: #ecfdf5;
- --text: #1e293b;
- --text-muted: #64748b;
- --border: #d9e8e2;
- --surface: #ffffff;
- --table-head: #f8fafc;
- --row-alt: #fafafa;
- --row-hover: #f1f5f9;
- --bg-page: #f1f5f9;
- --shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
- --radius: 10px;
- --warn: #d97706;
- --warn-bg: #fffbeb;
- --danger: #dc2626;
- --danger-bg: #fef2f2;
- --info: #0284c7;
- --load-accent: #059669;
- --load-bg: #ecfdf5;
- --delivery-accent: #0284c7;
- --delivery-bg: #f0f9ff;
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- min-height: 100vh;
- font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
- font-size: 14px;
- line-height: 1.5;
- color: var(--text);
- background: var(--bg-page);
- }
- .page { max-width: 1440px; margin: 0 auto; padding: 24px 20px 80px; }
- .hero {
- background: linear-gradient(135deg, #ecfdf5 0%, #fff 60%, #f0f9ff 100%);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 22px 24px;
- margin-bottom: 16px;
- box-shadow: var(--shadow);
- }
- .hero h1 { margin: 0 0 8px; font-size: 22px; font-weight: 700; }
- .hero h1 em { font-style: normal; color: var(--primary-dark); }
- .hero p { margin: 0; color: var(--text-muted); max-width: 960px; }
- .compare-bar {
- display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
- margin-top: 14px;
- }
- .compare-btn {
- border: 1px solid var(--border);
- background: #fff;
- color: var(--text-muted);
- border-radius: 999px;
- padding: 8px 16px;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- font-family: inherit;
- transition: all .15s ease;
- }
- .compare-btn:hover { border-color: #a7f3d0; color: var(--primary-dark); }
- .compare-btn.active {
- background: var(--primary);
- border-color: var(--primary);
- color: #fff;
- }
- .compare-btn .badge {
- display: inline-block;
- margin-left: 6px;
- padding: 1px 7px;
- border-radius: 999px;
- font-size: 11px;
- font-weight: 700;
- background: rgba(255,255,255,.22);
- }
- .compare-btn:not(.active) .badge {
- background: var(--primary-bg);
- color: var(--primary-dark);
- }
- .verdict {
- margin-left: auto;
- font-size: 12px;
- color: var(--text-muted);
- }
- .verdict strong { color: var(--primary-dark); }
- .analysis {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- gap: 12px;
- margin-bottom: 16px;
- }
- @media (max-width: 1100px) { .analysis { grid-template-columns: 1fr; } }
- .analysis-card {
- background: #fff;
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 14px 16px;
- box-shadow: var(--shadow);
- }
- .analysis-card h3 {
- margin: 0 0 8px;
- font-size: 13px;
- font-weight: 700;
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .analysis-card ul {
- margin: 0;
- padding-left: 18px;
- color: var(--text-muted);
- font-size: 12px;
- }
- .analysis-card li { margin-bottom: 4px; }
- .analysis-card.recommended {
- border-color: #a7f3d0;
- background: linear-gradient(180deg, #fff 0%, #f8fffb 100%);
- }
- .tag-rec {
- font-size: 10px;
- font-weight: 700;
- letter-spacing: .04em;
- text-transform: uppercase;
- color: var(--primary-dark);
- background: var(--primary-bg);
- border: 1px solid #a7f3d0;
- border-radius: 4px;
- padding: 2px 6px;
- }
- .panel {
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- box-shadow: var(--shadow);
- overflow: hidden;
- }
- .panel-top {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 16px 20px;
- border-bottom: 1px solid var(--border);
- }
- .panel-top h2 {
- margin: 0;
- font-size: 16px;
- font-weight: 700;
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .panel-top h2::before {
- content: "";
- width: 3px;
- height: 16px;
- border-radius: 2px;
- background: var(--primary);
- }
- .type-tabs {
- display: flex;
- gap: 4px;
- padding: 10px 16px 0;
- border-bottom: 1px solid var(--border);
- background: #fff;
- }
- .type-tab {
- border: none;
- background: transparent;
- padding: 10px 14px;
- font-size: 13px;
- font-weight: 600;
- color: var(--text-muted);
- cursor: pointer;
- border-bottom: 2px solid transparent;
- font-family: inherit;
- }
- .type-tab.active { color: var(--primary-dark); border-bottom-color: var(--primary); }
- .toolbar {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
- padding: 12px 16px;
- border-bottom: 1px solid var(--border);
- background: var(--table-head);
- }
- .btn {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- height: 36px;
- padding: 0 14px;
- border-radius: 8px;
- font-size: 13px;
- font-weight: 600;
- border: 1px solid var(--border);
- background: #fff;
- color: var(--text);
- cursor: pointer;
- font-family: inherit;
- }
- .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
- .btn-ghost { background: #fff; }
- .search {
- height: 36px;
- border: 1px solid var(--border);
- border-radius: 8px;
- padding: 0 12px;
- min-width: 260px;
- font: inherit;
- }
- .sel-hint {
- font-size: 12px;
- color: var(--text-muted);
- margin-right: auto;
- }
- .sel-hint .num { color: var(--primary-dark); font-weight: 700; }
- .scheme { display: none; }
- .scheme.active { display: block; }
- table.list {
- width: 100%;
- border-collapse: collapse;
- font-size: 13px;
- }
- table.list th {
- background: var(--table-head);
- color: var(--text-muted);
- font-size: 12px;
- font-weight: 600;
- text-align: left;
- padding: 10px 12px;
- border-bottom: 1px solid var(--border);
- white-space: nowrap;
- }
- table.list td {
- padding: 10px 12px;
- border-bottom: 1px solid var(--border);
- vertical-align: top;
- }
- table.list tbody tr:hover td { background: var(--row-hover); }
- table.list tbody tr:nth-child(even) td { background: var(--row-alt); }
- table.list tbody tr:hover:nth-child(even) td { background: var(--row-hover); }
- .link { color: var(--primary-dark); font-weight: 600; cursor: pointer; text-decoration: none; }
- .tag {
- display: inline-flex;
- align-items: center;
- padding: 2px 8px;
- border-radius: 4px;
- font-size: 12px;
- font-weight: 600;
- white-space: nowrap;
- }
- .tag-success { background: var(--primary-dark); color: #fff; }
- .tag-danger { background: var(--danger); color: #fff; }
- .tag-warning { background: var(--warn); color: #fff; }
- .tag-info { background: transparent; color: var(--text); }
- .muted { color: var(--text-muted); font-size: 12px; }
- .stack > div + div { margin-top: 4px; }
- .product-line { line-height: 1.45; }
- .product-line .qty { color: var(--primary-dark); font-weight: 600; }
- /* 方案 B:主从分行 */
- .plan-group { border-bottom: 2px solid #e2e8f0; }
- .plan-group:last-child { border-bottom: none; }
- .plan-master td {
- background: #fff !important;
- padding-top: 14px;
- padding-bottom: 8px;
- border-bottom: none;
- }
- .plan-master .master-bar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 10px 16px;
- }
- .plan-master .master-no {
- font-size: 15px;
- font-weight: 700;
- color: var(--primary-dark);
- }
- .plan-master .master-meta {
- display: flex;
- flex-wrap: wrap;
- gap: 8px 14px;
- font-size: 12px;
- color: var(--text-muted);
- }
- .plan-master .master-meta b { color: var(--text); font-weight: 600; }
- .plan-master .master-actions {
- margin-left: auto;
- display: flex;
- gap: 6px;
- }
- .plan-master .master-actions .btn { height: 32px; padding: 0 10px; font-size: 12px; }
- .stop-row td {
- padding-top: 6px;
- padding-bottom: 10px;
- border-bottom: 1px dashed #e2e8f0;
- }
- .stop-row:last-child td { border-bottom: none; padding-bottom: 14px; }
- .stop-badge {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 3px 10px;
- border-radius: 999px;
- font-size: 11px;
- font-weight: 700;
- letter-spacing: .02em;
- }
- .stop-badge.load {
- background: var(--load-bg);
- color: var(--load-accent);
- border: 1px solid #a7f3d0;
- }
- .stop-badge.delivery {
- background: var(--delivery-bg);
- color: var(--delivery-accent);
- border: 1px solid #bae6fd;
- }
- .stop-indent { padding-left: 36px !important; }
- .stop-customer { font-weight: 600; color: var(--text); }
- .stop-addr { font-size: 12px; color: var(--text-muted); margin-top: 2px; max-width: 280px; }
- .internal-tag {
- font-size: 10px;
- font-weight: 600;
- color: var(--primary-dark);
- background: var(--primary-bg);
- border: 1px solid #a7f3d0;
- border-radius: 4px;
- padding: 1px 5px;
- margin-left: 6px;
- vertical-align: middle;
- }
- .merge-cell {
- border-left: 3px solid transparent;
- }
- .stop-row.load .merge-cell { border-left-color: var(--load-accent); }
- .stop-row.delivery .merge-cell { border-left-color: var(--delivery-accent); }
- /* 方案 C:树形展开 */
- .tree-toggle {
- width: 22px;
- height: 22px;
- border: 1px solid var(--border);
- border-radius: 6px;
- background: #fff;
- color: var(--text-muted);
- cursor: pointer;
- font-size: 14px;
- line-height: 1;
- vertical-align: middle;
- margin-right: 6px;
- }
- .tree-child { display: none; }
- .tree-child.open { display: table-row; }
- .tree-child td:first-child { padding-left: 42px; }
- .tree-child td { background: #fafcff !important; font-size: 12px; }
- .tree-label {
- display: inline-block;
- min-width: 52px;
- font-size: 11px;
- font-weight: 700;
- color: var(--text-muted);
- }
- .pager {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 12px 16px;
- border-top: 1px solid var(--border);
- font-size: 12px;
- color: var(--text-muted);
- }
- .note-box {
- margin: 16px 20px 20px;
- padding: 12px 14px;
- border-radius: 8px;
- background: var(--primary-bg);
- border: 1px solid #a7f3d0;
- font-size: 12px;
- color: #065f46;
- }
- .note-box.warn {
- background: var(--warn-bg);
- border-color: rgba(217,119,6,.25);
- color: #92400e;
- }
- .hidden-col { color: var(--text-muted); font-style: italic; }
- /* 方案 D:合并单元格(rowspan) */
- table.list-merge { table-layout: fixed; }
- table.list-merge th.col-plan,
- table.list-merge td.col-plan {
- background: #fff;
- border-right: 2px solid #cbd5e1;
- }
- table.list-merge th.col-stop,
- table.list-merge td.col-stop {
- background: #fafcff;
- }
- table.list-merge thead tr:last-child th.col-stop {
- background: #f0fdf9;
- color: var(--primary-dark);
- border-bottom: 2px solid #a7f3d0;
- }
- table.list-merge thead .th-group-plan {
- text-align: center;
- font-size: 11px;
- letter-spacing: .06em;
- color: var(--text-muted);
- border-bottom: none;
- padding-bottom: 4px;
- }
- table.list-merge thead .th-group-stop {
- text-align: center;
- font-size: 11px;
- letter-spacing: .06em;
- color: var(--primary-dark);
- border-bottom: none;
- padding-bottom: 4px;
- }
- table.list-merge td.cell-merge {
- vertical-align: middle;
- background: #fff !important;
- border-right: 2px solid #cbd5e1;
- }
- table.list-merge tbody tr.plan-block-last td.col-stop {
- border-bottom: 2px solid #cbd5e1;
- }
- table.list-merge tbody tr:hover td.col-stop {
- background: var(--row-hover) !important;
- }
- .merge-plan-no {
- display: block;
- font-size: 14px;
- font-weight: 700;
- margin-bottom: 6px;
- }
- .merge-plan-stack { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
- .merge-copy-stack { display: flex; flex-direction: column; gap: 4px; }
- .merge-copy-stack .btn { width: 100%; justify-content: center; font-size: 11px; height: 28px; padding: 0 6px; }
- .thead-sub th { font-size: 11px; padding-top: 0; }
- </style>
- </head>
- <body>
- <div class="page">
- <header class="hero">
- <h1>运输计划列表 · <em>布局方案对比</em></h1>
- <p>
- 左侧<strong>计划主信息合并单元格</strong>(勾选、编号、状态、车牌、复制等只出现一次),右侧按行展开<strong>装货 → 送货#1 → 送货#2 …</strong>,
- 段内客户、地址、产品、数量各占自己的列。既保留「一个主计划」的整体感,又看清每段明细。
- </p>
- <div class="compare-bar">
- <button type="button" class="compare-btn active" data-scheme="d">方案 D · 合并单元格<span class="badge">推荐</span></button>
- <button type="button" class="compare-btn" data-scheme="a">方案 A · 现状<span class="badge">扁平一行</span></button>
- <button type="button" class="compare-btn" data-scheme="b">方案 B · 主从分行</button>
- <button type="button" class="compare-btn" data-scheme="c">方案 C · 可展开树</button>
- <span class="verdict">推荐:<strong>方案 D</strong>(主信息 rowspan + 装送货分行)</span>
- </div>
- </header>
- <div class="analysis">
- <div class="analysis-card recommended">
- <h3>方案 D · 合并单元格 <span class="tag-rec">推荐</span></h3>
- <ul>
- <li>左侧列 <code>rowspan</code>:序号、计划编号、状态、日期、车牌、复制 — 视觉上仍是「一条计划」</li>
- <li>右侧列按段分行:装货 1 行 + 送货 N 行,客户/产品/数量段内对齐</li>
- <li>比 B 少一行「计划头横条」,比 A 清晰,比 C 不用点开</li>
- <li>Element Plus:<code>el-table :span-method</code> 即可实现</li>
- </ul>
- </div>
- <div class="analysis-card">
- <h3>方案 A · 现状(当前代码)</h3>
- <ul>
- <li>一行展示整单:装货客户一列、送货客户摘要一列</li>
- <li>产品列多行堆叠,看不出属于哪一段</li>
- <li>多段送货时「送货客户」只能是「A / B / C」摘要</li>
- <li>实现简单,分页/导出/勾选都按 plan 维度,无需改 API</li>
- </ul>
- </div>
- <div class="analysis-card">
- <h3>方案 B · 主从分行</h3>
- <ul>
- <li>计划头单独占一行,装送货在下方 — 主信息重复感略强</li>
- <li>段信息清晰,但与 D 相比多占一行高度</li>
- </ul>
- </div>
- <div class="analysis-card">
- <h3>方案 C · 树形可展开</h3>
- <ul>
- <li>默认只显示计划行,点击展开看装送货段</li>
- <li>行高最省,适合计划量特别大的场景</li>
- <li>多一段操作(展开)才能看到段信息</li>
- <li>Element Plus 可用 expand 行;批量操作仍按 plan 勾选</li>
- </ul>
- </div>
- </div>
- <section class="panel">
- <div class="panel-top"><h2>运输计划</h2></div>
- <div class="type-tabs">
- <button type="button" class="type-tab active">全部</button>
- <button type="button" class="type-tab">直销单</button>
- <button type="button" class="type-tab">入库单</button>
- <button type="button" class="type-tab">出库单</button>
- <button type="button" class="type-tab">待安排</button>
- </div>
- <div class="toolbar">
- <span class="sel-hint">已选 <span class="num">1</span> 项</span>
- <input class="search" placeholder="计划编号/供方/需方/产品名称/车牌号" />
- <button type="button" class="btn btn-primary">新增计划</button>
- <button type="button" class="btn btn-ghost">安排运输</button>
- <button type="button" class="btn btn-ghost">打印单据</button>
- </div>
- <!-- ========== 方案 D:合并单元格(推荐) ========== -->
- <div id="scheme-d" class="scheme active">
- <table class="list list-merge">
- <colgroup>
- <col style="width:40px" />
- <col style="width:52px" />
- <col style="width:148px" />
- <col style="width:88px" />
- <col style="width:96px" />
- <col style="width:80px" />
- <col style="width:108px" />
- <col style="width:108px" />
- <col style="width:72px" />
- <col style="width:140px" />
- <col style="width:88px" />
- <col style="width:160px" />
- <col style="width:72px" />
- <col style="width:72px" />
- </colgroup>
- <thead>
- <tr>
- <th class="col-plan th-group-plan" colspan="8">计划主信息(合并单元格)</th>
- <th class="col-stop th-group-stop" colspan="6">装货 / 送货段信息(按行展开)</th>
- </tr>
- <tr class="thead-sub">
- <th class="col-plan"><input type="checkbox" checked /></th>
- <th class="col-plan">序号</th>
- <th class="col-plan">计划编号</th>
- <th class="col-plan">跟进状态</th>
- <th class="col-plan">计划日期</th>
- <th class="col-plan">单据类型</th>
- <th class="col-plan">车牌号</th>
- <th class="col-plan">复制</th>
- <th class="col-stop">段次</th>
- <th class="col-stop">客户</th>
- <th class="col-stop">地址 / 时间</th>
- <th class="col-stop">段内产品</th>
- <th class="col-stop">计划数量</th>
- <th class="col-stop">地磅</th>
- </tr>
- </thead>
- <tbody>
- <!-- 计划 1:1装 + 2送 = 3 行,左侧 rowspan=3 -->
- <tr>
- <td class="cell-merge col-plan" rowspan="3"><input type="checkbox" checked /></td>
- <td class="cell-merge col-plan" rowspan="3">1</td>
- <td class="cell-merge col-plan" rowspan="3">
- <a class="link merge-plan-no" href="#">PL20250826001</a>
- <span class="muted" style="font-size:11px">跟进:张三</span>
- </td>
- <td class="cell-merge col-plan" rowspan="3"><span class="tag tag-success">已安排</span></td>
- <td class="cell-merge col-plan" rowspan="3">2025-08-26</td>
- <td class="cell-merge col-plan" rowspan="3">出库单</td>
- <td class="cell-merge col-plan" rowspan="3">皖A·12345</td>
- <td class="cell-merge col-plan" rowspan="3">
- <div class="merge-copy-stack">
- <button type="button" class="btn btn-ghost">复制给司机</button>
- <button type="button" class="btn btn-ghost">复制给现场</button>
- </div>
- </td>
- <td class="col-stop"><span class="stop-badge load">装货</span></td>
- <td class="col-stop">
- <span class="stop-customer">安庆市山峡化工有限公司</span>
- <span class="internal-tag">内部</span>
- </td>
- <td class="col-stop"><span class="stop-addr">安庆市 … · 08-26 08:00</span></td>
- <td class="col-stop">
- <div class="product-line">硫酸 <span class="muted">32%</span></div>
- <div class="product-line">盐酸 <span class="muted">31%</span></div>
- </td>
- <td class="col-stop">
- <div class="product-line"><span class="qty">28 吨</span></div>
- <div class="product-line"><span class="qty">12 吨</span></div>
- </td>
- <td class="col-stop"><span class="tag tag-success" style="font-size:11px">出库</span></td>
- </tr>
- <tr>
- <td class="col-stop"><span class="stop-badge delivery">送货 #1</span></td>
- <td class="col-stop"><span class="stop-customer">合肥某某贸易有限公司</span></td>
- <td class="col-stop"><span class="stop-addr">合肥市 … · 08-26 14:00</span></td>
- <td class="col-stop"><div class="product-line">硫酸 <span class="muted">32%</span></div></td>
- <td class="col-stop"><span class="qty">28 吨</span></td>
- <td class="col-stop muted">—</td>
- </tr>
- <tr class="plan-block-last">
- <td class="col-stop"><span class="stop-badge delivery">送货 #2</span></td>
- <td class="col-stop"><span class="stop-customer">芜湖某某物流有限公司</span></td>
- <td class="col-stop"><span class="stop-addr">芜湖市 … · 08-26 17:30</span></td>
- <td class="col-stop"><div class="product-line">盐酸 <span class="muted">31%</span></div></td>
- <td class="col-stop"><span class="qty">12 吨</span></td>
- <td class="col-stop muted">—</td>
- </tr>
- <!-- 计划 2:1装 + 1送 = 2 行 -->
- <tr>
- <td class="cell-merge col-plan" rowspan="2"><input type="checkbox" /></td>
- <td class="cell-merge col-plan" rowspan="2">2</td>
- <td class="cell-merge col-plan" rowspan="2">
- <a class="link merge-plan-no" href="#">PL20250826002</a>
- </td>
- <td class="cell-merge col-plan" rowspan="2"><span class="tag tag-danger">待安排</span></td>
- <td class="cell-merge col-plan" rowspan="2">2025-08-26</td>
- <td class="cell-merge col-plan" rowspan="2">入库单</td>
- <td class="cell-merge col-plan muted" rowspan="2">—</td>
- <td class="cell-merge col-plan hidden-col" rowspan="2">—</td>
- <td class="col-stop"><span class="stop-badge load">装货</span></td>
- <td class="col-stop"><span class="stop-customer">江苏某某供应商</span></td>
- <td class="col-stop"><span class="stop-addr">南京市 … · 08-26 09:00</span></td>
- <td class="col-stop"><div class="product-line">甲醇</div></td>
- <td class="col-stop"><span class="qty">20 吨</span></td>
- <td class="col-stop muted">—</td>
- </tr>
- <tr class="plan-block-last">
- <td class="col-stop"><span class="stop-badge delivery">送货 #1</span></td>
- <td class="col-stop">
- <span class="stop-customer">安徽云栋化工有限公司</span>
- <span class="internal-tag">内部</span>
- </td>
- <td class="col-stop"><span class="stop-addr">安庆市 … · 08-26 15:00</span></td>
- <td class="col-stop"><div class="product-line">甲醇</div></td>
- <td class="col-stop"><span class="qty">20 吨</span></td>
- <td class="col-stop"><span class="tag tag-success" style="font-size:11px">入库</span></td>
- </tr>
- <!-- 计划 3:直销 1装+1送 -->
- <tr>
- <td class="cell-merge col-plan" rowspan="2"><input type="checkbox" /></td>
- <td class="cell-merge col-plan" rowspan="2">3</td>
- <td class="cell-merge col-plan" rowspan="2">
- <a class="link merge-plan-no" href="#">PL20250825018</a>
- </td>
- <td class="cell-merge col-plan" rowspan="2"><span class="tag tag-warning">等通知</span></td>
- <td class="cell-merge col-plan" rowspan="2">2025-08-25</td>
- <td class="cell-merge col-plan" rowspan="2">直销单</td>
- <td class="cell-merge col-plan muted" rowspan="2">—</td>
- <td class="cell-merge col-plan hidden-col" rowspan="2">—</td>
- <td class="col-stop"><span class="stop-badge load">装货</span></td>
- <td class="col-stop"><span class="stop-customer">山东某某工厂</span></td>
- <td class="col-stop"><span class="stop-addr">济南市 … · 08-25 10:00</span></td>
- <td class="col-stop"><div class="product-line">双氧水</div></td>
- <td class="col-stop"><span class="qty">15 吨</span></td>
- <td class="col-stop muted">—</td>
- </tr>
- <tr class="plan-block-last">
- <td class="col-stop"><span class="stop-badge delivery">送货 #1</span></td>
- <td class="col-stop"><span class="stop-customer">南京某某客户</span></td>
- <td class="col-stop"><span class="stop-addr">南京市 … · 08-25 16:00</span></td>
- <td class="col-stop"><div class="product-line">双氧水</div></td>
- <td class="col-stop"><span class="qty">15 吨</span></td>
- <td class="col-stop muted">—</td>
- </tr>
- </tbody>
- </table>
- <div class="note-box">
- <strong>方案 D 结构:</strong>
- 表头分两区 — 左「计划主信息」、右「装货/送货段信息」。
- 每个计划在左侧占一块合并单元格(高度 = 装货 1 行 + 送货 N 行),右侧逐段展开。
- 扫列表时先看左块认计划,再看右块认路线与货量,符合调度习惯。
- </div>
- </div>
- <!-- ========== 方案 A:现状 ========== -->
- <div id="scheme-a" class="scheme">
- <table class="list">
- <thead>
- <tr>
- <th style="width:40px"><input type="checkbox" checked /></th>
- <th style="width:52px">序号</th>
- <th>计划编号</th>
- <th>跟进状态</th>
- <th>计划日期</th>
- <th>单据类型</th>
- <th>装货客户</th>
- <th>送货客户</th>
- <th>车牌号</th>
- <th>产品名称</th>
- <th>计划数量</th>
- <th>复制</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td><input type="checkbox" checked /></td>
- <td>1</td>
- <td><a class="link" href="#">PL20250826001</a></td>
- <td><span class="tag tag-success">已安排</span></td>
- <td>2025-08-26</td>
- <td>出库单</td>
- <td>安庆市山峡化工有限公司</td>
- <td class="muted">合肥某某贸易 / 芜湖某某物流</td>
- <td>皖A·12345</td>
- <td class="stack">
- <div class="product-line">硫酸 <span class="qty">28 吨</span></div>
- <div class="product-line">盐酸 <span class="qty">12 吨</span></div>
- </td>
- <td class="stack">
- <div>28 吨</div>
- <div>12 吨</div>
- </td>
- <td><button type="button" class="btn btn-ghost" style="height:30px;padding:0 8px;font-size:12px">复制给司机</button></td>
- </tr>
- <tr>
- <td><input type="checkbox" /></td>
- <td>2</td>
- <td><a class="link" href="#">PL20250826002</a></td>
- <td><span class="tag tag-danger">待安排</span></td>
- <td>2025-08-26</td>
- <td>入库单</td>
- <td>江苏某某供应商</td>
- <td>安徽云栋化工有限公司</td>
- <td class="muted">—</td>
- <td class="stack">
- <div class="product-line">甲醇 <span class="qty">20 吨</span></div>
- </td>
- <td>20 吨</td>
- <td class="hidden-col">—</td>
- </tr>
- <tr>
- <td><input type="checkbox" /></td>
- <td>3</td>
- <td><a class="link" href="#">PL20250825018</a></td>
- <td><span class="tag tag-warning">等通知</span></td>
- <td>2025-08-25</td>
- <td>直销单</td>
- <td>山东某某工厂</td>
- <td>南京某某客户</td>
- <td class="muted">—</td>
- <td class="stack">
- <div class="product-line">双氧水 <span class="qty">15 吨</span></div>
- </td>
- <td>15 吨</td>
- <td class="hidden-col">—</td>
- </tr>
- </tbody>
- </table>
- <div class="note-box warn">
- <strong>现状痛点(样例 PL20250826001):</strong>
- 两个送货客户挤在「送货客户」一列;两种产品堆在「产品名称」列,无法看出 28 吨硫酸去合肥、12 吨盐酸去芜湖。
- 多段计划越多,这一行越难扫读。
- </div>
- </div>
- <!-- ========== 方案 B:主从分行 ========== -->
- <div id="scheme-b" class="scheme">
- <table class="list">
- <thead>
- <tr>
- <th style="width:40px"><input type="checkbox" checked /></th>
- <th>段次 / 客户</th>
- <th>地址 / 时间</th>
- <th>段内产品</th>
- <th>计划数量</th>
- <th>地磅</th>
- </tr>
- </thead>
- <tbody>
- <!-- 计划 1 -->
- <tr class="plan-group plan-master">
- <td><input type="checkbox" checked /></td>
- <td colspan="5">
- <div class="master-bar">
- <a class="link master-no" href="#">PL20250826001</a>
- <span class="tag tag-success">已安排</span>
- <span class="tag tag-info">出库单</span>
- <div class="master-meta">
- <span>计划日期 <b>2025-08-26</b></span>
- <span>车牌 <b>皖A·12345</b></span>
- <span>跟进人 <b>张三</b></span>
- </div>
- <div class="master-actions">
- <button type="button" class="btn btn-ghost">查看</button>
- <button type="button" class="btn btn-ghost">复制给司机</button>
- <button type="button" class="btn btn-ghost">调整运输</button>
- </div>
- </div>
- </td>
- </tr>
- <tr class="plan-group stop-row load">
- <td></td>
- <td class="merge-cell stop-indent">
- <span class="stop-badge load">装货</span>
- <span class="stop-customer">安庆市山峡化工有限公司</span>
- <span class="internal-tag">内部</span>
- <div class="stop-addr">安徽省安庆市 … · 08-26 08:00</div>
- </td>
- <td class="muted">出库 · 已勾地磅</td>
- <td>
- <div class="product-line">硫酸 <span class="muted">32%工业级</span></div>
- <div class="product-line">盐酸 <span class="muted">31%</span></div>
- </td>
- <td>
- <div class="product-line"><span class="qty">28 吨</span></div>
- <div class="product-line"><span class="qty">12 吨</span></div>
- </td>
- <td><span class="tag tag-success" style="font-size:11px">出库单</span></td>
- </tr>
- <tr class="plan-group stop-row delivery">
- <td></td>
- <td class="merge-cell stop-indent">
- <span class="stop-badge delivery">送货 #1</span>
- <span class="stop-customer">合肥某某贸易有限公司</span>
- <div class="stop-addr">合肥市蜀山区 … · 08-26 14:00</div>
- </td>
- <td class="muted">—</td>
- <td><div class="product-line">硫酸 <span class="muted">32%工业级</span></div></td>
- <td><span class="qty">28 吨</span></td>
- <td class="muted">—</td>
- </tr>
- <tr class="plan-group stop-row delivery">
- <td></td>
- <td class="merge-cell stop-indent">
- <span class="stop-badge delivery">送货 #2</span>
- <span class="stop-customer">芜湖某某物流有限公司</span>
- <div class="stop-addr">芜湖市镜湖区 … · 08-26 17:30</div>
- </td>
- <td class="muted">—</td>
- <td><div class="product-line">盐酸 <span class="muted">31%</span></div></td>
- <td><span class="qty">12 吨</span></td>
- <td class="muted">—</td>
- </tr>
- <!-- 计划 2 -->
- <tr class="plan-group plan-master">
- <td><input type="checkbox" /></td>
- <td colspan="5">
- <div class="master-bar">
- <a class="link master-no" href="#">PL20250826002</a>
- <span class="tag tag-danger">待安排</span>
- <span class="tag tag-info">入库单</span>
- <div class="master-meta">
- <span>计划日期 <b>2025-08-26</b></span>
- <span>车牌 <b class="muted">未安排</b></span>
- </div>
- <div class="master-actions">
- <button type="button" class="btn btn-primary" style="height:32px;padding:0 10px;font-size:12px">安排运输</button>
- </div>
- </div>
- </td>
- </tr>
- <tr class="plan-group stop-row load">
- <td></td>
- <td class="merge-cell stop-indent">
- <span class="stop-badge load">装货</span>
- <span class="stop-customer">江苏某某供应商</span>
- <div class="stop-addr">南京市 … · 08-26 09:00</div>
- </td>
- <td class="muted">—</td>
- <td><div class="product-line">甲醇</div></td>
- <td><span class="qty">20 吨</span></td>
- <td class="muted">—</td>
- </tr>
- <tr class="plan-group stop-row delivery">
- <td></td>
- <td class="merge-cell stop-indent">
- <span class="stop-badge delivery">送货 #1</span>
- <span class="stop-customer">安徽云栋化工有限公司</span>
- <span class="internal-tag">内部</span>
- <div class="stop-addr">安庆市 … · 08-26 15:00</div>
- </td>
- <td class="muted">入库 · 已勾地磅</td>
- <td><div class="product-line">甲醇</div></td>
- <td><span class="qty">20 吨</span></td>
- <td><span class="tag tag-success" style="font-size:11px">入库单</span></td>
- </tr>
- </tbody>
- </table>
- <div class="note-box">
- <strong>方案 B 优点:</strong>
- 计划头只出现一次(编号/状态/车辆/操作);装货、送货 #1、#2 各占一行,段内产品数量清晰。
- 勾选仍在计划头,与现有工具栏「已选 N 项 / 安排运输 / 作废」兼容。
- 列可收敛为:段次·客户、地址·时间、段内产品、数量、地磅/现场标记。
- </div>
- </div>
- <!-- ========== 方案 C:树形展开 ========== -->
- <div id="scheme-c" class="scheme">
- <table class="list">
- <thead>
- <tr>
- <th style="width:40px"><input type="checkbox" checked /></th>
- <th style="width:52px">序号</th>
- <th>计划编号</th>
- <th>跟进状态</th>
- <th>计划日期</th>
- <th>装货 → 送货摘要</th>
- <th>车牌号</th>
- <th>产品概要</th>
- <th>操作</th>
- </tr>
- </thead>
- <tbody>
- <tr data-tree="p1">
- <td><input type="checkbox" checked /></td>
- <td>1</td>
- <td>
- <button type="button" class="tree-toggle" data-target="p1" aria-label="展开">−</button>
- <a class="link" href="#">PL20250826001</a>
- </td>
- <td><span class="tag tag-success">已安排</span></td>
- <td>2025-08-26</td>
- <td class="muted">山峡化工 → 合肥贸易 / 芜湖物流(2 段送货)</td>
- <td>皖A·12345</td>
- <td>硫酸 28t + 盐酸 12t</td>
- <td><button type="button" class="btn btn-ghost" style="height:30px;padding:0 8px;font-size:12px">查看</button></td>
- </tr>
- <tr class="tree-child open" data-tree-child="p1">
- <td></td>
- <td></td>
- <td colspan="7">
- <span class="tree-label">装货</span>
- <strong>安庆市山峡化工有限公司</strong>
- <span class="muted"> · 硫酸 28t、盐酸 12t · 出库地磅</span>
- </td>
- </tr>
- <tr class="tree-child open" data-tree-child="p1">
- <td></td>
- <td></td>
- <td colspan="7">
- <span class="tree-label">送货#1</span>
- <strong>合肥某某贸易有限公司</strong>
- <span class="muted"> · 硫酸 28t</span>
- </td>
- </tr>
- <tr class="tree-child open" data-tree-child="p1">
- <td></td>
- <td></td>
- <td colspan="7">
- <span class="tree-label">送货#2</span>
- <strong>芜湖某某物流有限公司</strong>
- <span class="muted"> · 盐酸 12t</span>
- </td>
- </tr>
- <tr data-tree="p2">
- <td><input type="checkbox" /></td>
- <td>2</td>
- <td>
- <button type="button" class="tree-toggle" data-target="p2" aria-label="展开">+</button>
- <a class="link" href="#">PL20250826002</a>
- </td>
- <td><span class="tag tag-danger">待安排</span></td>
- <td>2025-08-26</td>
- <td class="muted">江苏供应商 → 安徽云栋(1 段)</td>
- <td class="muted">—</td>
- <td>甲醇 20t</td>
- <td><button type="button" class="btn btn-primary" style="height:30px;padding:0 8px;font-size:12px">安排</button></td>
- </tr>
- <tr class="tree-child" data-tree-child="p2">
- <td></td>
- <td></td>
- <td colspan="7">
- <span class="tree-label">装货</span>
- <strong>江苏某某供应商</strong>
- <span class="muted"> · 甲醇 20t</span>
- </td>
- </tr>
- <tr class="tree-child" data-tree-child="p2">
- <td></td>
- <td></td>
- <td colspan="7">
- <span class="tree-label">送货#1</span>
- <strong>安徽云栋化工有限公司</strong>
- <span class="muted"> · 甲醇 20t · 入库地磅</span>
- </td>
- </tr>
- </tbody>
- </table>
- <div class="note-box warn">
- <strong>方案 C 权衡:</strong>
- 默认折叠时与现状行高接近;展开后信息等价于方案 B,但多一次点击。
- 适合「列表以扫计划号/状态为主,偶尔下钻看段」的场景。
- </div>
- </div>
- <div class="pager">
- <span>共 128 条计划 · 第 1 / 7 页</span>
- <span>分页仍按<strong>计划</strong>计数,不按段行计数</span>
- </div>
- </section>
- <section class="analysis" style="margin-top:16px">
- <div class="analysis-card recommended" style="grid-column: 1 / -1">
- <h3>实施建议(方案 D · 合并单元格)</h3>
- <ul>
- <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>
- <li><strong>数据:</strong>列表 API 返回 <code>load</code> + <code>deliveries[]</code> 及段内 <code>products</code>。</li>
- <li><strong>交互:</strong>勾选/双击/工具栏仍按 <strong>planId</strong>;仅首段行渲染 checkbox;段行 hover 时整组高亮(可选)。</li>
- <li><strong>列分区:</strong>计划主信息 8 列 + 段信息 6 列;列设置可对两侧分组配置。</li>
- <li><strong>单段计划:</strong>rowspan=2(1 装 + 1 送),与多段结构一致。</li>
- <li><strong>导出:</strong>Excel 仍可扁平「一行一计划」,与 UI 展示解耦。</li>
- </ul>
- </div>
- </section>
- </div>
- <script>
- document.querySelectorAll('.compare-btn').forEach(btn => {
- btn.addEventListener('click', () => {
- const id = btn.dataset.scheme
- document.querySelectorAll('.compare-btn').forEach(b => b.classList.remove('active'))
- document.querySelectorAll('.scheme').forEach(s => s.classList.remove('active'))
- btn.classList.add('active')
- document.getElementById('scheme-' + id).classList.add('active')
- })
- })
- document.querySelectorAll('.tree-toggle').forEach(btn => {
- btn.addEventListener('click', () => {
- const id = btn.dataset.target
- const rows = document.querySelectorAll('[data-tree-child="' + id + '"]')
- const open = btn.textContent.trim() === '+'
- rows.forEach(r => r.classList.toggle('open', open))
- btn.textContent = open ? '−' : '+'
- })
- })
- </script>
- </body>
- </html>
|