732 lines
26 KiB
HTML
732 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>细胞产品来源链路 Demo</title>
|
||
<style>
|
||
:root {
|
||
--primary: #10b981;
|
||
--primary-dark: #059669;
|
||
--primary-light: #ecfdf5;
|
||
--primary-border: #a7f3d0;
|
||
--text: #1f2937;
|
||
--text-muted: #6b7280;
|
||
--text-light: #9ca3af;
|
||
--sample-bg: #eff6ff;
|
||
--sample-border: #93c5fd;
|
||
--line-color: #d1d5db;
|
||
--line-active: #10b981;
|
||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
}
|
||
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
font-family: var(--font-family);
|
||
background: #f9fafb;
|
||
color: var(--text);
|
||
padding: 24px;
|
||
}
|
||
|
||
h1 { font-size: 18px; font-weight: 600; margin-bottom: 8px; }
|
||
.subtitle { font-size: 13px; color: var(--text-muted); margin-bottom: 24px; }
|
||
|
||
.scenario-tabs {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.scenario-tab {
|
||
padding: 6px 16px;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
border: 1px solid var(--line-color);
|
||
background: white;
|
||
color: var(--text-muted);
|
||
transition: all 0.2s;
|
||
}
|
||
.scenario-tab:hover { border-color: var(--primary); color: var(--primary); }
|
||
.scenario-tab.active { background: var(--primary); color: white; border-color: var(--primary); }
|
||
|
||
.scenario-desc {
|
||
font-size: 13px;
|
||
color: var(--text-muted);
|
||
background: #fffbeb;
|
||
border: 1px solid #fde68a;
|
||
border-radius: 8px;
|
||
padding: 10px 16px;
|
||
margin-bottom: 20px;
|
||
line-height: 1.6;
|
||
}
|
||
.scenario-desc strong { color: var(--text); }
|
||
|
||
.scenario-panel { display: none; }
|
||
.scenario-panel.active { display: block; }
|
||
|
||
.chain-container {
|
||
background: white;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 12px;
|
||
padding: 32px 24px 24px;
|
||
overflow-x: auto;
|
||
min-height: 200px;
|
||
}
|
||
|
||
.chain-section-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.chain-section-title::before {
|
||
content: '';
|
||
display: inline-block;
|
||
width: 3px;
|
||
height: 14px;
|
||
background: var(--primary);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.chain-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
position: relative;
|
||
}
|
||
|
||
.chain-node {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
z-index: 2;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.node-card {
|
||
width: 148px;
|
||
padding: 10px 12px;
|
||
border-radius: 8px;
|
||
border: 1.5px solid var(--primary-border);
|
||
background: var(--primary-light);
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
position: relative;
|
||
}
|
||
.node-card:hover {
|
||
box-shadow: 0 4px 12px rgba(16,185,129,0.2);
|
||
transform: translateY(-1px);
|
||
}
|
||
.node-card.sample {
|
||
background: var(--sample-bg);
|
||
border-color: var(--sample-border);
|
||
}
|
||
.node-card.sample:hover {
|
||
box-shadow: 0 4px 12px rgba(147,197,253,0.3);
|
||
}
|
||
.node-card.current {
|
||
border-color: var(--primary);
|
||
border-width: 2px;
|
||
box-shadow: 0 0 0 3px rgba(16,185,129,0.15);
|
||
}
|
||
.node-card.current::after {
|
||
content: '当前';
|
||
position: absolute;
|
||
top: -10px;
|
||
right: -8px;
|
||
background: var(--primary);
|
||
color: white;
|
||
font-size: 10px;
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.node-type-tag {
|
||
display: inline-block;
|
||
font-size: 10px;
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
margin-bottom: 4px;
|
||
font-weight: 500;
|
||
}
|
||
.node-type-tag.product { background: #d1fae5; color: #065f46; }
|
||
.node-type-tag.sample-type { background: #dbeafe; color: #1e40af; }
|
||
|
||
.node-code { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.node-name { font-size: 11px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.node-passage { font-size: 10px; color: var(--text-light); margin-top: 2px; }
|
||
|
||
/* 连线 — 加长到 180px */
|
||
.chain-connector {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
position: relative;
|
||
min-width: 180px;
|
||
height: 60px;
|
||
z-index: 1;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.connector-label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 1px;
|
||
font-size: 10px;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
padding: 3px 8px;
|
||
background: white;
|
||
border: 1px solid #f3f4f6;
|
||
border-radius: 4px;
|
||
position: absolute;
|
||
top: 2px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
.connector-label .wo { color: var(--primary-dark); font-weight: 500; }
|
||
.connector-label .task { color: var(--text-light); }
|
||
|
||
.connector-line {
|
||
position: absolute;
|
||
bottom: 14px;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: var(--line-color);
|
||
}
|
||
.connector-line.active { background: var(--line-active); }
|
||
|
||
.connector-arrow {
|
||
position: absolute;
|
||
bottom: 10px;
|
||
right: -5px;
|
||
width: 0;
|
||
height: 0;
|
||
border-top: 5px solid transparent;
|
||
border-bottom: 5px solid transparent;
|
||
border-left: 7px solid var(--line-color);
|
||
}
|
||
.connector-line.active + .connector-arrow { border-left-color: var(--line-active); }
|
||
|
||
/* 分叉 */
|
||
.fork-area {
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
margin-left: 0;
|
||
}
|
||
|
||
.fork-row {
|
||
display: flex;
|
||
align-items: center;
|
||
position: relative;
|
||
min-height: 72px;
|
||
padding-left: 40px;
|
||
}
|
||
|
||
.fork-area::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 18px;
|
||
top: 36px;
|
||
bottom: 36px;
|
||
width: 2px;
|
||
background: var(--line-color);
|
||
}
|
||
|
||
.fork-row::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 18px;
|
||
top: 50%;
|
||
width: 22px;
|
||
height: 2px;
|
||
background: var(--line-color);
|
||
}
|
||
.fork-row::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 18px;
|
||
top: 0;
|
||
height: 50%;
|
||
width: 2px;
|
||
background: white;
|
||
z-index: 1;
|
||
}
|
||
.fork-row:last-child::after {
|
||
top: auto;
|
||
bottom: 0;
|
||
}
|
||
.fork-row:first-child::after {
|
||
top: 0;
|
||
bottom: auto;
|
||
height: 50%;
|
||
background: white;
|
||
}
|
||
|
||
/* 图例 */
|
||
.legend {
|
||
display: flex;
|
||
gap: 20px;
|
||
margin-top: 24px;
|
||
padding-top: 16px;
|
||
border-top: 1px solid #f3f4f6;
|
||
}
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
}
|
||
.legend-dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 3px;
|
||
border: 1.5px solid;
|
||
}
|
||
.legend-dot.product { background: var(--primary-light); border-color: var(--primary-border); }
|
||
.legend-dot.sample { background: var(--sample-bg); border-color: var(--sample-border); }
|
||
.legend-dot.current-node { background: var(--primary-light); border-color: var(--primary); border-width: 2px; }
|
||
.legend-dot.line-legend { background: transparent; border: none; width: 24px; height: 2px; border-radius: 0; background: var(--line-active); }
|
||
|
||
.popover-detail {
|
||
display: none;
|
||
position: absolute;
|
||
top: calc(100% + 8px);
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
background: white;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
min-width: 200px;
|
||
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
||
z-index: 100;
|
||
font-size: 12px;
|
||
}
|
||
.popover-detail.visible { display: block; }
|
||
.popover-detail .detail-row { display: flex; justify-content: space-between; padding: 3px 0; }
|
||
.popover-detail .detail-label { color: var(--text-muted); }
|
||
.popover-detail .detail-value { color: var(--text); font-weight: 500; }
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: var(--text-muted);
|
||
font-size: 13px;
|
||
}
|
||
.empty-state .empty-icon { font-size: 32px; margin-bottom: 8px; opacity: 0.4; }
|
||
|
||
.sub-label {
|
||
font-size: 11px;
|
||
color: var(--text-light);
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.sub-label .arrow { color: var(--primary); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<h1>细胞产品来源链路 — 设计 Demo</h1>
|
||
<p class="subtitle">展示细胞产品查看弹窗中的「来源链路」可视化方案。点击任意节点可查看详情。</p>
|
||
|
||
<div class="scenario-tabs">
|
||
<div class="scenario-tab active" onclick="switchScenario('s1', this)">场景1:简单链路</div>
|
||
<div class="scenario-tab" onclick="switchScenario('s2', this)">场景2:一生多(一个产品产出多个新产品)</div>
|
||
<div class="scenario-tab" onclick="switchScenario('s3', this)">场景3:完整追溯(往前看祖先 + 往后看子代)</div>
|
||
<div class="scenario-tab" onclick="switchScenario('s4', this)">场景4:手动录入(无祖先,但有子代)</div>
|
||
</div>
|
||
|
||
<div id="desc-s1" class="scenario-desc active">
|
||
<strong>场景1 说明:</strong>最简单的情况。一个样本经过一次生产工单,产出一个细胞产品(P1代)。这个P1代产品又经过另一个生产工单,产出了当前查看的P2代产品。每个节点之间只有一条连线。
|
||
</div>
|
||
<div id="desc-s2" class="scenario-desc" style="display:none;">
|
||
<strong>场景2 说明:</strong>当前产品(P1代)被进一步拿去做生产,<strong>一次生产产出了 3 个不同的新细胞产品</strong>。就像一块面团可以同时做出多个包子 — 连线从当前产品分叉出去,每条分叉线上面标注了对应的工单号和任务号。这就是「一生多」。
|
||
</div>
|
||
<div id="desc-s3" class="scenario-desc" style="display:none;">
|
||
<strong>场景3 说明:</strong>最完整的情况。全部在<strong>同一行</strong>展示 — 从左到右:<strong>样本 → P1(父产品) → P2(当前) → 分叉出2个P3(子产品)</strong>。祖先在左边,当前在中间,子代直接从当前节点往右延伸分叉。不分两行,一条链路从头看到尾。
|
||
</div>
|
||
<div id="desc-s4" class="scenario-desc" style="display:none;">
|
||
<strong>场景4 说明:</strong>手动录入的细胞产品是「起源」— 它没有祖先(不是从样本或别的产品生产来的),但后续它可以被拿去生产新的子产品。所以它左边没有祖先链路,但右边会分叉出子代。只有从未被用于生产的「孤立产品」才会显示空状态。
|
||
</div>
|
||
|
||
<div id="s1" class="scenario-panel active">
|
||
<div class="chain-container">
|
||
<div class="chain-section-title">来源链路</div>
|
||
|
||
<div class="chain-row">
|
||
<div class="chain-node">
|
||
<div class="node-card sample" onclick="togglePopover(this)">
|
||
<span class="node-type-tag sample-type">样本</span>
|
||
<div class="node-code">ZKZX34000627</div>
|
||
<div class="node-name">脐带血干细胞</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chain-connector">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260519001</span>
|
||
<span class="task">任务 T20260519001</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260520001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P1 代</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chain-connector">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260521002</span>
|
||
<span class="task">任务 T20260521002</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
|
||
<div class="chain-node">
|
||
<div class="node-card current" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260522001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P2 代</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="legend">
|
||
<div class="legend-item"><div class="legend-dot sample"></div>样本</div>
|
||
<div class="legend-item"><div class="legend-dot product"></div>细胞产品</div>
|
||
<div class="legend-item"><div class="legend-dot current-node"></div>当前查看</div>
|
||
<div class="legend-item"><div class="legend-dot line-legend"></div>生产连线(标注工单/任务号)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="s2" class="scenario-panel">
|
||
<div class="chain-container">
|
||
<div class="chain-section-title">来源链路</div>
|
||
<div class="sub-label"><span class="arrow">●</span> 当前产品被拿去生产,一次产出了 3 个新产品(分叉)</div>
|
||
|
||
<div class="chain-row">
|
||
<div class="chain-node">
|
||
<div class="node-card sample" onclick="togglePopover(this)">
|
||
<span class="node-type-tag sample-type">样本</span>
|
||
<div class="node-code">ZKZX34000627</div>
|
||
<div class="node-name">脐带血干细胞</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chain-connector">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260519001</span>
|
||
<span class="task">任务 T20260519001</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
|
||
<div class="chain-node">
|
||
<div class="node-card current" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260520001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P1 代</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="fork-area">
|
||
<div class="fork-row">
|
||
<div class="chain-connector" style="min-width:160px;">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260522003</span>
|
||
<span class="task">任务 T20260522003</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260523001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P2 代</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="fork-row">
|
||
<div class="chain-connector" style="min-width:160px;">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260522003</span>
|
||
<span class="task">任务 T20260522004</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260523002</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P2 代</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="fork-row">
|
||
<div class="chain-connector" style="min-width:160px;">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260522005</span>
|
||
<span class="task">任务 T20260522005</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260523003</div>
|
||
<div class="node-name">围产期造血干细胞</div>
|
||
<div class="node-passage">P1 代</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="legend">
|
||
<div class="legend-item"><div class="legend-dot sample"></div>样本</div>
|
||
<div class="legend-item"><div class="legend-dot product"></div>细胞产品</div>
|
||
<div class="legend-item"><div class="legend-dot current-node"></div>当前查看</div>
|
||
<div class="legend-item"><div class="legend-dot line-legend"></div>生产连线</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="s3" class="scenario-panel">
|
||
<div class="chain-container">
|
||
<div class="chain-section-title">来源链路(完整双向追溯)</div>
|
||
<div class="sub-label"><span class="arrow">←</span> 祖先(从哪来)<span style="margin: 0 12px; color: #d1d5db;">|</span> 当前产品 <span style="margin: 0 12px; color: #d1d5db;">|</span> <span class="arrow">→</span> 子代(被拿去做什么)— 全部在同一行</div>
|
||
|
||
<div class="chain-row">
|
||
<div class="chain-node">
|
||
<div class="node-card sample" onclick="togglePopover(this)">
|
||
<span class="node-type-tag sample-type">样本</span>
|
||
<div class="node-code">ZKZX34000627</div>
|
||
<div class="node-name">脐带血干细胞</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chain-connector">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260519001</span>
|
||
<span class="task">任务 T20260519001</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260520001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P1 代(父产品)</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chain-connector">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260521002</span>
|
||
<span class="task">任务 T20260521002</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
|
||
<div class="chain-node">
|
||
<div class="node-card current" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260522001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P2 代(当前)</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="fork-area">
|
||
<div class="fork-row">
|
||
<div class="chain-connector" style="min-width:160px;">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260523003</span>
|
||
<span class="task">任务 T20260523003</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260524001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P3 代(子产品1)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="fork-row">
|
||
<div class="chain-connector" style="min-width:160px;">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260523004</span>
|
||
<span class="task">任务 T20260523004</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260524002</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P3 代(子产品2)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="legend">
|
||
<div class="legend-item"><div class="legend-dot sample"></div>样本</div>
|
||
<div class="legend-item"><div class="legend-dot product"></div>细胞产品</div>
|
||
<div class="legend-item"><div class="legend-dot current-node"></div>当前查看</div>
|
||
<div class="legend-item"><div class="legend-dot line-legend"></div>生产连线</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="s4" class="scenario-panel">
|
||
<div class="chain-container">
|
||
<div class="chain-section-title">来源链路</div>
|
||
<div class="sub-label"><span class="arrow">●</span> 手动录入产品(起源节点),无祖先,但后续被拿去生产了子产品</div>
|
||
|
||
<div class="chain-row">
|
||
<div class="chain-node">
|
||
<div class="node-card current" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260520001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">手动录入(起源)</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="fork-area">
|
||
<div class="fork-row">
|
||
<div class="chain-connector" style="min-width:160px;">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260522003</span>
|
||
<span class="task">任务 T20260522003</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260523001</div>
|
||
<div class="node-name">脐带间充质干细胞</div>
|
||
<div class="node-passage">P1 代</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="fork-row">
|
||
<div class="chain-connector" style="min-width:160px;">
|
||
<div class="connector-label">
|
||
<span class="wo">工单 WO20260522004</span>
|
||
<span class="task">任务 T20260522004</span>
|
||
</div>
|
||
<div class="connector-line active"></div>
|
||
<div class="connector-arrow"></div>
|
||
</div>
|
||
<div class="chain-node">
|
||
<div class="node-card" onclick="togglePopover(this)">
|
||
<span class="node-type-tag product">细胞产品</span>
|
||
<div class="node-code">CP20260523002</div>
|
||
<div class="node-name">围产期造血干细胞</div>
|
||
<div class="node-passage">P1 代</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="legend">
|
||
<div class="legend-item"><div class="legend-dot product"></div>细胞产品</div>
|
||
<div class="legend-item"><div class="legend-dot current-node"></div>当前查看(手动录入/起源)</div>
|
||
<div class="legend-item"><div class="legend-dot line-legend"></div>生产连线</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function switchScenario(id, tab) {
|
||
document.querySelectorAll('.scenario-panel').forEach(function(p) { p.classList.remove('active'); });
|
||
document.querySelectorAll('.scenario-tab').forEach(function(t) { t.classList.remove('active'); });
|
||
document.querySelectorAll('.scenario-desc').forEach(function(d) { d.style.display = 'none'; });
|
||
document.getElementById(id).classList.add('active');
|
||
tab.classList.add('active');
|
||
var descMap = { s1: 'desc-s1', s2: 'desc-s2', s3: 'desc-s3', s4: 'desc-s4' };
|
||
document.getElementById(descMap[id]).style.display = 'block';
|
||
}
|
||
|
||
function togglePopover(card) {
|
||
var existing = card.querySelector('.popover-detail');
|
||
if (existing) { existing.classList.toggle('visible'); return; }
|
||
|
||
var code = card.querySelector('.node-code') ? card.querySelector('.node-code').textContent : '';
|
||
var name = card.querySelector('.node-name') ? card.querySelector('.node-name').textContent : '';
|
||
var isCurrent = card.classList.contains('current');
|
||
var isSample = card.classList.contains('sample');
|
||
var passage = card.querySelector('.node-passage') ? card.querySelector('.node-passage').textContent : '';
|
||
var status = isSample ? '已入库' : '冷冻中';
|
||
|
||
var popover = document.createElement('div');
|
||
popover.className = 'popover-detail visible';
|
||
var html = '<div class="detail-row"><span class="detail-label">编码</span><span class="detail-value">' + code + '</span></div>';
|
||
html += '<div class="detail-row"><span class="detail-label">名称</span><span class="detail-value">' + name + '</span></div>';
|
||
html += '<div class="detail-row"><span class="detail-label">状态</span><span class="detail-value">' + status + '</span></div>';
|
||
if (!isSample) { html += '<div class="detail-row"><span class="detail-label">类型</span><span class="detail-value">细胞产品</span></div>'; }
|
||
if (passage) { html += '<div class="detail-row"><span class="detail-label">代次</span><span class="detail-value">' + passage + '</span></div>'; }
|
||
if (isCurrent) { html += '<div class="detail-row"><span class="detail-label">标识</span><span class="detail-value" style="color:#10b981">当前查看</span></div>'; }
|
||
popover.innerHTML = html;
|
||
card.appendChild(popover);
|
||
|
||
setTimeout(function() {
|
||
document.addEventListener('click', function close(e) {
|
||
if (!popover.contains(e.target) && e.target !== card) {
|
||
popover.classList.remove('visible');
|
||
document.removeEventListener('click', close);
|
||
}
|
||
});
|
||
}, 10);
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|