demo-protocol-audit-timeline-tabs.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>采集协议单 · 横向时间线 Tab Demo</title>
  7. <style>
  8. *,
  9. *::before,
  10. *::after {
  11. box-sizing: border-box;
  12. margin: 0;
  13. padding: 0;
  14. }
  15. :root {
  16. --primary: #10b981;
  17. --primary-dark: #059669;
  18. --primary-bg: #ecfdf5;
  19. --mint-border: #c5e0d4;
  20. --text: #1e293b;
  21. --muted: #64748b;
  22. --surface: #fff;
  23. --page-bg: #f0f2f5;
  24. --border: #e2e8f0;
  25. }
  26. body {
  27. font-family:
  28. Inter,
  29. -apple-system,
  30. BlinkMacSystemFont,
  31. 'PingFang SC',
  32. 'Hiragino Sans GB',
  33. 'Microsoft YaHei',
  34. sans-serif;
  35. font-size: 14px;
  36. line-height: 1.5;
  37. color: var(--text);
  38. background: var(--page-bg);
  39. padding: 32px 24px 48px;
  40. }
  41. .page-head {
  42. max-width: 1120px;
  43. margin: 0 auto 28px;
  44. }
  45. .page-head h1 {
  46. font-size: 20px;
  47. font-weight: 600;
  48. margin-bottom: 8px;
  49. }
  50. .page-head p {
  51. color: var(--muted);
  52. max-width: 760px;
  53. }
  54. .demo-block {
  55. max-width: 1120px;
  56. margin: 0 auto 28px;
  57. background: var(--surface);
  58. border: 1px solid var(--border);
  59. border-radius: 10px;
  60. overflow: hidden;
  61. box-shadow: 0 1px 3px rgb(15 23 42 / 6%);
  62. }
  63. .demo-label {
  64. display: flex;
  65. align-items: center;
  66. justify-content: space-between;
  67. gap: 12px;
  68. padding: 12px 20px;
  69. border-bottom: 1px solid var(--border);
  70. background: #fafbfc;
  71. }
  72. .demo-label strong {
  73. font-size: 13px;
  74. font-weight: 600;
  75. }
  76. .demo-label span {
  77. font-size: 12px;
  78. color: var(--muted);
  79. }
  80. .panel-shell {
  81. padding: 0;
  82. }
  83. .panel-content {
  84. padding: 20px 24px 24px;
  85. border-top: 1px solid var(--border);
  86. min-height: 120px;
  87. }
  88. .panel-content h3 {
  89. font-size: 15px;
  90. font-weight: 600;
  91. margin-bottom: 6px;
  92. }
  93. .panel-content p {
  94. color: var(--muted);
  95. font-size: 13px;
  96. }
  97. /* ============================================================
  98. 方案 A:经典步骤条(圆点 + 连线 + 下方标题)
  99. ============================================================ */
  100. .timeline-a {
  101. padding: 24px 32px 8px;
  102. }
  103. .timeline-a__track {
  104. display: flex;
  105. align-items: flex-start;
  106. position: relative;
  107. }
  108. .timeline-a__step {
  109. flex: 1;
  110. display: flex;
  111. flex-direction: column;
  112. align-items: center;
  113. gap: 10px;
  114. border: none;
  115. background: transparent;
  116. cursor: pointer;
  117. padding: 0 8px;
  118. min-width: 0;
  119. text-align: center;
  120. }
  121. .timeline-a__node-wrap {
  122. position: relative;
  123. width: 100%;
  124. display: flex;
  125. justify-content: center;
  126. align-items: center;
  127. }
  128. .timeline-a__line {
  129. position: absolute;
  130. top: 50%;
  131. left: 50%;
  132. right: -50%;
  133. height: 2px;
  134. background: var(--border);
  135. transform: translateY(-50%);
  136. z-index: 0;
  137. }
  138. .timeline-a__step:last-child .timeline-a__line {
  139. display: none;
  140. }
  141. .timeline-a__dot {
  142. position: relative;
  143. z-index: 1;
  144. width: 14px;
  145. height: 14px;
  146. border-radius: 50%;
  147. border: 2px solid var(--border);
  148. background: var(--surface);
  149. transition: all 0.2s ease;
  150. }
  151. .timeline-a__title {
  152. font-size: 13px;
  153. font-weight: 600;
  154. color: var(--muted);
  155. transition: color 0.2s ease;
  156. }
  157. .timeline-a__desc {
  158. font-size: 11px;
  159. color: #94a3b8;
  160. line-height: 1.35;
  161. max-width: 140px;
  162. }
  163. .timeline-a__step.is-active .timeline-a__dot {
  164. width: 18px;
  165. height: 18px;
  166. border-color: var(--primary);
  167. background: var(--primary);
  168. box-shadow: 0 0 0 4px rgb(16 185 129 / 18%);
  169. }
  170. .timeline-a__step.is-active .timeline-a__title {
  171. color: var(--primary-dark);
  172. }
  173. .timeline-a__step.is-done .timeline-a__dot {
  174. border-color: var(--primary);
  175. background: var(--primary-bg);
  176. }
  177. .timeline-a__step.is-done .timeline-a__line {
  178. background: var(--primary);
  179. }
  180. .timeline-a__step.is-done .timeline-a__title {
  181. color: var(--text);
  182. }
  183. .timeline-a__step:hover:not(.is-active) .timeline-a__title {
  184. color: var(--primary-dark);
  185. }
  186. /* ============================================================
  187. 方案 B:带序号胶囊 + 图标(偏流程感)
  188. ============================================================ */
  189. .timeline-b {
  190. padding: 20px 24px 10px;
  191. }
  192. .timeline-b__track {
  193. display: flex;
  194. align-items: stretch;
  195. gap: 0;
  196. position: relative;
  197. }
  198. .timeline-b__step {
  199. flex: 1;
  200. position: relative;
  201. display: flex;
  202. flex-direction: column;
  203. align-items: center;
  204. gap: 8px;
  205. border: none;
  206. background: transparent;
  207. cursor: pointer;
  208. padding: 0 6px 4px;
  209. min-width: 0;
  210. }
  211. .timeline-b__connector {
  212. position: absolute;
  213. top: 22px;
  214. left: calc(50% + 28px);
  215. width: calc(100% - 56px);
  216. height: 2px;
  217. background: linear-gradient(90deg, var(--border), var(--border));
  218. z-index: 0;
  219. }
  220. .timeline-b__step:last-child .timeline-b__connector {
  221. display: none;
  222. }
  223. .timeline-b__step.is-done .timeline-b__connector {
  224. background: linear-gradient(90deg, var(--primary), var(--primary));
  225. }
  226. .timeline-b__badge {
  227. position: relative;
  228. z-index: 1;
  229. width: 44px;
  230. height: 44px;
  231. border-radius: 50%;
  232. border: 2px solid var(--border);
  233. background: #f8fafc;
  234. display: flex;
  235. align-items: center;
  236. justify-content: center;
  237. font-size: 18px;
  238. transition: all 0.2s ease;
  239. }
  240. .timeline-b__num {
  241. position: absolute;
  242. top: -4px;
  243. right: -4px;
  244. width: 18px;
  245. height: 18px;
  246. border-radius: 50%;
  247. background: #e2e8f0;
  248. color: #475569;
  249. font-size: 10px;
  250. font-weight: 700;
  251. display: flex;
  252. align-items: center;
  253. justify-content: center;
  254. border: 2px solid var(--surface);
  255. }
  256. .timeline-b__title {
  257. font-size: 13px;
  258. font-weight: 600;
  259. color: var(--muted);
  260. }
  261. .timeline-b__desc {
  262. font-size: 11px;
  263. color: #94a3b8;
  264. text-align: center;
  265. line-height: 1.35;
  266. max-width: 132px;
  267. }
  268. .timeline-b__step.is-active .timeline-b__badge {
  269. border-color: var(--primary);
  270. background: var(--primary-bg);
  271. box-shadow: 0 0 0 4px rgb(16 185 129 / 14%);
  272. }
  273. .timeline-b__step.is-active .timeline-b__num {
  274. background: var(--primary);
  275. color: #fff;
  276. }
  277. .timeline-b__step.is-active .timeline-b__title {
  278. color: var(--primary-dark);
  279. }
  280. .timeline-b__step.is-done .timeline-b__badge {
  281. border-color: var(--primary);
  282. background: var(--primary);
  283. color: #fff;
  284. }
  285. .timeline-b__step.is-done .timeline-b__num {
  286. background: var(--primary-dark);
  287. color: #fff;
  288. }
  289. .timeline-b__step.is-done .timeline-b__title {
  290. color: var(--text);
  291. }
  292. /* ============================================================
  293. 方案 C:轨道式 Pill Tab(推荐 · 最贴近 B 端列表页)
  294. ============================================================ */
  295. .timeline-c {
  296. padding: 16px 20px 0;
  297. }
  298. .timeline-c__rail {
  299. display: flex;
  300. align-items: center;
  301. background: #f8fafc;
  302. border: 1px solid var(--border);
  303. border-radius: 10px;
  304. padding: 6px;
  305. position: relative;
  306. overflow: hidden;
  307. }
  308. .timeline-c__progress {
  309. position: absolute;
  310. top: 6px;
  311. bottom: 6px;
  312. left: 6px;
  313. width: calc(25% - 3px);
  314. background: var(--surface);
  315. border: 1px solid var(--mint-border);
  316. border-radius: 8px;
  317. box-shadow: 0 1px 3px rgb(15 23 42 / 8%);
  318. transition:
  319. transform 0.25s ease,
  320. width 0.25s ease;
  321. z-index: 0;
  322. }
  323. .timeline-c__step {
  324. flex: 1;
  325. position: relative;
  326. z-index: 1;
  327. display: flex;
  328. align-items: center;
  329. justify-content: center;
  330. gap: 8px;
  331. min-height: 44px;
  332. padding: 8px 10px;
  333. border: none;
  334. background: transparent;
  335. cursor: pointer;
  336. border-radius: 8px;
  337. transition: color 0.2s ease;
  338. }
  339. .timeline-c__index {
  340. width: 22px;
  341. height: 22px;
  342. border-radius: 50%;
  343. background: #e2e8f0;
  344. color: #475569;
  345. font-size: 11px;
  346. font-weight: 700;
  347. display: flex;
  348. align-items: center;
  349. justify-content: center;
  350. flex-shrink: 0;
  351. transition: all 0.2s ease;
  352. }
  353. .timeline-c__text {
  354. display: flex;
  355. flex-direction: column;
  356. align-items: flex-start;
  357. min-width: 0;
  358. text-align: left;
  359. }
  360. .timeline-c__title {
  361. font-size: 13px;
  362. font-weight: 600;
  363. color: var(--muted);
  364. white-space: nowrap;
  365. }
  366. .timeline-c__desc {
  367. font-size: 11px;
  368. color: #94a3b8;
  369. white-space: nowrap;
  370. overflow: hidden;
  371. text-overflow: ellipsis;
  372. max-width: 100%;
  373. }
  374. .timeline-c__chevron {
  375. position: absolute;
  376. right: -1px;
  377. top: 50%;
  378. transform: translateY(-50%);
  379. color: #cbd5e1;
  380. font-size: 12px;
  381. pointer-events: none;
  382. }
  383. .timeline-c__step:last-child .timeline-c__chevron {
  384. display: none;
  385. }
  386. .timeline-c__step.is-active .timeline-c__index {
  387. background: var(--primary);
  388. color: #fff;
  389. }
  390. .timeline-c__step.is-active .timeline-c__title {
  391. color: var(--primary-dark);
  392. }
  393. .timeline-c__step.is-done .timeline-c__index {
  394. background: var(--primary-bg);
  395. color: var(--primary-dark);
  396. border: 1px solid var(--mint-border);
  397. }
  398. .timeline-c__step.is-done .timeline-c__title {
  399. color: var(--text);
  400. }
  401. .timeline-c__meta {
  402. display: flex;
  403. justify-content: space-between;
  404. padding: 10px 4px 0;
  405. font-size: 11px;
  406. color: #94a3b8;
  407. }
  408. /* ============================================================
  409. 方案 D:箭头分段条(强调业务先后顺序)
  410. ============================================================ */
  411. .timeline-d {
  412. padding: 18px 20px 8px;
  413. }
  414. .timeline-d__track {
  415. display: flex;
  416. filter: drop-shadow(0 1px 2px rgb(15 23 42 / 6%));
  417. }
  418. .timeline-d__step {
  419. flex: 1;
  420. position: relative;
  421. min-height: 56px;
  422. border: none;
  423. cursor: pointer;
  424. padding: 10px 28px 10px 22px;
  425. text-align: left;
  426. background: #f8fafc;
  427. clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%, 14px 50%);
  428. margin-left: -10px;
  429. transition:
  430. background 0.2s ease,
  431. color 0.2s ease;
  432. }
  433. .timeline-d__step:first-child {
  434. margin-left: 0;
  435. clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%);
  436. border-radius: 8px 0 0 8px;
  437. padding-left: 18px;
  438. }
  439. .timeline-d__step:last-child {
  440. clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 14px 50%);
  441. border-radius: 0 8px 8px 0;
  442. padding-right: 18px;
  443. }
  444. .timeline-d__title {
  445. display: block;
  446. font-size: 13px;
  447. font-weight: 600;
  448. color: var(--muted);
  449. margin-bottom: 2px;
  450. }
  451. .timeline-d__desc {
  452. display: block;
  453. font-size: 11px;
  454. color: #94a3b8;
  455. line-height: 1.3;
  456. }
  457. .timeline-d__step.is-active {
  458. background: var(--primary-bg);
  459. z-index: 2;
  460. }
  461. .timeline-d__step.is-active .timeline-d__title {
  462. color: var(--primary-dark);
  463. }
  464. .timeline-d__step.is-done {
  465. background: #eefbf5;
  466. z-index: 1;
  467. }
  468. .timeline-d__step.is-done .timeline-d__title {
  469. color: #047857;
  470. }
  471. .timeline-d__step:hover:not(.is-active) {
  472. background: #f1f5f9;
  473. }
  474. @media (max-width: 960px) {
  475. .timeline-c__desc,
  476. .timeline-a__desc,
  477. .timeline-b__desc,
  478. .timeline-d__desc {
  479. display: none;
  480. }
  481. .timeline-c__text {
  482. align-items: center;
  483. }
  484. .timeline-b__badge {
  485. width: 38px;
  486. height: 38px;
  487. font-size: 16px;
  488. }
  489. }
  490. @media (max-width: 720px) {
  491. body {
  492. padding: 16px 12px 32px;
  493. }
  494. .timeline-a,
  495. .timeline-b,
  496. .timeline-c,
  497. .timeline-d {
  498. overflow-x: auto;
  499. -webkit-overflow-scrolling: touch;
  500. }
  501. .timeline-a__track,
  502. .timeline-b__track {
  503. min-width: 640px;
  504. }
  505. .timeline-c__rail {
  506. min-width: 680px;
  507. }
  508. .timeline-d__track {
  509. min-width: 720px;
  510. }
  511. }
  512. </style>
  513. </head>
  514. <body>
  515. <header class="page-head">
  516. <h1>采集协议单 · 横向时间线 Tab 样式 Demo</h1>
  517. <p>
  518. 将「协议单管理 → 缴费信息 → 储存证管理 → 满月待办管理」四个视图切换,做成带先后顺序感的时间线 Tab。
  519. 点击各步骤可切换下方占位内容。色值对齐 Fresh Mint(主色 #10b981)。
  520. </p>
  521. </header>
  522. <!-- 方案 A -->
  523. <section class="demo-block" data-demo="a">
  524. <div class="demo-label">
  525. <strong>方案 A · 经典步骤条</strong>
  526. <span>圆点 + 连线,轻量、常见于表单分步</span>
  527. </div>
  528. <div class="panel-shell">
  529. <nav class="timeline-a" role="tablist" aria-label="方案A">
  530. <div class="timeline-a__track"></div>
  531. </nav>
  532. <div class="panel-content" data-content="a">
  533. <h3>协议单管理</h3>
  534. <p>协议审核与状态维护 — 此处为 Tab 内容占位区(列表 / 筛选 / 表格)。</p>
  535. </div>
  536. </div>
  537. </section>
  538. <!-- 方案 B -->
  539. <section class="demo-block" data-demo="b">
  540. <div class="demo-label">
  541. <strong>方案 B · 序号 + 图标节点</strong>
  542. <span>流程感更强,适合强调四段业务链路</span>
  543. </div>
  544. <div class="panel-shell">
  545. <nav class="timeline-b" role="tablist" aria-label="方案B">
  546. <div class="timeline-b__track"></div>
  547. </nav>
  548. <div class="panel-content" data-content="b">
  549. <h3>协议单管理</h3>
  550. <p>协议审核与状态维护 — 此处为 Tab 内容占位区。</p>
  551. </div>
  552. </div>
  553. </section>
  554. <!-- 方案 C -->
  555. <section class="demo-block" data-demo="c">
  556. <div class="demo-label">
  557. <strong>方案 C · 轨道 Pill Tab(推荐)</strong>
  558. <span>与现有 B 端面板最协调,可保留副标题</span>
  559. </div>
  560. <div class="panel-shell">
  561. <nav class="timeline-c" role="tablist" aria-label="方案C">
  562. <div class="timeline-c__rail">
  563. <div class="timeline-c__progress"></div>
  564. </div>
  565. <div class="timeline-c__meta">
  566. <span>当前环节</span>
  567. <span data-meta="c">步骤 1 / 4</span>
  568. </div>
  569. </nav>
  570. <div class="panel-content" data-content="c">
  571. <h3>协议单管理</h3>
  572. <p>协议审核与状态维护 — 此处为 Tab 内容占位区。</p>
  573. </div>
  574. </div>
  575. </section>
  576. <!-- 方案 D -->
  577. <section class="demo-block" data-demo="d">
  578. <div class="demo-label">
  579. <strong>方案 D · 箭头分段条</strong>
  580. <span>视觉冲击力大,窄屏需横向滚动</span>
  581. </div>
  582. <div class="panel-shell">
  583. <nav class="timeline-d" role="tablist" aria-label="方案D">
  584. <div class="timeline-d__track"></div>
  585. </nav>
  586. <div class="panel-content" data-content="d">
  587. <h3>协议单管理</h3>
  588. <p>协议审核与状态维护 — 此处为 Tab 内容占位区。</p>
  589. </div>
  590. </div>
  591. </section>
  592. <script>
  593. const STEPS = [
  594. {
  595. key: 'protocol',
  596. title: '协议单管理',
  597. desc: '协议审核与状态维护',
  598. icon: '📄'
  599. },
  600. {
  601. key: 'payment-info',
  602. title: '缴费信息',
  603. desc: '缴费计划与到账查询',
  604. icon: '💳'
  605. },
  606. {
  607. key: 'storage-cert',
  608. title: '储存证管理',
  609. desc: '储存证开具与管理',
  610. icon: '🏅'
  611. },
  612. {
  613. key: 'fullmoon-todo',
  614. title: '满月待办管理',
  615. desc: '满月保险待办办理',
  616. icon: '📅'
  617. }
  618. ]
  619. function renderTimelineA(container, activeIndex, onSelect) {
  620. const track = container.querySelector('.timeline-a__track')
  621. track.innerHTML = STEPS.map((step, index) => {
  622. const state =
  623. index === activeIndex ? 'is-active' : index < activeIndex ? 'is-done' : ''
  624. return `
  625. <button type="button" class="timeline-a__step ${state}" role="tab" aria-selected="${index === activeIndex}" data-index="${index}">
  626. <div class="timeline-a__node-wrap">
  627. <span class="timeline-a__line"></span>
  628. <span class="timeline-a__dot"></span>
  629. </div>
  630. <span class="timeline-a__title">${step.title}</span>
  631. <span class="timeline-a__desc">${step.desc}</span>
  632. </button>
  633. `
  634. }).join('')
  635. track.querySelectorAll('.timeline-a__step').forEach((btn) => {
  636. btn.addEventListener('click', () => onSelect(Number(btn.dataset.index)))
  637. })
  638. }
  639. function renderTimelineB(container, activeIndex, onSelect) {
  640. const track = container.querySelector('.timeline-b__track')
  641. track.innerHTML = STEPS.map((step, index) => {
  642. const state =
  643. index === activeIndex ? 'is-active' : index < activeIndex ? 'is-done' : ''
  644. return `
  645. <button type="button" class="timeline-b__step ${state}" role="tab" aria-selected="${index === activeIndex}" data-index="${index}">
  646. <span class="timeline-b__connector"></span>
  647. <span class="timeline-b__badge">
  648. ${step.icon}
  649. <span class="timeline-b__num">${index + 1}</span>
  650. </span>
  651. <span class="timeline-b__title">${step.title}</span>
  652. <span class="timeline-b__desc">${step.desc}</span>
  653. </button>
  654. `
  655. }).join('')
  656. track.querySelectorAll('.timeline-b__step').forEach((btn) => {
  657. btn.addEventListener('click', () => onSelect(Number(btn.dataset.index)))
  658. })
  659. }
  660. function renderTimelineC(container, activeIndex, onSelect) {
  661. const rail = container.querySelector('.timeline-c__rail')
  662. const progress = rail.querySelector('.timeline-c__progress')
  663. const buttons = STEPS.map((step, index) => {
  664. const state =
  665. index === activeIndex ? 'is-active' : index < activeIndex ? 'is-done' : ''
  666. return `
  667. <button type="button" class="timeline-c__step ${state}" role="tab" aria-selected="${index === activeIndex}" data-index="${index}">
  668. <span class="timeline-c__index">${index + 1}</span>
  669. <span class="timeline-c__text">
  670. <span class="timeline-c__title">${step.title}</span>
  671. <span class="timeline-c__desc">${step.desc}</span>
  672. </span>
  673. <span class="timeline-c__chevron">›</span>
  674. </button>
  675. `
  676. }).join('')
  677. progress.insertAdjacentHTML('afterend', buttons)
  678. progress.style.width = `calc(${100 / STEPS.length}% - 3px)`
  679. progress.style.transform = `translateX(${activeIndex * 100}%)`
  680. rail.querySelectorAll('.timeline-c__step').forEach((btn) => {
  681. btn.addEventListener('click', () => onSelect(Number(btn.dataset.index)))
  682. })
  683. const meta = container.querySelector('[data-meta="c"]')
  684. if (meta) meta.textContent = `步骤 ${activeIndex + 1} / ${STEPS.length}`
  685. }
  686. function renderTimelineD(container, activeIndex, onSelect) {
  687. const track = container.querySelector('.timeline-d__track')
  688. track.innerHTML = STEPS.map((step, index) => {
  689. const state =
  690. index === activeIndex ? 'is-active' : index < activeIndex ? 'is-done' : ''
  691. return `
  692. <button type="button" class="timeline-d__step ${state}" role="tab" aria-selected="${index === activeIndex}" data-index="${index}">
  693. <span class="timeline-d__title">${step.title}</span>
  694. <span class="timeline-d__desc">${step.desc}</span>
  695. </button>
  696. `
  697. }).join('')
  698. track.querySelectorAll('.timeline-d__step').forEach((btn) => {
  699. btn.addEventListener('click', () => onSelect(Number(btn.dataset.index)))
  700. })
  701. }
  702. function updateContent(demoKey, activeIndex) {
  703. const step = STEPS[activeIndex]
  704. const panel = document.querySelector(`[data-content="${demoKey}"]`)
  705. if (!panel) return
  706. panel.innerHTML = `
  707. <h3>${step.title}</h3>
  708. <p>${step.desc} — 此处为 Tab 内容占位区(列表 / 筛选 / 表格)。</p>
  709. `
  710. }
  711. function initDemo(demoKey, renderFn) {
  712. let activeIndex = 0
  713. const section = document.querySelector(`[data-demo="${demoKey}"]`)
  714. const nav = section.querySelector('nav')
  715. function refresh() {
  716. if (demoKey === 'c') {
  717. nav.querySelector('.timeline-c__rail').innerHTML =
  718. '<div class="timeline-c__progress"></div>'
  719. }
  720. renderFn(nav, activeIndex, (index) => {
  721. activeIndex = index
  722. refresh()
  723. })
  724. updateContent(demoKey, activeIndex)
  725. }
  726. refresh()
  727. }
  728. initDemo('a', renderTimelineA)
  729. initDemo('b', renderTimelineB)
  730. initDemo('c', renderTimelineC)
  731. initDemo('d', renderTimelineD)
  732. </script>
  733. </body>
  734. </html>