zlwu hai 4 meses
pai
achega
124b15d094

+ 334 - 0
docs/qc-wo-sample-tab-demos/_shared.css

@@ -0,0 +1,334 @@
+/* 质检工单 · 同样本多工单 Tab 选型 Demo(Fresh Mint Token) */
+:root {
+  --primary: #10b981;
+  --primary-dark: #059669;
+  --primary-bg: #ecfdf5;
+  --text: #1e293b;
+  --text-muted: #64748b;
+  --border: #e2e8f0;
+  --border-soft: #d9e8e2;
+  --bg-page: #f8fafc;
+  --surface: #fff;
+  --radius: 10px;
+  --radius-sm: 8px;
+  --reception: #047857;
+  --reception-bg: #ecfdf5;
+  --reception-border: #a7f3d0;
+  --production: #b45309;
+  --production-bg: #fffbeb;
+  --production-border: #fcd34d;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+body {
+  margin: 0;
+  min-height: 100vh;
+  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
+  font-size: 14px;
+  line-height: 1.5;
+  color: var(--text);
+  background: var(--bg-page);
+}
+
+.demo-banner {
+  position: sticky;
+  top: 0;
+  z-index: 200;
+  padding: 12px 24px;
+  background: var(--surface);
+  border-bottom: 1px solid var(--border);
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 16px;
+  flex-wrap: wrap;
+  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
+}
+
+.demo-banner h1 {
+  margin: 0;
+  font-size: 15px;
+  font-weight: 600;
+}
+
+.demo-banner p {
+  margin: 4px 0 0;
+  font-size: 12px;
+  color: var(--text-muted);
+  max-width: 760px;
+  line-height: 1.55;
+}
+
+.demo-banner__actions {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+  flex-shrink: 0;
+}
+
+.demo-link {
+  display: inline-flex;
+  align-items: center;
+  height: 32px;
+  padding: 0 12px;
+  border-radius: var(--radius-sm);
+  border: 1px solid var(--border);
+  background: var(--surface);
+  font-size: 12px;
+  font-weight: 500;
+  color: var(--text);
+  text-decoration: none;
+  transition: border-color 0.15s, background 0.15s;
+}
+
+.demo-link:hover {
+  border-color: #a7f3d0;
+  background: var(--primary-bg);
+  color: var(--primary-dark);
+}
+
+.page-container {
+  max-width: 960px;
+  margin: 0 auto;
+  padding: 20px 24px 48px;
+}
+
+.compare-stack {
+  display: flex;
+  flex-direction: column;
+  gap: 28px;
+}
+
+.compare-block {
+  background: var(--surface);
+  border: 1px solid var(--border-soft);
+  border-radius: var(--radius);
+  overflow: hidden;
+}
+
+.compare-block__label {
+  padding: 14px 18px;
+  background: #fafafa;
+  border-bottom: 1px solid var(--border);
+}
+
+.compare-block__label h2 {
+  margin: 0;
+  font-size: 14px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-wrap: wrap;
+}
+
+.compare-block__label p {
+  margin: 6px 0 0;
+  font-size: 12px;
+  color: var(--text-muted);
+  line-height: 1.55;
+}
+
+.variant-tag {
+  display: inline-flex;
+  align-items: center;
+  height: 22px;
+  padding: 0 8px;
+  border-radius: 6px;
+  font-size: 11px;
+  font-weight: 700;
+  letter-spacing: 0.02em;
+}
+
+.variant-tag--baseline {
+  background: #f1f5f9;
+  color: #475569;
+}
+
+.variant-tag--rec {
+  background: var(--primary-bg);
+  color: var(--primary-dark);
+  border: 1px solid #a7f3d0;
+}
+
+.compare-block__body {
+  padding: 16px;
+  background: #f1f5f9;
+}
+
+.pros-cons {
+  margin-top: 8px;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  font-size: 12px;
+}
+
+.pro {
+  color: #047857;
+}
+
+.con {
+  color: #94a3b8;
+}
+
+/* ── 样本组卡片骨架 ── */
+.qc-sample-row {
+  display: flex;
+  flex-direction: column;
+  min-width: 0;
+  border: 1px solid #e2e8f0;
+  border-radius: 10px;
+  background: #fff;
+  overflow: hidden;
+  transition: border-color 0.15s ease, box-shadow 0.15s ease;
+}
+
+.qc-sample-row:hover {
+  border-color: #a8d4c4;
+  box-shadow: 0 2px 8px rgba(4, 120, 87, 0.08);
+}
+
+.qc-sample-row__ctx {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 8px 12px;
+  padding: 12px 16px;
+  background: linear-gradient(180deg, var(--primary-bg) 0%, #fff 100%);
+  border-bottom: 1px solid #d1fae5;
+}
+
+.qc-sample-row__idx {
+  flex-shrink: 0;
+  font-size: 12px;
+  color: var(--text-muted);
+  min-width: 20px;
+}
+
+.qc-sample-row__code {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1e293b;
+  font-family: ui-monospace, Menlo, monospace;
+}
+
+.qc-sample-row__sub {
+  flex: 1;
+  min-width: 0;
+  font-size: 12px;
+  color: var(--text-muted);
+}
+
+.qc-sample-row__badge {
+  flex-shrink: 0;
+  font-size: 11px;
+  font-weight: 600;
+  padding: 2px 10px;
+  border-radius: 999px;
+  background: var(--primary-bg);
+  color: #047857;
+  border: 1px solid #d1fae5;
+}
+
+/* 工单内容占位 */
+.qc-card-placeholder {
+  padding: 14px 16px;
+  background: #fafafa;
+  border-top: 1px solid var(--border);
+}
+
+.qc-card-placeholder__head {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 10px;
+}
+
+.qc-card-placeholder__no {
+  font-size: 15px;
+  font-weight: 600;
+  font-family: ui-monospace, Menlo, monospace;
+}
+
+.qc-card-placeholder__meta {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 8px;
+  font-size: 12px;
+  color: var(--text-muted);
+}
+
+.qc-card-placeholder__meta span strong {
+  display: block;
+  font-size: 11px;
+  font-weight: 500;
+  color: #94a3b8;
+}
+
+.qc-card-placeholder__tasks {
+  margin-top: 10px;
+  padding: 10px 12px;
+  background: #f8fafc;
+  border-radius: 8px;
+  font-size: 12px;
+  color: var(--text-muted);
+}
+
+/* Tag 模拟 */
+.tag {
+  display: inline-flex;
+  align-items: center;
+  height: 22px;
+  padding: 0 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+  border: 1px solid transparent;
+}
+
+.tag--success {
+  background: #ecfdf5;
+  color: #047857;
+  border-color: #a7f3d0;
+}
+
+.tag--warning {
+  background: #fffbeb;
+  color: #b45309;
+  border-color: #fcd34d;
+}
+
+.tag--info {
+  background: #f1f5f9;
+  color: #475569;
+  border-color: #e2e8f0;
+}
+
+.tag--reception {
+  background: var(--reception-bg);
+  color: var(--reception);
+  border-color: var(--reception-border);
+}
+
+.tag--production {
+  background: var(--production-bg);
+  color: var(--production);
+  border-color: var(--production-border);
+}
+
+/* 通用 tab 按钮 reset */
+.tab-btn {
+  font-family: inherit;
+  cursor: pointer;
+  user-select: none;
+}
+
+.tab-btn:focus-visible {
+  outline: 2px solid var(--primary);
+  outline-offset: 2px;
+}

+ 1057 - 0
docs/qc-wo-sample-tab-demos/compare-all-v2.html

@@ -0,0 +1,1057 @@
+<!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>

+ 968 - 0
docs/qc-wo-sample-tab-demos/compare-all.html

@@ -0,0 +1,968 @@
+<!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>

+ 422 - 0
docs/qc-wo-sample-tab-demos/independent-pills.html

@@ -0,0 +1,422 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>质检工单 · 独立胶囊 Tab Demo</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>
+      /* ── 共享:胶囊按钮内容(对齐现网 QcWorkOrderSampleGroup) ── */
+      .pill-btn {
+        display: inline-flex;
+        align-items: center;
+        gap: 6px;
+        min-height: 34px;
+        padding: 8px 16px;
+        border-radius: 10px;
+        font-size: 12px;
+        font-family: inherit;
+        color: #1e293b;
+        cursor: pointer;
+        white-space: nowrap;
+        transition:
+          background 0.15s,
+          color 0.15s,
+          border-color 0.15s,
+          box-shadow 0.15s;
+      }
+
+      .pill-btn:focus-visible {
+        outline: 2px solid #10b981;
+        outline-offset: 2px;
+      }
+
+      .pill-index {
+        flex-shrink: 0;
+        width: 18px;
+        height: 18px;
+        border-radius: 50%;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 11px;
+        font-weight: 700;
+        background: #e2e8f0;
+      }
+
+      .pill-source {
+        display: inline-flex;
+        align-items: center;
+        gap: 4px;
+        padding: 1px 6px;
+        border-radius: 4px;
+        font-size: 11px;
+        font-weight: 600;
+        background: #f1f5f9;
+      }
+
+      .pill-source--reception {
+        color: var(--reception);
+        background: var(--reception-bg);
+      }
+
+      .pill-source--production {
+        color: var(--production);
+        background: var(--production-bg);
+      }
+
+      .pill-source__dot {
+        width: 6px;
+        height: 6px;
+        border-radius: 50%;
+        background: currentColor;
+      }
+
+      .pill-no {
+        font-family: ui-monospace, Menlo, monospace;
+      }
+
+      .pill-btn.is-active {
+        color: #fff;
+        background: var(--primary);
+        border-color: var(--primary);
+        box-shadow: 0 2px 6px rgba(16, 185, 129, 0.28);
+      }
+
+      .pill-btn.is-active .pill-index {
+        color: #fff;
+        background: rgb(255 255 255 / 28%);
+      }
+
+      .pill-btn.is-active .pill-source,
+      .pill-btn.is-active .pill-source--reception,
+      .pill-btn.is-active .pill-source--production {
+        color: #fff;
+        background: rgb(255 255 255 / 22%);
+      }
+
+      .pill-btn.is-active .tag {
+        background: rgb(255 255 255 / 22%);
+        color: #fff;
+        border-color: rgb(255 255 255 / 35%);
+      }
+
+      .pill-btn:not(.is-active) .tag {
+        background: #f1f5f9;
+        color: #1e293b;
+        border-color: #e2e8f0;
+      }
+
+      /* ── A · 现网:轨道内连体 ── */
+      .grouped-zone {
+        padding: 8px 16px;
+        border-bottom: 1px solid var(--border);
+        background: #f8fafc;
+      }
+
+      .grouped-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;
+      }
+
+      .grouped-track .pill-btn {
+        border: none;
+        border-radius: 8px;
+        background: transparent;
+      }
+
+      .grouped-track .pill-btn:hover:not(.is-active) {
+        background: #f1f5f9;
+      }
+
+      /* ── B/C/D · 独立胶囊 ── */
+      .indep-zone {
+        padding: 12px 16px;
+        border-bottom: 1px solid var(--border);
+        background: #f8fafc;
+      }
+
+      .indep-zone__hint {
+        margin-bottom: 10px;
+        font-size: 12px;
+        color: var(--text-muted);
+      }
+
+      .indep-row {
+        display: flex;
+        flex-wrap: wrap;
+        align-items: center;
+      }
+
+      .indep-row--gap-md {
+        gap: 16px;
+      }
+
+      .indep-row--gap-lg {
+        gap: 24px;
+      }
+
+      .indep-row--gap-xl {
+        gap: 32px;
+      }
+
+      /* 每个胶囊独立边框 + 白底 */
+      .indep-row .pill-btn {
+        border: 2px solid #e2e8f0;
+        background: #fff;
+        box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
+      }
+
+      .indep-row .pill-btn:hover:not(.is-active) {
+        border-color: #cbd5e1;
+        box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
+      }
+
+      /* 未选中时按来源保留边框色 */
+      .indep-row .pill-btn[data-source='reception']:not(.is-active) {
+        border-color: var(--reception-border);
+      }
+
+      .indep-row .pill-btn[data-source='production']:not(.is-active) {
+        border-color: var(--production-border);
+      }
+
+      .indep-row .pill-btn[data-source='reception']:not(.is-active):hover {
+        border-color: var(--reception);
+        background: var(--reception-bg);
+      }
+
+      .indep-row .pill-btn[data-source='production']:not(.is-active):hover {
+        border-color: var(--production);
+        background: var(--production-bg);
+      }
+
+      /* D · 独立 + 更大胶囊 */
+      .indep-row--large .pill-btn {
+        min-height: 40px;
+        padding: 10px 18px;
+        font-size: 13px;
+        border-radius: 12px;
+      }
+
+      .indep-row--large .pill-source {
+        font-size: 12px;
+        padding: 2px 8px;
+      }
+
+      .gap-label {
+        display: inline-flex;
+        align-items: center;
+        height: 22px;
+        padding: 0 8px;
+        margin-left: 8px;
+        border-radius: 6px;
+        font-size: 11px;
+        font-weight: 600;
+        background: #f1f5f9;
+        color: var(--text-muted);
+      }
+    </style>
+  </head>
+  <body>
+    <header class="demo-banner">
+      <div>
+        <h1>独立胶囊 Tab · 对比 Demo</h1>
+        <p>
+          在现网胶囊内容结构不变的前提下,将每个 Tab 从<strong>轨道连体</strong>改为<strong>独立胶囊</strong>,并加大间距。
+          下方 A 为现网对照,B/C/D 为独立方案(间距 16 / 24 / 32px,均可点击切换)。
+        </p>
+      </div>
+      <div class="demo-banner__actions">
+        <a class="demo-link" href="index.html">← 索引</a>
+        <a class="demo-link" href="compare-all-v2.html">第二组方案</a>
+      </div>
+    </header>
+
+    <div class="page-container">
+      <div class="compare-stack">
+        <section class="compare-block">
+          <div class="compare-block__label">
+            <h2>
+              <span class="variant-tag variant-tag--baseline">A</span>
+              现网 · 轨道内连体(对照)
+            </h2>
+            <p>多个 Tab 包在同一 <code>panel-pill-tabs__track</code> 绿底容器内,胶囊之间 gap 10px,视觉上像一组。</p>
+          </div>
+          <div class="compare-block__body" id="block-a"></div>
+        </section>
+
+        <section class="compare-block">
+          <div class="compare-block__label">
+            <h2>
+              <span class="variant-tag variant-tag--rec">推荐</span>
+              <span class="variant-tag">B</span>
+              独立胶囊 · 间距 16px
+            </h2>
+            <p>去掉外层轨道,每个 Tab 自带白底 + 2px 边框 + 轻阴影;未选中按来源保留绿/琥珀边框色。</p>
+          </div>
+          <div class="compare-block__body" id="block-b"></div>
+        </section>
+
+        <section class="compare-block">
+          <div class="compare-block__label">
+            <h2><span class="variant-tag">C</span> 独立胶囊 · 间距 24px</h2>
+            <p>同 B,胶囊间距加大到 24px,分离感更强。</p>
+          </div>
+          <div class="compare-block__body" id="block-c"></div>
+        </section>
+
+        <section class="compare-block">
+          <div class="compare-block__label">
+            <h2><span class="variant-tag">D</span> 独立胶囊 · 间距 32px + 略放大</h2>
+            <p>间距 32px,胶囊 min-height 40px、圆角 12px,点击区域更大。</p>
+          </div>
+          <div class="compare-block__body" id="block-d"></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',
+            statusLabel: '已完成',
+            statusClass: 'tag--success'
+          },
+          {
+            id: 'wo-production',
+            source: 'production',
+            label: '生产过程',
+            no: 'QC-IN-20250620-0038',
+            statusLabel: '已配置',
+            statusClass: 'tag--warning'
+          }
+        ]
+      }
+
+      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 pillBtn(wo, i, activeId) {
+        const active = wo.id === activeId
+        return `
+          <button type="button" class="pill-btn tab-btn ${active ? 'is-active' : ''}"
+            data-id="${wo.id}" data-source="${wo.source}" role="tab" aria-selected="${active}">
+            <span class="pill-index">${i + 1}</span>
+            <span class="pill-source pill-source--${wo.source}">
+              <span class="pill-source__dot"></span>${wo.label}
+            </span>
+            <span class="pill-no">${wo.no}</span>
+            <span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
+          </button>`
+      }
+
+      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__tasks">▼ 质检任务列表(${wo.label})</div>
+          </div>`
+      }
+
+      function wrap(tabsHtml, activeId) {
+        const active = MOCK.workOrders.find((w) => w.id === activeId)
+        return `
+          <article class="qc-sample-row">
+            ${ctxHeader()}
+            ${tabsHtml}
+            ${cardPlaceholder(active)}
+          </article>`
+      }
+
+      function renderGrouped(activeId) {
+        const pills = MOCK.workOrders.map((wo, i) => pillBtn(wo, i, activeId)).join('')
+        return wrap(
+          `<div class="grouped-zone">
+            <div class="grouped-track" role="tablist" aria-label="同样本质检工单">${pills}</div>
+          </div>`,
+          activeId
+        )
+      }
+
+      function renderIndependent(activeId, gapClass, extraRowClass, hint) {
+        const pills = MOCK.workOrders.map((wo, i) => pillBtn(wo, i, activeId)).join('')
+        const gapLabel = gapClass.replace('indep-row--gap-', '') + 'px'
+        return wrap(
+          `<div class="indep-zone">
+            ${hint ? `<div class="indep-zone__hint">${hint}</div>` : ''}
+            <div class="indep-row ${gapClass} ${extraRowClass || ''}" role="tablist" aria-label="同样本质检工单">
+              ${pills}
+            </div>
+          </div>`,
+          activeId
+        )
+      }
+
+      const BLOCKS = {
+        a: { render: (id) => renderGrouped(id), default: 'wo-reception' },
+        b: {
+          render: (id) =>
+            renderIndependent(id, 'indep-row--gap-md', '', '每个胶囊独立展示,间距 16px'),
+          default: 'wo-reception'
+        },
+        c: {
+          render: (id) =>
+            renderIndependent(id, 'indep-row--gap-lg', '', '每个胶囊独立展示,间距 24px'),
+          default: 'wo-reception'
+        },
+        d: {
+          render: (id) =>
+            renderIndependent(
+              id,
+              'indep-row--gap-xl',
+              'indep-row--large',
+              '每个胶囊独立展示,间距 32px,尺寸略放大'
+            ),
+          default: 'wo-reception'
+        }
+      }
+
+      const state = Object.fromEntries(Object.entries(BLOCKS).map(([k, v]) => [k, v.default]))
+
+      function mount(key) {
+        const el = document.getElementById(`block-${key}`)
+        el.innerHTML = BLOCKS[key].render(state[key])
+        el.querySelectorAll('[data-id]').forEach((btn) => {
+          btn.addEventListener('click', () => {
+            state[key] = btn.dataset.id
+            mount(key)
+          })
+        })
+      }
+
+      Object.keys(BLOCKS).forEach(mount)
+    </script>
+  </body>
+</html>

+ 263 - 0
docs/qc-wo-sample-tab-demos/index.html

@@ -0,0 +1,263 @@
+<!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>
+      .index-hero {
+        max-width: 960px;
+        margin: 0 auto;
+        padding: 32px 24px 12px;
+      }
+
+      .index-hero h1 {
+        margin: 0 0 8px;
+        font-size: 22px;
+      }
+
+      .index-hero p {
+        margin: 0;
+        color: var(--text-muted);
+        max-width: 720px;
+        line-height: 1.6;
+      }
+
+      .index-cta {
+        max-width: 960px;
+        margin: 0 auto;
+        padding: 8px 24px 12px;
+        display: flex;
+        flex-wrap: wrap;
+        gap: 12px;
+      }
+
+      .index-cta a {
+        display: inline-flex;
+        align-items: center;
+        gap: 8px;
+        height: 44px;
+        padding: 0 20px;
+        border-radius: 10px;
+        font-size: 14px;
+        font-weight: 600;
+        text-decoration: none;
+        transition: background 0.15s;
+      }
+
+      .index-cta a.primary {
+        background: var(--primary);
+        color: #fff;
+        box-shadow: 0 2px 8px rgba(16, 185, 129, 0.25);
+      }
+
+      .index-cta a.primary:hover {
+        background: var(--primary-dark);
+      }
+
+      .index-cta a.secondary {
+        background: var(--surface);
+        color: var(--text);
+        border: 1px solid var(--border);
+      }
+
+      .index-cta a.secondary:hover {
+        border-color: #a7f3d0;
+        background: var(--primary-bg);
+        color: var(--primary-dark);
+      }
+
+      .batch-section {
+        max-width: 960px;
+        margin: 0 auto;
+        padding: 16px 24px 8px;
+      }
+
+      .batch-section h2 {
+        margin: 0 0 4px;
+        font-size: 16px;
+        font-weight: 600;
+      }
+
+      .batch-section p {
+        margin: 0 0 12px;
+        font-size: 13px;
+        color: var(--text-muted);
+      }
+
+      .index-grid {
+        max-width: 960px;
+        margin: 0 auto;
+        padding: 0 24px 32px;
+        display: grid;
+        grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+        gap: 16px;
+      }
+
+      .index-card {
+        background: var(--surface);
+        border: 1px solid var(--border);
+        border-radius: 12px;
+        padding: 18px;
+      }
+
+      .index-card h3 {
+        margin: 0 0 6px;
+        font-size: 15px;
+        font-weight: 600;
+        color: var(--primary-dark);
+      }
+
+      .index-card p {
+        margin: 0;
+        font-size: 13px;
+        color: var(--text-muted);
+        line-height: 1.5;
+      }
+
+      .index-card .tag {
+        display: inline-block;
+        margin-top: 12px;
+        padding: 2px 8px;
+        border-radius: 6px;
+        font-size: 11px;
+        font-weight: 600;
+        background: #f1f5f9;
+        color: var(--text-muted);
+      }
+
+      .index-card .tag--rec {
+        background: var(--primary-bg);
+        color: var(--primary-dark);
+        border: 1px solid #a7f3d0;
+      }
+
+      .index-card .tag--new {
+        background: #fffbeb;
+        color: #b45309;
+        border: 1px solid #fcd34d;
+      }
+
+      .context-box {
+        max-width: 960px;
+        margin: 0 auto 8px;
+        padding: 0 24px;
+      }
+
+      .context-box-inner {
+        padding: 14px 16px;
+        background: #fffbeb;
+        border: 1px solid #fcd34d;
+        border-radius: 10px;
+        font-size: 13px;
+        color: #92400e;
+        line-height: 1.55;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="index-hero">
+      <h1>质检工单 · 细胞存储 · 同样本多工单 Tab 选型</h1>
+      <p>
+        针对 <code>QcWorkOrderSampleGroup.vue</code> 中「同一样本下样本接收 + 生产过程两条工单」的切换区域。
+        共两组 HTML 原型,均可点击交互。
+      </p>
+    </div>
+
+    <div class="context-box">
+      <div class="context-box-inner">
+        <strong>问题:</strong>用户难以感知卡片内存在两类不同质检工单,且需要点击切换。
+      </div>
+    </div>
+
+    <div class="index-cta">
+      <a class="primary" href="independent-pills.html">独立胶囊 Tab(最新)→</a>
+      <a class="secondary" href="compare-all-v2.html">第二组 · 强对比方案</a>
+      <a class="secondary" href="compare-all.html">第一组 · 渐进改良方案</a>
+    </div>
+
+    <div class="batch-section">
+      <h2>独立胶囊 Tab(现网结构 + 分离间距)</h2>
+      <p>去掉轨道连体容器,每个 Tab 独立白底胶囊,间距 16 / 24 / 32px 三档对比。</p>
+    </div>
+    <div class="index-grid">
+      <div class="index-card">
+        <h3>A · 轨道内连体</h3>
+        <p>现网对照:<code>panel-pill-tabs__track</code> 绿底容器。</p>
+        <span class="tag">对照</span>
+      </div>
+      <div class="index-card">
+        <h3>B · 独立胶囊 16px</h3>
+        <p>独立边框 + 来源色,间距 16px。</p>
+        <span class="tag tag--rec">推荐</span>
+        <span class="tag tag--new">最新</span>
+      </div>
+      <div class="index-card">
+        <h3>C · 独立胶囊 24px</h3>
+        <p>分离感更强。</p>
+        <span class="tag tag--new">最新</span>
+      </div>
+      <div class="index-card">
+        <h3>D · 独立胶囊 32px 放大</h3>
+        <p>最大间距 + 略放大点击区域。</p>
+        <span class="tag tag--new">最新</span>
+      </div>
+    </div>
+
+    <div class="batch-section">
+      <h2>第二组 · 强对比(推荐先看)</h2>
+      <p>加大色块面积、两条工单始终可见、未选中显式「切换/点击」 affordance。</p>
+    </div>
+    <div class="index-grid">
+      <div class="index-card">
+        <h3>N1 · 双色分栏顶栏</h3>
+        <p>整宽 50/50 绿/琥珀分栏,未选中降饱和 + 👆 动画提示。</p>
+        <span class="tag tag--rec">推荐</span>
+        <span class="tag tag--new">第二组</span>
+      </div>
+      <div class="index-card">
+        <h3>N2 · 双行 Radio 清单</h3>
+        <p>列表单选形态,未选中行「切换至此」按钮。</p>
+        <span class="tag tag--new">第二组</span>
+      </div>
+      <div class="index-card">
+        <h3>N3 · 大开关 Toggle</h3>
+        <p>居中 oversized 二选一开关,emoji + 粗体类别名。</p>
+        <span class="tag tag--rec">推荐</span>
+        <span class="tag tag--new">第二组</span>
+      </div>
+      <div class="index-card">
+        <h3>N4 · 图标宫格</h3>
+        <p>56px 大图标 + 虚线边框 = 可点击,视觉重量最大。</p>
+        <span class="tag tag--new">第二组</span>
+      </div>
+      <div class="index-card">
+        <h3>N5 · 双摘要条</h3>
+        <p>提示横幅 + 并排 Chip,未选中绿色「切换」按钮。</p>
+        <span class="tag tag--new">第二组</span>
+      </div>
+      <div class="index-card">
+        <h3>N6 · 左导航 + 右预览</h3>
+        <p>Master-Detail,左侧工单列表始终可见。</p>
+        <span class="tag tag--new">第二组</span>
+      </div>
+    </div>
+
+    <div class="batch-section">
+      <h2>第一组 · 渐进改良</h2>
+      <p>在现有 pill-tab 形态上微调,改动量较小。</p>
+    </div>
+    <div class="index-grid">
+      <div class="index-card">
+        <h3>V0–V5</h3>
+        <p>胶囊 Tab、双卡片、步骤条、Segmented、色条 Tab 等 6 套。</p>
+        <span class="tag">第一组</span>
+      </div>
+    </div>
+  </body>
+</html>

+ 1 - 0
web-admin/src/graphify-out/cache/ast/31521ad3f25f885d648e347323debd9226792f74f035c85a080826adb0cc942c.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/b32800f63ee411ce4ce9f1385e11fb4426b82a55965f25cbafd8e4671bbb7e65.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersamplegroup_vue", "label": "QcWorkOrderSampleGroup.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}