Files
zkxw/docs/demos/2026-10-08-freight-link-existing-demo.html
T
zlwuandCursor db31990ebe feat: 细胞存储缴费催缴、统一样本信息与协议支付能力增强
汇总近期业务改动:催缴菜单与后端、样本信息快捷登记与进度、运费关联、客户档案扩展、协议优惠/期次与押金划转,并同步 SQL 迁移与管理端页面收敛。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 21:20:37 +08:00

747 lines
22 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" />
<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;
--bg-page: #f8fafc;
--panel: #ffffff;
--text: #0f172a;
--text-secondary: #334155;
--text-muted: #64748b;
--border: #e2e8f0;
--radius: 10px;
--radius-sm: 8px;
--warning: #d97706;
--warning-bg: #fffbeb;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: var(--bg-page);
}
.page {
max-width: 1100px;
margin: 0 auto;
padding: 28px 24px 64px;
}
.hero {
margin-bottom: 28px;
}
.hero h1 {
margin: 0 0 8px;
font-size: 22px;
font-weight: 700;
letter-spacing: -0.02em;
}
.hero h1::before {
content: "";
display: inline-block;
width: 4px;
height: 1.1em;
margin-right: 10px;
vertical-align: -0.15em;
border-radius: 2px;
background: var(--primary);
}
.hero p {
margin: 0;
color: var(--text-muted);
max-width: 720px;
}
.context-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 16px 0 0;
}
.pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
background: var(--primary-bg);
color: var(--primary-dark);
border: 1px solid #a7f3d0;
}
.pill.muted {
background: #f1f5f9;
color: var(--text-muted);
border-color: var(--border);
}
.section {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px 20px 16px;
margin-bottom: 20px;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}
.section-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
}
.section-head h2 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.section-head .desc {
margin: 4px 0 0;
font-size: 13px;
color: var(--text-muted);
}
.badge {
flex-shrink: 0;
padding: 2px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.02em;
}
.badge.now {
background: #f1f5f9;
color: var(--text-muted);
}
.badge.rec {
background: var(--primary-bg);
color: var(--primary-dark);
border: 1px solid #a7f3d0;
}
.badge.alt {
background: var(--warning-bg);
color: var(--warning);
border: 1px solid #fde68a;
}
/* Dialog chrome */
.dialog {
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
background: #fff;
}
.dialog-hd {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
font-weight: 600;
font-size: 15px;
}
.dialog-hd .x {
color: var(--text-muted);
font-weight: 400;
cursor: default;
}
.dialog-bd { padding: 14px 18px 8px; }
.hint {
margin: 0 0 12px;
font-size: 13px;
color: var(--text-muted);
}
.tabs {
display: inline-flex;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
overflow: hidden;
margin-bottom: 12px;
}
.tab {
padding: 7px 16px;
font-size: 13px;
color: var(--text-secondary);
background: #fff;
border-right: 1px solid var(--border);
}
.tab:last-child { border-right: none; }
.tab.active {
background: var(--primary);
color: #fff;
font-weight: 600;
}
.dialog-ft {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 18px;
border-top: 1px solid var(--border);
background: #fafbfc;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 14px;
border-radius: var(--radius-sm);
font-size: 13px;
font-weight: 500;
border: 1px solid var(--border);
background: #fff;
color: var(--text-secondary);
cursor: default;
}
.btn.main {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
/* Tables */
table.list {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
table.list th,
table.list td {
padding: 10px 12px;
border-bottom: 1px solid var(--border);
text-align: left;
vertical-align: middle;
}
table.list th {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
background: #f8fafc;
white-space: nowrap;
}
table.list tr:nth-child(even) td { background: #fafbfc; }
table.list tr.selected td {
background: var(--primary-bg) !important;
outline: none;
}
table.list tr.selected {
box-shadow: inset 3px 0 0 var(--primary);
}
table.list td.right { text-align: right; }
table.list td.center { text-align: center; }
.muted { color: var(--text-muted); }
.tag {
display: inline-flex;
align-items: center;
padding: 1px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
line-height: 1.6;
border: 1px solid transparent;
}
.tag.primary {
background: var(--primary-bg);
color: var(--primary-dark);
border-color: #a7f3d0;
}
.tag.success {
background: #ecfdf5;
color: #047857;
border-color: #a7f3d0;
}
.tag.warning {
background: #fffbeb;
color: #b45309;
border-color: #fde68a;
}
.tag.muted {
background: #f1f5f9;
color: var(--text-muted);
border-color: var(--border);
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 4px;
max-width: 280px;
}
.chip {
display: inline-block;
max-width: 100%;
padding: 1px 7px;
border-radius: 4px;
font-size: 11px;
background: #f1f5f9;
color: var(--text-secondary);
border: 1px solid var(--border);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chip.more {
background: #fff;
color: var(--text-muted);
}
.summary-line {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.45;
}
.summary-line .k {
color: var(--text-muted);
}
.expand-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 4px;
border: 1px solid var(--border);
background: #fff;
font-size: 12px;
color: var(--primary-dark);
cursor: default;
}
.detail-row td {
background: #f8fafc !important;
padding: 0 12px 12px 36px !important;
border-bottom: 1px solid var(--border);
}
.detail-box {
margin-top: 0;
padding: 10px 12px;
border: 1px dashed #a7f3d0;
border-radius: var(--radius-sm);
background: #fff;
}
.detail-box table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.detail-box th,
.detail-box td {
padding: 6px 8px;
border-bottom: 1px solid #f1f5f9;
text-align: left;
}
.detail-box th {
color: var(--text-muted);
font-weight: 600;
background: transparent;
}
.detail-box tr:last-child td { border-bottom: none; }
.pros-cons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 14px;
}
@media (max-width: 720px) {
.pros-cons { grid-template-columns: 1fr; }
}
.pros, .cons {
padding: 10px 12px;
border-radius: var(--radius-sm);
font-size: 13px;
}
.pros {
background: var(--primary-bg);
border: 1px solid #a7f3d0;
}
.cons {
background: #fef2f2;
border: 1px solid #fecaca;
}
.pros h4, .cons h4 {
margin: 0 0 6px;
font-size: 12px;
font-weight: 600;
}
.pros ul, .cons ul {
margin: 0;
padding-left: 18px;
color: var(--text-secondary);
}
.vote {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
}
.vote label {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: #fff;
cursor: pointer;
min-width: 200px;
transition: border-color 0.15s, box-shadow 0.15s;
}
.vote label:hover {
border-color: #a7f3d0;
}
.vote input { accent-color: var(--primary); }
.vote label.picked {
border-color: var(--primary);
background: var(--primary-bg);
box-shadow: 0 0 0 1px var(--primary);
}
.footer-note {
margin-top: 8px;
font-size: 12px;
color: var(--text-muted);
}
.choose-panel {
border: 2px solid var(--primary);
background: linear-gradient(180deg, #ecfdf5 0%, #fff 48%);
}
</style>
</head>
<body>
<div class="page">
<header class="hero">
<h1>配置运费 → 关联已有:列表展示方案</h1>
<p>
采集协议单(细胞存储 / 科研捐赠)与「运费信息」页共用「配置运费」弹窗。
当前「关联已有」仅展示运输方式、金额、关联数、创建时间,难以判断该挂哪一笔批次。
下方对比三种展示方式(示例数据为假数据)。
</p>
<div class="context-bar">
<span class="pill">入口:协议单 · 样本及运输信息</span>
<span class="pill">入口:运费信息 · 配置运费</span>
<span class="pill muted">API 已有 includeItems(协议单号 / 样本编码 / 来源)</span>
</div>
</header>
<!-- A: Status quo -->
<section class="section">
<div class="section-head">
<div>
<h2>方案 A · 现状(过简)</h2>
<p class="desc">仅批次摘要四列,多笔「顺丰快递 / 同金额」时无法区分。</p>
</div>
<span class="badge now">现状</span>
</div>
<div class="dialog">
<div class="dialog-hd">配置运费 <span class="x">✕</span></div>
<div class="dialog-bd">
<p class="hint">将运费应用到已选中的 2 个样本(可跨协议共用一笔)。</p>
<div class="tabs">
<span class="tab">新建运费</span>
<span class="tab active">关联已有</span>
</div>
<table class="list">
<thead>
<tr>
<th>运输方式</th>
<th style="text-align:right">运费(元)</th>
<th style="text-align:center">关联样本数</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
<tr class="selected">
<td>顺丰快递</td>
<td class="right">¥280.00</td>
<td class="center">3</td>
<td>2026-10-07 14:22</td>
</tr>
<tr>
<td>顺丰快递</td>
<td class="right">¥280.00</td>
<td class="center">2</td>
<td>2026-10-06 09:10</td>
</tr>
<tr>
<td>专线车</td>
<td class="right">¥1,200.00</td>
<td class="center">5</td>
<td>2026-10-05 16:48</td>
</tr>
</tbody>
</table>
</div>
<div class="dialog-ft">
<button class="btn" type="button">取消</button>
<button class="btn main" type="button">确定应用</button>
</div>
</div>
<div class="pros-cons">
<div class="pros"><h4>优点</h4><ul><li>实现简单、弹窗窄</li></ul></div>
<div class="cons"><h4>问题</h4><ul><li>同方式同金额无法辨认</li><li>看不到已挂了哪些协议/样本</li></ul></div>
</div>
</section>
<!-- B: Summary columns -->
<section class="section">
<div class="section-head">
<div>
<h2>方案 B · 批次行内摘要(推荐)</h2>
<p class="desc">
列表请求带 <code>includeItems=true</code>,增加「已关联样本」摘要列:来源 Tag + 协议单号/样本编码 chips(超出折叠为 +N)。
弹窗略加宽,仍保持一行一批次、点选即关联。
</p>
</div>
<span class="badge rec">推荐</span>
</div>
<div class="dialog">
<div class="dialog-hd">配置运费 <span class="x">✕</span></div>
<div class="dialog-bd">
<p class="hint">将运费应用到已选中的 2 个样本(可跨协议共用一笔)。点击一行选择要关联的批次。</p>
<div class="tabs">
<span class="tab">新建运费</span>
<span class="tab active">关联已有</span>
</div>
<table class="list">
<thead>
<tr>
<th>运输方式</th>
<th style="text-align:right">运费(元)</th>
<th style="text-align:center">关联数</th>
<th>已关联样本</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
<tr class="selected">
<td><span class="tag primary">顺丰快递</span></td>
<td class="right">¥280.00</td>
<td class="center">3</td>
<td>
<div class="chips">
<span class="tag success">细胞存储</span>
<span class="chip">XY-2026-1001</span>
<span class="chip">S-A001</span>
<span class="chip">S-A002</span>
<span class="chip more">+1</span>
</div>
<div class="summary-line"><span class="k">协议</span> XY-2026-1001 · XY-2026-1008</div>
</td>
<td>2026-10-07 14:22</td>
</tr>
<tr>
<td><span class="tag primary">顺丰快递</span></td>
<td class="right">¥280.00</td>
<td class="center">2</td>
<td>
<div class="chips">
<span class="tag warning">科研捐赠</span>
<span class="chip">JZ-2026-0033</span>
<span class="chip">D-B011</span>
<span class="chip">D-B012</span>
</div>
</td>
<td>2026-10-06 09:10</td>
</tr>
<tr>
<td><span class="tag primary">专线车</span></td>
<td class="right">¥1,200.00</td>
<td class="center">5</td>
<td>
<div class="chips">
<span class="tag success">细胞存储</span>
<span class="tag warning">科研捐赠</span>
<span class="chip">XY-2026-0888</span>
<span class="chip">JZ-2026-0012</span>
<span class="chip more">+3 样本</span>
</div>
</td>
<td>2026-10-05 16:48</td>
</tr>
</tbody>
</table>
</div>
<div class="dialog-ft">
<button class="btn" type="button">取消</button>
<button class="btn main" type="button">确定应用</button>
</div>
</div>
<div class="pros-cons">
<div class="pros">
<h4>优点</h4>
<ul>
<li>一眼区分同金额批次</li>
<li>复用现有 list + includeItems,无需新接口</li>
<li>协议单弹窗与运费页弹窗可共用同一表格结构</li>
</ul>
</div>
<div class="cons">
<h4>取舍</h4>
<ul>
<li>多样本时 chips 需截断</li>
<li>弹窗建议加宽至约 900~960px</li>
</ul>
</div>
</div>
</section>
<!-- C: Expand detail -->
<section class="section">
<div class="section-head">
<div>
<h2>方案 C · 行展开明细</h2>
<p class="desc">
主表仍较精简;点击「明细」展开已关联样本表(来源 / 协议单号 / 样本编码 / 样本类型),对齐「运费信息」台账字段。
</p>
</div>
<span class="badge alt">备选</span>
</div>
<div class="dialog">
<div class="dialog-hd">配置运费 <span class="x">✕</span></div>
<div class="dialog-bd">
<p class="hint">将运费应用到已选中的 2 个样本。可展开查看批次下样本后再选择。</p>
<div class="tabs">
<span class="tab">新建运费</span>
<span class="tab active">关联已有</span>
</div>
<table class="list">
<thead>
<tr>
<th style="width:72px"></th>
<th>运输方式</th>
<th style="text-align:right">运费(元)</th>
<th style="text-align:center">关联数</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
<tr class="selected">
<td><span class="expand-btn">▾ 明细</span></td>
<td><span class="tag primary">顺丰快递</span></td>
<td class="right">¥280.00</td>
<td class="center">3</td>
<td>2026-10-07 14:22</td>
</tr>
<tr class="detail-row">
<td colspan="5">
<div class="detail-box">
<table>
<thead>
<tr>
<th>来源</th>
<th>协议单号</th>
<th>样本编码</th>
<th>样本类型</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="tag success">细胞存储</span></td>
<td>XY-2026-1001</td>
<td>S-A001</td>
<td>脐带血</td>
</tr>
<tr>
<td><span class="tag success">细胞存储</span></td>
<td>XY-2026-1001</td>
<td>S-A002</td>
<td>脐带血</td>
</tr>
<tr>
<td><span class="tag success">细胞存储</span></td>
<td>XY-2026-1008</td>
<td>S-C019</td>
<td>胎盘</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
<tr>
<td><span class="expand-btn">▸ 明细</span></td>
<td><span class="tag primary">顺丰快递</span></td>
<td class="right">¥280.00</td>
<td class="center">2</td>
<td>2026-10-06 09:10</td>
</tr>
<tr>
<td><span class="expand-btn">▸ 明细</span></td>
<td><span class="tag primary">专线车</span></td>
<td class="right">¥1,200.00</td>
<td class="center">5</td>
<td>2026-10-05 16:48</td>
</tr>
</tbody>
</table>
</div>
<div class="dialog-ft">
<button class="btn" type="button">取消</button>
<button class="btn main" type="button">确定应用</button>
</div>
</div>
<div class="pros-cons">
<div class="pros">
<h4>优点</h4>
<ul>
<li>明细完整,与运费台账一致</li>
<li>默认视图不拥挤</li>
</ul>
</div>
<div class="cons">
<h4>取舍</h4>
<ul>
<li>多一次展开操作,扫一眼成本更高</li>
<li>交互与表格高度略复杂</li>
</ul>
</div>
</div>
</section>
<!-- Choose -->
<section class="section choose-panel">
<div class="section-head">
<div>
<h2>请选择倾向方案</h2>
<p class="desc">点选后告诉我你的选择(或回复 A / B / C)。也可回复「B + 可展开看全量」等组合。</p>
</div>
</div>
<div class="vote" id="vote">
<label data-v="a">
<input type="radio" name="plan" value="a" />
<span><strong>A</strong> 保持现状</span>
</label>
<label data-v="b">
<input type="radio" name="plan" value="b" checked />
<span><strong>B</strong> 行内摘要(推荐)</span>
</label>
<label data-v="c">
<input type="radio" name="plan" value="c" />
<span><strong>C</strong> 行展开明细</span>
</label>
</div>
<p class="footer-note">
文件位置:<code>docs/demos/2026-10-08-freight-link-existing-demo.html</code>
· 用浏览器直接打开即可预览。
</p>
</section>
</div>
<script>
const vote = document.getElementById('vote')
function sync() {
vote.querySelectorAll('label').forEach((lab) => {
const input = lab.querySelector('input')
lab.classList.toggle('picked', !!(input && input.checked))
})
}
vote.addEventListener('change', sync)
sync()
</script>
</body>
</html>