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

969 lines
30 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>
/* ── V0 现网基线 ── */
.v0-tabs {
padding: 8px 16px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
}
.v0-tabs__track {
display: inline-flex;
flex-wrap: wrap;
gap: 10px;
padding: 6px;
background: linear-gradient(180deg, var(--primary-bg) 0%, #fff 100%);
border: 1px solid #d1fae5;
border-radius: 10px;
}
.v0-tabs__btn {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 34px;
padding: 8px 16px;
border: none;
border-radius: 8px;
background: transparent;
font-size: 12px;
color: #1e293b;
transition: background 0.15s, color 0.15s;
}
.v0-tabs__btn:hover:not(.is-active) {
background: #f1f5f9;
}
.v0-tabs__btn.is-active {
background: var(--primary);
color: #fff;
box-shadow: 0 1px 3px rgba(16, 185, 129, 0.28);
}
.v0-tabs__btn:not(.is-active) .v0-source {
color: #1e293b;
background: #f1f5f9;
}
.v0-tabs__btn.is-active .v0-source {
color: #fff;
background: rgb(255 255 255 / 22%);
}
.v0-tabs__btn:not(.is-active) .tag {
background: #f1f5f9;
color: #1e293b;
border-color: #e2e8f0;
}
.v0-tabs__btn.is-active .tag {
background: rgb(255 255 255 / 22%);
color: #fff;
border-color: rgb(255 255 255 / 35%);
}
.v0-index {
width: 18px;
height: 18px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
background: #e2e8f0;
}
.v0-tabs__btn.is-active .v0-index {
background: rgb(255 255 255 / 28%);
}
.v0-source {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 1px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
}
.v0-source__dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
.v0-no {
font-family: ui-monospace, Menlo, monospace;
}
/* ── V1 分类色保留 + 引导 ── */
.v1-zone {
padding: 10px 16px 8px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
}
.v1-hint {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
font-size: 12px;
color: var(--text-muted);
}
.v1-hint svg {
flex-shrink: 0;
color: var(--primary);
}
.v1-hint strong {
color: var(--primary-dark);
font-weight: 600;
}
.v1-tabs__track {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.v1-tabs__btn {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 40px;
padding: 8px 14px;
border: 2px solid var(--border);
border-radius: 10px;
background: #fff;
font-size: 13px;
color: var(--text);
transition: border-color 0.15s, box-shadow 0.15s, transform 0.1s;
}
.v1-tabs__btn:hover:not(.is-active) {
border-color: #cbd5e1;
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.06);
}
.v1-tabs__btn.is-active {
border-color: var(--primary);
background: var(--primary-bg);
box-shadow: 0 0 0 1px var(--primary);
}
.v1-tabs__btn.is-active[data-source='reception'] {
border-color: var(--reception);
background: var(--reception-bg);
box-shadow: 0 0 0 1px var(--reception);
}
.v1-tabs__btn.is-active[data-source='production'] {
border-color: var(--production);
background: var(--production-bg);
box-shadow: 0 0 0 1px var(--production);
}
.v1-tabs__btn:not(.is-active)::after {
content: '点击查看';
font-size: 11px;
color: var(--text-muted);
margin-left: 2px;
}
.v1-source-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
}
.v1-source-badge--reception {
background: var(--reception-bg);
color: var(--reception);
border: 1px solid var(--reception-border);
}
.v1-source-badge--production {
background: var(--production-bg);
color: var(--production);
border: 1px solid var(--production-border);
}
/* ── V2 双卡片切换器 ── */
.v2-zone {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
}
.v2-label {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 10px;
}
.v2-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
@media (max-width: 560px) {
.v2-grid {
grid-template-columns: 1fr;
}
}
.v2-card {
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 14px;
border: 2px solid var(--border);
border-radius: 10px;
background: #fff;
text-align: left;
transition: border-color 0.15s, box-shadow 0.15s;
}
.v2-card:hover:not(.is-active) {
border-color: #cbd5e1;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06);
}
.v2-card.is-active {
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.08);
}
.v2-card[data-source='reception'].is-active {
border-color: var(--reception);
background: linear-gradient(180deg, var(--reception-bg) 0%, #fff 40%);
}
.v2-card[data-source='production'].is-active {
border-color: var(--production);
background: linear-gradient(180deg, var(--production-bg) 0%, #fff 40%);
}
.v2-card:not(.is-active)::before {
content: '点击切换';
position: absolute;
top: 10px;
right: 10px;
font-size: 11px;
color: var(--text-muted);
padding: 2px 8px;
border-radius: 999px;
background: #f1f5f9;
border: 1px solid var(--border);
}
.v2-card__top {
display: flex;
align-items: center;
gap: 8px;
}
.v2-card__icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.v2-card[data-source='reception'] .v2-card__icon {
background: var(--reception-bg);
color: var(--reception);
}
.v2-card[data-source='production'] .v2-card__icon {
background: var(--production-bg);
color: var(--production);
}
.v2-card__title {
font-size: 14px;
font-weight: 600;
}
.v2-card__no {
font-size: 12px;
font-family: ui-monospace, Menlo, monospace;
color: var(--text-muted);
}
.v2-card__foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
/* ── V3 流程步骤 ── */
.v3-zone {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
}
.v3-steps {
display: flex;
align-items: stretch;
gap: 0;
}
@media (max-width: 560px) {
.v3-steps {
flex-direction: column;
}
.v3-step__connector {
display: none;
}
}
.v3-step {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px 14px;
border: 2px solid var(--border);
background: #fff;
text-align: left;
transition: border-color 0.15s, background 0.15s;
}
.v3-step:first-child {
border-radius: 10px 0 0 10px;
}
.v3-step:last-child {
border-radius: 0 10px 10px 0;
}
.v3-step:hover:not(.is-active) {
background: #fafafa;
cursor: pointer;
}
.v3-step.is-active[data-source='reception'] {
border-color: var(--reception);
background: var(--reception-bg);
}
.v3-step.is-active[data-source='production'] {
border-color: var(--production);
background: var(--production-bg);
}
.v3-step:not(.is-active) {
opacity: 0.85;
}
.v3-step:not(.is-active)::after {
content: '点击切换至此工单';
font-size: 11px;
color: var(--text-muted);
}
.v3-step__connector {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
flex-shrink: 0;
color: #94a3b8;
background: #f1f5f9;
border-top: 2px solid var(--border);
border-bottom: 2px solid var(--border);
}
.v3-step__num {
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
}
.v3-step__name {
font-size: 14px;
font-weight: 600;
}
.v3-step__row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
/* ── V4 全宽分段控制器 ── */
.v4-zone {
padding: 10px 16px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
}
.v4-segment {
display: flex;
width: 100%;
padding: 4px;
gap: 4px;
background: #e2e8f0;
border-radius: 10px;
}
.v4-segment__btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 10px 12px;
border: none;
border-radius: 8px;
background: transparent;
font-size: 13px;
color: var(--text-muted);
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.v4-segment__btn:hover:not(.is-active) {
background: rgb(255 255 255 / 60%);
color: var(--text);
}
.v4-segment__btn.is-active {
background: #fff;
color: var(--text);
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.1);
font-weight: 600;
}
.v4-segment__btn.is-active[data-source='reception'] .v4-segment__type {
color: var(--reception);
}
.v4-segment__btn.is-active[data-source='production'] .v4-segment__type {
color: var(--production);
}
.v4-segment__type {
font-size: 14px;
font-weight: 600;
}
.v4-segment__sub {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
font-family: ui-monospace, Menlo, monospace;
}
/* ── V5 左侧竖条 + 大标签(推荐) ── */
.v5-zone {
padding: 10px 16px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
}
.v5-row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.v5-prefix {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
white-space: nowrap;
}
.v5-tabs {
display: flex;
flex: 1;
flex-wrap: wrap;
gap: 8px;
min-width: 0;
}
.v5-tab {
display: inline-flex;
align-items: stretch;
min-height: 44px;
padding: 0;
border: 2px solid var(--border);
border-radius: 10px;
background: #fff;
overflow: hidden;
transition: border-color 0.15s, box-shadow 0.15s;
}
.v5-tab:hover:not(.is-active) {
border-color: #94a3b8;
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.06);
}
.v5-tab.is-active {
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary);
}
.v5-tab.is-active[data-source='reception'] {
border-color: var(--reception);
box-shadow: 0 0 0 1px var(--reception);
}
.v5-tab.is-active[data-source='production'] {
border-color: var(--production);
box-shadow: 0 0 0 1px var(--production);
}
.v5-tab__accent {
width: 5px;
flex-shrink: 0;
}
.v5-tab[data-source='reception'] .v5-tab__accent {
background: var(--reception);
}
.v5-tab[data-source='production'] .v5-tab__accent {
background: var(--production);
}
.v5-tab__body {
display: flex;
flex-direction: column;
justify-content: center;
gap: 2px;
padding: 8px 14px 8px 10px;
text-align: left;
}
.v5-tab__type {
font-size: 13px;
font-weight: 600;
color: var(--text);
}
.v5-tab__meta {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: var(--text-muted);
}
.v5-tab__meta code {
font-family: ui-monospace, Menlo, monospace;
font-size: 11px;
}
.v5-tab:not(.is-active) .v5-tab__hint {
font-size: 10px;
color: var(--primary-dark);
font-weight: 500;
}
</style>
</head>
<body>
<header class="demo-banner">
<div>
<h1>质检工单 · 细胞存储 · 同样本多工单 Tab 选型</h1>
<p>
场景:同一样本编码下存在<strong>样本接收</strong>与<strong>生产过程</strong>两条质检工单,需在卡片内切换查看。
下方 6 套方案均可点击交互,请对比哪套最能传达「可切换、类别不同」。
</p>
</div>
<div class="demo-banner__actions">
<a class="demo-link" href="index.html">← 索引</a>
</div>
</header>
<div class="page-container">
<div class="compare-stack">
<!-- V0 -->
<section class="compare-block" data-variant="v0">
<div class="compare-block__label">
<h2>
<span class="variant-tag variant-tag--baseline">V0</span>
现网基线(当前实现)
</h2>
<p>胶囊 Tab 内嵌序号 + 来源标签 + 工单号 + 状态;未选中时来源与状态均灰化,类别差异不明显。</p>
<div class="pros-cons">
<div class="con">✗ 未选中 Tab 全灰,样本接收 / 生产过程难以区分</div>
<div class="con">✗ 交互感弱,易被当成静态信息条</div>
</div>
</div>
<div class="compare-block__body" id="block-v0"></div>
</section>
<!-- V1 -->
<section class="compare-block" data-variant="v1">
<div class="compare-block__label">
<h2>
<span class="variant-tag">V1</span>
分类色保留 + 引导文案 +「点击查看」
</h2>
<p>渐进改良:保留横向 Tab 布局,未选中仍保留绿/琥珀来源色,并加一行引导 + 未选中 Tab 后缀「点击查看」。</p>
<div class="pros-cons">
<div class="pro">✓ 改动量小,与现网结构最接近</div>
<div class="pro">✓ 类别色始终可见</div>
<div class="con">✗ 引导文案熟悉后可能略冗余</div>
</div>
</div>
<div class="compare-block__body" id="block-v1"></div>
</section>
<!-- V5 推荐放前面一点 - actually keep order logical -->
<!-- V2 -->
<section class="compare-block" data-variant="v2">
<div class="compare-block__label">
<h2>
<span class="variant-tag variant-tag--rec">推荐</span>
<span class="variant-tag">V2</span>
双卡片切换器
</h2>
<p>两条工单并排展示为可点击卡片,图标 + 大标题区分来源,未选中角标「点击切换」,选中侧渐变高亮。</p>
<div class="pros-cons">
<div class="pro">✓ 可点击性最强,两类工单一眼可辨</div>
<div class="pro">✓ 移动端可纵向堆叠</div>
<div class="con">✗ 占用垂直空间略多</div>
</div>
</div>
<div class="compare-block__body" id="block-v2"></div>
</section>
<!-- V3 -->
<section class="compare-block" data-variant="v3">
<div class="compare-block__label">
<h2><span class="variant-tag">V3</span> 流程步骤条(接收 → 生产)</h2>
<p>强调业务先后顺序:样本接收在前、生产过程在后,中间箭头连接;未选中步骤提示「点击切换至此工单」。</p>
<div class="pros-cons">
<div class="pro">✓ 传达「同一流程两阶段」的业务语义</div>
<div class="con">✗ 若未来出现 3+ 条工单,横向会拥挤</div>
</div>
</div>
<div class="compare-block__body" id="block-v3"></div>
</section>
<!-- V4 -->
<section class="compare-block" data-variant="v4">
<div class="compare-block__label">
<h2><span class="variant-tag">V4</span> 全宽分段控制器(iOS Segmented)</h2>
<p>类似系统分段控件,占满卡片宽度;选中项白底浮起,类型名加粗,下方显示工单号与状态。</p>
<div class="pros-cons">
<div class="pro">✓ 切换控件形态 familiar,点击区域大</div>
<div class="con">✗ 类别色仅体现在选中态文字,未选中时仍偏中性</div>
</div>
</div>
<div class="compare-block__body" id="block-v4"></div>
</section>
<!-- V5 -->
<section class="compare-block" data-variant="v5">
<div class="compare-block__label">
<h2>
<span class="variant-tag variant-tag--rec">推荐</span>
<span class="variant-tag">V5</span>
左侧色条 + 大标签 Tab
</h2>
<p>
「切换工单:」前缀 + 带左侧竖条(绿=接收、琥珀=生产)的大按钮;未选中显示「点此查看」微提示,选中展示完整工单号与状态。
</p>
<div class="pros-cons">
<div class="pro">✓ 类别色条 + 44px 触控高度,可发现性与无障碍兼顾</div>
<div class="pro">✓ 信息密度适中,适合 2~3 条工单</div>
<div class="con">✗ 比 V0 略占高度</div>
</div>
</div>
<div class="compare-block__body" id="block-v5"></div>
</section>
</div>
</div>
<script>
const MOCK = {
sampleCode: 'YB-2025-0618-0042',
subtitle: '张三 · 间充质干细胞 · 5×10⁶/支',
workOrders: [
{
id: 'wo-reception',
source: 'reception',
label: '样本接收',
no: 'QC-IN-20250618-0012',
status: 'ywc',
statusLabel: '已完成',
statusClass: 'tag--success'
},
{
id: 'wo-production',
source: 'production',
label: '生产过程',
no: 'QC-IN-20250620-0038',
status: 'ypz',
statusLabel: '已配置',
statusClass: 'tag--warning'
}
]
}
const ICONS = {
reception: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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 wrapSampleRow(inner, activeWo) {
return `
<article class="qc-sample-row">
${ctxHeader()}
${inner}
${cardPlaceholder(activeWo)}
</article>`
}
/* ── V0 ── */
function renderV0(activeId) {
const active = MOCK.workOrders.find((w) => w.id === activeId)
const tabs = MOCK.workOrders
.map(
(wo, i) => `
<button type="button" class="v0-tabs__btn tab-btn ${wo.id === activeId ? 'is-active' : ''}"
data-id="${wo.id}" role="tab" aria-selected="${wo.id === activeId}">
<span class="v0-index">${i + 1}</span>
<span class="v0-source"><span class="v0-source__dot"></span>${wo.label}</span>
<span class="v0-no">${wo.no}</span>
<span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
</button>`
)
.join('')
return wrapSampleRow(
`<div class="v0-tabs"><div class="v0-tabs__track" role="tablist">${tabs}</div></div>`,
active
)
}
/* ── V1 ── */
function renderV1(activeId) {
const active = MOCK.workOrders.find((w) => w.id === activeId)
const tabs = MOCK.workOrders
.map(
(wo) => `
<button type="button" class="v1-tabs__btn tab-btn ${wo.id === activeId ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}" role="tab">
<span class="v1-source-badge v1-source-badge--${wo.source}">${wo.label}</span>
<span style="font-family:ui-monospace,Menlo,monospace;font-size:12px">${wo.no}</span>
<span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
</button>`
)
.join('')
return wrapSampleRow(
`<div class="v1-zone">
<div class="v1-hint">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
同一样本含 <strong>${MOCK.workOrders.length} 条质检工单</strong>,点击下方切换查看
</div>
<div class="v1-tabs__track" role="tablist">${tabs}</div>
</div>`,
active
)
}
/* ── V2 ── */
function renderV2(activeId) {
const active = MOCK.workOrders.find((w) => w.id === activeId)
const cards = MOCK.workOrders
.map(
(wo) => `
<button type="button" class="v2-card tab-btn ${wo.id === activeId ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}">
<div class="v2-card__top">
<span class="v2-card__icon">${ICONS[wo.source]}</span>
<div>
<div class="v2-card__title">${wo.label}</div>
<div class="v2-card__no">${wo.no}</div>
</div>
</div>
<div class="v2-card__foot">
<span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
</div>
</button>`
)
.join('')
return wrapSampleRow(
`<div class="v2-zone">
<div class="v2-label">选择要查看的质检工单</div>
<div class="v2-grid">${cards}</div>
</div>`,
active
)
}
/* ── V3 ── */
function renderV3(activeId) {
const active = MOCK.workOrders.find((w) => w.id === activeId)
const [wo1, wo2] = MOCK.workOrders
return wrapSampleRow(
`<div class="v3-zone">
<div class="v3-steps" role="tablist">
<button type="button" class="v3-step tab-btn ${wo1.id === activeId ? 'is-active' : ''}"
data-id="${wo1.id}" data-source="${wo1.source}" role="tab">
<span class="v3-step__num">阶段 1</span>
<span class="v3-step__name">${wo1.label}</span>
<span class="v3-step__row">
<code style="font-size:11px">${wo1.no}</code>
<span class="tag ${wo1.statusClass}">${wo1.statusLabel}</span>
</span>
</button>
<div class="v3-step__connector" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</div>
<button type="button" class="v3-step tab-btn ${wo2.id === activeId ? 'is-active' : ''}"
data-id="${wo2.id}" data-source="${wo2.source}" role="tab">
<span class="v3-step__num">阶段 2</span>
<span class="v3-step__name">${wo2.label}</span>
<span class="v3-step__row">
<code style="font-size:11px">${wo2.no}</code>
<span class="tag ${wo2.statusClass}">${wo2.statusLabel}</span>
</span>
</button>
</div>
</div>`,
active
)
}
/* ── V4 ── */
function renderV4(activeId) {
const active = MOCK.workOrders.find((w) => w.id === activeId)
const btns = MOCK.workOrders
.map(
(wo) => `
<button type="button" class="v4-segment__btn tab-btn ${wo.id === activeId ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}" role="tab">
<span class="v4-segment__type">${wo.label}</span>
<span class="v4-segment__sub">
<span>${wo.no.slice(-8)}</span>
<span class="tag ${wo.statusClass}" style="height:18px;padding:0 6px;font-size:10px">${wo.statusLabel}</span>
</span>
</button>`
)
.join('')
return wrapSampleRow(`<div class="v4-zone"><div class="v4-segment" role="tablist">${btns}</div></div>`, active)
}
/* ── V5 ── */
function renderV5(activeId) {
const active = MOCK.workOrders.find((w) => w.id === activeId)
const tabs = MOCK.workOrders
.map(
(wo) => `
<button type="button" class="v5-tab tab-btn ${wo.id === activeId ? 'is-active' : ''}"
data-id="${wo.id}" data-source="${wo.source}" role="tab">
<span class="v5-tab__accent"></span>
<span class="v5-tab__body">
<span class="v5-tab__type">${wo.label}</span>
<span class="v5-tab__meta">
${wo.id === activeId ? `<code>${wo.no}</code><span class="tag ${wo.statusClass}">${wo.statusLabel}</span>` : `<span class="v5-tab__hint">点此查看 →</span>`}
</span>
</span>
</button>`
)
.join('')
return wrapSampleRow(
`<div class="v5-zone">
<div class="v5-row">
<span class="v5-prefix">切换工单</span>
<div class="v5-tabs" role="tablist">${tabs}</div>
</div>
</div>`,
active
)
}
const VARIANTS = {
v0: { render: renderV0, default: 'wo-reception' },
v1: { render: renderV1, default: 'wo-reception' },
v2: { render: renderV2, default: 'wo-reception' },
v3: { render: renderV3, default: 'wo-reception' },
v4: { render: renderV4, default: 'wo-reception' },
v5: { render: renderV5, default: 'wo-reception' }
}
const state = {}
Object.keys(VARIANTS).forEach((k) => {
state[k] = VARIANTS[k].default
})
function mountVariant(key) {
const el = document.getElementById(`block-${key}`)
el.innerHTML = VARIANTS[key].render(state[key])
el.querySelectorAll('[data-id]').forEach((btn) => {
btn.addEventListener('click', () => {
state[key] = btn.dataset.id
mountVariant(key)
})
})
}
Object.keys(VARIANTS).forEach(mountVariant)
</script>
</body>
</html>