docs: add protocol-audit list-mode design and Excel-aligned demo.
Capture the confirmed flat sample-row list for cell-storage protocol audit, including co-storage rule and merged collect date. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
702c8a2f12
commit
8f00db9400
2 files changed
+656
No files matched your search
@@ -0,0 +1,470 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>协议单审核 · 列表模式(已定稿预览)</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;
|
||||
--danger: #dc2626;
|
||||
--danger-bg: #fef2f2;
|
||||
}
|
||||
* { 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: 100%; margin: 0 auto; padding: 24px 20px 80px; }
|
||||
.hero { max-width: 1100px; margin-bottom: 18px; }
|
||||
.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 0 8px; color: var(--text-muted); max-width: 920px; }
|
||||
.context-bar { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
||||
.pill {
|
||||
display: inline-flex; align-items: center;
|
||||
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); }
|
||||
|
||||
.decisions {
|
||||
max-width: 1100px;
|
||||
margin: 0 0 18px;
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 13px;
|
||||
}
|
||||
.decisions h3 { margin: 0 0 8px; font-size: 14px; }
|
||||
.decisions ul { margin: 0; padding-left: 18px; color: var(--text-secondary); }
|
||||
.decisions li { margin: 4px 0; }
|
||||
|
||||
.panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
.panel-top { padding: 14px 18px 10px; border-bottom: 1px solid var(--border); }
|
||||
.panel-top h2 { margin: 0; font-size: 15px; font-weight: 600; }
|
||||
.panel-top h2::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 1em;
|
||||
margin-right: 8px;
|
||||
vertical-align: -0.1em;
|
||||
border-radius: 2px;
|
||||
background: var(--primary);
|
||||
}
|
||||
.panel-top .sub { margin: 6px 0 0; font-size: 13px; color: var(--text-muted); }
|
||||
|
||||
.filter-block { padding: 12px 18px 8px; border-bottom: 1px solid var(--border); }
|
||||
.filter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
.field label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.field .ctrl {
|
||||
height: 36px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: #fff;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.sel { font-size: 13px; color: var(--text-secondary); margin-right: 4px; }
|
||||
.sel b { color: var(--primary-dark); }
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.btn.main {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.table-scroll { overflow-x: auto; }
|
||||
table.list {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
min-width: 1540px;
|
||||
}
|
||||
table.list th,
|
||||
table.list td {
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.list th {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
background: #f8fafc;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
table.list td.left { text-align: left; }
|
||||
table.list tr.batch-a td { background: #fff; }
|
||||
table.list tr.batch-b td { background: #fafbfc; }
|
||||
table.list tr:hover td { background: #f1f5f9 !important; }
|
||||
table.list tr.group-a { box-shadow: inset 3px 0 0 #10b981; }
|
||||
table.list tr.group-b { box-shadow: inset 3px 0 0 #94a3b8; }
|
||||
table.list tr.alert-row td { color: var(--danger); }
|
||||
|
||||
.mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.muted { color: var(--text-muted); }
|
||||
.name-strong { font-weight: 600; }
|
||||
|
||||
.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.success { background: #ecfdf5; color: #047857; border-color: #a7f3d0; }
|
||||
.tag.warning { background: #fffbeb; color: #b45309; border-color: #fde68a; }
|
||||
.tag.info { background: #f1f5f9; color: #64748b; border-color: var(--border); }
|
||||
|
||||
.op-row { display: inline-flex; flex-wrap: wrap; gap: 4px; justify-content: center; }
|
||||
.op {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 26px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 18px;
|
||||
border-top: 1px solid var(--border);
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.callout {
|
||||
margin-top: 16px;
|
||||
max-width: 1100px;
|
||||
padding: 12px 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--primary-bg);
|
||||
border: 1px solid #a7f3d0;
|
||||
font-size: 13px;
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
.note {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
max-width: 1100px;
|
||||
}
|
||||
.pay-multi {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="hero">
|
||||
<h1>协议单审核 · 列表模式(已定稿预览)</h1>
|
||||
<p>
|
||||
将 <code>/#/market/cell-storage/protocol-audit?tab=cell_storage</code> 从卡片改为
|
||||
<strong>纯扁平行列表</strong>(一行一样本,协议字段重复),列对齐客户 Excel。
|
||||
</p>
|
||||
<div class="context-bar">
|
||||
<span class="pill">纯扁平行 · 已确认</span>
|
||||
<span class="pill">年月日 → 采集日期 · 已确认</span>
|
||||
<span class="pill">是否同存 = 样本数 ≥ 2</span>
|
||||
<span class="pill muted">P02 · Fresh Mint</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="decisions">
|
||||
<h3>已确认决策</h3>
|
||||
<ul>
|
||||
<li><strong>行粒度:</strong>一行一样本;同一协议多样本占多行,协议字段重复显示(不做 rowspan)。</li>
|
||||
<li><strong>采集日期:</strong>原 Excel「年 / 月 / 日」合并为一列,取值自 <code>sampleCollectTime</code> 的日期部分(如 2025-09-15)。</li>
|
||||
<li><strong>采集时间:</strong>保留完整时间列(含时分)。</li>
|
||||
<li><strong>是否同存:</strong>计算列,不落库;同协议样本数 ≥ 2 →「是」,否则「否」(同协议各行相同)。</li>
|
||||
<li><strong>操作列:</strong>保留审核 / 查看 / 编辑 / 删除(Excel 无,系统需要);批量操作按协议 ID 去重。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-top">
|
||||
<h2>列表预览 · 纯扁平行</h2>
|
||||
<p class="sub">
|
||||
郭梦朵协议 ZKZX34000849 含 2 个样本 → 占两行,是否同存均为「是」;左侧绿条仅作示意分组(实现可不加)。
|
||||
</p>
|
||||
</div>
|
||||
<div class="filter-block">
|
||||
<div class="filter-grid">
|
||||
<div class="field"><label>关键词</label><div class="ctrl">姓名/身份证号/电话/入库编码</div></div>
|
||||
<div class="field"><label>协议状态</label><div class="ctrl">全部</div></div>
|
||||
<div class="field"><label>创建时间</label><div class="ctrl">开始日期 至 结束日期</div></div>
|
||||
<div class="field"><label>样本采集时间</label><div class="ctrl">开始日期 至 结束日期</div></div>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<span class="btn">重置</span>
|
||||
<span class="btn main">搜索</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<span class="sel">已选 <b>0</b> 项</span>
|
||||
<span class="btn main">新增</span>
|
||||
<span class="btn">退费登记</span>
|
||||
<span class="btn">审核</span>
|
||||
<span class="btn">协议生效</span>
|
||||
<span class="btn">协议过期</span>
|
||||
<span class="btn">协议作废</span>
|
||||
<span class="btn">协议转科研</span>
|
||||
<span class="btn">批量删除</span>
|
||||
</div>
|
||||
<div class="table-scroll">
|
||||
<table class="list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>选</th>
|
||||
<th>序号</th>
|
||||
<th>采集日期</th>
|
||||
<th>实验室流转编号</th>
|
||||
<th>母亲姓名</th>
|
||||
<th>协议编号</th>
|
||||
<th>样本类型</th>
|
||||
<th>是否同存</th>
|
||||
<th>采集地区</th>
|
||||
<th>采集医院</th>
|
||||
<th>采集时间</th>
|
||||
<th>缴费方式</th>
|
||||
<th>首期缴费时间</th>
|
||||
<th>市场专员</th>
|
||||
<th>地区经理</th>
|
||||
<th>运输方式</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="batch-a group-a">
|
||||
<td><input type="checkbox" /></td>
|
||||
<td>1</td>
|
||||
<td>2025-09-15</td>
|
||||
<td class="mono left">B0000004</td>
|
||||
<td class="name-strong">郭梦朵</td>
|
||||
<td class="mono left">ZKZX34000849</td>
|
||||
<td class="left">围产期造血干细胞</td>
|
||||
<td><span class="tag warning">是</span></td>
|
||||
<td>亳州区域</td>
|
||||
<td class="left">涡阳县人民医院</td>
|
||||
<td class="left">2025-09-15 08:51</td>
|
||||
<td><span class="tag success">趸交</span></td>
|
||||
<td class="left">2025-09-15 09:20</td>
|
||||
<td>马丽</td>
|
||||
<td>张颖</td>
|
||||
<td>专线车</td>
|
||||
<td>
|
||||
<div class="op-row">
|
||||
<span class="op">审核</span>
|
||||
<span class="op">查看</span>
|
||||
<span class="op">编辑</span>
|
||||
<span class="op">删除</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="batch-a group-a">
|
||||
<td><input type="checkbox" /></td>
|
||||
<td>2</td>
|
||||
<td>2025-09-15</td>
|
||||
<td class="mono left">B0000004A</td>
|
||||
<td class="name-strong">郭梦朵</td>
|
||||
<td class="mono left">ZKZX34000849</td>
|
||||
<td class="left">围产期间充质干细胞</td>
|
||||
<td><span class="tag warning">是</span></td>
|
||||
<td>亳州区域</td>
|
||||
<td class="left">涡阳县人民医院</td>
|
||||
<td class="left">2025-09-15 08:51</td>
|
||||
<td><span class="tag warning">分期</span></td>
|
||||
<td class="left">
|
||||
<div class="pay-multi">
|
||||
<span>2025-09-15 09:20</span>
|
||||
<span>2025-10-15 10:05</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>马丽</td>
|
||||
<td>张颖</td>
|
||||
<td>专线车</td>
|
||||
<td>
|
||||
<div class="op-row">
|
||||
<span class="op">审核</span>
|
||||
<span class="op">查看</span>
|
||||
<span class="op">编辑</span>
|
||||
<span class="op">删除</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="batch-b group-b alert-row">
|
||||
<td><input type="checkbox" /></td>
|
||||
<td>3</td>
|
||||
<td>2025-10-10</td>
|
||||
<td class="mono left muted">—</td>
|
||||
<td class="name-strong">邵悦</td>
|
||||
<td class="mono left">ZKZX34000861</td>
|
||||
<td class="left">围产期造血干细胞</td>
|
||||
<td><span class="tag info">否</span></td>
|
||||
<td>亳州区域</td>
|
||||
<td class="left">涡阳县中医院</td>
|
||||
<td class="left">2025-10-10 14:22</td>
|
||||
<td><span class="tag warning">分期</span></td>
|
||||
<td class="left">
|
||||
<div class="pay-multi">
|
||||
<span>2025-10-10 15:01</span>
|
||||
<span>2025-11-10 09:18</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>张慧茹</td>
|
||||
<td>张颖</td>
|
||||
<td>专线车</td>
|
||||
<td>
|
||||
<div class="op-row">
|
||||
<span class="op">审核</span>
|
||||
<span class="op">查看</span>
|
||||
<span class="op">编辑</span>
|
||||
<span class="op">删除</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="batch-a group-b">
|
||||
<td><input type="checkbox" /></td>
|
||||
<td>4</td>
|
||||
<td>2025-10-12</td>
|
||||
<td class="mono left">B0000007</td>
|
||||
<td class="name-strong">尹璐</td>
|
||||
<td class="mono left">ZKZX34000870</td>
|
||||
<td class="left">围产期造血干细胞</td>
|
||||
<td><span class="tag info">否</span></td>
|
||||
<td>亳州区域</td>
|
||||
<td class="left">利辛县人民医院</td>
|
||||
<td class="left">2025-10-12 11:05</td>
|
||||
<td><span class="tag success">趸交</span></td>
|
||||
<td class="left">2025-10-12 11:40</td>
|
||||
<td>马丽</td>
|
||||
<td>张颖</td>
|
||||
<td>专线车</td>
|
||||
<td>
|
||||
<div class="op-row">
|
||||
<span class="op">审核</span>
|
||||
<span class="op">查看</span>
|
||||
<span class="op">编辑</span>
|
||||
<span class="op">删除</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pager">共 3 条协议 / 4 行样本 · 接口分页仍按协议 · 前端 flatten · < 1 ></div>
|
||||
</div>
|
||||
|
||||
<div class="callout">
|
||||
<strong>实现要点:</strong>接口仍返回协议列表(含 <code>sampleItems[]</code>);前端 flatten 成样本行;无样本时仍保留 1 行(流转编号等为空)。
|
||||
勾选多样本行时,批量审核/删除按 <code>protocolId</code> 去重。
|
||||
</div>
|
||||
<p class="note">
|
||||
文件:<code>docs/demos/2026-10-09-protocol-audit-list-mode-demo.html</code>
|
||||
· 若本预览 OK,回复「设计没问题」或指出要改的点,我再写正式设计文档并进入实现计划。
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,186 @@
|
||||
# 协议单审核(细胞存储)卡片 → 列表模式 — 实现设计
|
||||
|
||||
> 日期:2026-10-09
|
||||
> 状态:已确认(2026-10-09)
|
||||
> 关联:
|
||||
> - 页面:`/#/market/cell-storage/protocol-audit?tab=cell_storage`
|
||||
> - 源码:`web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue`
|
||||
> - Demo:`docs/demos/2026-10-09-protocol-audit-list-mode-demo.html`
|
||||
> - 模式:P02 复杂业务列表(`docs/web-admin-ui-pattern-index.md`)
|
||||
> - 参考实现:样本运费 flatten(`web-admin/src/utils/sample-freight-flatten.ts`),本需求**不做 rowspan**
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与问题
|
||||
|
||||
「采集协议单」细胞存储 Tab 当前为卡片模式,一屏信息密度低、不利于按客户 Excel 习惯扫读与对照。
|
||||
|
||||
客户提供的 Excel 为**样本粒度扁平行**:一行对应一个实验室流转编号(样本);同一协议下多个样本占多行,协议级字段重复。系统中一个协议单对应 `sampleItems[]` 多条样本,需在列表中完整展开。
|
||||
|
||||
---
|
||||
|
||||
## 2. 已确认决策
|
||||
|
||||
| 项 | 结论 |
|
||||
|----|------|
|
||||
| 展示形态 | **纯扁平行**:一行一样本;协议字段重复;**不做 rowspan** |
|
||||
| 年 / 月 / 日 | 合并为一列 **采集日期**(取自 `sampleCollectTime` 的日期部分,格式 `YYYY-MM-DD`) |
|
||||
| 采集时间 | 保留完整时间列(含时分,沿用现有 `formatDisplayOrDash`) |
|
||||
| 是否同存 | **计算列,不落库**:同协议 `sampleItems.length >= 2` →「是」,否则「否」;同协议各样本行显示相同值 |
|
||||
| 无样本协议 | 仍占 **1 行**,样本相关列显示 `—` |
|
||||
| 分页 | 接口仍按**协议条数**分页;前端对当前页列表 flatten |
|
||||
| 勾选 / 批量 | 行可选;审核、状态变更、删除等按 `protocolId` **去重**后执行 |
|
||||
| 行内操作 | 审核 / 查看 / 编辑 / 删除(作用对象为协议,与现网一致) |
|
||||
| 范围 | 仅细胞存储 Tab(`ProtocolAuditTab`);科研捐赠 Tab 本轮不动 |
|
||||
| 筛选 / 工具栏 / 弹窗 | 沿用现有;不改审核、状态变更、表单、查看、退费业务规则 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 目标与非目标
|
||||
|
||||
### 3.1 目标
|
||||
|
||||
- 卡片列表改为 `el-table` 列表,列对齐客户 Excel(见 §4)。
|
||||
- 正确展示一协议多样本(多行)。
|
||||
- 保留现有筛选、工具栏批量操作与各类弹窗能力。
|
||||
|
||||
### 3.2 非目标
|
||||
|
||||
- 不改科研捐赠协议 Tab 的卡片/列表。
|
||||
- 不新增「是否同存」数据库字段或字典。
|
||||
- 不改为按样本分页(避免破坏现有协议列表 API 契约)。
|
||||
- 不做 Excel 导出(若后续需要另开需求)。
|
||||
- 不在本需求中扩展支付 summary 接口字段(优先用现有 `paymentSummary`;见 §5.3 缺口处理)。
|
||||
|
||||
---
|
||||
|
||||
## 4. UI 规格
|
||||
|
||||
### 4.1 页面骨架
|
||||
|
||||
与现网及 P02 一致:
|
||||
|
||||
1. `filter-block`(关键词、协议状态、创建时间、样本采集时间)
|
||||
2. `list-toolbar`(已选 N 项 + 新增 + 退费登记 + 审核 + 状态变更 + 批量删除)
|
||||
3. `list-body.list-wrap` → `el-table.list-table`(`stripe`,无 `border`)
|
||||
4. `pagination.list-pager-bar`(`total` 仍为协议总数)
|
||||
|
||||
视觉:Fresh Mint;状态/方式类字段用 `el-tag`;操作列 `fixed="right"`,`min-width` ≥ 260。
|
||||
|
||||
### 4.2 表格列(从左到右)
|
||||
|
||||
| 列 | 来源 | 说明 |
|
||||
|----|------|------|
|
||||
| 选择 | — | `type="selection"`;见 §5.2 去重 |
|
||||
| 序号 | 前端 | 当前页扁平行从 1 起的行号(仅展示用;翻页后重新从 1 计) |
|
||||
| 采集日期 | `sampleCollectTime` | 日期部分 `YYYY-MM-DD`;无则 `—` |
|
||||
| 实验室流转编号 | `sampleItems[].labStorageCode` | monospace;空则 `—` |
|
||||
| 母亲姓名 | `motherName` | |
|
||||
| 协议编号 | `protocolNo` | |
|
||||
| 样本类型 | `sampleItems[].sampleTypeName` | |
|
||||
| 是否同存 | 计算 | `el-tag`:是=`warning`,否=`info` |
|
||||
| 采集地区 | `regionName` | |
|
||||
| 采集医院 | `hospitalName` | |
|
||||
| 采集时间 | `sampleCollectTime` | 完整展示 |
|
||||
| 缴费方式 | `paymentSummary.payMode` | `el-tag` + `payModeLabel` / 注册表 label |
|
||||
| 首期缴费时间 | `paymentSummary` | 见 §5.3 |
|
||||
| 市场专员 | `salesUserName` | |
|
||||
| 地区经理 | `regionManagerUserName` | |
|
||||
| 运输方式 | `sampleItems[].transport` | `transportTypeLabel`;可附简短摘要(快递公司/专线等),避免过长 |
|
||||
| 操作 | — | 审核、查看、编辑、删除(协议级) |
|
||||
|
||||
双击行:打开协议查看弹窗(与现卡片 dblclick 一致)。
|
||||
|
||||
### 4.3 不在首期列表展示的卡片字段(刻意收缩)
|
||||
|
||||
卡片上的新生儿、接收结论、质检结论、缴费面板详情等**不进入首期列表列**,以免列宽失控;详情仍通过「查看」弹窗获取。若业务后续要求加列,另开变更。
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据与交互
|
||||
|
||||
### 5.1 Flatten
|
||||
|
||||
前端将 `CollectionProtocolRow[]` 展开为扁平行,建议工具函数(可新建 `utils/protocol-audit-flatten.ts`,风格对齐 `sample-freight-flatten.ts`):
|
||||
|
||||
```ts
|
||||
type ProtocolAuditFlatRow = {
|
||||
rowKey: string // `${protocolId}|${sampleCode||idx}`
|
||||
protocolId: string
|
||||
protocol: CollectionProtocolRow
|
||||
isCoStored: boolean // sampleCount >= 2
|
||||
sampleCount: number
|
||||
sampleIndex: number // 0-based within protocol
|
||||
sampleTypeName: string
|
||||
sampleCode: string
|
||||
labStorageCode: string
|
||||
transportType?: string
|
||||
transportSummary: string
|
||||
collectDate: string // YYYY-MM-DD
|
||||
// …展示所需其它从 protocol / sample 拷贝的字段
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- `sampleItems` 非空:每个 item 一行。
|
||||
- `sampleItems` 空:仍一行,样本字段空串 / `—`,`isCoStored = false`。
|
||||
- `是否同存`:基于该协议展开前的样本条数(空样本计 0 → 否)。
|
||||
|
||||
### 5.2 选择与批量
|
||||
|
||||
- 表格 `@selection-change` 得到扁平行集合。
|
||||
- 「已选 N 项」:**N = 去重后的协议数**(与批量语义一致),避免「选了 2 个样本显示已选 2 项但只对应 1 个协议」的歧义。
|
||||
- 批量审核 / 状态变更 / 删除:`unique protocols` by `protocolId`。
|
||||
- 退费登记:仍要求恰好 1 个协议(去重后长度为 1)。
|
||||
|
||||
### 5.3 缴费方式与首期缴费时间
|
||||
|
||||
- **缴费方式**:`paymentSummary.payMode` + label(`payModeLabel` 或 `payModeLabel()`)。
|
||||
- **首期缴费时间**:现有 list enrichment 的 `paymentDetails` 含 `installmentDueDate`,明细表有 `transfer_time` 但 summary **尚未透出**。
|
||||
- **首期实现(最小)**:展示首期(`installmentNo === 1` 或排序后第一条)的 `installmentDueDate`;多条已付/多期时,若仅有 dueDate,可多行叠展示 dueDate(与 Demo 中多行时间类似)。
|
||||
- **优选增强(同需求内若改动面小)**:在 `build_protocol_payment_summary` / `_payment_detail_row` 增加 `transferTime`,列表优先展示已转账时间,否则回退 dueDate。无 summary 时显示 `—`。
|
||||
|
||||
本设计允许「仅前端用现有字段」或「顺带透出 transferTime」;实现计划中二选一,优先小改动透出 `transferTime` 若触及后端文件很少。
|
||||
|
||||
### 5.4 运输方式
|
||||
|
||||
优先 `transportType` → `transportTypeLabel`;无运输信息显示 `—`。不强制拼完整运单摘要(列宽有限);完整信息在查看弹窗。
|
||||
|
||||
---
|
||||
|
||||
## 6. 后端
|
||||
|
||||
- **默认不改列表 API 契约**:继续 `GET` 协议分页 + 已有 `sampleItems` / `paymentSummary` enrichment。
|
||||
- **可选小改**:`paymentDetails[]` 增加 `transferTime`(见 §5.3),需同步 TS 类型与(若有)单测。
|
||||
- 不新增「是否同存」存储字段。
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试要点
|
||||
|
||||
- 0 / 1 / 2+ 样本协议:行数、是否同存、空样本行。
|
||||
- 采集日期与采集时间均来自同一 `sampleCollectTime`,空值显示 `—`。
|
||||
- 勾选同一协议两行样本 → 已选 1 项;批量删除只调一次该协议。
|
||||
- 筛选、分页、新增/编辑/查看/审核/状态变更/退费回归通过。
|
||||
- 列使用 `min-width`;操作列固定右侧不被裁切。
|
||||
|
||||
---
|
||||
|
||||
## 8. 文件影响(预期)
|
||||
|
||||
| 区域 | 文件 |
|
||||
|------|------|
|
||||
| 主改 | `web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue` |
|
||||
| 新增 | `web-admin/src/utils/protocol-audit-flatten.ts`(及可选 `*.test.ts`) |
|
||||
| 可选 | `backend/app/services/qc_work_order_fee_summary.py`、`web-admin/src/api/qc-work-order.ts`(`transferTime`) |
|
||||
| 文档 | 本设计;Demo 已存在 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 验收标准
|
||||
|
||||
1. 细胞存储协议审核页为表格列表,不再使用协议卡片流。
|
||||
2. 一协议多样本时,列表出现多行,协议字段重复,是否同存均为「是」。
|
||||
3. 列包含 §4.2 所列字段;年/月/日不以三列出现,而为「采集日期」。
|
||||
4. 现有工具栏与弹窗行为可用,批量操作按协议去重正确。
|
||||
Reference in new issue
Block a user