Files
shanxia2.0/docs/fresh-mint-pl-plan-list-layout-demo.html
T
2026-08-27 14:23:18 +08:00

1055 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>