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