Files
zkxw/docs/demos/2026-10-08-sample-freight-menu-demo.html
T
zlwuandCursor 57d6241804 docs: add sample freight menu design and list demo
Document the cell-storage freight info menu with rowspan list, dual-source samples, and dual entry with protocol forms; include a static HTML preview.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-08 09:39:07 +08:00

618 lines
21 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;
--danger: #ef4444;
--warning: #d97706;
--info: #64748b;
--sidebar: 220px;
--topbar: 56px;
}
* { 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);
}
.app {
display: grid;
grid-template-columns: var(--sidebar) 1fr;
min-height: 100vh;
}
.sidebar {
background: #0f172a;
color: #e2e8f0;
padding: 16px 0;
}
.sidebar .brand {
padding: 8px 20px 20px;
font-weight: 700;
font-size: 15px;
color: #fff;
}
.sidebar .group-title {
padding: 12px 20px 6px;
font-size: 11px;
color: #94a3b8;
letter-spacing: 0.04em;
}
.sidebar a {
display: block;
padding: 9px 20px 9px 28px;
color: #cbd5e1;
text-decoration: none;
font-size: 13px;
}
.sidebar a:hover { background: rgba(255,255,255,0.04); color: #fff; }
.sidebar a.active {
background: rgba(16, 185, 129, 0.18);
color: #6ee7b7;
border-right: 3px solid var(--primary);
font-weight: 600;
}
.sidebar a.l1 {
padding-left: 20px;
font-weight: 600;
color: #f1f5f9;
}
.main { display: flex; flex-direction: column; min-width: 0; }
.topbar {
height: var(--topbar);
background: var(--panel);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
padding: 0 24px;
color: var(--text-muted);
font-size: 13px;
}
.topbar strong { color: var(--text-secondary); font-weight: 600; }
.content { padding: 24px; }
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.panel-top {
padding: 16px 20px 12px;
border-bottom: 1px solid var(--border);
}
.panel-top h2 {
margin: 0;
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
}
.panel-top h2::before {
content: "";
width: 3px;
height: 16px;
border-radius: 2px;
background: var(--primary);
}
.panel-top .hint {
margin: 8px 0 0;
font-size: 12px;
color: var(--text-muted);
}
.filter-block {
padding: 16px 20px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px 16px;
}
.field label {
display: block;
font-size: 11px;
color: var(--text-muted);
margin-bottom: 4px;
}
.field input, .field select {
width: 100%;
height: 36px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 0 10px;
font: inherit;
color: var(--text);
background: #fff;
}
.field input::placeholder { color: #94a3b8; }
.toolbar {
padding: 12px 20px;
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
border-top: 1px solid var(--border);
}
.selected-tip {
font-size: 13px;
color: var(--text-muted);
margin-right: 4px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 14px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: #fff;
color: var(--text-secondary);
font: inherit;
font-weight: 500;
cursor: pointer;
}
.btn:hover { border-color: #cbd5e1; }
.btn-main {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.btn-main:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.btn-op {
height: 28px;
padding: 0 10px;
font-size: 12px;
border-radius: 6px;
}
.list-body { border-top: 1px solid var(--border); }
table.list-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.list-table th {
background: #f8fafc;
color: var(--text-muted);
font-weight: 600;
font-size: 12px;
text-align: left;
padding: 10px 12px;
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
.list-table td {
padding: 10px 12px;
border-bottom: 1px solid var(--border);
vertical-align: middle;
color: var(--text-secondary);
font-size: 13px;
}
.list-table tbody tr:nth-child(even) td:not(.merged) {
background: #fafbfc;
}
.list-table td.merged {
background: #fff;
border-right: 1px solid var(--border);
vertical-align: middle;
}
.list-table tr:hover td:not(.merged) { background: var(--primary-bg); }
.list-table .mono { font-variant-numeric: tabular-nums; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.list-table .amount { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
.list-table .center { text-align: center; }
.tag {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
.tag.success { background: var(--primary-bg); color: var(--primary-dark); }
.tag.info { background: #f1f5f9; color: var(--info); }
.tag.warn { background: #fffbeb; color: var(--warning); }
.pager {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
padding: 12px 20px;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 13px;
}
.legend {
margin-top: 12px;
padding: 12px 14px;
background: var(--primary-bg);
border: 1px solid #a7f3d0;
border-radius: var(--radius-sm);
color: var(--primary-dark);
font-size: 12px;
}
.legend code {
background: rgba(255,255,255,0.7);
padding: 1px 6px;
border-radius: 4px;
}
/* modal */
.mask {
position: fixed; inset: 0;
background: rgba(15, 23, 42, 0.45);
display: none;
align-items: center;
justify-content: center;
z-index: 50;
backdrop-filter: none;
}
.mask.open { display: flex; }
.dialog {
width: min(720px, calc(100vw - 32px));
max-height: calc(100vh - 100px);
background: #fff;
border-radius: var(--radius);
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 20px 40px rgba(15,23,42,0.18);
}
.dialog header, .dialog footer {
padding: 14px 20px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.dialog footer {
border-bottom: 0;
border-top: 1px solid var(--border);
justify-content: flex-end;
gap: 8px;
}
.dialog h3 { margin: 0; font-size: 16px; }
.dialog .body { padding: 16px 20px; overflow: auto; }
.form-grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.form-grid-2 .full { grid-column: 1 / -1; }
.tabs {
display: inline-flex;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
overflow: hidden;
margin-bottom: 14px;
}
.tabs button {
border: 0;
background: #fff;
padding: 8px 14px;
cursor: pointer;
font: inherit;
color: var(--text-muted);
}
.tabs button.active {
background: var(--primary-bg);
color: var(--primary-dark);
font-weight: 600;
}
.sample-pick {
border: 1px solid var(--border);
border-radius: var(--radius-sm);
max-height: 220px;
overflow: auto;
}
.sample-pick label {
display: flex;
gap: 10px;
align-items: center;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
cursor: pointer;
}
.sample-pick label:last-child { border-bottom: 0; }
.sample-pick label:hover { background: var(--primary-bg); }
@media (max-width: 1100px) {
.filter-block { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.app { grid-template-columns: 1fr; }
.sidebar { display: none; }
}
</style>
</head>
<body>
<div class="app">
<aside class="sidebar">
<div class="brand">中科兴皖 · Demo</div>
<div class="group-title">市场运营</div>
<a class="l1" href="javascript:;">细胞存储</a>
<a href="javascript:;">预签约订单</a>
<a href="javascript:;">缴费信息</a>
<a href="javascript:;">样本信息管理</a>
<a class="active" href="javascript:;">运费信息</a>
<a href="javascript:;">缴费财务确认及开票</a>
<div class="group-title">说明</div>
<a href="javascript:;">协议单内仍可快捷配置</a>
</aside>
<div class="main">
<div class="topbar">
细胞存储 / <strong>运费信息</strong>
<span style="margin-left:auto">静态 Demo · Fresh Mint</span>
</div>
<div class="content">
<div class="panel">
<div class="panel-top">
<h2>运费信息</h2>
<p class="hint">
一行逻辑对应一笔运费批次;左侧「运输方式 / 运费 / 关联数」合并单元格。
右侧按实际关联展示细胞存储与科研捐赠样本(一次专线车可混装)。
</p>
</div>
<div class="filter-block">
<div class="field">
<label>运输方式</label>
<select>
<option value="">全部</option>
<option>顺丰快递</option>
<option selected>专线车</option>
</select>
</div>
<div class="field">
<label>来源</label>
<select>
<option selected>全部(含细胞存储 / 科研捐赠)</option>
<option>仅细胞存储</option>
<option>仅科研捐赠</option>
</select>
</div>
<div class="field">
<label>协议单号 / 样本编码</label>
<input placeholder="输入检索" />
</div>
<div class="field">
<label>创建时间</label>
<input type="text" placeholder="起止日期" value="2026-10-01 ~ 2026-10-08" />
</div>
</div>
<div class="toolbar">
<span class="selected-tip" id="selTip">已选 0 项</span>
<button class="btn btn-main" type="button" onclick="openDialog()">
<span>+</span> 配置运费
</button>
<button class="btn" type="button" id="btnUnlink" disabled onclick="alert('Demo:取消所选样本的运费关联')">
取消运费关联
</button>
<button class="btn" type="button" onclick="alert('Demo:编辑批次运输方式/金额')">编辑运费</button>
</div>
<div class="list-body">
<table class="list-table">
<thead>
<tr>
<th style="width:44px" class="center"><input type="checkbox" title="demo" disabled /></th>
<th style="width:100px">运输方式</th>
<th style="width:100px">运费(元)</th>
<th style="width:72px" class="center">关联数</th>
<th style="width:96px">来源</th>
<th style="width:140px">协议单号</th>
<th style="width:160px">样本编码</th>
<th style="width:120px">样本类型</th>
<th style="width:150px">创建时间</th>
<th style="width:160px">操作</th>
</tr>
</thead>
<tbody>
<!-- 批次 A:专线车 800,3 个样本(合并) -->
<tr>
<td class="center merged" rowspan="3"><input type="checkbox" class="row-check" data-batch="A" /></td>
<td class="merged" rowspan="3"><span class="tag success">专线车</span></td>
<td class="merged amount" rowspan="3">¥800.00</td>
<td class="merged center" rowspan="3">3</td>
<td><span class="tag success">细胞存储</span></td>
<td class="mono">CP20261008001</td>
<td class="mono">ZK-UC-20261008-01</td>
<td>脐带血</td>
<td class="merged" rowspan="3">2026-10-08 09:12:00</td>
<td class="merged" rowspan="3">
<button class="btn btn-op" type="button">编辑</button>
<button class="btn btn-op" type="button">取消关联</button>
</td>
</tr>
<tr>
<td><span class="tag info">科研捐赠</span></td>
<td class="mono">RD20261008012</td>
<td class="mono">RD-PL-20261008-03</td>
<td>胎盘</td>
</tr>
<tr>
<td><span class="tag success">细胞存储</span></td>
<td class="mono">CP20261008007</td>
<td class="mono">ZK-UC-20261008-07</td>
<td>脐带血</td>
</tr>
<!-- 批次 B:顺丰 120,1 个样本 -->
<tr>
<td class="center merged"><input type="checkbox" class="row-check" data-batch="B" /></td>
<td class="merged"><span class="tag warn">顺丰快递</span></td>
<td class="merged amount">¥120.00</td>
<td class="merged center">1</td>
<td><span class="tag success">细胞存储</span></td>
<td class="mono">CP20261007022</td>
<td class="mono">ZK-UC-20261007-22</td>
<td>脐带血</td>
<td class="merged">2026-10-07 16:40:11</td>
<td class="merged">
<button class="btn btn-op" type="button">编辑</button>
<button class="btn btn-op" type="button">取消关联</button>
</td>
</tr>
<!-- 批次 C:同城闪送 260,2 个捐赠样本 -->
<tr>
<td class="center merged" rowspan="2"><input type="checkbox" class="row-check" data-batch="C" /></td>
<td class="merged" rowspan="2"><span class="tag info">同城闪送</span></td>
<td class="merged amount" rowspan="2">¥260.00</td>
<td class="merged center" rowspan="2">2</td>
<td><span class="tag info">科研捐赠</span></td>
<td class="mono">RD20261006003</td>
<td class="mono">RD-CB-20261006-01</td>
<td>脐带</td>
<td class="merged" rowspan="2">2026-10-06 11:05:33</td>
<td class="merged" rowspan="2">
<button class="btn btn-op" type="button">编辑</button>
<button class="btn btn-op" type="button">取消关联</button>
</td>
</tr>
<tr>
<td><span class="tag info">科研捐赠</span></td>
<td class="mono">RD20261006003</td>
<td class="mono">RD-CB-20261006-02</td>
<td>脐带</td>
</tr>
<!-- 批次 D:空批次示意 -->
<tr>
<td class="center merged"><input type="checkbox" class="row-check" data-batch="D" /></td>
<td class="merged"><span class="tag success">专线车</span></td>
<td class="merged amount">¥500.00</td>
<td class="merged center">0</td>
<td colspan="3" style="color:var(--text-muted)">— 暂无关联样本 —</td>
<td class="merged">2026-10-05 18:20:00</td>
<td class="merged">
<button class="btn btn-op" type="button">编辑</button>
<button class="btn btn-op btn-main" type="button" style="color:#fff;background:var(--primary);border-color:var(--primary)">关联样本</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pager">
<span>共 4 笔运费 · 6 条样本明细</span>
<button class="btn btn-op" type="button" disabled>上一页</button>
<span>1 / 1</span>
<button class="btn btn-op" type="button" disabled>下一页</button>
</div>
</div>
<div class="legend">
<strong>Demo 说明:</strong>
浅色竖条标题对齐 Fresh Mint;批次 A 展示「专线车一次拉 3 个样本(含细胞存储 + 科研捐赠)」的合并单元格效果。
正式实现走 Vue + Element Plus,文件路径:
<code>docs/demos/2026-10-08-sample-freight-menu-demo.html</code>
</div>
</div>
</div>
</div>
<div class="mask" id="mask" onclick="if(event.target===this)closeDialog()">
<div class="dialog" role="dialog" aria-modal="true">
<header>
<h3>配置运费</h3>
<button class="btn btn-op" type="button" onclick="closeDialog()">关闭</button>
</header>
<div class="body">
<div class="tabs">
<button type="button" class="active" data-tab="create" onclick="switchTab(this)">新建运费</button>
<button type="button" data-tab="link" onclick="switchTab(this)">关联已有</button>
</div>
<div id="tab-create">
<div class="form-grid-2">
<div class="field">
<label>运输方式</label>
<input list="modes" placeholder="选择或输入(可自动入字典)" value="专线车" />
<datalist id="modes">
<option value="顺丰快递"></option>
<option value="专线车"></option>
<option value="同城闪送"></option>
</datalist>
</div>
<div class="field">
<label>运费(元)</label>
<input type="number" min="0" step="0.01" value="800" />
</div>
<div class="field full">
<label>勾选要挂上的样本(可混选细胞存储 / 科研捐赠)</label>
<div class="sample-pick">
<label><input type="checkbox" checked /> <span class="tag success">细胞存储</span> CP20261008001 · ZK-UC-20261008-01</label>
<label><input type="checkbox" checked /> <span class="tag info">科研捐赠</span> RD20261008012 · RD-PL-20261008-03</label>
<label><input type="checkbox" /> <span class="tag success">细胞存储</span> CP20261008009 · ZK-UC-20261008-09</label>
<label><input type="checkbox" /> <span class="tag info">科研捐赠</span> RD20261008001 · RD-CB-20261008-01</label>
</div>
</div>
</div>
</div>
<div id="tab-link" style="display:none;color:var(--text-muted);font-size:13px">
Demo 占位:此处为近期运费批次单选列表(方式 / 金额 / 关联数 / 时间),确定后挂到已选样本。
</div>
</div>
<footer>
<button class="btn" type="button" onclick="closeDialog()">取消</button>
<button class="btn btn-main" type="button" onclick="alert('Demo:调用 create/link API'); closeDialog()">确定应用</button>
</footer>
</div>
</div>
<script>
function openDialog() { document.getElementById('mask').classList.add('open') }
function closeDialog() { document.getElementById('mask').classList.remove('open') }
function switchTab(btn) {
document.querySelectorAll('.tabs button').forEach(b => b.classList.remove('active'))
btn.classList.add('active')
const create = btn.dataset.tab === 'create'
document.getElementById('tab-create').style.display = create ? '' : 'none'
document.getElementById('tab-link').style.display = create ? 'none' : ''
}
function refreshSel() {
const n = document.querySelectorAll('.row-check:checked').length
document.getElementById('selTip').textContent = '已选 ' + n + ' 项'
document.getElementById('btnUnlink').disabled = n === 0
}
document.querySelectorAll('.row-check').forEach(el => el.addEventListener('change', refreshSel))
</script>
</body>
</html>