828 lines
23 KiB
HTML
828 lines
23 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>
|
||
<style>
|
||
*,
|
||
*::before,
|
||
*::after {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
:root {
|
||
--primary: #10b981;
|
||
--primary-dark: #059669;
|
||
--primary-bg: #ecfdf5;
|
||
--mint-border: #c5e0d4;
|
||
--text: #1e293b;
|
||
--muted: #64748b;
|
||
--surface: #fff;
|
||
--page-bg: #f0f2f5;
|
||
--border: #e2e8f0;
|
||
}
|
||
|
||
body {
|
||
font-family:
|
||
Inter,
|
||
-apple-system,
|
||
BlinkMacSystemFont,
|
||
'PingFang SC',
|
||
'Hiragino Sans GB',
|
||
'Microsoft YaHei',
|
||
sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
color: var(--text);
|
||
background: var(--page-bg);
|
||
padding: 32px 24px 48px;
|
||
}
|
||
|
||
.page-head {
|
||
max-width: 1120px;
|
||
margin: 0 auto 28px;
|
||
}
|
||
|
||
.page-head h1 {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.page-head p {
|
||
color: var(--muted);
|
||
max-width: 760px;
|
||
}
|
||
|
||
.demo-block {
|
||
max-width: 1120px;
|
||
margin: 0 auto 28px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
box-shadow: 0 1px 3px rgb(15 23 42 / 6%);
|
||
}
|
||
|
||
.demo-label {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 12px 20px;
|
||
border-bottom: 1px solid var(--border);
|
||
background: #fafbfc;
|
||
}
|
||
|
||
.demo-label strong {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.demo-label span {
|
||
font-size: 12px;
|
||
color: var(--muted);
|
||
}
|
||
|
||
.panel-shell {
|
||
padding: 0;
|
||
}
|
||
|
||
.panel-content {
|
||
padding: 20px 24px 24px;
|
||
border-top: 1px solid var(--border);
|
||
min-height: 120px;
|
||
}
|
||
|
||
.panel-content h3 {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.panel-content p {
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
}
|
||
|
||
/* ============================================================
|
||
方案 A:经典步骤条(圆点 + 连线 + 下方标题)
|
||
============================================================ */
|
||
.timeline-a {
|
||
padding: 24px 32px 8px;
|
||
}
|
||
|
||
.timeline-a__track {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
position: relative;
|
||
}
|
||
|
||
.timeline-a__step {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
padding: 0 8px;
|
||
min-width: 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.timeline-a__node-wrap {
|
||
position: relative;
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.timeline-a__line {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
right: -50%;
|
||
height: 2px;
|
||
background: var(--border);
|
||
transform: translateY(-50%);
|
||
z-index: 0;
|
||
}
|
||
|
||
.timeline-a__step:last-child .timeline-a__line {
|
||
display: none;
|
||
}
|
||
|
||
.timeline-a__dot {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid var(--border);
|
||
background: var(--surface);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.timeline-a__title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--muted);
|
||
transition: color 0.2s ease;
|
||
}
|
||
|
||
.timeline-a__desc {
|
||
font-size: 11px;
|
||
color: #94a3b8;
|
||
line-height: 1.35;
|
||
max-width: 140px;
|
||
}
|
||
|
||
.timeline-a__step.is-active .timeline-a__dot {
|
||
width: 18px;
|
||
height: 18px;
|
||
border-color: var(--primary);
|
||
background: var(--primary);
|
||
box-shadow: 0 0 0 4px rgb(16 185 129 / 18%);
|
||
}
|
||
|
||
.timeline-a__step.is-active .timeline-a__title {
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.timeline-a__step.is-done .timeline-a__dot {
|
||
border-color: var(--primary);
|
||
background: var(--primary-bg);
|
||
}
|
||
|
||
.timeline-a__step.is-done .timeline-a__line {
|
||
background: var(--primary);
|
||
}
|
||
|
||
.timeline-a__step.is-done .timeline-a__title {
|
||
color: var(--text);
|
||
}
|
||
|
||
.timeline-a__step:hover:not(.is-active) .timeline-a__title {
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
/* ============================================================
|
||
方案 B:带序号胶囊 + 图标(偏流程感)
|
||
============================================================ */
|
||
.timeline-b {
|
||
padding: 20px 24px 10px;
|
||
}
|
||
|
||
.timeline-b__track {
|
||
display: flex;
|
||
align-items: stretch;
|
||
gap: 0;
|
||
position: relative;
|
||
}
|
||
|
||
.timeline-b__step {
|
||
flex: 1;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
padding: 0 6px 4px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.timeline-b__connector {
|
||
position: absolute;
|
||
top: 22px;
|
||
left: calc(50% + 28px);
|
||
width: calc(100% - 56px);
|
||
height: 2px;
|
||
background: linear-gradient(90deg, var(--border), var(--border));
|
||
z-index: 0;
|
||
}
|
||
|
||
.timeline-b__step:last-child .timeline-b__connector {
|
||
display: none;
|
||
}
|
||
|
||
.timeline-b__step.is-done .timeline-b__connector {
|
||
background: linear-gradient(90deg, var(--primary), var(--primary));
|
||
}
|
||
|
||
.timeline-b__badge {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 50%;
|
||
border: 2px solid var(--border);
|
||
background: #f8fafc;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.timeline-b__num {
|
||
position: absolute;
|
||
top: -4px;
|
||
right: -4px;
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
background: #e2e8f0;
|
||
color: #475569;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 2px solid var(--surface);
|
||
}
|
||
|
||
.timeline-b__title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--muted);
|
||
}
|
||
|
||
.timeline-b__desc {
|
||
font-size: 11px;
|
||
color: #94a3b8;
|
||
text-align: center;
|
||
line-height: 1.35;
|
||
max-width: 132px;
|
||
}
|
||
|
||
.timeline-b__step.is-active .timeline-b__badge {
|
||
border-color: var(--primary);
|
||
background: var(--primary-bg);
|
||
box-shadow: 0 0 0 4px rgb(16 185 129 / 14%);
|
||
}
|
||
|
||
.timeline-b__step.is-active .timeline-b__num {
|
||
background: var(--primary);
|
||
color: #fff;
|
||
}
|
||
|
||
.timeline-b__step.is-active .timeline-b__title {
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.timeline-b__step.is-done .timeline-b__badge {
|
||
border-color: var(--primary);
|
||
background: var(--primary);
|
||
color: #fff;
|
||
}
|
||
|
||
.timeline-b__step.is-done .timeline-b__num {
|
||
background: var(--primary-dark);
|
||
color: #fff;
|
||
}
|
||
|
||
.timeline-b__step.is-done .timeline-b__title {
|
||
color: var(--text);
|
||
}
|
||
|
||
/* ============================================================
|
||
方案 C:轨道式 Pill Tab(推荐 · 最贴近 B 端列表页)
|
||
============================================================ */
|
||
.timeline-c {
|
||
padding: 16px 20px 0;
|
||
}
|
||
|
||
.timeline-c__rail {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #f8fafc;
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 6px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.timeline-c__progress {
|
||
position: absolute;
|
||
top: 6px;
|
||
bottom: 6px;
|
||
left: 6px;
|
||
width: calc(25% - 3px);
|
||
background: var(--surface);
|
||
border: 1px solid var(--mint-border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 1px 3px rgb(15 23 42 / 8%);
|
||
transition:
|
||
transform 0.25s ease,
|
||
width 0.25s ease;
|
||
z-index: 0;
|
||
}
|
||
|
||
.timeline-c__step {
|
||
flex: 1;
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
min-height: 44px;
|
||
padding: 8px 10px;
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
border-radius: 8px;
|
||
transition: color 0.2s ease;
|
||
}
|
||
|
||
.timeline-c__index {
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 50%;
|
||
background: #e2e8f0;
|
||
color: #475569;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.timeline-c__text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
min-width: 0;
|
||
text-align: left;
|
||
}
|
||
|
||
.timeline-c__title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--muted);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.timeline-c__desc {
|
||
font-size: 11px;
|
||
color: #94a3b8;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.timeline-c__chevron {
|
||
position: absolute;
|
||
right: -1px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: #cbd5e1;
|
||
font-size: 12px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.timeline-c__step:last-child .timeline-c__chevron {
|
||
display: none;
|
||
}
|
||
|
||
.timeline-c__step.is-active .timeline-c__index {
|
||
background: var(--primary);
|
||
color: #fff;
|
||
}
|
||
|
||
.timeline-c__step.is-active .timeline-c__title {
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.timeline-c__step.is-done .timeline-c__index {
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
border: 1px solid var(--mint-border);
|
||
}
|
||
|
||
.timeline-c__step.is-done .timeline-c__title {
|
||
color: var(--text);
|
||
}
|
||
|
||
.timeline-c__meta {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 10px 4px 0;
|
||
font-size: 11px;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
/* ============================================================
|
||
方案 D:箭头分段条(强调业务先后顺序)
|
||
============================================================ */
|
||
.timeline-d {
|
||
padding: 18px 20px 8px;
|
||
}
|
||
|
||
.timeline-d__track {
|
||
display: flex;
|
||
filter: drop-shadow(0 1px 2px rgb(15 23 42 / 6%));
|
||
}
|
||
|
||
.timeline-d__step {
|
||
flex: 1;
|
||
position: relative;
|
||
min-height: 56px;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 10px 28px 10px 22px;
|
||
text-align: left;
|
||
background: #f8fafc;
|
||
clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%, 14px 50%);
|
||
margin-left: -10px;
|
||
transition:
|
||
background 0.2s ease,
|
||
color 0.2s ease;
|
||
}
|
||
|
||
.timeline-d__step:first-child {
|
||
margin-left: 0;
|
||
clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%);
|
||
border-radius: 8px 0 0 8px;
|
||
padding-left: 18px;
|
||
}
|
||
|
||
.timeline-d__step:last-child {
|
||
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 14px 50%);
|
||
border-radius: 0 8px 8px 0;
|
||
padding-right: 18px;
|
||
}
|
||
|
||
.timeline-d__title {
|
||
display: block;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--muted);
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.timeline-d__desc {
|
||
display: block;
|
||
font-size: 11px;
|
||
color: #94a3b8;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.timeline-d__step.is-active {
|
||
background: var(--primary-bg);
|
||
z-index: 2;
|
||
}
|
||
|
||
.timeline-d__step.is-active .timeline-d__title {
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.timeline-d__step.is-done {
|
||
background: #eefbf5;
|
||
z-index: 1;
|
||
}
|
||
|
||
.timeline-d__step.is-done .timeline-d__title {
|
||
color: #047857;
|
||
}
|
||
|
||
.timeline-d__step:hover:not(.is-active) {
|
||
background: #f1f5f9;
|
||
}
|
||
|
||
@media (max-width: 960px) {
|
||
.timeline-c__desc,
|
||
.timeline-a__desc,
|
||
.timeline-b__desc,
|
||
.timeline-d__desc {
|
||
display: none;
|
||
}
|
||
|
||
.timeline-c__text {
|
||
align-items: center;
|
||
}
|
||
|
||
.timeline-b__badge {
|
||
width: 38px;
|
||
height: 38px;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 720px) {
|
||
body {
|
||
padding: 16px 12px 32px;
|
||
}
|
||
|
||
.timeline-a,
|
||
.timeline-b,
|
||
.timeline-c,
|
||
.timeline-d {
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.timeline-a__track,
|
||
.timeline-b__track {
|
||
min-width: 640px;
|
||
}
|
||
|
||
.timeline-c__rail {
|
||
min-width: 680px;
|
||
}
|
||
|
||
.timeline-d__track {
|
||
min-width: 720px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header class="page-head">
|
||
<h1>采集协议单 · 横向时间线 Tab 样式 Demo</h1>
|
||
<p>
|
||
将「协议单管理 → 缴费信息 → 储存证管理 → 满月待办管理」四个视图切换,做成带先后顺序感的时间线 Tab。
|
||
点击各步骤可切换下方占位内容。色值对齐 Fresh Mint(主色 #10b981)。
|
||
</p>
|
||
</header>
|
||
|
||
<!-- 方案 A -->
|
||
<section class="demo-block" data-demo="a">
|
||
<div class="demo-label">
|
||
<strong>方案 A · 经典步骤条</strong>
|
||
<span>圆点 + 连线,轻量、常见于表单分步</span>
|
||
</div>
|
||
<div class="panel-shell">
|
||
<nav class="timeline-a" role="tablist" aria-label="方案A">
|
||
<div class="timeline-a__track"></div>
|
||
</nav>
|
||
<div class="panel-content" data-content="a">
|
||
<h3>协议单管理</h3>
|
||
<p>协议审核与状态维护 — 此处为 Tab 内容占位区(列表 / 筛选 / 表格)。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 方案 B -->
|
||
<section class="demo-block" data-demo="b">
|
||
<div class="demo-label">
|
||
<strong>方案 B · 序号 + 图标节点</strong>
|
||
<span>流程感更强,适合强调四段业务链路</span>
|
||
</div>
|
||
<div class="panel-shell">
|
||
<nav class="timeline-b" role="tablist" aria-label="方案B">
|
||
<div class="timeline-b__track"></div>
|
||
</nav>
|
||
<div class="panel-content" data-content="b">
|
||
<h3>协议单管理</h3>
|
||
<p>协议审核与状态维护 — 此处为 Tab 内容占位区。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 方案 C -->
|
||
<section class="demo-block" data-demo="c">
|
||
<div class="demo-label">
|
||
<strong>方案 C · 轨道 Pill Tab(推荐)</strong>
|
||
<span>与现有 B 端面板最协调,可保留副标题</span>
|
||
</div>
|
||
<div class="panel-shell">
|
||
<nav class="timeline-c" role="tablist" aria-label="方案C">
|
||
<div class="timeline-c__rail">
|
||
<div class="timeline-c__progress"></div>
|
||
</div>
|
||
<div class="timeline-c__meta">
|
||
<span>当前环节</span>
|
||
<span data-meta="c">步骤 1 / 4</span>
|
||
</div>
|
||
</nav>
|
||
<div class="panel-content" data-content="c">
|
||
<h3>协议单管理</h3>
|
||
<p>协议审核与状态维护 — 此处为 Tab 内容占位区。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 方案 D -->
|
||
<section class="demo-block" data-demo="d">
|
||
<div class="demo-label">
|
||
<strong>方案 D · 箭头分段条</strong>
|
||
<span>视觉冲击力大,窄屏需横向滚动</span>
|
||
</div>
|
||
<div class="panel-shell">
|
||
<nav class="timeline-d" role="tablist" aria-label="方案D">
|
||
<div class="timeline-d__track"></div>
|
||
</nav>
|
||
<div class="panel-content" data-content="d">
|
||
<h3>协议单管理</h3>
|
||
<p>协议审核与状态维护 — 此处为 Tab 内容占位区。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<script>
|
||
const STEPS = [
|
||
{
|
||
key: 'protocol',
|
||
title: '协议单管理',
|
||
desc: '协议审核与状态维护',
|
||
icon: '📄'
|
||
},
|
||
{
|
||
key: 'payment-info',
|
||
title: '缴费信息',
|
||
desc: '缴费计划与到账查询',
|
||
icon: '💳'
|
||
},
|
||
{
|
||
key: 'storage-cert',
|
||
title: '储存证管理',
|
||
desc: '储存证开具与管理',
|
||
icon: '🏅'
|
||
},
|
||
{
|
||
key: 'fullmoon-todo',
|
||
title: '满月待办管理',
|
||
desc: '满月保险待办办理',
|
||
icon: '📅'
|
||
}
|
||
]
|
||
|
||
function renderTimelineA(container, activeIndex, onSelect) {
|
||
const track = container.querySelector('.timeline-a__track')
|
||
track.innerHTML = STEPS.map((step, index) => {
|
||
const state =
|
||
index === activeIndex ? 'is-active' : index < activeIndex ? 'is-done' : ''
|
||
return `
|
||
<button type="button" class="timeline-a__step ${state}" role="tab" aria-selected="${index === activeIndex}" data-index="${index}">
|
||
<div class="timeline-a__node-wrap">
|
||
<span class="timeline-a__line"></span>
|
||
<span class="timeline-a__dot"></span>
|
||
</div>
|
||
<span class="timeline-a__title">${step.title}</span>
|
||
<span class="timeline-a__desc">${step.desc}</span>
|
||
</button>
|
||
`
|
||
}).join('')
|
||
track.querySelectorAll('.timeline-a__step').forEach((btn) => {
|
||
btn.addEventListener('click', () => onSelect(Number(btn.dataset.index)))
|
||
})
|
||
}
|
||
|
||
function renderTimelineB(container, activeIndex, onSelect) {
|
||
const track = container.querySelector('.timeline-b__track')
|
||
track.innerHTML = STEPS.map((step, index) => {
|
||
const state =
|
||
index === activeIndex ? 'is-active' : index < activeIndex ? 'is-done' : ''
|
||
return `
|
||
<button type="button" class="timeline-b__step ${state}" role="tab" aria-selected="${index === activeIndex}" data-index="${index}">
|
||
<span class="timeline-b__connector"></span>
|
||
<span class="timeline-b__badge">
|
||
${step.icon}
|
||
<span class="timeline-b__num">${index + 1}</span>
|
||
</span>
|
||
<span class="timeline-b__title">${step.title}</span>
|
||
<span class="timeline-b__desc">${step.desc}</span>
|
||
</button>
|
||
`
|
||
}).join('')
|
||
track.querySelectorAll('.timeline-b__step').forEach((btn) => {
|
||
btn.addEventListener('click', () => onSelect(Number(btn.dataset.index)))
|
||
})
|
||
}
|
||
|
||
function renderTimelineC(container, activeIndex, onSelect) {
|
||
const rail = container.querySelector('.timeline-c__rail')
|
||
const progress = rail.querySelector('.timeline-c__progress')
|
||
const buttons = STEPS.map((step, index) => {
|
||
const state =
|
||
index === activeIndex ? 'is-active' : index < activeIndex ? 'is-done' : ''
|
||
return `
|
||
<button type="button" class="timeline-c__step ${state}" role="tab" aria-selected="${index === activeIndex}" data-index="${index}">
|
||
<span class="timeline-c__index">${index + 1}</span>
|
||
<span class="timeline-c__text">
|
||
<span class="timeline-c__title">${step.title}</span>
|
||
<span class="timeline-c__desc">${step.desc}</span>
|
||
</span>
|
||
<span class="timeline-c__chevron">›</span>
|
||
</button>
|
||
`
|
||
}).join('')
|
||
progress.insertAdjacentHTML('afterend', buttons)
|
||
progress.style.width = `calc(${100 / STEPS.length}% - 3px)`
|
||
progress.style.transform = `translateX(${activeIndex * 100}%)`
|
||
rail.querySelectorAll('.timeline-c__step').forEach((btn) => {
|
||
btn.addEventListener('click', () => onSelect(Number(btn.dataset.index)))
|
||
})
|
||
const meta = container.querySelector('[data-meta="c"]')
|
||
if (meta) meta.textContent = `步骤 ${activeIndex + 1} / ${STEPS.length}`
|
||
}
|
||
|
||
function renderTimelineD(container, activeIndex, onSelect) {
|
||
const track = container.querySelector('.timeline-d__track')
|
||
track.innerHTML = STEPS.map((step, index) => {
|
||
const state =
|
||
index === activeIndex ? 'is-active' : index < activeIndex ? 'is-done' : ''
|
||
return `
|
||
<button type="button" class="timeline-d__step ${state}" role="tab" aria-selected="${index === activeIndex}" data-index="${index}">
|
||
<span class="timeline-d__title">${step.title}</span>
|
||
<span class="timeline-d__desc">${step.desc}</span>
|
||
</button>
|
||
`
|
||
}).join('')
|
||
track.querySelectorAll('.timeline-d__step').forEach((btn) => {
|
||
btn.addEventListener('click', () => onSelect(Number(btn.dataset.index)))
|
||
})
|
||
}
|
||
|
||
function updateContent(demoKey, activeIndex) {
|
||
const step = STEPS[activeIndex]
|
||
const panel = document.querySelector(`[data-content="${demoKey}"]`)
|
||
if (!panel) return
|
||
panel.innerHTML = `
|
||
<h3>${step.title}</h3>
|
||
<p>${step.desc} — 此处为 Tab 内容占位区(列表 / 筛选 / 表格)。</p>
|
||
`
|
||
}
|
||
|
||
function initDemo(demoKey, renderFn) {
|
||
let activeIndex = 0
|
||
const section = document.querySelector(`[data-demo="${demoKey}"]`)
|
||
const nav = section.querySelector('nav')
|
||
|
||
function refresh() {
|
||
if (demoKey === 'c') {
|
||
nav.querySelector('.timeline-c__rail').innerHTML =
|
||
'<div class="timeline-c__progress"></div>'
|
||
}
|
||
renderFn(nav, activeIndex, (index) => {
|
||
activeIndex = index
|
||
refresh()
|
||
})
|
||
updateContent(demoKey, activeIndex)
|
||
}
|
||
|
||
refresh()
|
||
}
|
||
|
||
initDemo('a', renderTimelineA)
|
||
initDemo('b', renderTimelineB)
|
||
initDemo('c', renderTimelineC)
|
||
initDemo('d', renderTimelineD)
|
||
</script>
|
||
</body>
|
||
</html>
|