docs: add storage-cert QC info design and HTML demo.
Capture the confirmed sample-level JSON payload approach, field maps from the Excel sheets, and UI entry points for QC fill and market view. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
776031cb4c
commit
55ed0d8929
2 files changed
+1139
No files matched your search
@@ -0,0 +1,855 @@
|
||||
<!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;
|
||||
--border-mint: #d9e8e2;
|
||||
--radius: 10px;
|
||||
--radius-sm: 8px;
|
||||
--shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
|
||||
--warning: #d97706;
|
||||
--warning-bg: #fffbeb;
|
||||
--danger: #dc2626;
|
||||
--info: #64748b;
|
||||
}
|
||||
* { 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: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 20px 80px;
|
||||
}
|
||||
.hero { margin-bottom: 20px; max-width: 920px; }
|
||||
.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); }
|
||||
.context-bar { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
||||
.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);
|
||||
}
|
||||
|
||||
/* Scene tabs */
|
||||
.scene-tabs {
|
||||
display: flex; flex-wrap: wrap; gap: 8px;
|
||||
margin-bottom: 16px; padding: 4px;
|
||||
background: #fff; border: 1px solid var(--border); border-radius: var(--radius);
|
||||
}
|
||||
.scene-tab {
|
||||
border: 1px solid transparent; background: transparent;
|
||||
padding: 8px 14px; border-radius: 8px; cursor: pointer;
|
||||
font-size: 13px; font-weight: 500; color: var(--text-secondary);
|
||||
}
|
||||
.scene-tab:hover { background: #f8fafc; }
|
||||
.scene-tab.active {
|
||||
background: var(--primary-bg); color: var(--primary-dark);
|
||||
border-color: #a7f3d0;
|
||||
}
|
||||
.scene { display: none; }
|
||||
.scene.active { display: block; }
|
||||
|
||||
.section {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 18px 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.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 .hint { margin: 4px 0 0; font-size: 12px; color: var(--text-muted); }
|
||||
|
||||
.note {
|
||||
padding: 10px 12px; border-radius: 8px;
|
||||
background: var(--warning-bg); border: 1px solid #fde68a;
|
||||
color: #92400e; font-size: 13px; margin-bottom: 14px;
|
||||
}
|
||||
.note.ok {
|
||||
background: var(--primary-bg); border-color: #a7f3d0; color: var(--primary-dark);
|
||||
}
|
||||
|
||||
/* QC card mock */
|
||||
.protocol-bar {
|
||||
display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: center;
|
||||
padding: 10px 12px; background: #f8fafc; border: 1px solid var(--border);
|
||||
border-radius: 8px; margin-bottom: 12px; font-size: 13px;
|
||||
}
|
||||
.protocol-bar strong { color: var(--text); }
|
||||
.sample-pills { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
|
||||
.sample-pill {
|
||||
padding: 6px 12px; border-radius: 999px; font-size: 12px;
|
||||
border: 1px solid var(--border); background: #fff; color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.sample-pill.active {
|
||||
border-color: var(--primary); background: var(--primary-bg); color: var(--primary-dark);
|
||||
font-weight: 600;
|
||||
}
|
||||
.wo-card {
|
||||
border: 1px solid var(--border-mint);
|
||||
border-radius: 10px;
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
}
|
||||
.wo-card-top {
|
||||
display: flex; flex-wrap: wrap; justify-content: space-between;
|
||||
gap: 10px; margin-bottom: 10px;
|
||||
}
|
||||
.wo-meta { font-size: 13px; color: var(--text-secondary); }
|
||||
.wo-meta .code { font-weight: 600; color: var(--text); }
|
||||
.tag {
|
||||
display: inline-flex; align-items: center;
|
||||
padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 500;
|
||||
}
|
||||
.tag.success { background: var(--primary-bg); color: var(--primary-dark); }
|
||||
.tag.warning { background: var(--warning-bg); color: var(--warning); }
|
||||
.tag.info { background: #f1f5f9; color: var(--info); }
|
||||
.wo-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
height: 32px; padding: 0 12px; border-radius: 8px;
|
||||
border: 1px solid var(--border); background: #fff;
|
||||
font-size: 13px; color: var(--text-secondary); cursor: pointer;
|
||||
}
|
||||
.btn:hover { border-color: #94a3b8; }
|
||||
.btn.primary {
|
||||
background: var(--primary); border-color: var(--primary);
|
||||
color: #fff; font-weight: 500;
|
||||
}
|
||||
.btn.primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
|
||||
.btn.accent {
|
||||
background: var(--primary-bg); border-color: #a7f3d0;
|
||||
color: var(--primary-dark); font-weight: 500;
|
||||
}
|
||||
.btn.ghost { background: transparent; }
|
||||
.btn.sm { height: 28px; padding: 0 10px; font-size: 12px; }
|
||||
.btn-icon {
|
||||
width: 14px; height: 14px; display: inline-block;
|
||||
border: 1.5px solid currentColor; border-radius: 3px; opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Sample info table mock */
|
||||
.toolbar {
|
||||
display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.toolbar .selected { font-size: 13px; color: var(--text-muted); margin-right: 4px; }
|
||||
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 8px; }
|
||||
table.list {
|
||||
width: 100%; border-collapse: collapse; min-width: 900px;
|
||||
}
|
||||
table.list th, table.list td {
|
||||
padding: 10px 12px; text-align: left; border-bottom: 1px solid var(--border);
|
||||
font-size: 13px; white-space: nowrap;
|
||||
}
|
||||
table.list th {
|
||||
background: #f8fafc; color: var(--text-muted); font-weight: 600; font-size: 12px;
|
||||
}
|
||||
table.list tr:nth-child(even) td { background: #fafafa; }
|
||||
table.list tr:hover td { background: #f1f5f9; }
|
||||
table.list tr.highlight td { background: #ecfdf5; }
|
||||
.ops { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
|
||||
/* Dialog */
|
||||
.dialog-mask {
|
||||
position: fixed; inset: 0; background: rgba(15, 23, 42, 0.45);
|
||||
display: none; align-items: flex-start; justify-content: center;
|
||||
padding: 40px 16px; z-index: 50; overflow-y: auto;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
.dialog-mask.open { display: flex; }
|
||||
.dialog {
|
||||
width: min(920px, 100%);
|
||||
background: #fff; border-radius: 12px;
|
||||
box-shadow: 0 20px 40px rgba(15, 23, 42, 0.18);
|
||||
display: flex; flex-direction: column;
|
||||
max-height: calc(100vh - 80px);
|
||||
}
|
||||
.dialog.wide { width: min(1020px, 100%); }
|
||||
.dialog-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 16px 20px; border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dialog-header h3 { margin: 0; font-size: 16px; font-weight: 600; }
|
||||
.dialog-header .sub {
|
||||
margin: 4px 0 0; font-size: 12px; color: var(--text-muted); font-weight: 400;
|
||||
}
|
||||
.dialog-close {
|
||||
width: 32px; height: 32px; border: none; background: #f1f5f9;
|
||||
border-radius: 8px; cursor: pointer; font-size: 18px; color: var(--text-muted);
|
||||
}
|
||||
.dialog-body {
|
||||
padding: 16px 20px; overflow-y: auto; flex: 1;
|
||||
}
|
||||
.dialog-footer {
|
||||
display: flex; justify-content: flex-end; gap: 8px;
|
||||
padding: 12px 20px; border-top: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ctx-row {
|
||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
|
||||
margin-bottom: 16px; padding: 12px; background: #f8fafc;
|
||||
border: 1px solid var(--border); border-radius: 8px;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.ctx-row { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
.ctx-item label {
|
||||
display: block; font-size: 11px; color: var(--text-muted); margin-bottom: 2px;
|
||||
}
|
||||
.ctx-item .val { font-size: 13px; font-weight: 500; color: var(--text); }
|
||||
|
||||
.field-group {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.field-group-title {
|
||||
padding: 10px 14px;
|
||||
background: var(--primary-bg);
|
||||
color: var(--primary-dark);
|
||||
font-size: 13px; font-weight: 600;
|
||||
border-bottom: 1px solid #a7f3d0;
|
||||
}
|
||||
.field-group-title.alt { background: #f1f5f9; color: var(--text-secondary); border-bottom-color: var(--border); }
|
||||
.field-group-title.meta { background: #fffbeb; color: #92400e; border-bottom-color: #fde68a; }
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px 14px;
|
||||
padding: 14px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.form-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
@media (max-width: 560px) {
|
||||
.form-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
.form-item { display: flex; flex-direction: column; gap: 4px; }
|
||||
.form-item.span-2 { grid-column: span 2; }
|
||||
.form-item.span-3 { grid-column: 1 / -1; }
|
||||
.form-item label {
|
||||
font-size: 11px; color: var(--text-muted); font-weight: 500;
|
||||
}
|
||||
.form-item input, .form-item select, .form-item textarea {
|
||||
height: 36px; padding: 0 10px;
|
||||
border: 1px solid var(--border); border-radius: 8px;
|
||||
font-size: 13px; color: var(--text); background: #fff;
|
||||
font-family: inherit;
|
||||
}
|
||||
.form-item textarea {
|
||||
height: auto; min-height: 72px; padding: 8px 10px; resize: vertical;
|
||||
}
|
||||
.form-item input:disabled, .form-item textarea:disabled, .form-item select:disabled {
|
||||
background: #f8fafc; color: var(--text-secondary);
|
||||
}
|
||||
.form-item.readonly .val-box {
|
||||
min-height: 36px; padding: 8px 10px;
|
||||
border: 1px solid var(--border); border-radius: 8px;
|
||||
background: #f8fafc; font-size: 13px; color: var(--text-secondary);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.type-switch {
|
||||
display: inline-flex; gap: 6px; margin-bottom: 12px;
|
||||
}
|
||||
.field-count {
|
||||
font-size: 12px; color: var(--text-muted);
|
||||
}
|
||||
|
||||
.flow {
|
||||
display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
|
||||
font-size: 13px; color: var(--text-secondary);
|
||||
}
|
||||
.flow .step {
|
||||
padding: 6px 10px; background: #fff; border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.flow .arrow { color: var(--text-muted); }
|
||||
|
||||
.legend {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.legend { grid-template-columns: 1fr; }
|
||||
}
|
||||
.legend-card {
|
||||
border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
|
||||
background: #fff;
|
||||
}
|
||||
.legend-card h4 { margin: 0 0 8px; font-size: 14px; }
|
||||
.legend-card ul {
|
||||
margin: 0; padding-left: 18px; color: var(--text-secondary); font-size: 12px;
|
||||
}
|
||||
.legend-card li { margin-bottom: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="hero">
|
||||
<h1>储存证质检信息 · Demo</h1>
|
||||
<p>
|
||||
质检在「细胞存储质检」工单卡片上<strong>填写</strong>储存证质检信息(字段对齐 Excel 两个 sheet);
|
||||
市场在「样本信息管理 · 细胞存储」上<strong>查看</strong>同一份数据,用于誊写到储存证。
|
||||
</p>
|
||||
<div class="context-bar">
|
||||
<span class="pill">Excel:围产期造血干细胞</span>
|
||||
<span class="pill">Excel:脐带间充质干细胞</span>
|
||||
<span class="pill muted">质检工单 / 我的质检任务</span>
|
||||
<span class="pill muted">样本信息管理</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>业务链路(示意)</h2>
|
||||
<p class="hint">当前 Demo 不改业务逻辑,只对齐交互与字段分组</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<span class="step">质检完成检测</span>
|
||||
<span class="arrow">→</span>
|
||||
<span class="step" style="border-color:#a7f3d0;background:var(--primary-bg);color:var(--primary-dark);font-weight:600;">填写储存证质检信息</span>
|
||||
<span class="arrow">→</span>
|
||||
<span class="step">市场「样本信息」查看</span>
|
||||
<span class="arrow">→</span>
|
||||
<span class="step">誊写 / 办理储存证</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scene-tabs" role="tablist">
|
||||
<button type="button" class="scene-tab active" data-scene="overview">① 字段对照</button>
|
||||
<button type="button" class="scene-tab" data-scene="qc-card">② 质检卡片入口</button>
|
||||
<button type="button" class="scene-tab" data-scene="fill-hsc">③ 填写 · 围产期造血</button>
|
||||
<button type="button" class="scene-tab" data-scene="fill-msc">④ 填写 · 脐带间充质</button>
|
||||
<button type="button" class="scene-tab" data-scene="sample-info">⑤ 样本信息查看入口</button>
|
||||
</div>
|
||||
|
||||
<!-- ① overview -->
|
||||
<div class="scene active" id="scene-overview">
|
||||
<div class="note ok">
|
||||
两个 sheet 共用「身份键 + 母亲血检」骨架,检测报告区字段按样本类型分叉;右侧收尾字段也略有差异。
|
||||
</div>
|
||||
<div class="legend">
|
||||
<div class="legend-card">
|
||||
<h4>围产期造血干细胞(约 34 列)</h4>
|
||||
<ul>
|
||||
<li><strong>身份</strong>:序号、实验室流转编码、协议编码、母亲姓名</li>
|
||||
<li><strong>采集运输</strong>:采集时间、运输温度</li>
|
||||
<li><strong>造血干细胞检测报告</strong>:接收/处理/冻存时间、重量、冻存体积、TNC、CD34+、CD34+CD45dim、活性、细菌、HBsAg/HCV/TP/HIV、CMV-IgM</li>
|
||||
<li><strong>母亲血液检测报告</strong>:HBsAg、HBV DNA、抗HCV、HCV RNA、抗TP、CMV IgM、抗HIV、HIV-1RNA</li>
|
||||
<li><strong>收尾</strong>:HLA 分型、储存日期、储存年限、检测结果评论、报告发出日期</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="legend-card">
|
||||
<h4>脐带间充质干细胞(约 34 列)</h4>
|
||||
<ul>
|
||||
<li><strong>额外</strong>:是否邮寄</li>
|
||||
<li><strong>身份</strong>:序号、实验室流转编码、协议编码、母亲姓名</li>
|
||||
<li><strong>采集运输</strong>:脐带采集时间、运输温度</li>
|
||||
<li><strong>脐带间充质检测报告</strong>:接收/处理/冻存时间、储存总量(管)、可提供细胞类型/数量/存活率、内毒素/细菌/真菌/支原体、形态、免疫表型</li>
|
||||
<li><strong>母亲血液检测报告</strong>:与造血 sheet 同一组 8 项</li>
|
||||
<li><strong>收尾</strong>:检测结果评判、报告发出日期、储存日期(P2)、冷冻方式、储存温度、储存年限</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" style="margin-top:16px;">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>Demo 建议排除出「质检填写」的列</h2>
|
||||
<p class="hint">这些更像列表管理列 / 市场侧信息,可在澄清时确认</p>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin:0;font-size:13px;color:var(--text-secondary);">
|
||||
建议默认<strong>只读预填、不让质检手填</strong>:序号、协议编码、母亲姓名(从样本/协议带出)。
|
||||
「是否邮寄」偏市场物流,是否纳入质检表待确认。
|
||||
「实验室流转编码」是否等于系统样本编码 / 入库编码,待确认。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ② qc card -->
|
||||
<div class="scene" id="scene-qc-card">
|
||||
<div class="section">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>质检工单 / 我的质检任务 · 细胞存储质检</h2>
|
||||
<p class="hint">在工单卡片操作区新增「填写储存证质检信息」(示意高亮)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note">入口放在<strong>工单卡片</strong>(非任务子卡「填写结果」),因为储存证信息是样本级整表,不是单项检测任务结果。</div>
|
||||
|
||||
<div class="protocol-bar">
|
||||
<span>协议:<strong>ZKZX34000852</strong></span>
|
||||
<span>客户:高居巧</span>
|
||||
<span>来源:细胞存储</span>
|
||||
</div>
|
||||
<div class="sample-pills">
|
||||
<span class="sample-pill active">B0000002 · 围产期造血干细胞</span>
|
||||
<span class="sample-pill">B0000002-MSC · 脐带间充质(示意)</span>
|
||||
</div>
|
||||
<div class="wo-card">
|
||||
<div class="wo-card-top">
|
||||
<div class="wo-meta">
|
||||
<div class="code">入库质检工单 WO-2026-1008-012</div>
|
||||
<div style="margin-top:4px;">
|
||||
<span class="tag success">已完成</span>
|
||||
<span class="tag info" style="margin-left:6px;">结论:通过</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-count">样本类型 → 决定弹窗字段模板</div>
|
||||
</div>
|
||||
<div class="wo-actions">
|
||||
<button type="button" class="btn">查看</button>
|
||||
<button type="button" class="btn">特殊样本</button>
|
||||
<button type="button" class="btn accent" onclick="openFill('hsc')">
|
||||
<span class="btn-icon"></span> 填写储存证质检信息
|
||||
</button>
|
||||
<button type="button" class="btn ghost">更多</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="height:14px;"></div>
|
||||
<div class="wo-card" style="opacity:0.92;">
|
||||
<div class="wo-card-top">
|
||||
<div class="wo-meta">
|
||||
<div class="code">入库质检工单 WO-2026-1009-003</div>
|
||||
<div style="margin-top:4px;">
|
||||
<span class="tag warning">进行中</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wo-actions">
|
||||
<button type="button" class="btn primary">配置检测</button>
|
||||
<button type="button" class="btn accent" onclick="openFill('msc')">
|
||||
<span class="btn-icon"></span> 填写储存证质检信息
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ③ fill HSC summary (opens dialog) -->
|
||||
<div class="scene" id="scene-fill-hsc">
|
||||
<div class="section">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>填写弹窗 · 围产期造血干细胞</h2>
|
||||
<p class="hint">分组 = Excel 合并表头;三列栅格;长文本(HLA / 评论)跨列</p>
|
||||
</div>
|
||||
<button type="button" class="btn primary" onclick="openFill('hsc')">打开填写弹窗</button>
|
||||
</div>
|
||||
<p style="margin:0;font-size:13px;color:var(--text-muted);">
|
||||
字段按 Excel sheet「围产期造血干细胞」展开:造血检测报告 15 项 + 母亲血检 8 项 + HLA/储存/评论/报告日。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ④ fill MSC -->
|
||||
<div class="scene" id="scene-fill-msc">
|
||||
<div class="section">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>填写弹窗 · 脐带间充质干细胞</h2>
|
||||
<p class="hint">与造血模板共用母亲血检区块;检测报告与收尾字段不同</p>
|
||||
</div>
|
||||
<button type="button" class="btn primary" onclick="openFill('msc')">打开填写弹窗</button>
|
||||
</div>
|
||||
<p style="margin:0;font-size:13px;color:var(--text-muted);">
|
||||
含「是否邮寄」、储存总量(管)、可提供细胞*、内毒素/真菌/支原体、冷冻方式、储存温度等。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ⑤ sample info -->
|
||||
<div class="scene" id="scene-sample-info">
|
||||
<div class="section">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>样本信息管理 · 细胞存储 Tab</h2>
|
||||
<p class="hint">行操作增加「查看储存证的质检信息」;无数据时按钮可禁用或提示未填写</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<span class="selected">已选 0 项</span>
|
||||
<button type="button" class="btn primary">登记入库</button>
|
||||
<button type="button" class="btn">登记储存证</button>
|
||||
<button type="button" class="btn">登记保险</button>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>样本编码</th>
|
||||
<th>协议编码</th>
|
||||
<th>母亲姓名</th>
|
||||
<th>样本类型</th>
|
||||
<th>质检</th>
|
||||
<th>储存证</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="highlight">
|
||||
<td>B0000002</td>
|
||||
<td>ZKZX34000852</td>
|
||||
<td>高居巧</td>
|
||||
<td>围产期造血干细胞</td>
|
||||
<td><span class="tag success">已完成</span></td>
|
||||
<td><span class="tag warning">未办理</span></td>
|
||||
<td>
|
||||
<div class="ops">
|
||||
<button type="button" class="btn sm">查看</button>
|
||||
<button type="button" class="btn sm accent" onclick="openView('hsc')">查看储存证的质检信息</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>BC0000003</td>
|
||||
<td>ZKQD34000831</td>
|
||||
<td>牛晨晨</td>
|
||||
<td>脐带间充质干细胞</td>
|
||||
<td><span class="tag success">已完成</span></td>
|
||||
<td><span class="tag success">已办理</span></td>
|
||||
<td>
|
||||
<div class="ops">
|
||||
<button type="button" class="btn sm">查看</button>
|
||||
<button type="button" class="btn sm accent" onclick="openView('msc')">查看储存证的质检信息</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>B0000099</td>
|
||||
<td>ZKZX34000999</td>
|
||||
<td>示例未填</td>
|
||||
<td>围产期造血干细胞</td>
|
||||
<td><span class="tag warning">进行中</span></td>
|
||||
<td><span class="tag info">—</span></td>
|
||||
<td>
|
||||
<div class="ops">
|
||||
<button type="button" class="btn sm">查看</button>
|
||||
<button type="button" class="btn sm" disabled style="opacity:0.45;cursor:not-allowed;">查看储存证的质检信息</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fill dialog -->
|
||||
<div class="dialog-mask" id="fillMask" onclick="maskClose(event,'fillMask')">
|
||||
<div class="dialog wide" role="dialog" aria-modal="true">
|
||||
<div class="dialog-header">
|
||||
<div>
|
||||
<h3 id="fillTitle">填写储存证质检信息</h3>
|
||||
<div class="sub" id="fillSub">按样本类型切换字段模板</div>
|
||||
</div>
|
||||
<button type="button" class="dialog-close" onclick="closeMask('fillMask')" aria-label="关闭">×</button>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<div class="ctx-row">
|
||||
<div class="ctx-item"><label>实验室流转编码</label><div class="val" id="ctxLab">B0000002</div></div>
|
||||
<div class="ctx-item"><label>协议编码</label><div class="val" id="ctxProto">ZKZX34000852</div></div>
|
||||
<div class="ctx-item"><label>母亲姓名</label><div class="val" id="ctxMother">高居巧</div></div>
|
||||
<div class="ctx-item"><label>样本类型</label><div class="val" id="ctxType">围产期造血干细胞</div></div>
|
||||
</div>
|
||||
|
||||
<div id="formHsc">
|
||||
<div class="field-group">
|
||||
<div class="field-group-title alt">采集与运输</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>造血干细胞采集时间</label><input value="2025年9月7日15时36分" /></div>
|
||||
<div class="form-item"><label>运输温度 /℃</label><input value="17" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<div class="field-group-title">造血干细胞检测报告</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>造血干细胞接收时间</label><input value="2025.9.8 9:00" /></div>
|
||||
<div class="form-item"><label>处理时间</label><input value="2025.9.8 9:30" /></div>
|
||||
<div class="form-item"><label>冻存时间</label><input value="2025.9.8 11:57" /></div>
|
||||
<div class="form-item"><label>造血干细胞重量/g</label><input value="56" /></div>
|
||||
<div class="form-item"><label>造血干细胞冻存体积/mL</label><input value="21.8" /></div>
|
||||
<div class="form-item"><label>有核细胞数 TNC / *10E8</label><input value="2.28" /></div>
|
||||
<div class="form-item"><label>CD34+ 细胞计数 / *10E6</label><input value="16.3" /></div>
|
||||
<div class="form-item"><label>CD34+ CD45dim / *10E5</label><input value="1.27" /></div>
|
||||
<div class="form-item"><label>细胞活性/%</label><input value="90.44" /></div>
|
||||
<div class="form-item"><label>细菌检测</label><select><option>阴</option><option>阳</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>乙型肝炎病毒表面抗原(HBsAg)</label><select><option>阴</option><option>阳</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>丙型肝炎病毒抗体(抗HCV)</label><select><option>阴</option><option>阳</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>梅毒螺旋体特异性抗体(抗TP)</label><select><option>阴</option><option>阳</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>人类免疫缺陷病毒抗体(抗HIV-1/2)</label><select><option>阴</option><option>阳</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>巨细胞病毒 IgM 抗体 (CMV-IgM)</label><select><option>阴</option><option>阳</option><option>/</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<div class="field-group-title">母亲血液检测报告</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>乙型肝炎病毒表面抗原(HBsAg)</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>乙型肝炎病毒核酸(HBV DNA)</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>丙型肝炎病毒抗体(抗HCV)</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>丙型肝炎病毒核酸(HCV RNA)</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>梅毒螺旋体特异性抗体(抗TP)</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>巨细胞病毒 IgM 抗体 (CMV IgM)</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>人类免疫缺陷病毒抗体(抗HIV-1/2)</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>人类免疫缺陷病毒核酸(1型) (HIV-1RNA)</label><select><option>阴</option><option>/</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<div class="field-group-title meta">储存证收尾信息</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item span-3"><label>HLA 分型</label><textarea>A 02:06/24:02
|
||||
B 07:02(M)/57:01(T)
|
||||
C 06:02/07:02
|
||||
DRB1 13:01/15:01
|
||||
DQB1 06:02/06:03</textarea></div>
|
||||
<div class="form-item"><label>储存日期</label><input placeholder="YYYY-MM-DD" /></div>
|
||||
<div class="form-item"><label>储存年限</label><input value="20" /></div>
|
||||
<div class="form-item"><label>检测结果评论</label><input value="合格" /></div>
|
||||
<div class="form-item"><label>报告发出日期</label><input placeholder="YYYY-MM-DD" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="formMsc" style="display:none;">
|
||||
<div class="field-group">
|
||||
<div class="field-group-title alt">采集与运输 / 邮寄</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>是否邮寄</label><select><option>是</option><option selected>否</option></select></div>
|
||||
<div class="form-item"><label>脐带采集时间</label><input value="2025.11.25 15:49" /></div>
|
||||
<div class="form-item"><label>运输温度 /℃</label><input value="" placeholder="可填「常温」或数值" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<div class="field-group-title">脐带间充质干细胞检测报告</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>脐带接收时间</label><input value="2025.11.26 10:00" /></div>
|
||||
<div class="form-item"><label>处理时间</label><input value="2025.11.26 16:00" /></div>
|
||||
<div class="form-item"><label>冻存时间</label><input value="2025.12.21" /></div>
|
||||
<div class="form-item"><label>储存总量(管)</label><input value="17" placeholder="Excel 中有 =0+17 公式" /></div>
|
||||
<div class="form-item"><label>可提供细胞类型</label><input value="间充质干细胞" /></div>
|
||||
<div class="form-item"><label>可提供细胞数量 / *10E7</label><input value="8.5" /></div>
|
||||
<div class="form-item"><label>可提供细胞存活率 %</label><input value="97.1" /></div>
|
||||
<div class="form-item"><label>内毒素检测</label><select><option>阴</option><option>阳</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>细菌检测</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>真菌检测</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>支原体检测</label><select><option>阴</option><option>/</option></select></div>
|
||||
<div class="form-item"><label>可提供细胞形态</label><input value="短梭/贴壁型" /></div>
|
||||
<div class="form-item span-2"><label>可提供细胞免疫表型特征</label><input value="符合间充质干细胞表型特性" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<div class="field-group-title">母亲血液检测报告</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>乙型肝炎病毒表面抗原(HBsAg)</label><select><option>阴</option></select></div>
|
||||
<div class="form-item"><label>乙型肝炎病毒核酸(HBV DNA)</label><select><option>/</option><option>阴</option></select></div>
|
||||
<div class="form-item"><label>丙型肝炎病毒抗体(抗HCV)</label><select><option>阴</option></select></div>
|
||||
<div class="form-item"><label>丙型肝炎病毒核酸(HCV RNA)</label><select><option>/</option><option>阴</option></select></div>
|
||||
<div class="form-item"><label>梅毒螺旋体特异性抗体(抗TP)</label><select><option>阴</option></select></div>
|
||||
<div class="form-item"><label>巨细胞病毒 IgM 抗体 (CMV IgM)</label><select><option>阴</option></select></div>
|
||||
<div class="form-item"><label>人类免疫缺陷病毒抗体(抗HIV-1/2)</label><select><option>阴</option></select></div>
|
||||
<div class="form-item"><label>人类免疫缺陷病毒核酸(1型) (HIV-1RNA)</label><select><option>阴</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<div class="field-group-title meta">储存证收尾信息</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>检测结果评判</label><input value="合格" /></div>
|
||||
<div class="form-item"><label>报告发出日期</label><input value="2026-03-14" /></div>
|
||||
<div class="form-item"><label>储存日期 (P2冻存日期)</label><input value="2025.12.22" /></div>
|
||||
<div class="form-item"><label>冷冻方式</label><input value="程序降温盒降温" /></div>
|
||||
<div class="form-item"><label>储存温度</label><input value="-196℃" /></div>
|
||||
<div class="form-item"><label>储存年限</label><input value="20" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button type="button" class="btn" onclick="closeMask('fillMask')">取消</button>
|
||||
<button type="button" class="btn primary" onclick="alert('Demo:保存成功(未接后端)'); closeMask('fillMask');">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View dialog -->
|
||||
<div class="dialog-mask" id="viewMask" onclick="maskClose(event,'viewMask')">
|
||||
<div class="dialog wide" role="dialog" aria-modal="true">
|
||||
<div class="dialog-header">
|
||||
<div>
|
||||
<h3>查看储存证的质检信息</h3>
|
||||
<div class="sub" id="viewSub">只读 · 字段与质检填写一致</div>
|
||||
</div>
|
||||
<button type="button" class="dialog-close" onclick="closeMask('viewMask')" aria-label="关闭">×</button>
|
||||
</div>
|
||||
<div class="dialog-body" id="viewBody">
|
||||
<!-- filled by JS -->
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button type="button" class="btn primary" onclick="closeMask('viewMask')">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Scene tabs
|
||||
document.querySelectorAll('.scene-tab').forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
document.querySelectorAll('.scene-tab').forEach(function (t) { t.classList.remove('active'); });
|
||||
document.querySelectorAll('.scene').forEach(function (s) { s.classList.remove('active'); });
|
||||
tab.classList.add('active');
|
||||
document.getElementById('scene-' + tab.dataset.scene).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
function closeMask(id) {
|
||||
document.getElementById(id).classList.remove('open');
|
||||
}
|
||||
function maskClose(e, id) {
|
||||
if (e.target.id === id) closeMask(id);
|
||||
}
|
||||
|
||||
function openFill(type) {
|
||||
var isHsc = type === 'hsc';
|
||||
document.getElementById('formHsc').style.display = isHsc ? '' : 'none';
|
||||
document.getElementById('formMsc').style.display = isHsc ? 'none' : '';
|
||||
document.getElementById('fillTitle').textContent = '填写储存证质检信息';
|
||||
document.getElementById('fillSub').textContent = isHsc
|
||||
? '模板:围产期造血干细胞(对齐 Excel sheet)'
|
||||
: '模板:脐带间充质干细胞(对齐 Excel sheet)';
|
||||
document.getElementById('ctxLab').textContent = isHsc ? 'B0000002' : 'BC0000003';
|
||||
document.getElementById('ctxProto').textContent = isHsc ? 'ZKZX34000852' : 'ZKQD34000831';
|
||||
document.getElementById('ctxMother').textContent = isHsc ? '高居巧' : '牛晨晨';
|
||||
document.getElementById('ctxType').textContent = isHsc ? '围产期造血干细胞' : '脐带间充质干细胞';
|
||||
document.getElementById('fillMask').classList.add('open');
|
||||
}
|
||||
|
||||
function ro(label, val, span) {
|
||||
var cls = 'form-item readonly' + (span ? ' span-' + span : '');
|
||||
return '<div class="' + cls + '"><label>' + label + '</label><div class="val-box">' + (val || '—') + '</div></div>';
|
||||
}
|
||||
|
||||
function openView(type) {
|
||||
var isHsc = type === 'hsc';
|
||||
document.getElementById('viewSub').textContent = isHsc
|
||||
? '样本 B0000002 · 围产期造血干细胞 · 只读'
|
||||
: '样本 BC0000003 · 脐带间充质干细胞 · 只读';
|
||||
var html = '';
|
||||
html += '<div class="ctx-row">' +
|
||||
'<div class="ctx-item"><label>实验室流转编码</label><div class="val">' + (isHsc ? 'B0000002' : 'BC0000003') + '</div></div>' +
|
||||
'<div class="ctx-item"><label>协议编码</label><div class="val">' + (isHsc ? 'ZKZX34000852' : 'ZKQD34000831') + '</div></div>' +
|
||||
'<div class="ctx-item"><label>母亲姓名</label><div class="val">' + (isHsc ? '高居巧' : '牛晨晨') + '</div></div>' +
|
||||
'<div class="ctx-item"><label>样本类型</label><div class="val">' + (isHsc ? '围产期造血干细胞' : '脐带间充质干细胞') + '</div></div>' +
|
||||
'</div>';
|
||||
|
||||
if (isHsc) {
|
||||
html += '<div class="field-group"><div class="field-group-title alt">采集与运输</div><div class="form-grid">' +
|
||||
ro('造血干细胞采集时间', '2025年9月7日15时36分') +
|
||||
ro('运输温度 /℃', '17') +
|
||||
'</div></div>';
|
||||
html += '<div class="field-group"><div class="field-group-title">造血干细胞检测报告</div><div class="form-grid">' +
|
||||
ro('造血干细胞接收时间', '2025.9.8 9:00') +
|
||||
ro('处理时间', '2025.9.8 9:30') +
|
||||
ro('冻存时间', '2025.9.8 11:57') +
|
||||
ro('造血干细胞重量/g', '56') +
|
||||
ro('造血干细胞冻存体积/mL', '21.8') +
|
||||
ro('有核细胞数 TNC / *10E8', '2.28') +
|
||||
ro('CD34+ 细胞计数 / *10E6', '16.3') +
|
||||
ro('CD34+ CD45dim / *10E5', '1.27') +
|
||||
ro('细胞活性/%', '90.44') +
|
||||
ro('细菌检测', '阴') +
|
||||
ro('乙型肝炎病毒表面抗原(HBsAg)', '阴') +
|
||||
ro('丙型肝炎病毒抗体(抗HCV)', '阴') +
|
||||
ro('梅毒螺旋体特异性抗体(抗TP)', '阴') +
|
||||
ro('人类免疫缺陷病毒抗体(抗HIV-1/2)', '阴') +
|
||||
ro('巨细胞病毒 IgM 抗体 (CMV-IgM)', '阴') +
|
||||
'</div></div>';
|
||||
html += '<div class="field-group"><div class="field-group-title">母亲血液检测报告</div><div class="form-grid">' +
|
||||
ro('HBsAg', '阴') + ro('HBV DNA', '阴') + ro('抗HCV', '阴') + ro('HCV RNA', '阴') +
|
||||
ro('抗TP', '阴') + ro('CMV IgM', '阴') + ro('抗HIV-1/2', '阴') + ro('HIV-1RNA', '阴') +
|
||||
'</div></div>';
|
||||
html += '<div class="field-group"><div class="field-group-title meta">储存证收尾信息</div><div class="form-grid">' +
|
||||
ro('HLA 分型', 'A 02:06/24:02\\nB 07:02(M)/57:01(T)\\nC 06:02/07:02\\nDRB1 13:01/15:01\\nDQB1 06:02/06:03', 3) +
|
||||
ro('储存日期', '—') + ro('储存年限', '20') + ro('检测结果评论', '合格') + ro('报告发出日期', '—') +
|
||||
'</div></div>';
|
||||
} else {
|
||||
html += '<div class="field-group"><div class="field-group-title alt">采集与运输 / 邮寄</div><div class="form-grid">' +
|
||||
ro('是否邮寄', '—') + ro('脐带采集时间', '2025.11.25 15:49') + ro('运输温度 /℃', '—') +
|
||||
'</div></div>';
|
||||
html += '<div class="field-group"><div class="field-group-title">脐带间充质干细胞检测报告</div><div class="form-grid">' +
|
||||
ro('脐带接收时间', '2025.11.26 10:00') + ro('处理时间', '2025.11.26 16:00') + ro('冻存时间', '2025.12.21') +
|
||||
ro('储存总量(管)', '17') + ro('可提供细胞类型', '间充质干细胞') +
|
||||
ro('可提供细胞数量 / *10E7', '8.5') + ro('可提供细胞存活率 %', '97.1') +
|
||||
ro('内毒素检测', '阴') + ro('细菌检测', '阴') + ro('真菌检测', '阴') + ro('支原体检测', '阴') +
|
||||
ro('可提供细胞形态', '短梭/贴壁型') +
|
||||
ro('可提供细胞免疫表型特征', '符合间充质干细胞表型特性', 2) +
|
||||
'</div></div>';
|
||||
html += '<div class="field-group"><div class="field-group-title">母亲血液检测报告</div><div class="form-grid">' +
|
||||
ro('HBsAg', '阴') + ro('HBV DNA', '/') + ro('抗HCV', '阴') + ro('HCV RNA', '/') +
|
||||
ro('抗TP', '阴') + ro('CMV IgM', '阴') + ro('抗HIV-1/2', '阴') + ro('HIV-1RNA', '阴') +
|
||||
'</div></div>';
|
||||
html += '<div class="field-group"><div class="field-group-title meta">储存证收尾信息</div><div class="form-grid">' +
|
||||
ro('检测结果评判', '合格') + ro('报告发出日期', '2026-03-14') +
|
||||
ro('储存日期 (P2冻存日期)', '2025.12.22') + ro('冷冻方式', '程序降温盒降温') +
|
||||
ro('储存温度', '-196℃') + ro('储存年限', '20') +
|
||||
'</div></div>';
|
||||
}
|
||||
document.getElementById('viewBody').innerHTML = html;
|
||||
document.getElementById('viewMask').classList.add('open');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,284 @@
|
||||
# 储存证质检信息 — 实现设计(方案 A)
|
||||
|
||||
> 日期:2026-10-09
|
||||
> 状态:已确认(2026-10-09)
|
||||
> 关联:质检工单 / 我的质检任务「细胞存储质检」;样本信息管理「细胞存储」;Excel《干细胞储存证数据》
|
||||
> Demo:`docs/demos/2026-10-09-storage-cert-qc-info-demo.html`
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
市场办理储存证时,质检数据目前来自 Excel 手工表(两个 sheet):
|
||||
|
||||
| Sheet | 样本类型 |
|
||||
|-------|----------|
|
||||
| 围产期造血干细胞 | 围产期造血干细胞 |
|
||||
| 脐带间充质干细胞 | 脐带间充质干细胞 |
|
||||
|
||||
流程为:质检部门填表 → 市场部门誊写到储存证。系统内已有入库质检工单与样本信息管理,但**没有**与储存证誊写对齐的结构化质检信息落库与查看入口。
|
||||
|
||||
现有 `biz_protocol_storage_cert` 为**协议级**办理状态(办理时间、附件等),不承载上述检测明细。
|
||||
|
||||
---
|
||||
|
||||
## 2. 目标与非目标
|
||||
|
||||
### 2.1 目标
|
||||
|
||||
- 质检在「质检工单」「我的质检任务」的「细胞存储质检」**工单卡片**上,可「填写储存证质检信息」(按整份样本一张表)。
|
||||
- 字段分组与 Excel 对应 sheet 业务列一致;按样本类型切换两套模板。
|
||||
- 市场在「样本信息管理 · 细胞存储」行操作可「查看储存证的质检信息」(只读);未填时提示「质检尚未填写」。
|
||||
- 数据按**样本级**持久化,可反复编辑覆盖;允许部分填写即保存。
|
||||
|
||||
### 2.2 非目标
|
||||
|
||||
- 不自动生成 / 打印储存证 PDF。
|
||||
- 不改「登记储存证」办理流,不扩展 `biz_protocol_storage_cert` 明细列。
|
||||
- 不从接收单 / 质检任务结果自动回填检测数值(本期全部手填)。
|
||||
- 不做 Excel 批量导入。
|
||||
- 不为其它样本类型(如胎盘亚全能)做模板;其它类型不显示填写/查看按钮。
|
||||
- 不做必填校验、不做乐观锁。
|
||||
|
||||
---
|
||||
|
||||
## 3. 已确认产品决策
|
||||
|
||||
| 决策点 | 结论 |
|
||||
|--------|------|
|
||||
| 填写入口 | 工单卡片(与查看 / 配置检测同级),非整条子任务「填写结果」 |
|
||||
| 粒度 | 整份样本一张表 |
|
||||
| 查看可用性 | 始终可点;未填弹窗提示「质检尚未填写」 |
|
||||
| 样本类型范围 | 仅围产期造血干细胞、脐带间充质干细胞 |
|
||||
| 再次编辑 | 可反复打开保存,覆盖上次 |
|
||||
| 数值来源 | 全部手填;系统只带出身份信息 |
|
||||
| 校验 | 允许部分填写即保存(草稿式) |
|
||||
| 存储方案 | 方案 A:样本级新表 + JSON `payload` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据模型
|
||||
|
||||
### 4.1 表 `biz_sample_storage_cert_qc`
|
||||
|
||||
| 字段 | 类型(示意) | 说明 |
|
||||
|------|--------------|------|
|
||||
| `id` | VARCHAR(64) | UUID PK |
|
||||
| `sample_code` | VARCHAR(64) | 样本编码;跨表关联键之一 |
|
||||
| `collection_protocol_id` | VARCHAR(64) | 冗余协议 id,便于权限/展示;**非**唯一键 |
|
||||
| `qc_work_order_id` | VARCHAR(64) NULL | 可选,最近一次从哪张工单填写 |
|
||||
| `sample_type_key` | VARCHAR(32) | 模板键:`perinatal_hsc` / `uc_msc` |
|
||||
| `payload` | JSON / TEXT | 业务字段对象(见 §5) |
|
||||
| `filled_flag` | CHAR(1) | `0`/`1`;任意一次成功保存后为 `1` |
|
||||
| 公共字段 | | `tenant_no` … `del_flag`(对齐 database-conventions) |
|
||||
|
||||
**唯一性**:`tenant_no + sample_code` 在 `del_flag=0` 下唯一;写入走 upsert。
|
||||
|
||||
**关联键**:读写与列表关联一律用 `tenant_no + sample_code`(对齐细胞存储样本跨表约定),不用 `collection_protocol_id` 作唯一匹配。
|
||||
|
||||
### 4.2 模板键映射
|
||||
|
||||
与现网名称常量对齐(`SAMPLE_TYPE_PERINATAL_HSC` / `SAMPLE_TYPE_UC_MSC`):
|
||||
|
||||
| 样本类型名称 | `sample_type_key` |
|
||||
|--------------|-------------------|
|
||||
| 围产期造血干细胞 | `perinatal_hsc` |
|
||||
| 脐带间充质干细胞 | `uc_msc` |
|
||||
|
||||
服务端根据样本当前类型解析 `sample_type_key`,**不信任**客户端随意改模板。非以上类型 → 拒绝写入。
|
||||
|
||||
### 4.3 身份信息(不进 payload)
|
||||
|
||||
弹窗顶部只读展示(从协议 / 样本主数据带出):
|
||||
|
||||
- 实验室流转编码:本期约定展示 **`sample_code`**(与系统样本编码一致)
|
||||
- 协议编码
|
||||
- 母亲姓名
|
||||
- 样本类型名称
|
||||
|
||||
排除 Excel「序号」列(列表管理用,不入库)。
|
||||
|
||||
---
|
||||
|
||||
## 5. Payload 字段(对齐 Excel)
|
||||
|
||||
键名用稳定英文 snake_case;界面 label 用中文(与 Excel 表头一致)。值为字符串或数字均可,空字段可省略或存 `null`/`""`。阴阳性常见值:`阴` / `阳` / `/`。
|
||||
|
||||
### 5.1 `perinatal_hsc`(围产期造血干细胞)
|
||||
|
||||
**采集与运输**
|
||||
|
||||
| key | 中文 |
|
||||
|-----|------|
|
||||
| `collection_time` | 造血干细胞采集时间 |
|
||||
| `transport_temp_c` | 运输温度 /℃ |
|
||||
|
||||
**造血干细胞检测报告**
|
||||
|
||||
| key | 中文 |
|
||||
|-----|------|
|
||||
| `receive_time` | 造血干细胞接收时间 |
|
||||
| `process_time` | 处理时间 |
|
||||
| `cryo_time` | 冻存时间 |
|
||||
| `weight_g` | 造血干细胞重量/g |
|
||||
| `cryo_volume_ml` | 造血干细胞冻存体积/mL |
|
||||
| `tnc_1e8` | 有核细胞数 TNC / *10E8 |
|
||||
| `cd34_count_1e6` | CD34+ 细胞计数 / *10E6 |
|
||||
| `cd34_cd45dim_1e5` | CD34+ CD45dim 细胞计数 / *10E5 |
|
||||
| `viability_pct` | 细胞活性/% |
|
||||
| `bacteria_test` | 细菌检测 |
|
||||
| `hbsag` | 乙型肝炎病毒表面抗原(HBsAg) |
|
||||
| `anti_hcv` | 丙型肝炎病毒抗体(抗HCV) |
|
||||
| `anti_tp` | 梅毒螺旋体特异性抗体(抗TP) |
|
||||
| `anti_hiv` | 人类免疫缺陷病毒抗体(抗HIV-1/2) |
|
||||
| `cmv_igm` | 巨细胞病毒 IgM 抗体 (CMV-IgM) |
|
||||
|
||||
**母亲血液检测报告**
|
||||
|
||||
| key | 中文 |
|
||||
|-----|------|
|
||||
| `mother_hbsag` | 乙型肝炎病毒表面抗原(HBsAg) |
|
||||
| `mother_hbv_dna` | 乙型肝炎病毒核酸(HBV DNA) |
|
||||
| `mother_anti_hcv` | 丙型肝炎病毒抗体(抗HCV) |
|
||||
| `mother_hcv_rna` | 丙型肝炎病毒核酸(HCV RNA) |
|
||||
| `mother_anti_tp` | 梅毒螺旋体特异性抗体(抗TP) |
|
||||
| `mother_cmv_igm` | 巨细胞病毒 IgM 抗体 (CMV IgM) |
|
||||
| `mother_anti_hiv` | 人类免疫缺陷病毒抗体(抗HIV-1/2) |
|
||||
| `mother_hiv1_rna` | 人类免疫缺陷病毒核酸(1型) (HIV-1RNA) |
|
||||
|
||||
**储存证收尾**
|
||||
|
||||
| key | 中文 |
|
||||
|-----|------|
|
||||
| `hla_typing` | HLA 分型 |
|
||||
| `storage_date` | 储存日期 |
|
||||
| `storage_years` | 储存年限 |
|
||||
| `result_comment` | 检测结果评论 |
|
||||
| `report_issue_date` | 报告发出日期 |
|
||||
|
||||
### 5.2 `uc_msc`(脐带间充质干细胞)
|
||||
|
||||
**采集与运输 / 邮寄**
|
||||
|
||||
| key | 中文 |
|
||||
|-----|------|
|
||||
| `mail_flag` | 是否邮寄 |
|
||||
| `collection_time` | 脐带采集时间 |
|
||||
| `transport_temp_c` | 运输温度 /℃ |
|
||||
|
||||
**脐带间充质干细胞检测报告**
|
||||
|
||||
| key | 中文 |
|
||||
|-----|------|
|
||||
| `receive_time` | 脐带接收时间 |
|
||||
| `process_time` | 处理时间 |
|
||||
| `cryo_time` | 冻存时间 |
|
||||
| `storage_tube_count` | 储存总量(管) |
|
||||
| `available_cell_type` | 可提供细胞类型 |
|
||||
| `available_cell_count_1e7` | 可提供细胞数量 / *10E7 |
|
||||
| `available_viability_pct` | 可提供细胞存活率 % |
|
||||
| `endotoxin_test` | 内毒素检测 |
|
||||
| `bacteria_test` | 细菌检测 |
|
||||
| `fungus_test` | 真菌检测 |
|
||||
| `mycoplasma_test` | 支原体检测 |
|
||||
| `available_cell_morphology` | 可提供细胞形态 |
|
||||
| `available_immunophenotype` | 可提供细胞免疫表型特征 |
|
||||
|
||||
**母亲血液检测报告**:与 §5.1 母亲血检 8 个 key 相同。
|
||||
|
||||
**储存证收尾**
|
||||
|
||||
| key | 中文 |
|
||||
|-----|------|
|
||||
| `result_judgment` | 检测结果评判 |
|
||||
| `report_issue_date` | 报告发出日期 |
|
||||
| `storage_date_p2` | 储存日期 (P2冻存日期) |
|
||||
| `freeze_method` | 冷冻方式 |
|
||||
| `storage_temp` | 储存温度 |
|
||||
| `storage_years` | 储存年限 |
|
||||
|
||||
---
|
||||
|
||||
## 6. API
|
||||
|
||||
建议前缀:`/web/admin/sample-storage-cert-qc`。
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/{sample_code}` | 读详情:身份只读字段 + `sampleTypeKey` + `filledFlag` + `payload` |
|
||||
| PUT | `/{sample_code}` | Upsert;body:`payload`(object),可选 `qcWorkOrderId` |
|
||||
|
||||
### 6.1 行为
|
||||
|
||||
- GET 无行:HTTP 200,`filledFlag=false`,`payload={}`(或 null),**不** 404。
|
||||
- PUT:校验样本存在、租户、数据权限、类型为两种之一;写 `sample_type_key`;`filled_flag=1`;覆盖 `payload`。
|
||||
- 列表接口本期不扩展整份 payload;样本信息列表点「查看」再 GET。
|
||||
|
||||
### 6.2 权限
|
||||
|
||||
- PUT:具备质检工单或我的质检任务相关菜单权限(与工单卡片操作对齐;不额外强制「必须是任务执行人」,除非现有卡片按钮已有更严规则则跟随)。
|
||||
- GET:样本信息管理菜单可访问;质检填写弹窗预填亦用同一 GET。
|
||||
|
||||
---
|
||||
|
||||
## 7. 前端
|
||||
|
||||
### 7.1 质检入口
|
||||
|
||||
- 页面:`qc-work-order`、`my-qc-task`,Tab「细胞存储质检」(`inbound_cell_storage`)。
|
||||
- 组件:`QcWorkOrderSingleCard` 操作区增加「填写储存证质检信息」。
|
||||
- 显示条件:当前样本类型名为围产期造血干细胞或脐带间充质干细胞。
|
||||
- 弹窗:可编辑;GET 预填 → PUT 保存;manage / mine 均显示(规则相同)。
|
||||
|
||||
### 7.2 样本信息入口
|
||||
|
||||
- 页面:`/#/market/cell-storage/sample-info`,「细胞存储」Tab。
|
||||
- 行操作增加「查看储存证的质检信息」;显示条件同两种类型。
|
||||
- 始终可点;`filledFlag=false` 时弹窗空态文案「质检尚未填写」(可保留身份区);已填则只读同模板字段。
|
||||
- 市场侧不可编辑。
|
||||
|
||||
### 7.3 UI 约定
|
||||
|
||||
- `label-position="top"`;分组对齐 Demo / Excel 合并表头;栅格最多约 3 列,窄屏降列。
|
||||
- 一套字段元数据配置驱动填写(可编)与查看(只读)。
|
||||
- 对齐 Fresh Mint / `dialog-form-layout`;遮罩无 backdrop blur。
|
||||
- Demo:`docs/demos/2026-10-09-storage-cert-qc-info-demo.html`。
|
||||
|
||||
### 7.4 样本类型变更(边界)
|
||||
|
||||
极少见。打开表单按**当前**样本类型选模板;保存时按当前模板**整包覆盖** `payload`(实现简单,可接受)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误处理摘要
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| 不支持类型 PUT | 400;前端不展示按钮 |
|
||||
| 样本不存在 / 无权限 | 统一 403/404 |
|
||||
| GET 无记录 | 200 + 未填形态 |
|
||||
| payload 非 object | 400 |
|
||||
| 并发保存 | 后写覆盖 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 测试要点
|
||||
|
||||
- 后端:upsert 覆盖、租户隔离、`filled_flag`、类型门禁、GET 空记录。
|
||||
- 前端:两类型按钮显隐;质检保存后市场只读可见;未填空态;manage/mine 均有入口。
|
||||
|
||||
---
|
||||
|
||||
## 10. 实现落点(指引,非任务清单)
|
||||
|
||||
| 层 | 预期触点 |
|
||||
|----|----------|
|
||||
| SQL | `docs/sql/migrations/2026-10-09-sample-storage-cert-qc.sql` |
|
||||
| Model / CRUD / API | `backend/app/models`、`crud`、`api`(新模块或挂入 qc / sample 相关路由并注册) |
|
||||
| FE API | `web-admin/src/api/` |
|
||||
| 字段元数据 | `web-admin/src/utils/` 或业务组件内配置 |
|
||||
| 填写/查看弹窗 | 共用业务组件 |
|
||||
| 工单卡片 | `QcWorkOrderSingleCard.vue` |
|
||||
| 样本信息 | `UnifiedSampleInfoPage.vue`(细胞存储操作列) |
|
||||
|
||||
详细步骤在实现计划中展开(writing-plans)。
|
||||
Reference in new issue
Block a user