423 lines
13 KiB
HTML
423 lines
13 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>质检工单 · 独立胶囊 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>
|