1055 lines
42 KiB
HTML
1055 lines
42 KiB
HTML
<!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>
|