| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968 |
- <!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>
|