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

490 lines
20 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;
--load-border: #a7f3d0;
--delivery-border: #bae6fd;
}
* { 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: 1760px; 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: 1080px; }
.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; }
.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;
}
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 8px; }
table.list {
width: 100%;
border-collapse: collapse;
min-width: 1480px;
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: 1;
background: var(--head);
color: var(--muted);
font-size: 12px;
font-weight: 600;
white-space: nowrap;
text-align: left;
}
table.list thead th.num,
table.list td.num { text-align: right; }
table.list thead th.center,
table.list td.center { text-align: center; }
table.list tbody tr:nth-child(even) { background: var(--alt); }
table.list tbody tr:hover { background: #f1f5f9; }
table.list a { color: var(--primary-dark); font-weight: 500; text-decoration: none; }
table.list a:hover { text-decoration: underline; }
.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.info {
background: transparent; color: var(--text);
border: 1px solid #cbd5e1; font-weight: 500;
}
.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: 6px; }
.stack .sub { color: var(--muted); font-size: 12px; }
.name-with-mark {
display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px;
}
.th-group-load { background: #ecfdf5 !important; color: var(--primary-dark) !important; }
.th-group-need { background: #e0f2fe !important; color: #0369a1 !important; }
.cell-load { background: rgba(236, 253, 245, 0.45); }
.cell-need { background: rgba(240, 249, 255, 0.55); }
.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>(取消合并单元格)</h1>
<p>
案例数据来自测试库运输计划 <strong>PO20261009022</strong>:
装货客户「安庆市山峡化工有限公司」、送货客户「桐城运城制版有限公司」、
3 个产品(硫酸 / 聚合氯化铝 / 液碱);需方合同 <code>SX/X-JT20261008006</code>,供方合同为空。
目标:去掉供/需纵向拆行 + rowspan,改为扁平分列,每一物理行独立完整。
</p>
<div class="pills">
<span class="pill">PO20261009022</span>
<span class="pill">皖H21522</span>
<span class="pill">程欢欢</span>
<span class="pill muted">黄天宝(押)、沈卫斌(驾)</span>
<span class="pill warn">待安排</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>按计划产品对齐成行(本单 3 行)。
「合同」拆成 <strong>供合同 / 需合同</strong> 两列;计划量若两侧一致可只保留一列,也可再拆供/需计划量。
<strong>无 rowspan / colspan</strong>,计划级字段在每行重复展示(或仅首行填、其余留空——本 demo 采用重复,便于导出/筛选)。
</p>
<p class="ok">
能解决:去掉合并单元格后,前端不再依赖 <code>span-method</code>,导出 Excel 也是标准扁平行;
筛选「需合同」「产品」时行语义清晰。代价是列变宽、计划级信息有重复。
</p>
<div class="table-wrap">
<table class="list">
<thead>
<tr>
<th>计划编号</th>
<th class="center">跟进状态</th>
<th>车牌号</th>
<th>人员</th>
<th>跟进人</th>
<th>装货客户</th>
<th>送货客户</th>
<th>装货地址/时间</th>
<th>送货地址/时间</th>
<th class="th-group-load">供合同</th>
<th class="th-group-need">需合同</th>
<th>产品名称</th>
<th class="center">型号/规格</th>
<th class="center">包装规格</th>
<th class="center">单位</th>
<th class="num">计划量</th>
<th class="num">实际量</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#">PO20261009022</a></td>
<td class="center"><span class="tag warn">待安排</span></td>
<td>皖H21522</td>
<td>黄天宝(押)、沈卫斌(驾)</td>
<td>程欢欢</td>
<td>安庆市山峡化工有限公司</td>
<td>桐城运城制版有限公司</td>
<td>
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-12</span>
</div>
</td>
<td>
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-12</span>
</div>
</td>
<td class="cell-load"><span class="muted">—</span></td>
<td class="cell-need"><a href="#">SX/X-JT20261008006</a></td>
<td>
<span class="name-with-mark">
98% 工业硫酸
<span class="tag warn">毒</span>
</span>
</td>
<td class="center">98%</td>
<td class="center">25kg/桶</td>
<td class="center">吨</td>
<td class="num">1</td>
<td class="num">0</td>
</tr>
<tr>
<td><a href="#">PO20261009022</a></td>
<td class="center"><span class="tag warn">待安排</span></td>
<td>皖H21522</td>
<td>黄天宝(押)、沈卫斌(驾)</td>
<td>程欢欢</td>
<td>安庆市山峡化工有限公司</td>
<td>桐城运城制版有限公司</td>
<td>
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-12</span>
</div>
</td>
<td>
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-12</span>
</div>
</td>
<td class="cell-load"><span class="muted">—</span></td>
<td class="cell-need"><a href="#">SX/X-JT20261008006</a></td>
<td>聚合氯化铝</td>
<td class="center"><span class="muted">—</span></td>
<td class="center">25Kg/袋</td>
<td class="center">吨</td>
<td class="num">1</td>
<td class="num">0</td>
</tr>
<tr>
<td><a href="#">PO20261009022</a></td>
<td class="center"><span class="tag warn">待安排</span></td>
<td>皖H21522</td>
<td>黄天宝(押)、沈卫斌(驾)</td>
<td>程欢欢</td>
<td>安庆市山峡化工有限公司</td>
<td>桐城运城制版有限公司</td>
<td>
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-12</span>
</div>
</td>
<td>
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-12</span>
</div>
</td>
<td class="cell-load"><span class="muted">—</span></td>
<td class="cell-need"><a href="#">SX/X-JT20261008006</a></td>
<td>32%液碱</td>
<td class="center">32%</td>
<td class="center">25kg/桶</td>
<td class="center">吨</td>
<td class="num">0.25</td>
<td class="num">0</td>
</tr>
</tbody>
</table>
</div>
<p class="meta">
行粒度:计划产品(装/送两侧按 <code>plan_product_id</code> 对齐)。本单 load×3 + delivery×3 收敛为 <strong>3 行</strong>,不再拆成 6 行供/需。
</p>
</section>
<!-- 方案 B -->
<section class="panel" id="panel-b">
<p class="desc">
<strong>方案 B:</strong>整份计划只占 <strong>1 行</strong>。供/需合同、产品明细在单元格内用多行文本堆叠,
同样 <strong>无合并单元格</strong>。适合「一眼看完整单」;不适合按产品行勾选/导出明细。
</p>
<p class="ok">实现成本最低(去掉 flatten + span-method 即可),但表格行高随产品数膨胀,筛选产品时仍是计划粒度。</p>
<div class="table-wrap">
<table class="list" style="min-width: 1320px;">
<thead>
<tr>
<th>计划编号</th>
<th class="center">跟进状态</th>
<th>车牌号</th>
<th>人员</th>
<th>跟进人</th>
<th>装货客户</th>
<th>送货客户</th>
<th>装货地址/时间</th>
<th>送货地址/时间</th>
<th class="th-group-load">供合同</th>
<th class="th-group-need">需合同</th>
<th>产品明细</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#">PO20261009022</a></td>
<td class="center"><span class="tag warn">待安排</span></td>
<td>皖H21522</td>
<td>黄天宝(押)、沈卫斌(驾)</td>
<td>程欢欢</td>
<td>安庆市山峡化工有限公司</td>
<td>桐城运城制版有限公司</td>
<td>
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-12</span>
</div>
</td>
<td>
<div class="stack">
<span class="muted">—</span>
<span class="sub">2026-10-12</span>
</div>
</td>
<td class="cell-load"><span class="muted">—</span></td>
<td class="cell-need"><a href="#">SX/X-JT20261008006</a></td>
<td>
<div class="stack">
<span class="name-with-mark">98% 工业硫酸 <span class="tag warn">毒</span> · 98% · 25kg/桶 · 1 吨</span>
<span>聚合氯化铝 · 25Kg/袋 · 1 吨</span>
<span>32%液碱 · 32% · 25kg/桶 · 0.25 吨</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- 对照现状 -->
<section class="panel" id="panel-c">
<p class="pain">
<strong>现状对照:</strong>一份计划拆成供行 + 需行(本单 6 行),「合同 / 产品…」共用列头;
计划编号、装货客户等靠 <code>rowspan</code> 合并。视觉上「从合同起供/需多行」,也是导出与勾选复杂的根源。
</p>
<div class="table-wrap">
<table class="list" style="min-width: 1200px;">
<thead>
<tr>
<th>计划编号</th>
<th>装货客户</th>
<th>送货客户</th>
<th>合同</th>
<th>产品名称</th>
<th class="center">型号/规格</th>
<th class="center">包装规格</th>
<th class="center">单位</th>
<th class="num">计划量</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="6"><a href="#">PO20261009022</a></td>
<td rowspan="6">安庆市山峡化工有限公司</td>
<td rowspan="6">桐城运城制版有限公司</td>
<td rowspan="3" class="cell-load"><span class="tag side-load">供</span> <span class="muted">—</span></td>
<td class="cell-load"><span class="name-with-mark">98% 工业硫酸 <span class="tag warn">毒</span></span></td>
<td class="center cell-load">98%</td>
<td class="center cell-load">25kg/桶</td>
<td class="center cell-load">吨</td>
<td class="num cell-load">1</td>
</tr>
<tr>
<td class="cell-load">聚合氯化铝</td>
<td class="center cell-load"><span class="muted">—</span></td>
<td class="center cell-load">25Kg/袋</td>
<td class="center cell-load">吨</td>
<td class="num cell-load">1</td>
</tr>
<tr>
<td class="cell-load">32%液碱</td>
<td class="center cell-load">32%</td>
<td class="center cell-load">25kg/桶</td>
<td class="center cell-load">吨</td>
<td class="num cell-load">0.25</td>
</tr>
<tr>
<td rowspan="3" class="cell-need"><span class="tag side-need">需</span> <a href="#">SX/X-JT20261008006</a></td>
<td class="cell-need"><span class="name-with-mark">98% 工业硫酸 <span class="tag warn">毒</span></span></td>
<td class="center cell-need">98%</td>
<td class="center cell-need">25kg/桶</td>
<td class="center cell-need">吨</td>
<td class="num cell-need">1</td>
</tr>
<tr>
<td class="cell-need">聚合氯化铝</td>
<td class="center cell-need"><span class="muted">—</span></td>
<td class="center cell-need">25Kg/袋</td>
<td class="center cell-need">吨</td>
<td class="num cell-need">1</td>
</tr>
<tr>
<td class="cell-need">32%液碱</td>
<td class="center cell-need">32%</td>
<td class="center cell-need">25kg/桶</td>
<td class="center cell-need">吨</td>
<td class="num cell-need">0.25</td>
</tr>
</tbody>
</table>
</div>
<p class="meta">本对照刻意保留 rowspan,仅用于对比;方案 A/B 均不使用合并单元格。</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>