Files
zkxw/docs/qc-wo-sample-tab-demos/compare-all-v2.html
T
2026-06-18 15:09:50 +08:00

1058 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>质检工单 · 同样本多工单 Tab 选型 · 第二组(强对比)</title>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="_shared.css" />
<style>
.batch-note {
max-width: 960px;
margin: 0 auto;
padding: 0 24px 16px;
}
.batch-note-inner {
padding: 12px 16px;
background: var(--primary-bg);
border: 1px solid #a7f3d0;
border-radius: 10px;
font-size: 13px;
color: #065f46;
line-height: 1.55;
}
/* ═══ N1 · 双色分栏顶栏 ═══ */
.n1-switcher {
display: grid;
grid-template-columns: 1fr 1fr;
border-bottom: 3px solid var(--border);
}
.n1-half {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 72px;
padding: 14px 16px;
border: none;
font-family: inherit;
cursor: pointer;
transition: filter 0.15s, transform 0.1s;
}
.n1-half:active {
transform: scale(0.98);
}
.n1-half--reception {
background: linear-gradient(180deg, #d1fae5 0%, var(--reception-bg) 100%);
color: var(--reception);
}
.n1-half--production {
background: linear-gradient(180deg, #fef3c7 0%, var(--production-bg) 100%);
color: var(--production);
}
.n1-half:not(.is-active) {
filter: saturate(0.35) brightness(1.08);
}
.n1-half:not(.is-active)::after {
content: '👆 点击切换';
position: absolute;
bottom: 6px;
font-size: 11px;
font-weight: 600;
opacity: 0.85;
animation: nudge 2s ease-in-out infinite;
}
@keyframes nudge {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-2px);
}
}
.n1-half.is-active {
box-shadow: inset 0 -4px 0 currentColor;
filter: none;
}
.n1-half.is-active::before {
content: '当前查看';
position: absolute;
top: 6px;
right: 8px;
font-size: 10px;
font-weight: 700;
padding: 2px 8px;
border-radius: 999px;
background: rgb(255 255 255 / 75%);
}
.n1-half__title {
font-size: 16px;
font-weight: 700;
letter-spacing: 0.02em;
}
.n1-half__sub {
font-size: 11px;
font-family: ui-monospace, Menlo, monospace;
opacity: 0.9;
}
.n1-divider {
position: absolute;
left: 50%;
top: 12px;
bottom: 12px;
width: 2px;
background: rgb(15 23 42 / 12%);
transform: translateX(-50%);
pointer-events: none;
z-index: 1;
}
.n1-switcher-wrap {
position: relative;
}
/* ═══ N2 · 双行清单 ═══ */
.n2-list {
border-bottom: 1px solid var(--border);
background: #fff;
}
.n2-list__head {
padding: 10px 16px 6px;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
}
.n2-row {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 12px 16px;
border: none;
border-top: 1px solid var(--border);
background: #fff;
font-family: inherit;
text-align: left;
cursor: pointer;
transition: background 0.15s;
}
.n2-row:hover:not(.is-active) {
background: #f8fafc;
}
.n2-row.is-active {
background: var(--primary-bg);
box-shadow: inset 4px 0 0 var(--primary);
}
.n2-row.is-active[data-source='reception'] {
background: var(--reception-bg);
box-shadow: inset 4px 0 0 var(--reception);
}
.n2-row.is-active[data-source='production'] {
background: var(--production-bg);
box-shadow: inset 4px 0 0 var(--production);
}
.n2-row__radio {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #cbd5e1;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.n2-row.is-active .n2-row__radio {
border-color: var(--primary);
background: var(--primary);
}
.n2-row.is-active[data-source='reception'] .n2-row__radio {
border-color: var(--reception);
background: var(--reception);
}
.n2-row.is-active[data-source='production'] .n2-row__radio {
border-color: var(--production);
background: var(--production);
}
.n2-row.is-active .n2-row__radio::after {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
background: #fff;
}
.n2-row__main {
flex: 1;
min-width: 0;
}
.n2-row__type {
font-size: 14px;
font-weight: 600;
}
.n2-row__no {
font-size: 12px;
font-family: ui-monospace, Menlo, monospace;
color: var(--text-muted);
margin-top: 2px;
}
.n2-row:not(.is-active) .n2-row__action {
flex-shrink: 0;
font-size: 12px;
font-weight: 600;
color: var(--primary-dark);
padding: 4px 10px;
border-radius: 6px;
background: var(--primary-bg);
border: 1px solid #a7f3d0;
}
.n2-row.is-active .n2-row__action {
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
}
/* ═══ N3 · 大开关 Toggle ═══ */
.n3-zone {
padding: 16px;
border-bottom: 1px solid var(--border);
background: #f1f5f9;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.n3-label {
font-size: 13px;
font-weight: 600;
color: var(--text);
}
.n3-toggle {
display: flex;
width: 100%;
max-width: 420px;
padding: 5px;
border-radius: 14px;
background: #e2e8f0;
border: 2px solid #cbd5e1;
box-shadow: inset 0 2px 4px rgb(15 23 42 / 8%);
}
.n3-toggle__btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 14px 12px;
border: none;
border-radius: 10px;
background: transparent;
font-family: inherit;
font-size: 15px;
font-weight: 600;
color: var(--text-muted);
cursor: pointer;
transition: all 0.2s ease;
}
.n3-toggle__btn.is-active[data-source='reception'] {
background: #fff;
color: var(--reception);
box-shadow: 0 2px 8px rgb(4 120 87 / 20%);
}
.n3-toggle__btn.is-active[data-source='production'] {
background: #fff;
color: var(--production);
box-shadow: 0 2px 8px rgb(180 83 9 / 20%);
}
.n3-toggle__btn:not(.is-active):hover {
color: var(--text);
background: rgb(255 255 255 / 50%);
}
.n3-toggle__icon {
font-size: 22px;
line-height: 1;
}
.n3-toggle__hint {
font-size: 10px;
font-weight: 500;
opacity: 0.8;
}
.n3-current {
font-size: 12px;
color: var(--text-muted);
font-family: ui-monospace, Menlo, monospace;
}
/* ═══ N4 · 图标宫格二选一 ═══ */
.n4-zone {
padding: 16px;
border-bottom: 1px solid var(--border);
background: #fff;
}
.n4-head {
text-align: center;
margin-bottom: 14px;
}
.n4-head__title {
font-size: 14px;
font-weight: 700;
color: var(--text);
}
.n4-head__sub {
font-size: 12px;
color: var(--text-muted);
margin-top: 4px;
}
.n4-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.n4-tile {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 20px 16px 16px;
border: 3px solid var(--border);
border-radius: 14px;
background: #fff;
font-family: inherit;
cursor: pointer;
transition: border-color 0.15s, transform 0.1s, box-shadow 0.15s;
}
.n4-tile:hover:not(.is-active) {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgb(15 23 42 / 10%);
}
.n4-tile[data-source='reception']:not(.is-active) {
border-color: var(--reception-border);
border-style: dashed;
}
.n4-tile[data-source='production']:not(.is-active) {
border-color: var(--production-border);
border-style: dashed;
}
.n4-tile[data-source='reception'].is-active {
border-color: var(--reception);
border-style: solid;
background: var(--reception-bg);
box-shadow: 0 4px 12px rgb(4 120 87 / 15%);
}
.n4-tile[data-source='production'].is-active {
border-color: var(--production);
border-style: solid;
background: var(--production-bg);
box-shadow: 0 4px 12px rgb(180 83 9 / 15%);
}
.n4-tile__icon {
width: 56px;
height: 56px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.n4-tile[data-source='reception'] .n4-tile__icon {
background: #fff;
color: var(--reception);
border: 2px solid var(--reception-border);
}
.n4-tile[data-source='production'] .n4-tile__icon {
background: #fff;
color: var(--production);
border: 2px solid var(--production-border);
}
.n4-tile__name {
font-size: 16px;
font-weight: 700;
}
.n4-tile__cta {
font-size: 12px;
font-weight: 600;
padding: 6px 14px;
border-radius: 999px;
}
.n4-tile:not(.is-active) .n4-tile__cta {
background: #f1f5f9;
color: var(--text);
border: 1px solid var(--border);
}
.n4-tile[data-source='reception']:not(.is-active) .n4-tile__cta {
background: var(--reception-bg);
color: var(--reception);
border: 1px solid var(--reception-border);
}
.n4-tile[data-source='production']:not(.is-active) .n4-tile__cta {
background: var(--production-bg);
color: var(--production);
border: 1px solid var(--production-border);
}
.n4-tile.is-active .n4-tile__cta {
background: rgb(255 255 255 / 80%);
color: inherit;
border: 1px solid rgb(0 0 0 / 8%);
}
.n4-tile__meta {
font-size: 11px;
font-family: ui-monospace, Menlo, monospace;
color: var(--text-muted);
}
/* ═══ N5 · 双摘要条(两条始终可见) ═══ */
.n5-zone {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
display: flex;
flex-direction: column;
gap: 8px;
}
.n5-banner {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 8px;
background: #fff;
border: 1px solid var(--border);
font-size: 12px;
color: var(--text-muted);
}
.n5-banner strong {
color: var(--primary-dark);
}
.n5-strip {
display: flex;
align-items: stretch;
gap: 8px;
min-height: 56px;
}
.n5-chip {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border: 2px solid var(--border);
border-radius: 10px;
background: #fff;
font-family: inherit;
text-align: left;
cursor: pointer;
transition: all 0.15s;
}
.n5-chip:not(.is-active) {
opacity: 0.72;
}
.n5-chip:not(.is-active):hover {
opacity: 1;
border-color: #94a3b8;
}
.n5-chip.is-active[data-source='reception'] {
opacity: 1;
border-color: var(--reception);
background: var(--reception-bg);
box-shadow: 0 0 0 1px var(--reception);
}
.n5-chip.is-active[data-source='production'] {
opacity: 1;
border-color: var(--production);
background: var(--production-bg);
box-shadow: 0 0 0 1px var(--production);
}
.n5-chip__dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.n5-chip[data-source='reception'] .n5-chip__dot {
background: var(--reception);
}
.n5-chip[data-source='production'] .n5-chip__dot {
background: var(--production);
}
.n5-chip__body {
flex: 1;
min-width: 0;
}
.n5-chip__type {
font-size: 13px;
font-weight: 700;
}
.n5-chip__status {
font-size: 11px;
color: var(--text-muted);
margin-top: 1px;
}
.n5-chip:not(.is-active) .n5-chip__switch {
flex-shrink: 0;
font-size: 11px;
font-weight: 700;
color: #fff;
padding: 4px 10px;
border-radius: 6px;
background: var(--primary);
}
.n5-chip.is-active .n5-chip__switch {
flex-shrink: 0;
font-size: 10px;
font-weight: 700;
padding: 3px 8px;
border-radius: 999px;
background: rgb(255 255 255 / 70%);
color: inherit;
border: 1px solid rgb(0 0 0 / 10%);
}
/* ═══ N6 · 左导航 + 右内容预览 ═══ */
.n6-layout {
display: flex;
border-bottom: 1px solid var(--border);
min-height: 120px;
}
@media (max-width: 560px) {
.n6-layout {
flex-direction: column;
}
}
.n6-nav {
width: 200px;
flex-shrink: 0;
background: #f1f5f9;
border-right: 1px solid var(--border);
padding: 10px 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
@media (max-width: 560px) {
.n6-nav {
width: 100%;
flex-direction: row;
border-right: none;
border-bottom: 1px solid var(--border);
}
}
.n6-nav__label {
padding: 4px 8px 6px;
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
}
@media (max-width: 560px) {
.n6-nav__label {
display: none;
}
}
.n6-nav__btn {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px 10px;
border: 2px solid transparent;
border-radius: 8px;
background: #fff;
font-family: inherit;
text-align: left;
cursor: pointer;
transition: all 0.15s;
}
@media (max-width: 560px) {
.n6-nav__btn {
flex: 1;
}
}
.n6-nav__btn:not(.is-active):hover {
border-color: #cbd5e1;
}
.n6-nav__btn.is-active[data-source='reception'] {
border-color: var(--reception);
background: var(--reception-bg);
}
.n6-nav__btn.is-active[data-source='production'] {
border-color: var(--production);
background: var(--production-bg);
}
.n6-nav__btn:not(.is-active)::after {
content: '点击';
font-size: 10px;
font-weight: 600;
color: var(--primary);
}
.n6-nav__type {
font-size: 13px;
font-weight: 700;
}
.n6-nav__no {
font-size: 10px;
font-family: ui-monospace, Menlo, monospace;
color: var(--text-muted);
word-break: break-all;
}
.n6-preview {
flex: 1;
padding: 12px 16px;
background: #fff;
display: flex;
flex-direction: column;
justify-content: center;
}
.n6-preview__label {
font-size: 11px;
color: var(--text-muted);
margin-bottom: 6px;
}
.n6-preview__title {
font-size: 15px;
font-weight: 600;
margin-bottom: 4px;
}
.n6-preview__no {
font-family: ui-monospace, Menlo, monospace;
font-size: 13px;
color: var(--text-muted);
}
.n6-preview__hint {
margin-top: 10px;
font-size: 12px;
color: var(--primary-dark);
font-weight: 500;
}
</style>
</head>
<body>
<header class="demo-banner">
<div>
<h1>第二组 · 强对比方案(6 套)</h1>
<p>
上一组反馈「不够明显」。本组加大触控面积、始终展示两条工单、强化未选中态的视觉提示(虚线边框、👆 动画、Radio
清单、大 Toggle 等)。<a href="compare-all.html" style="color: var(--primary-dark)">← 第一组</a>
</p>
</div>
<div class="demo-banner__actions">
<a class="demo-link" href="index.html">索引</a>
</div>
</header>
<div class="batch-note">
<div class="batch-note-inner">
设计原则:<strong>两条工单信息始终可见</strong>、<strong>未选中必须有明确「可点击」 affordance</strong>、<strong>来源类别用大面积色块区分</strong>(绿=样本接收,琥珀=生产过程)。
</div>
</div>
<div class="page-container">
<div class="compare-stack">
<section class="compare-block">
<div class="compare-block__label">
<h2>
<span class="variant-tag variant-tag--rec">推荐</span>
<span class="variant-tag">N1</span>
双色分栏顶栏(整宽可点)
</h2>
<p>卡片顶部 50/50 分栏:左绿「样本接收」、右琥珀「生产过程」。未选中侧降饱和 + 底部「👆 点击切换」动画;选中侧底部色条 +「当前查看」角标。</p>
<div class="pros-cons">
<div class="pro">✓ 类别与可点击性一眼可见,占用高度适中</div>
</div>
</div>
<div class="compare-block__body" id="block-n1"></div>
</section>
<section class="compare-block">
<div class="compare-block__label">
<h2><span class="variant-tag">N2</span> 双行 Radio 清单</h2>
<p>两条工单以列表行始终展示:Radio 圆点 + 类型 + 工单号。未选中行右侧绿色「切换至此」按钮;选中行左侧 4px 色条高亮。</p>
<div class="pros-cons">
<div class="pro">✓ 类似表单单选,学习成本最低</div>
<div class="pro">✓ 两条工单信息完整可见</div>
</div>
</div>
<div class="compare-block__body" id="block-n2"></div>
</section>
<section class="compare-block">
<div class="compare-block__label">
<h2>
<span class="variant-tag variant-tag--rec">推荐</span>
<span class="variant-tag">N3</span>
大开关 Toggle(二选一)
</h2>
<p>居中 oversized 分段开关,emoji 图标 + 15px 粗体类别名。切换时白底滑块 + 阴影动效,下方显示当前工单号。</p>
<div class="pros-cons">
<div class="pro">✓ 二选一语义最直白</div>
</div>
</div>
<div class="compare-block__body" id="block-n3"></div>
</section>
<section class="compare-block">
<div class="compare-block__label">
<h2><span class="variant-tag">N4</span> 图标宫格(虚线 = 可点)</h2>
<p>两个 56px 大图标方块并排:未选中虚线边框 +「点击查看」圆角按钮;选中实线边框 + 整卡染色 + 阴影浮起。</p>
<div class="pros-cons">
<div class="pro">✓ 视觉重量最大,最难被忽略</div>
<div class="con">✗ 垂直空间占用较多</div>
</div>
</div>
<div class="compare-block__body" id="block-n4"></div>
</section>
<section class="compare-block">
<div class="compare-block__label">
<h2><span class="variant-tag">N5</span> 双摘要条 + 切换按钮</h2>
<p>顶部提示「本样本有 2 条质检工单,请点击切换」;下方两条摘要 Chip 并排,未选中带绿色「切换」实心按钮。</p>
</div>
<div class="compare-block__body" id="block-n5"></div>
</section>
<section class="compare-block">
<div class="compare-block__label">
<h2><span class="variant-tag">N6</span> 左导航 + 右预览</h2>
<p>左侧固定 200px 导航列列出两条工单(未选中显示「点击」),右侧预览当前选中工单摘要——Master-Detail 模式。</p>
<div class="pros-cons">
<div class="pro">✓ 适合 3 条以上工单扩展</div>
<div class="con">✗ 横向空间占用大</div>
</div>
</div>
<div class="compare-block__body" id="block-n6"></div>
</section>
</div>
</div>
<script>
const MOCK = {
sampleCode: 'YB-2025-0618-0042',
subtitle: '张三 · 间充质干细胞 · 5×10⁶/支',
workOrders: [
{
id: 'wo-reception',
source: 'reception',
label: '样本接收',
emoji: '📦',
no: 'QC-IN-20250618-0012',
statusLabel: '已完成',
statusClass: 'tag--success'
},
{
id: 'wo-production',
source: 'production',
label: '生产过程',
emoji: '⚙️',
no: 'QC-IN-20250620-0038',
statusLabel: '已配置',
statusClass: 'tag--warning'
}
]
}
const ICONS = {
reception: `<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>`,
production: `<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>`
}
function ctxHeader() {
return `
<div class="qc-sample-row__ctx">
<span class="qc-sample-row__idx">1</span>
<span class="qc-sample-row__code">${MOCK.sampleCode}</span>
<span class="qc-sample-row__sub">${MOCK.subtitle}</span>
<span class="qc-sample-row__badge">${MOCK.workOrders.length} 条质检</span>
</div>`
}
function cardPlaceholder(wo) {
return `
<div class="qc-card-placeholder">
<div class="qc-card-placeholder__head">
<span class="qc-card-placeholder__no">${wo.no}</span>
<span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
<span class="tag tag--${wo.source}">${wo.label}</span>
</div>
<div class="qc-card-placeholder__meta">
<span><strong>触发来源</strong>${wo.label}</span>
<span><strong>细胞类型</strong>间充质干细胞</span>
<span><strong>创建时间</strong>2025-06-18 14:32</span>
</div>
<div class="qc-card-placeholder__tasks">▼ 质检任务列表(${wo.label})</div>
</div>`
}
function wrap(inner, activeWo) {
return `<article class="qc-sample-row">${ctxHeader()}${inner}${cardPlaceholder(activeWo)}</article>`
}
function renderN1(id) {
const active = MOCK.workOrders.find((w) => w.id === id)
const [a, b] = MOCK.workOrders
return wrap(
`<div class="n1-switcher-wrap">
<div class="n1-switcher" role="tablist">
<button type="button" class="n1-half n1-half--reception tab-btn ${a.id === id ? 'is-active' : ''}"
data-id="${a.id}" role="tab">
<span class="n1-half__title">${a.emoji} ${a.label}</span>
<span class="n1-half__sub">${a.no}</span>
</button>
<button type="button" class="n1-half n1-half--production tab-btn ${b.id === id ? 'is-active' : ''}"
data-id="${b.id}" role="tab">
<span class="n1-half__title">${b.emoji} ${b.label}</span>
<span class="n1-half__sub">${b.no}</span>
</button>
</div>
<div class="n1-divider"></div>
</div>`,
active
)
}
function renderN2(id) {
const active = MOCK.workOrders.find((w) => w.id === id)
const rows = MOCK.workOrders.map(
(wo) => `
<button type="button" class="n2-row tab-btn ${wo.id === id ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}" role="tab">
<span class="n2-row__radio"></span>
<span class="n2-row__main">
<div class="n2-row__type">${wo.label}</div>
<div class="n2-row__no">${wo.no}</div>
</span>
<span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
<span class="n2-row__action">${wo.id === id ? '正在查看' : '切换至此'}</span>
</button>`
).join('')
return wrap(
`<div class="n2-list">
<div class="n2-list__head">请选择要查看的质检工单(共 ${MOCK.workOrders.length} 条)</div>
${rows}
</div>`,
active
)
}
function renderN3(id) {
const active = MOCK.workOrders.find((w) => w.id === id)
const btns = MOCK.workOrders.map(
(wo) => `
<button type="button" class="n3-toggle__btn tab-btn ${wo.id === id ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}" role="tab">
<span class="n3-toggle__icon">${wo.emoji}</span>
<span>${wo.label}</span>
${wo.id !== id ? '<span class="n3-toggle__hint">点击切换</span>' : ''}
</button>`
).join('')
return wrap(
`<div class="n3-zone">
<div class="n3-label">本样本有两条质检工单,请选择查看:</div>
<div class="n3-toggle" role="tablist">${btns}</div>
<div class="n3-current">当前:${active.no} · ${active.statusLabel}</div>
</div>`,
active
)
}
function renderN4(id) {
const active = MOCK.workOrders.find((w) => w.id === id)
const tiles = MOCK.workOrders.map(
(wo) => `
<button type="button" class="n4-tile tab-btn ${wo.id === id ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}" role="tab">
<span class="n4-tile__icon">${ICONS[wo.source]}</span>
<span class="n4-tile__name">${wo.label}</span>
<span class="n4-tile__meta">${wo.no}</span>
<span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
<span class="n4-tile__cta">${wo.id === id ? '✓ 当前查看' : '点击查看'}</span>
</button>`
).join('')
return wrap(
`<div class="n4-zone">
<div class="n4-head">
<div class="n4-head__title">选择质检工单类型</div>
<div class="n4-head__sub">虚线边框 = 可点击切换 · 实线 = 当前查看</div>
</div>
<div class="n4-grid">${tiles}</div>
</div>`,
active
)
}
function renderN5(id) {
const active = MOCK.workOrders.find((w) => w.id === id)
const chips = MOCK.workOrders.map(
(wo) => `
<button type="button" class="n5-chip tab-btn ${wo.id === id ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}" role="tab">
<span class="n5-chip__dot"></span>
<span class="n5-chip__body">
<div class="n5-chip__type">${wo.label}</div>
<div class="n5-chip__status">${wo.no} · ${wo.statusLabel}</div>
</span>
<span class="n5-chip__switch">${wo.id === id ? '查看中' : '切换'}</span>
</button>`
).join('')
return wrap(
`<div class="n5-zone">
<div class="n5-banner">
💡 本样本有 <strong>2 条</strong>质检工单(样本接收 + 生产过程),请点击下方切换查看
</div>
<div class="n5-strip">${chips}</div>
</div>`,
active
)
}
function renderN6(id) {
const active = MOCK.workOrders.find((w) => w.id === id)
const nav = MOCK.workOrders.map(
(wo) => `
<button type="button" class="n6-nav__btn tab-btn ${wo.id === id ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}" role="tab">
<span class="n6-nav__type">${wo.label}</span>
<span class="n6-nav__no">${wo.no}</span>
<span class="tag ${wo.statusClass}" style="margin-top:4px;height:20px;padding:0 6px;font-size:10px">${wo.statusLabel}</span>
</button>`
).join('')
return wrap(
`<div class="n6-layout">
<nav class="n6-nav">
<div class="n6-nav__label">工单列表</div>
${nav}
</nav>
<div class="n6-preview">
<div class="n6-preview__label">当前选中</div>
<div class="n6-preview__title">${active.label}</div>
<div class="n6-preview__no">${active.no}</div>
<span class="tag ${active.statusClass}">${active.statusLabel}</span>
<div class="n6-preview__hint">← 点击左侧另一条工单可切换</div>
</div>
</div>`,
active
)
}
const VARIANTS = {
n1: renderN1,
n2: renderN2,
n3: renderN3,
n4: renderN4,
n5: renderN5,
n6: renderN6
}
const state = Object.fromEntries(Object.keys(VARIANTS).map((k) => [k, 'wo-reception']))
function mount(key) {
const el = document.getElementById(`block-${key}`)
el.innerHTML = VARIANTS[key](state[key])
el.querySelectorAll('[data-id]').forEach((btn) => {
btn.addEventListener('click', () => {
state[key] = btn.dataset.id
mount(key)
})
})
}
Object.keys(VARIANTS).forEach(mount)
</script>
</body>
</html>