Files
shanxia2.0/docs/demos/pl-plan-pair-stack-structure.html
T
2026-10-02 09:28:00 +08:00

563 lines
21 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>运输计划 · 装/送结构重设计</title>
<style>
:root {
--load: #059669;
--delivery: #0284c7;
--text: #1e293b;
--muted: #64748b;
--border: #e2e8f0;
--page: #eef2f6;
--panel: #fff;
--primary: #10b981;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 28px 20px 80px;
font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
font-size: 14px;
color: var(--text);
background: var(--page);
}
h1 { margin: 0 0 6px; font-size: 20px; font-weight: 700; }
.lead { margin: 0 0 10px; max-width: 860px; color: var(--muted); font-size: 13.5px; line-height: 1.55; }
.shift {
margin: 0 0 22px; padding: 12px 14px; max-width: 860px;
border-radius: 8px; background: #ecfdf5; border: 1px solid #a7f3d0;
color: #065f46; font-size: 13px; line-height: 1.5;
}
.scheme {
margin-bottom: 22px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
}
.scheme.is-rec { border-color: #6ee7b7; box-shadow: 0 0 0 1px #a7f3d0; }
.scheme__head {
display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
padding: 14px 16px; border-bottom: 1px solid var(--border);
background: #fafbfc;
}
.scheme__title { margin: 0; font-size: 15px; font-weight: 700; }
.scheme__why { margin: 4px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.badge {
flex-shrink: 0; padding: 2px 9px; border-radius: 999px;
font-size: 11px; font-weight: 600;
}
.badge--rec { color: #065f46; background: #ecfdf5; border: 1px solid #a7f3d0; }
.badge--alt { color: #475569; background: #f8fafc; border: 1px solid #e2e8f0; }
.scheme__body { padding: 16px; }
.scheme__note { margin: 12px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
table.t {
width: 100%;
border-collapse: collapse;
font-size: 13px;
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
}
table.t th, table.t td {
padding: 8px 10px;
border-bottom: 1px solid var(--border);
border-right: 1px solid var(--border);
vertical-align: middle;
}
table.t th:last-child, table.t td:last-child { border-right: none; }
table.t tr:last-child td { border-bottom: none; }
table.t thead th {
background: #f8fafc;
color: var(--muted);
font-size: 12px;
font-weight: 600;
text-align: center;
white-space: nowrap;
}
table.t .left { text-align: left; }
table.t .num { text-align: center; font-variant-numeric: tabular-nums; font-weight: 600; }
table.t .muted { color: var(--muted); font-weight: 500; }
.addr { font-weight: 600; line-height: 1.4; }
.time { margin-top: 2px; font-size: 12px; color: var(--muted); font-weight: 400; }
/* S1 二级表头 */
.s1 thead .group { background: #f1f5f9; color: var(--text); font-weight: 700; }
.s1 thead .sub-load { color: var(--load); }
.s1 thead .sub-delivery { color: var(--delivery); }
.s1 tbody td { background: #fff; }
/* S2 两行 */
.s2 .kind {
width: 36px; text-align: center; font-size: 11px; font-weight: 800;
}
.s2 .kind--load { color: var(--load); }
.s2 .kind--delivery { color: var(--delivery); }
.s2 tr.load-row td { background: #fff; }
.s2 tr.delivery-row td { background: #fafbfc; }
.s2 tr.pair-end td { border-bottom: 2px solid #94a3b8; }
/* S3 视图切换 */
.tabs {
display: inline-flex; gap: 0; margin-bottom: 10px;
border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
}
.tabs button {
appearance: none; border: none; background: #fff;
padding: 7px 14px; font-size: 13px; color: var(--muted); cursor: pointer;
font-family: inherit;
}
.tabs button + button { border-left: 1px solid var(--border); }
.tabs button.active {
background: var(--primary); color: #fff; font-weight: 600;
}
.view-panel { display: none; }
.view-panel.active { display: block; }
/* S4 左侧类型轨(整行共享) */
.s4 .rail-cell {
width: 28px; padding: 0 !important; vertical-align: stretch !important;
}
.s4 .rail {
display: flex; flex-direction: column; height: 100%; min-height: 64px;
}
.s4 .rail > span {
flex: 1; display: flex; align-items: center; justify-content: center;
writing-mode: vertical-rl; text-orientation: mixed;
font-size: 11px; font-weight: 800; letter-spacing: 0.12em;
color: #fff;
}
.s4 .rail > span.load { background: var(--load); }
.s4 .rail > span.delivery { background: var(--delivery); }
.s4 .stack { display: flex; flex-direction: column; gap: 0; }
.s4 .stack > div { padding: 5px 0; }
.s4 .stack > div + div {
border-top: 1px dashed var(--border); margin-top: 5px; padding-top: 5px;
}
/* S5 卡片对照 */
.cards { display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: stretch; }
.stop-card {
border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
background: #fff;
}
.stop-card--load { border-top: 3px solid var(--load); }
.stop-card--delivery { border-top: 3px solid var(--delivery); }
.stop-card__role {
font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
margin-bottom: 8px;
}
.stop-card--load .stop-card__role { color: var(--load); }
.stop-card--delivery .stop-card__role { color: var(--delivery); }
.stop-card__addr { font-weight: 600; margin-bottom: 4px; }
.stop-card__meta { font-size: 12px; color: var(--muted); }
.stop-card__kvs {
display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px;
margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
font-size: 12px;
}
.stop-card__kvs span { color: var(--muted); }
.stop-card__kvs b { display: block; margin-top: 2px; font-size: 14px; color: var(--text); font-variant-numeric: tabular-nums; }
.cards__arrow {
display: flex; align-items: center; justify-content: center;
color: #94a3b8; font-size: 18px;
}
.plan-meta {
margin-bottom: 10px; font-size: 12px; color: var(--muted);
}
.plan-meta strong { color: var(--text); }
.footer-pick {
margin-top: 8px; padding: 16px 18px; max-width: 860px;
background: #fff; border: 1px solid var(--border); border-radius: 10px;
}
.footer-pick h2 { margin: 0 0 8px; font-size: 14px; }
.footer-pick ol { margin: 0; padding-left: 18px; color: var(--muted); font-size: 13px; }
.footer-pick li { margin: 6px 0; }
.footer-pick strong { color: var(--text); }
</style>
</head>
<body>
<h1>装 / 送 · 结构重设计(不再靠格子里贴标记)</h1>
<p class="lead">
之前方案的问题:无论汉字、色条还是字色,都是<strong>在每个单元格里重复表达一次</strong>,列一多就吵。
这轮改从<strong>表格结构</strong>下手——让「装/送」只出现在表头、首列或视图切换里,正文格子尽量只放数据。
</p>
<p class="shift">
设计原则:标记出现次数 ≈ 1(表头 / 首列 / 工具栏),而不是 ≈ 列数 × 行数。
</p>
<!-- S1 -->
<section class="scheme is-rec">
<div class="scheme__head">
<div>
<h2 class="scheme__title">S1 · 二级表头拆列</h2>
<p class="scheme__why">「计划数量」拆成子列「装 / 送」,单元格里只有数字,汉字只出现在表头各一次。</p>
</div>
<span class="badge badge--rec">列表推荐</span>
</div>
<div class="scheme__body">
<table class="t s1">
<thead>
<tr>
<th class="left" rowspan="2">装货地址</th>
<th class="left" rowspan="2">送货地址</th>
<th class="group" colspan="2">计划数量</th>
<th class="group" colspan="2">单价</th>
<th class="group" colspan="2">金额</th>
</tr>
<tr>
<th class="sub-load">装</th>
<th class="sub-delivery">送</th>
<th class="sub-load">装</th>
<th class="sub-delivery">送</th>
<th class="sub-load">装</th>
<th class="sub-delivery">送</th>
</tr>
</thead>
<tbody>
<tr>
<td class="left">
<div class="addr">宜昌西陵化工园 A12</div>
<div class="time">09-16 08:30</div>
</td>
<td class="left">
<div class="addr">武汉东西湖物流大道 88</div>
<div class="time">09-16 16:00</div>
</td>
<td class="num">0.01</td>
<td class="num">0.01</td>
<td class="num">10</td>
<td class="num muted">—</td>
<td class="num">33</td>
<td class="num muted">—</td>
</tr>
<tr>
<td class="left">
<div class="addr">荆州某仓库</div>
<div class="time">09-17 09:00</div>
</td>
<td class="left">
<div class="addr">长沙物流园 B3</div>
<div class="time">09-17 18:00</div>
</td>
<td class="num">12</td>
<td class="num">12</td>
<td class="num">8.5</td>
<td class="num">9.0</td>
<td class="num">102</td>
<td class="num">108</td>
</tr>
</tbody>
</table>
<p class="scheme__note">
正文零「装/送」重复。代价:列变多(可用列设置隐藏次要子列)。地址也拆成两列,上下对齐更清晰。
Element Plus 原生支持多级表头,和现有列设置能力兼容。
</p>
</div>
</section>
<!-- S2 -->
<section class="scheme is-rec">
<div class="scheme__head">
<div>
<h2 class="scheme__title">S2 · 拆成两行(装一行、送一行)</h2>
<p class="scheme__why">一条配对变成上下两行;「装/送」只在最左类型列出现一次,其余列全是普通单元格。</p>
</div>
<span class="badge badge--rec">列表推荐</span>
</div>
<div class="scheme__body">
<table class="t s2">
<thead>
<tr>
<th></th>
<th class="left">地址 / 时间</th>
<th>计划数量</th>
<th>实际数量</th>
<th>单价</th>
<th>金额</th>
</tr>
</thead>
<tbody>
<tr class="load-row">
<td class="kind kind--load">装</td>
<td class="left">
<div class="addr">宜昌西陵化工园 A12</div>
<div class="time">09-16 08:30</div>
</td>
<td class="num">0.01</td>
<td class="num">0.01</td>
<td class="num">10</td>
<td class="num">33</td>
</tr>
<tr class="delivery-row pair-end">
<td class="kind kind--delivery">送</td>
<td class="left">
<div class="addr">武汉东西湖物流大道 88</div>
<div class="time">09-16 16:00</div>
</td>
<td class="num">0.01</td>
<td class="num">0.01</td>
<td class="num muted">—</td>
<td class="num muted">—</td>
</tr>
<tr class="load-row">
<td class="kind kind--load">装</td>
<td class="left">
<div class="addr">荆州某仓库</div>
<div class="time">09-17 09:00</div>
</td>
<td class="num">12</td>
<td class="num">12</td>
<td class="num">8.5</td>
<td class="num">102</td>
</tr>
<tr class="delivery-row pair-end">
<td class="kind kind--delivery">送</td>
<td class="left">
<div class="addr">长沙物流园 B3</div>
<div class="time">09-17 18:00</div>
</td>
<td class="num">12</td>
<td class="num">11.8</td>
<td class="num">9.0</td>
<td class="num">106.2</td>
</tr>
</tbody>
</table>
<p class="scheme__note">
这是物流/仓储系统里最常见的读法:横扫一行全是「装」的数据,再扫下一行全是「送」。
合并单元格(车牌、司机等计划级字段)可跨两行合并。改动比换皮肤大,但噪音下降最彻底。
</p>
</div>
</section>
<!-- S3 -->
<section class="scheme">
<div class="scheme__head">
<div>
<h2 class="scheme__title">S3 · 视图切换(一次只看一层)</h2>
<p class="scheme__why">默认看「装货」或「送货」单层列表;需要对照时再进对照模式。</p>
</div>
<span class="badge badge--alt">备选</span>
</div>
<div class="scheme__body">
<div class="tabs" id="s3-tabs">
<button type="button" class="active" data-view="load">装货</button>
<button type="button" data-view="delivery">送货</button>
<button type="button" data-view="both">对照</button>
</div>
<div class="view-panel active" data-view="load">
<table class="t">
<thead>
<tr>
<th class="left">装货地址</th>
<th>计划数量</th>
<th>单价</th>
<th>金额</th>
</tr>
</thead>
<tbody>
<tr>
<td class="left">
<div class="addr">宜昌西陵化工园 A12</div>
<div class="time">09-16 08:30</div>
</td>
<td class="num">0.01</td>
<td class="num">10</td>
<td class="num">33</td>
</tr>
</tbody>
</table>
</div>
<div class="view-panel" data-view="delivery">
<table class="t">
<thead>
<tr>
<th class="left">送货地址</th>
<th>计划数量</th>
<th>单价</th>
<th>金额</th>
</tr>
</thead>
<tbody>
<tr>
<td class="left">
<div class="addr">武汉东西湖物流大道 88</div>
<div class="time">09-16 16:00</div>
</td>
<td class="num">0.01</td>
<td class="num muted">—</td>
<td class="num muted">—</td>
</tr>
</tbody>
</table>
</div>
<div class="view-panel" data-view="both">
<p style="margin:0 0 8px;font-size:12px;color:var(--muted)">对照模式可用 S1 或 S2,此处示意用 S1 子列。</p>
<table class="t s1">
<thead>
<tr>
<th class="left" rowspan="2">装货地址</th>
<th class="left" rowspan="2">送货地址</th>
<th class="group" colspan="2">计划数量</th>
</tr>
<tr>
<th class="sub-load">装</th>
<th class="sub-delivery">送</th>
</tr>
</thead>
<tbody>
<tr>
<td class="left"><div class="addr">宜昌西陵化工园 A12</div></td>
<td class="left"><div class="addr">武汉东西湖物流大道 88</div></td>
<td class="num">0.01</td>
<td class="num">0.01</td>
</tr>
</tbody>
</table>
</div>
<p class="scheme__note">日常调度若多数时间只关心一侧(如只盯装货),噪音为零。需要装送对齐核对时切到对照。</p>
</div>
</section>
<!-- S4 -->
<section class="scheme">
<div class="scheme__head">
<div>
<h2 class="scheme__title">S4 · 整行共享左侧类型轨</h2>
<p class="scheme__why">保留「一格里上下叠」的数据结构,但类型只在表格最左侧出现一次(半格装、半格送)。</p>
</div>
<span class="badge badge--alt">小改动</span>
</div>
<div class="scheme__body">
<table class="t s4">
<thead>
<tr>
<th style="width:28px"></th>
<th class="left">地址</th>
<th>计划数量</th>
<th>单价</th>
<th>金额</th>
</tr>
</thead>
<tbody>
<tr>
<td class="rail-cell">
<div class="rail">
<span class="load">装</span>
<span class="delivery">送</span>
</div>
</td>
<td class="left">
<div class="stack">
<div>
<div class="addr">宜昌西陵化工园 A12</div>
<div class="time">09-16 08:30</div>
</div>
<div>
<div class="addr">武汉东西湖物流大道 88</div>
<div class="time">09-16 16:00</div>
</div>
</div>
</td>
<td>
<div class="stack">
<div class="num">0.01</div>
<div class="num">0.01</div>
</div>
</td>
<td>
<div class="stack">
<div class="num">10</div>
<div class="num muted">—</div>
</div>
</td>
<td>
<div class="stack">
<div class="num">33</div>
<div class="num muted">—</div>
</div>
</td>
</tr>
</tbody>
</table>
<p class="scheme__note">
对现有「配对叠放」改动最小:删掉每个 cell 里的 label,最左侧加一列类型轨。
数量列变干净,但行内仍要上下对齐扫读。
</p>
</div>
</section>
<!-- S5 -->
<section class="scheme">
<div class="scheme__head">
<div>
<h2 class="scheme__title">S5 · 左装右送卡片对照</h2>
<p class="scheme__why">不做密表叠字;每条计划一张对照卡,装/送各写一次角色名。</p>
</div>
<span class="badge badge--alt">备选</span>
</div>
<div class="scheme__body">
<div class="plan-meta">计划号 <strong>JH20260916001</strong> · 车牌 鄂A·12X34 · 司机 张三</div>
<div class="cards">
<div class="stop-card stop-card--load">
<div class="stop-card__role">装货</div>
<div class="stop-card__addr">宜昌西陵化工园 A12</div>
<div class="stop-card__meta">09-16 08:30</div>
<div class="stop-card__kvs">
<div><span>计划数量</span><b>0.01</b></div>
<div><span>单价</span><b>10</b></div>
<div><span>入库</span><b>0.01</b></div>
<div><span>金额</span><b>33</b></div>
</div>
</div>
<div class="cards__arrow">→</div>
<div class="stop-card stop-card--delivery">
<div class="stop-card__role">送货</div>
<div class="stop-card__addr">武汉东西湖物流大道 88</div>
<div class="stop-card__meta">09-16 16:00</div>
<div class="stop-card__kvs">
<div><span>计划数量</span><b>0.01</b></div>
<div><span>单价</span><b>—</b></div>
<div><span>出库</span><b>0.01</b></div>
<div><span>金额</span><b>—</b></div>
</div>
</div>
</div>
<p class="scheme__note">适合详情/移动端/低密度审核。桌面高密度调度列表通常仍应以 S1/S2 为主。</p>
</div>
</section>
<div class="footer-pick">
<h2>建议</h2>
<ol>
<li><strong>S1 二级表头</strong>:继续「一行一条配对」,但数量类字段左右对照,最适合你截图里的数量/单价/金额列。</li>
<li><strong>S2 拆两行</strong>:横扫读法最直觉,计划级字段用行合并;适合愿意改列表数据模型的情况。</li>
<li><strong>S4</strong>:想最小改动、先去掉满屏「装/送」时的过渡方案。</li>
</ol>
<p style="margin:12px 0 0;font-size:13px;color:#64748b">回复 S1 / S2 / S4(或组合,如「数量列用 S1,地址先保持叠放」),再落到正式页面。</p>
</div>
<script>
const tabs = document.getElementById('s3-tabs')
if (tabs) {
tabs.addEventListener('click', (e) => {
const btn = e.target.closest('button[data-view]')
if (!btn) return
const view = btn.getAttribute('data-view')
tabs.querySelectorAll('button').forEach((b) => b.classList.toggle('active', b === btn))
document.querySelectorAll('.view-panel').forEach((p) => {
p.classList.toggle('active', p.getAttribute('data-view') === view)
})
})
}
</script>
</body>
</html>