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>
618 lines
21 KiB
HTML
618 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>运费信息 · 列表 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>
|