Files
shanxia2.0/docs/demos/pl-plan-three-zone-merge-demo.html
T
2026-10-10 21:12:05 +08:00

594 lines
25 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;
--muted: #64748b;
--border: #d9e8e2;
--head: #f8fafc;
--page: #eef2f6;
--panel: #fff;
--warn: #d97706;
--danger: #dc2626;
--alt: #fafafa;
--load-bg: #ecfdf5;
--delivery-bg: #f0f9ff;
--base-bg: #f8fafc;
--load-border: #a7f3d0;
--delivery-border: #bae6fd;
--base-border: #cbd5e1;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 24px 20px 80px;
font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: var(--page);
}
.page { max-width: 1680px; margin: 0 auto; }
.hero {
background: linear-gradient(135deg, #ecfdf5 0%, #fff 55%, #f0f9ff 100%);
border: 1px solid var(--border);
border-radius: 10px;
padding: 20px 22px;
margin-bottom: 16px;
}
.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(--muted); max-width: 1100px; }
.pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.pill {
font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: 999px;
background: var(--primary-bg); color: var(--primary-dark); border: 1px solid #a7f3d0;
}
.pill.muted { background: #f1f5f9; color: var(--muted); border-color: #e2e8f0; }
.pill.warn { background: #fffbeb; color: var(--warn); border-color: #fde68a; }
.pill.base { background: #f1f5f9; color: #334155; border-color: #cbd5e1; }
.pill.load { background: var(--load-bg); color: var(--primary-dark); border-color: var(--load-border); }
.pill.need { background: var(--delivery-bg); color: #0369a1; border-color: var(--delivery-border); }
.tabs {
display: flex; flex-wrap: wrap; gap: 6px;
background: var(--panel); border: 1px solid var(--border);
border-radius: 10px 10px 0 0; padding: 8px 10px 0;
}
.tab {
border: none; background: transparent; cursor: pointer; font-family: inherit;
padding: 10px 14px; font-size: 13px; font-weight: 600; color: var(--muted);
border-bottom: 2px solid transparent;
}
.tab:hover { color: var(--primary-dark); }
.tab.active { color: var(--primary-dark); border-bottom-color: var(--primary); }
.tab .rec {
margin-left: 6px; font-size: 10px; padding: 1px 6px; border-radius: 999px;
background: var(--primary-bg); color: var(--primary-dark); border: 1px solid #a7f3d0;
}
.panel {
display: none;
background: var(--panel);
border: 1px solid var(--border);
border-top: none;
border-radius: 0 0 10px 10px;
padding: 16px 18px 22px;
}
.panel.active { display: block; }
.desc {
margin: 0 0 12px; padding: 12px 14px; border-radius: 8px;
background: #f0f9ff; border: 1px solid #bae6fd; color: #0369a1; font-size: 13px;
}
.desc strong { color: var(--text); }
.ok {
margin: 0 0 14px; padding: 10px 12px; border-radius: 8px;
background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46; font-size: 12.5px;
}
.pain {
margin: 0 0 14px; padding: 10px 12px; border-radius: 8px;
background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; font-size: 12.5px;
}
.legend {
display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 14px; font-size: 12px; color: var(--muted);
}
.legend span {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 8px; border-radius: 6px; border: 1px solid var(--border);
}
.swatch { width: 12px; height: 12px; border-radius: 3px; border: 1px solid rgba(0,0,0,.08); }
.swatch.base { background: #e2e8f0; }
.swatch.load { background: #a7f3d0; }
.swatch.need { background: #7dd3fc; }
.swatch.prod { background: #fde68a; }
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 8px; }
table.list {
width: 100%;
border-collapse: collapse;
min-width: 1280px;
background: #fff;
}
table.list th,
table.list td {
border-bottom: 1px solid var(--border);
border-right: 1px solid #eef2f0;
padding: 10px 12px;
vertical-align: top;
font-size: 13px;
}
table.list th:last-child,
table.list td:last-child { border-right: none; }
table.list thead th {
position: sticky; top: 0; z-index: 2;
background: var(--head);
color: var(--muted);
font-size: 12px;
font-weight: 600;
white-space: nowrap;
text-align: left;
}
table.list thead tr.zone-row th {
text-align: center;
font-size: 12px;
letter-spacing: 0.02em;
border-bottom: 1px solid var(--border);
}
table.list thead th.zone-base {
background: #e2e8f0 !important; color: #334155 !important;
}
table.list thead th.zone-load {
background: #d1fae5 !important; color: var(--primary-dark) !important;
}
table.list thead th.zone-need {
background: #bae6fd !important; color: #0369a1 !important;
}
table.list thead th.zone-prod {
background: #fef3c7 !important; color: #92400e !important;
}
table.list thead th.col-base { background: #f1f5f9 !important; }
table.list thead th.col-load { background: #ecfdf5 !important; color: var(--primary-dark) !important; }
table.list thead th.col-need { background: #e0f2fe !important; color: #0369a1 !important; }
table.list thead th.col-prod { background: #fffbeb !important; color: #92400e !important; }
table.list tbody tr:hover td { background: #f8fafc; }
table.list a { color: var(--primary-dark); font-weight: 500; text-decoration: none; }
table.list a:hover { text-decoration: underline; }
td.cell-base { background: rgba(241, 245, 249, 0.55); }
td.cell-load { background: rgba(236, 253, 245, 0.55); }
td.cell-need { background: rgba(240, 249, 255, 0.65); }
td.cell-prod { background: #fff; }
td.plan-end { border-bottom: 2px solid #94a3b8 !important; }
.tag {
display: inline-flex; align-items: center;
padding: 1px 8px; border-radius: 6px;
font-size: 12px; font-weight: 600; line-height: 1.45; white-space: nowrap;
}
.tag.success { background: #059669; color: #fff; }
.tag.warn { background: #d97706; color: #fff; }
.tag.danger { background: #dc2626; color: #fff; }
.tag.side-load {
background: var(--load-bg); color: var(--primary-dark);
border: 1px solid var(--load-border);
}
.tag.side-need {
background: var(--delivery-bg); color: #0369a1;
border: 1px solid var(--delivery-border);
}
.muted { color: var(--muted); }
.stack { display: flex; flex-direction: column; gap: 4px; }
.stack .sub { color: var(--muted); font-size: 12px; }
.name-with-mark {
display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px;
}
.seg-divider {
margin: 8px 0;
border: 0;
border-top: 1px dashed #cbd5e1;
}
.hint-row {
font-size: 11px; color: var(--muted); margin-top: 4px;
}
.meta {
margin-top: 14px; font-size: 12px; color: var(--muted);
}
.meta code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
background: #f1f5f9; padding: 1px 6px; border-radius: 4px;
}
</style>
</head>
<body>
<div class="page">
<header class="hero">
<h1>运输计划列表 · <em>三区列</em>(基础 / 装货 / 送货)合并 Demo</h1>
<p>
案例:<strong>PO20261009016</strong> —— 1 个装货段、2 个送货段;
展平后产品侧共 3 行(1 供产品 + 2 需产品)。
目标验证:列表分成<strong>基础信息列</strong>、<strong>装货段次列</strong>、<strong>送货段次列</strong>三块,并各自做合并单元格时的观感。
</p>
<div class="pills">
<span class="pill">PO20261009016</span>
<span class="pill warn">待安排</span>
<span class="pill muted">皖R4B299/皖R8131挂</span>
<span class="pill base">基础信息区</span>
<span class="pill load">装货段次区</span>
<span class="pill need">送货段次区</span>
</div>
</header>
<div class="tabs" role="tablist">
<button class="tab active" data-tab="a" type="button">
方案 A · 送货按段合并(跨产品行)<span class="rec">贴近你的描述</span>
</button>
<button class="tab" data-tab="b" type="button">方案 B · 送货整单合并(格内叠段)</button>
<button class="tab" data-tab="c" type="button">对照 · 现状(供行出现 —)</button>
</div>
<!-- 方案 A -->
<section class="panel active" id="panel-a">
<p class="desc">
<strong>方案 A:</strong>表头分三区。
行模型仍是「先 1 行供产品,再各送货段产品行」(本单共 3 行)。
<strong>基础区 / 装货区</strong>整单合并(rowspan=3);
<strong>送货区</strong>按送货段合并——第 1 个送货段 rowspan=2(盖住供产品行 + 该送货产品行),第 2 个送货段 rowspan=1。
送货列<strong>不再出现「—」占位行</strong>,但第 1 个送货客户会在视觉上与供产品行对齐(略高一截)。
</p>
<p class="ok">
可实现:三区列 + 合并单元格都成立;送货客户/地址恰好 2 段。
代价:多送货时,第一个送货段的合并格会「借」供侧产品行的高度,和产品行不是严格语义对齐。
</p>
<div class="legend">
<span><i class="swatch base"></i>基础信息(整单合并)</span>
<span><i class="swatch load"></i>装货段次(整单合并)</span>
<span><i class="swatch need"></i>送货段次(按段合并)</span>
<span><i class="swatch prod"></i>产品明细(不合并)</span>
</div>
<div class="table-wrap">
<table class="list">
<thead>
<tr class="zone-row">
<th class="zone-base" colspan="5">基础信息</th>
<th class="zone-load" colspan="3">装货段次</th>
<th class="zone-need" colspan="3">送货段次</th>
<th class="zone-prod" colspan="4">产品明细(供/需)</th>
</tr>
<tr>
<th class="col-base">计划编号</th>
<th class="col-base">跟进状态</th>
<th class="col-base">计划日期</th>
<th class="col-base">车牌号</th>
<th class="col-base">人员</th>
<th class="col-load">装货客户</th>
<th class="col-load">装货地址/时间</th>
<th class="col-load">供合同</th>
<th class="col-need">送货客户</th>
<th class="col-need">送货地址/时间</th>
<th class="col-need">需合同</th>
<th class="col-prod">侧别</th>
<th class="col-prod">产品名称</th>
<th class="col-prod">单位</th>
<th class="col-prod">计划量</th>
</tr>
</thead>
<tbody>
<tr>
<td class="cell-base" rowspan="3">
<a href="#">PO20261009016</a>
</td>
<td class="cell-base" rowspan="3"><span class="tag danger">待安排</span></td>
<td class="cell-base" rowspan="3">2026-10-13</td>
<td class="cell-base" rowspan="3">皖R4B299/皖R8131挂</td>
<td class="cell-base" rowspan="3">彭双青(驾)、汪的霞(押)</td>
<td class="cell-load" rowspan="3">铜陵华博精细化工有限公司</td>
<td class="cell-load" rowspan="3">
<div class="stack">
<span>铜官大道南段3888号铜陵市华兴化工有限公司</span>
<span class="sub">2026-10-13</span>
</div>
</td>
<td class="cell-load" rowspan="3"><span class="muted">—</span></td>
<td class="cell-need" rowspan="2">
合肥瞻宇环保科技有限公司
<div class="hint-row">rowspan=2(含上方供产品行高度)</div>
</td>
<td class="cell-need" rowspan="2">
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-13</span>
</div>
</td>
<td class="cell-need" rowspan="2"><span class="muted">—</span></td>
<td class="cell-prod"><span class="tag side-load">供</span></td>
<td class="cell-prod">示例装货产品</td>
<td class="cell-prod">吨</td>
<td class="cell-prod">30</td>
</tr>
<tr>
<td class="cell-prod"><span class="tag side-need">需</span></td>
<td class="cell-prod">示例送货产品 A</td>
<td class="cell-prod">吨</td>
<td class="cell-prod">18</td>
</tr>
<tr>
<td class="cell-need">
<span class="name-with-mark">
安庆市山峡化工有限公司
<span class="tag success">内部</span>
</span>
</td>
<td class="cell-need">
<div class="stack">
<span>高新区震虹路5号安庆市山峡化工有限公司</span>
<span class="sub">2026-10-13</span>
</div>
</td>
<td class="cell-need"><span class="muted">—</span></td>
<td class="cell-prod plan-end"><span class="tag side-need">需</span></td>
<td class="cell-prod plan-end">示例送货产品 B</td>
<td class="cell-prod plan-end">吨</td>
<td class="cell-prod plan-end">12</td>
</tr>
</tbody>
</table>
</div>
<p class="meta">
合并规则:基础/装货 <code>rowspan = 整单行数</code>;送货段
<code>首段 rowspan = 供产品行数 + 本段产品行数</code>,后续段
<code>rowspan = 本段产品行数</code>。
</p>
</section>
<!-- 方案 B -->
<section class="panel" id="panel-b">
<p class="desc">
<strong>方案 B:</strong>同样三区列表头。
<strong>基础 / 装货 / 送货</strong>全部按整单合并(rowspan=3);
送货区在<strong>一个单元格内</strong>用虚线叠放 2 个送货段,不要求和右侧产品行一一对应。
产品明细仍 3 行。
</p>
<p class="ok">
实现最简单(送货列改成计划级合并 + 格内 v-for);送货客户/地址视觉上就是 2 行信息,不会出现第三行「—」。
代价:送货段与产品行不再纵向对齐。
</p>
<div class="legend">
<span><i class="swatch base"></i>基础信息(整单合并)</span>
<span><i class="swatch load"></i>装货段次(整单合并)</span>
<span><i class="swatch need"></i>送货段次(整单合并,格内叠段)</span>
<span><i class="swatch prod"></i>产品明细(不合并)</span>
</div>
<div class="table-wrap">
<table class="list">
<thead>
<tr class="zone-row">
<th class="zone-base" colspan="5">基础信息</th>
<th class="zone-load" colspan="3">装货段次</th>
<th class="zone-need" colspan="3">送货段次</th>
<th class="zone-prod" colspan="4">产品明细(供/需)</th>
</tr>
<tr>
<th class="col-base">计划编号</th>
<th class="col-base">跟进状态</th>
<th class="col-base">计划日期</th>
<th class="col-base">车牌号</th>
<th class="col-base">人员</th>
<th class="col-load">装货客户</th>
<th class="col-load">装货地址/时间</th>
<th class="col-load">供合同</th>
<th class="col-need">送货客户</th>
<th class="col-need">送货地址/时间</th>
<th class="col-need">需合同</th>
<th class="col-prod">侧别</th>
<th class="col-prod">产品名称</th>
<th class="col-prod">单位</th>
<th class="col-prod">计划量</th>
</tr>
</thead>
<tbody>
<tr>
<td class="cell-base" rowspan="3"><a href="#">PO20261009016</a></td>
<td class="cell-base" rowspan="3"><span class="tag danger">待安排</span></td>
<td class="cell-base" rowspan="3">2026-10-13</td>
<td class="cell-base" rowspan="3">皖R4B299/皖R8131挂</td>
<td class="cell-base" rowspan="3">彭双青(驾)、汪的霞(押)</td>
<td class="cell-load" rowspan="3">铜陵华博精细化工有限公司</td>
<td class="cell-load" rowspan="3">
<div class="stack">
<span>铜官大道南段3888号铜陵市华兴化工有限公司</span>
<span class="sub">2026-10-13</span>
</div>
</td>
<td class="cell-load" rowspan="3"><span class="muted">—</span></td>
<td class="cell-need" rowspan="3">
<div class="stack">
<span>合肥瞻宇环保科技有限公司</span>
<hr class="seg-divider" />
<span class="name-with-mark">
安庆市山峡化工有限公司
<span class="tag success">内部</span>
</span>
</div>
</td>
<td class="cell-need" rowspan="3">
<div class="stack">
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-13</span>
</div>
<hr class="seg-divider" />
<div class="stack">
<span>高新区震虹路5号安庆市山峡化工有限公司</span>
<span class="sub">2026-10-13</span>
</div>
</div>
</td>
<td class="cell-need" rowspan="3">
<div class="stack">
<span class="muted">—</span>
<hr class="seg-divider" />
<span class="muted">—</span>
</div>
</td>
<td class="cell-prod"><span class="tag side-load">供</span></td>
<td class="cell-prod">示例装货产品</td>
<td class="cell-prod">吨</td>
<td class="cell-prod">30</td>
</tr>
<tr>
<td class="cell-prod"><span class="tag side-need">需</span></td>
<td class="cell-prod">示例送货产品 A</td>
<td class="cell-prod">吨</td>
<td class="cell-prod">18</td>
</tr>
<tr>
<td class="cell-prod plan-end"><span class="tag side-need">需</span></td>
<td class="cell-prod plan-end">示例送货产品 B</td>
<td class="cell-prod plan-end">吨</td>
<td class="cell-prod plan-end">12</td>
</tr>
</tbody>
</table>
</div>
<p class="meta">
合并规则:三区段信息列均为 <code>rowspan = 整单行数</code>;多送货在格内用分隔线叠放。
产品明细列不合并。
</p>
</section>
<!-- 对照 C:现状 -->
<section class="panel" id="panel-c">
<p class="desc">
<strong>对照 · 接近现状:</strong>同样三区列,但送货区按「展平行」各自出格——
供产品行没有可绑定的唯一送货客户时显示 <strong>—</strong>,再跟 2 个真实送货段,
于是送货客户/地址看起来像 <strong>3 行</strong>(你截图里的问题)。
</p>
<p class="pain">
问题:业务上只有 2 个送货段,列表却多出一行「—」占位,扫读时像第三个送货客户。
</p>
<div class="table-wrap">
<table class="list">
<thead>
<tr class="zone-row">
<th class="zone-base" colspan="5">基础信息</th>
<th class="zone-load" colspan="2">装货段次</th>
<th class="zone-need" colspan="2">送货段次</th>
<th class="zone-prod" colspan="3">产品明细</th>
</tr>
<tr>
<th class="col-base">计划编号</th>
<th class="col-base">跟进状态</th>
<th class="col-base">计划日期</th>
<th class="col-base">车牌号</th>
<th class="col-base">人员</th>
<th class="col-load">装货客户</th>
<th class="col-load">装货地址/时间</th>
<th class="col-need">送货客户</th>
<th class="col-need">送货地址/时间</th>
<th class="col-prod">侧别</th>
<th class="col-prod">产品名称</th>
<th class="col-prod">计划量</th>
</tr>
</thead>
<tbody>
<tr>
<td class="cell-base" rowspan="3"><a href="#">PO20261009016</a></td>
<td class="cell-base" rowspan="3"><span class="tag danger">待安排</span></td>
<td class="cell-base" rowspan="3">2026-10-13</td>
<td class="cell-base" rowspan="3">皖R4B299/皖R8131挂</td>
<td class="cell-base" rowspan="3">彭双青(驾)、汪的霞(押)</td>
<td class="cell-load" rowspan="3">铜陵华博精细化工有限公司</td>
<td class="cell-load" rowspan="3">
<div class="stack">
<span>铜官大道南段3888号…</span>
<span class="sub">2026-10-13</span>
</div>
</td>
<td class="cell-need"><span class="muted">—</span></td>
<td class="cell-need">
<div class="stack">
<span class="muted">—</span>
<span class="sub muted">—</span>
</div>
</td>
<td class="cell-prod"><span class="tag side-load">供</span></td>
<td class="cell-prod">示例装货产品</td>
<td class="cell-prod">30</td>
</tr>
<tr>
<td class="cell-need">合肥瞻宇环保科技有限公司</td>
<td class="cell-need">
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-13</span>
</div>
</td>
<td class="cell-prod"><span class="tag side-need">需</span></td>
<td class="cell-prod">示例送货产品 A</td>
<td class="cell-prod">18</td>
</tr>
<tr>
<td class="cell-need plan-end">
<span class="name-with-mark">
安庆市山峡化工有限公司
<span class="tag success">内部</span>
</span>
</td>
<td class="cell-need plan-end">
<div class="stack">
<span>高新区震虹路5号…</span>
<span class="sub">2026-10-13</span>
</div>
</td>
<td class="cell-prod plan-end"><span class="tag side-need">需</span></td>
<td class="cell-prod plan-end">示例送货产品 B</td>
<td class="cell-prod plan-end">12</td>
</tr>
</tbody>
</table>
</div>
<p class="meta">对照用:说明「三区列」本身可行,但若送货列仍跟供/需产品行一一出格,多送货时就会冒出「—」。</p>
</section>
</div>
<script>
const tabs = document.querySelectorAll('.tab')
const panels = {
a: document.getElementById('panel-a'),
b: document.getElementById('panel-b'),
c: document.getElementById('panel-c')
}
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
const key = tab.getAttribute('data-tab')
tabs.forEach((t) => t.classList.toggle('active', t === tab))
Object.entries(panels).forEach(([k, el]) => {
el.classList.toggle('active', k === key)
})
})
})
</script>
</body>
</html>