compare-all-v2.html 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  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 选型 · 第二组(强对比)</title>
  7. <link
  8. href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
  9. rel="stylesheet"
  10. />
  11. <link rel="stylesheet" href="_shared.css" />
  12. <style>
  13. .batch-note {
  14. max-width: 960px;
  15. margin: 0 auto;
  16. padding: 0 24px 16px;
  17. }
  18. .batch-note-inner {
  19. padding: 12px 16px;
  20. background: var(--primary-bg);
  21. border: 1px solid #a7f3d0;
  22. border-radius: 10px;
  23. font-size: 13px;
  24. color: #065f46;
  25. line-height: 1.55;
  26. }
  27. /* ═══ N1 · 双色分栏顶栏 ═══ */
  28. .n1-switcher {
  29. display: grid;
  30. grid-template-columns: 1fr 1fr;
  31. border-bottom: 3px solid var(--border);
  32. }
  33. .n1-half {
  34. position: relative;
  35. display: flex;
  36. flex-direction: column;
  37. align-items: center;
  38. justify-content: center;
  39. gap: 6px;
  40. min-height: 72px;
  41. padding: 14px 16px;
  42. border: none;
  43. font-family: inherit;
  44. cursor: pointer;
  45. transition: filter 0.15s, transform 0.1s;
  46. }
  47. .n1-half:active {
  48. transform: scale(0.98);
  49. }
  50. .n1-half--reception {
  51. background: linear-gradient(180deg, #d1fae5 0%, var(--reception-bg) 100%);
  52. color: var(--reception);
  53. }
  54. .n1-half--production {
  55. background: linear-gradient(180deg, #fef3c7 0%, var(--production-bg) 100%);
  56. color: var(--production);
  57. }
  58. .n1-half:not(.is-active) {
  59. filter: saturate(0.35) brightness(1.08);
  60. }
  61. .n1-half:not(.is-active)::after {
  62. content: '👆 点击切换';
  63. position: absolute;
  64. bottom: 6px;
  65. font-size: 11px;
  66. font-weight: 600;
  67. opacity: 0.85;
  68. animation: nudge 2s ease-in-out infinite;
  69. }
  70. @keyframes nudge {
  71. 0%,
  72. 100% {
  73. transform: translateY(0);
  74. }
  75. 50% {
  76. transform: translateY(-2px);
  77. }
  78. }
  79. .n1-half.is-active {
  80. box-shadow: inset 0 -4px 0 currentColor;
  81. filter: none;
  82. }
  83. .n1-half.is-active::before {
  84. content: '当前查看';
  85. position: absolute;
  86. top: 6px;
  87. right: 8px;
  88. font-size: 10px;
  89. font-weight: 700;
  90. padding: 2px 8px;
  91. border-radius: 999px;
  92. background: rgb(255 255 255 / 75%);
  93. }
  94. .n1-half__title {
  95. font-size: 16px;
  96. font-weight: 700;
  97. letter-spacing: 0.02em;
  98. }
  99. .n1-half__sub {
  100. font-size: 11px;
  101. font-family: ui-monospace, Menlo, monospace;
  102. opacity: 0.9;
  103. }
  104. .n1-divider {
  105. position: absolute;
  106. left: 50%;
  107. top: 12px;
  108. bottom: 12px;
  109. width: 2px;
  110. background: rgb(15 23 42 / 12%);
  111. transform: translateX(-50%);
  112. pointer-events: none;
  113. z-index: 1;
  114. }
  115. .n1-switcher-wrap {
  116. position: relative;
  117. }
  118. /* ═══ N2 · 双行清单 ═══ */
  119. .n2-list {
  120. border-bottom: 1px solid var(--border);
  121. background: #fff;
  122. }
  123. .n2-list__head {
  124. padding: 10px 16px 6px;
  125. font-size: 12px;
  126. font-weight: 600;
  127. color: var(--text-muted);
  128. }
  129. .n2-row {
  130. display: flex;
  131. align-items: center;
  132. gap: 12px;
  133. width: 100%;
  134. padding: 12px 16px;
  135. border: none;
  136. border-top: 1px solid var(--border);
  137. background: #fff;
  138. font-family: inherit;
  139. text-align: left;
  140. cursor: pointer;
  141. transition: background 0.15s;
  142. }
  143. .n2-row:hover:not(.is-active) {
  144. background: #f8fafc;
  145. }
  146. .n2-row.is-active {
  147. background: var(--primary-bg);
  148. box-shadow: inset 4px 0 0 var(--primary);
  149. }
  150. .n2-row.is-active[data-source='reception'] {
  151. background: var(--reception-bg);
  152. box-shadow: inset 4px 0 0 var(--reception);
  153. }
  154. .n2-row.is-active[data-source='production'] {
  155. background: var(--production-bg);
  156. box-shadow: inset 4px 0 0 var(--production);
  157. }
  158. .n2-row__radio {
  159. width: 20px;
  160. height: 20px;
  161. border-radius: 50%;
  162. border: 2px solid #cbd5e1;
  163. flex-shrink: 0;
  164. display: flex;
  165. align-items: center;
  166. justify-content: center;
  167. }
  168. .n2-row.is-active .n2-row__radio {
  169. border-color: var(--primary);
  170. background: var(--primary);
  171. }
  172. .n2-row.is-active[data-source='reception'] .n2-row__radio {
  173. border-color: var(--reception);
  174. background: var(--reception);
  175. }
  176. .n2-row.is-active[data-source='production'] .n2-row__radio {
  177. border-color: var(--production);
  178. background: var(--production);
  179. }
  180. .n2-row.is-active .n2-row__radio::after {
  181. content: '';
  182. width: 8px;
  183. height: 8px;
  184. border-radius: 50%;
  185. background: #fff;
  186. }
  187. .n2-row__main {
  188. flex: 1;
  189. min-width: 0;
  190. }
  191. .n2-row__type {
  192. font-size: 14px;
  193. font-weight: 600;
  194. }
  195. .n2-row__no {
  196. font-size: 12px;
  197. font-family: ui-monospace, Menlo, monospace;
  198. color: var(--text-muted);
  199. margin-top: 2px;
  200. }
  201. .n2-row:not(.is-active) .n2-row__action {
  202. flex-shrink: 0;
  203. font-size: 12px;
  204. font-weight: 600;
  205. color: var(--primary-dark);
  206. padding: 4px 10px;
  207. border-radius: 6px;
  208. background: var(--primary-bg);
  209. border: 1px solid #a7f3d0;
  210. }
  211. .n2-row.is-active .n2-row__action {
  212. flex-shrink: 0;
  213. font-size: 11px;
  214. font-weight: 600;
  215. color: var(--text-muted);
  216. }
  217. /* ═══ N3 · 大开关 Toggle ═══ */
  218. .n3-zone {
  219. padding: 16px;
  220. border-bottom: 1px solid var(--border);
  221. background: #f1f5f9;
  222. display: flex;
  223. flex-direction: column;
  224. align-items: center;
  225. gap: 12px;
  226. }
  227. .n3-label {
  228. font-size: 13px;
  229. font-weight: 600;
  230. color: var(--text);
  231. }
  232. .n3-toggle {
  233. display: flex;
  234. width: 100%;
  235. max-width: 420px;
  236. padding: 5px;
  237. border-radius: 14px;
  238. background: #e2e8f0;
  239. border: 2px solid #cbd5e1;
  240. box-shadow: inset 0 2px 4px rgb(15 23 42 / 8%);
  241. }
  242. .n3-toggle__btn {
  243. flex: 1;
  244. display: flex;
  245. flex-direction: column;
  246. align-items: center;
  247. gap: 4px;
  248. padding: 14px 12px;
  249. border: none;
  250. border-radius: 10px;
  251. background: transparent;
  252. font-family: inherit;
  253. font-size: 15px;
  254. font-weight: 600;
  255. color: var(--text-muted);
  256. cursor: pointer;
  257. transition: all 0.2s ease;
  258. }
  259. .n3-toggle__btn.is-active[data-source='reception'] {
  260. background: #fff;
  261. color: var(--reception);
  262. box-shadow: 0 2px 8px rgb(4 120 87 / 20%);
  263. }
  264. .n3-toggle__btn.is-active[data-source='production'] {
  265. background: #fff;
  266. color: var(--production);
  267. box-shadow: 0 2px 8px rgb(180 83 9 / 20%);
  268. }
  269. .n3-toggle__btn:not(.is-active):hover {
  270. color: var(--text);
  271. background: rgb(255 255 255 / 50%);
  272. }
  273. .n3-toggle__icon {
  274. font-size: 22px;
  275. line-height: 1;
  276. }
  277. .n3-toggle__hint {
  278. font-size: 10px;
  279. font-weight: 500;
  280. opacity: 0.8;
  281. }
  282. .n3-current {
  283. font-size: 12px;
  284. color: var(--text-muted);
  285. font-family: ui-monospace, Menlo, monospace;
  286. }
  287. /* ═══ N4 · 图标宫格二选一 ═══ */
  288. .n4-zone {
  289. padding: 16px;
  290. border-bottom: 1px solid var(--border);
  291. background: #fff;
  292. }
  293. .n4-head {
  294. text-align: center;
  295. margin-bottom: 14px;
  296. }
  297. .n4-head__title {
  298. font-size: 14px;
  299. font-weight: 700;
  300. color: var(--text);
  301. }
  302. .n4-head__sub {
  303. font-size: 12px;
  304. color: var(--text-muted);
  305. margin-top: 4px;
  306. }
  307. .n4-grid {
  308. display: grid;
  309. grid-template-columns: 1fr 1fr;
  310. gap: 12px;
  311. }
  312. .n4-tile {
  313. display: flex;
  314. flex-direction: column;
  315. align-items: center;
  316. gap: 10px;
  317. padding: 20px 16px 16px;
  318. border: 3px solid var(--border);
  319. border-radius: 14px;
  320. background: #fff;
  321. font-family: inherit;
  322. cursor: pointer;
  323. transition: border-color 0.15s, transform 0.1s, box-shadow 0.15s;
  324. }
  325. .n4-tile:hover:not(.is-active) {
  326. transform: translateY(-2px);
  327. box-shadow: 0 6px 16px rgb(15 23 42 / 10%);
  328. }
  329. .n4-tile[data-source='reception']:not(.is-active) {
  330. border-color: var(--reception-border);
  331. border-style: dashed;
  332. }
  333. .n4-tile[data-source='production']:not(.is-active) {
  334. border-color: var(--production-border);
  335. border-style: dashed;
  336. }
  337. .n4-tile[data-source='reception'].is-active {
  338. border-color: var(--reception);
  339. border-style: solid;
  340. background: var(--reception-bg);
  341. box-shadow: 0 4px 12px rgb(4 120 87 / 15%);
  342. }
  343. .n4-tile[data-source='production'].is-active {
  344. border-color: var(--production);
  345. border-style: solid;
  346. background: var(--production-bg);
  347. box-shadow: 0 4px 12px rgb(180 83 9 / 15%);
  348. }
  349. .n4-tile__icon {
  350. width: 56px;
  351. height: 56px;
  352. border-radius: 16px;
  353. display: flex;
  354. align-items: center;
  355. justify-content: center;
  356. }
  357. .n4-tile[data-source='reception'] .n4-tile__icon {
  358. background: #fff;
  359. color: var(--reception);
  360. border: 2px solid var(--reception-border);
  361. }
  362. .n4-tile[data-source='production'] .n4-tile__icon {
  363. background: #fff;
  364. color: var(--production);
  365. border: 2px solid var(--production-border);
  366. }
  367. .n4-tile__name {
  368. font-size: 16px;
  369. font-weight: 700;
  370. }
  371. .n4-tile__cta {
  372. font-size: 12px;
  373. font-weight: 600;
  374. padding: 6px 14px;
  375. border-radius: 999px;
  376. }
  377. .n4-tile:not(.is-active) .n4-tile__cta {
  378. background: #f1f5f9;
  379. color: var(--text);
  380. border: 1px solid var(--border);
  381. }
  382. .n4-tile[data-source='reception']:not(.is-active) .n4-tile__cta {
  383. background: var(--reception-bg);
  384. color: var(--reception);
  385. border: 1px solid var(--reception-border);
  386. }
  387. .n4-tile[data-source='production']:not(.is-active) .n4-tile__cta {
  388. background: var(--production-bg);
  389. color: var(--production);
  390. border: 1px solid var(--production-border);
  391. }
  392. .n4-tile.is-active .n4-tile__cta {
  393. background: rgb(255 255 255 / 80%);
  394. color: inherit;
  395. border: 1px solid rgb(0 0 0 / 8%);
  396. }
  397. .n4-tile__meta {
  398. font-size: 11px;
  399. font-family: ui-monospace, Menlo, monospace;
  400. color: var(--text-muted);
  401. }
  402. /* ═══ N5 · 双摘要条(两条始终可见) ═══ */
  403. .n5-zone {
  404. padding: 12px 16px;
  405. border-bottom: 1px solid var(--border);
  406. background: #f8fafc;
  407. display: flex;
  408. flex-direction: column;
  409. gap: 8px;
  410. }
  411. .n5-banner {
  412. display: flex;
  413. align-items: center;
  414. gap: 8px;
  415. padding: 8px 12px;
  416. border-radius: 8px;
  417. background: #fff;
  418. border: 1px solid var(--border);
  419. font-size: 12px;
  420. color: var(--text-muted);
  421. }
  422. .n5-banner strong {
  423. color: var(--primary-dark);
  424. }
  425. .n5-strip {
  426. display: flex;
  427. align-items: stretch;
  428. gap: 8px;
  429. min-height: 56px;
  430. }
  431. .n5-chip {
  432. flex: 1;
  433. display: flex;
  434. align-items: center;
  435. gap: 10px;
  436. padding: 10px 12px;
  437. border: 2px solid var(--border);
  438. border-radius: 10px;
  439. background: #fff;
  440. font-family: inherit;
  441. text-align: left;
  442. cursor: pointer;
  443. transition: all 0.15s;
  444. }
  445. .n5-chip:not(.is-active) {
  446. opacity: 0.72;
  447. }
  448. .n5-chip:not(.is-active):hover {
  449. opacity: 1;
  450. border-color: #94a3b8;
  451. }
  452. .n5-chip.is-active[data-source='reception'] {
  453. opacity: 1;
  454. border-color: var(--reception);
  455. background: var(--reception-bg);
  456. box-shadow: 0 0 0 1px var(--reception);
  457. }
  458. .n5-chip.is-active[data-source='production'] {
  459. opacity: 1;
  460. border-color: var(--production);
  461. background: var(--production-bg);
  462. box-shadow: 0 0 0 1px var(--production);
  463. }
  464. .n5-chip__dot {
  465. width: 10px;
  466. height: 10px;
  467. border-radius: 50%;
  468. flex-shrink: 0;
  469. }
  470. .n5-chip[data-source='reception'] .n5-chip__dot {
  471. background: var(--reception);
  472. }
  473. .n5-chip[data-source='production'] .n5-chip__dot {
  474. background: var(--production);
  475. }
  476. .n5-chip__body {
  477. flex: 1;
  478. min-width: 0;
  479. }
  480. .n5-chip__type {
  481. font-size: 13px;
  482. font-weight: 700;
  483. }
  484. .n5-chip__status {
  485. font-size: 11px;
  486. color: var(--text-muted);
  487. margin-top: 1px;
  488. }
  489. .n5-chip:not(.is-active) .n5-chip__switch {
  490. flex-shrink: 0;
  491. font-size: 11px;
  492. font-weight: 700;
  493. color: #fff;
  494. padding: 4px 10px;
  495. border-radius: 6px;
  496. background: var(--primary);
  497. }
  498. .n5-chip.is-active .n5-chip__switch {
  499. flex-shrink: 0;
  500. font-size: 10px;
  501. font-weight: 700;
  502. padding: 3px 8px;
  503. border-radius: 999px;
  504. background: rgb(255 255 255 / 70%);
  505. color: inherit;
  506. border: 1px solid rgb(0 0 0 / 10%);
  507. }
  508. /* ═══ N6 · 左导航 + 右内容预览 ═══ */
  509. .n6-layout {
  510. display: flex;
  511. border-bottom: 1px solid var(--border);
  512. min-height: 120px;
  513. }
  514. @media (max-width: 560px) {
  515. .n6-layout {
  516. flex-direction: column;
  517. }
  518. }
  519. .n6-nav {
  520. width: 200px;
  521. flex-shrink: 0;
  522. background: #f1f5f9;
  523. border-right: 1px solid var(--border);
  524. padding: 10px 8px;
  525. display: flex;
  526. flex-direction: column;
  527. gap: 6px;
  528. }
  529. @media (max-width: 560px) {
  530. .n6-nav {
  531. width: 100%;
  532. flex-direction: row;
  533. border-right: none;
  534. border-bottom: 1px solid var(--border);
  535. }
  536. }
  537. .n6-nav__label {
  538. padding: 4px 8px 6px;
  539. font-size: 11px;
  540. font-weight: 700;
  541. color: var(--text-muted);
  542. text-transform: uppercase;
  543. letter-spacing: 0.04em;
  544. }
  545. @media (max-width: 560px) {
  546. .n6-nav__label {
  547. display: none;
  548. }
  549. }
  550. .n6-nav__btn {
  551. display: flex;
  552. flex-direction: column;
  553. gap: 4px;
  554. padding: 12px 10px;
  555. border: 2px solid transparent;
  556. border-radius: 8px;
  557. background: #fff;
  558. font-family: inherit;
  559. text-align: left;
  560. cursor: pointer;
  561. transition: all 0.15s;
  562. }
  563. @media (max-width: 560px) {
  564. .n6-nav__btn {
  565. flex: 1;
  566. }
  567. }
  568. .n6-nav__btn:not(.is-active):hover {
  569. border-color: #cbd5e1;
  570. }
  571. .n6-nav__btn.is-active[data-source='reception'] {
  572. border-color: var(--reception);
  573. background: var(--reception-bg);
  574. }
  575. .n6-nav__btn.is-active[data-source='production'] {
  576. border-color: var(--production);
  577. background: var(--production-bg);
  578. }
  579. .n6-nav__btn:not(.is-active)::after {
  580. content: '点击';
  581. font-size: 10px;
  582. font-weight: 600;
  583. color: var(--primary);
  584. }
  585. .n6-nav__type {
  586. font-size: 13px;
  587. font-weight: 700;
  588. }
  589. .n6-nav__no {
  590. font-size: 10px;
  591. font-family: ui-monospace, Menlo, monospace;
  592. color: var(--text-muted);
  593. word-break: break-all;
  594. }
  595. .n6-preview {
  596. flex: 1;
  597. padding: 12px 16px;
  598. background: #fff;
  599. display: flex;
  600. flex-direction: column;
  601. justify-content: center;
  602. }
  603. .n6-preview__label {
  604. font-size: 11px;
  605. color: var(--text-muted);
  606. margin-bottom: 6px;
  607. }
  608. .n6-preview__title {
  609. font-size: 15px;
  610. font-weight: 600;
  611. margin-bottom: 4px;
  612. }
  613. .n6-preview__no {
  614. font-family: ui-monospace, Menlo, monospace;
  615. font-size: 13px;
  616. color: var(--text-muted);
  617. }
  618. .n6-preview__hint {
  619. margin-top: 10px;
  620. font-size: 12px;
  621. color: var(--primary-dark);
  622. font-weight: 500;
  623. }
  624. </style>
  625. </head>
  626. <body>
  627. <header class="demo-banner">
  628. <div>
  629. <h1>第二组 · 强对比方案(6 套)</h1>
  630. <p>
  631. 上一组反馈「不够明显」。本组加大触控面积、始终展示两条工单、强化未选中态的视觉提示(虚线边框、👆 动画、Radio
  632. 清单、大 Toggle 等)。<a href="compare-all.html" style="color: var(--primary-dark)">← 第一组</a>
  633. </p>
  634. </div>
  635. <div class="demo-banner__actions">
  636. <a class="demo-link" href="index.html">索引</a>
  637. </div>
  638. </header>
  639. <div class="batch-note">
  640. <div class="batch-note-inner">
  641. 设计原则:<strong>两条工单信息始终可见</strong>、<strong>未选中必须有明确「可点击」 affordance</strong>、<strong>来源类别用大面积色块区分</strong>(绿=样本接收,琥珀=生产过程)。
  642. </div>
  643. </div>
  644. <div class="page-container">
  645. <div class="compare-stack">
  646. <section class="compare-block">
  647. <div class="compare-block__label">
  648. <h2>
  649. <span class="variant-tag variant-tag--rec">推荐</span>
  650. <span class="variant-tag">N1</span>
  651. 双色分栏顶栏(整宽可点)
  652. </h2>
  653. <p>卡片顶部 50/50 分栏:左绿「样本接收」、右琥珀「生产过程」。未选中侧降饱和 + 底部「👆 点击切换」动画;选中侧底部色条 +「当前查看」角标。</p>
  654. <div class="pros-cons">
  655. <div class="pro">✓ 类别与可点击性一眼可见,占用高度适中</div>
  656. </div>
  657. </div>
  658. <div class="compare-block__body" id="block-n1"></div>
  659. </section>
  660. <section class="compare-block">
  661. <div class="compare-block__label">
  662. <h2><span class="variant-tag">N2</span> 双行 Radio 清单</h2>
  663. <p>两条工单以列表行始终展示:Radio 圆点 + 类型 + 工单号。未选中行右侧绿色「切换至此」按钮;选中行左侧 4px 色条高亮。</p>
  664. <div class="pros-cons">
  665. <div class="pro">✓ 类似表单单选,学习成本最低</div>
  666. <div class="pro">✓ 两条工单信息完整可见</div>
  667. </div>
  668. </div>
  669. <div class="compare-block__body" id="block-n2"></div>
  670. </section>
  671. <section class="compare-block">
  672. <div class="compare-block__label">
  673. <h2>
  674. <span class="variant-tag variant-tag--rec">推荐</span>
  675. <span class="variant-tag">N3</span>
  676. 大开关 Toggle(二选一)
  677. </h2>
  678. <p>居中 oversized 分段开关,emoji 图标 + 15px 粗体类别名。切换时白底滑块 + 阴影动效,下方显示当前工单号。</p>
  679. <div class="pros-cons">
  680. <div class="pro">✓ 二选一语义最直白</div>
  681. </div>
  682. </div>
  683. <div class="compare-block__body" id="block-n3"></div>
  684. </section>
  685. <section class="compare-block">
  686. <div class="compare-block__label">
  687. <h2><span class="variant-tag">N4</span> 图标宫格(虚线 = 可点)</h2>
  688. <p>两个 56px 大图标方块并排:未选中虚线边框 +「点击查看」圆角按钮;选中实线边框 + 整卡染色 + 阴影浮起。</p>
  689. <div class="pros-cons">
  690. <div class="pro">✓ 视觉重量最大,最难被忽略</div>
  691. <div class="con">✗ 垂直空间占用较多</div>
  692. </div>
  693. </div>
  694. <div class="compare-block__body" id="block-n4"></div>
  695. </section>
  696. <section class="compare-block">
  697. <div class="compare-block__label">
  698. <h2><span class="variant-tag">N5</span> 双摘要条 + 切换按钮</h2>
  699. <p>顶部提示「本样本有 2 条质检工单,请点击切换」;下方两条摘要 Chip 并排,未选中带绿色「切换」实心按钮。</p>
  700. </div>
  701. <div class="compare-block__body" id="block-n5"></div>
  702. </section>
  703. <section class="compare-block">
  704. <div class="compare-block__label">
  705. <h2><span class="variant-tag">N6</span> 左导航 + 右预览</h2>
  706. <p>左侧固定 200px 导航列列出两条工单(未选中显示「点击」),右侧预览当前选中工单摘要——Master-Detail 模式。</p>
  707. <div class="pros-cons">
  708. <div class="pro">✓ 适合 3 条以上工单扩展</div>
  709. <div class="con">✗ 横向空间占用大</div>
  710. </div>
  711. </div>
  712. <div class="compare-block__body" id="block-n6"></div>
  713. </section>
  714. </div>
  715. </div>
  716. <script>
  717. const MOCK = {
  718. sampleCode: 'YB-2025-0618-0042',
  719. subtitle: '张三 · 间充质干细胞 · 5×10⁶/支',
  720. workOrders: [
  721. {
  722. id: 'wo-reception',
  723. source: 'reception',
  724. label: '样本接收',
  725. emoji: '📦',
  726. no: 'QC-IN-20250618-0012',
  727. statusLabel: '已完成',
  728. statusClass: 'tag--success'
  729. },
  730. {
  731. id: 'wo-production',
  732. source: 'production',
  733. label: '生产过程',
  734. emoji: '⚙️',
  735. no: 'QC-IN-20250620-0038',
  736. statusLabel: '已配置',
  737. statusClass: 'tag--warning'
  738. }
  739. ]
  740. }
  741. const ICONS = {
  742. 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>`,
  743. 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>`
  744. }
  745. function ctxHeader() {
  746. return `
  747. <div class="qc-sample-row__ctx">
  748. <span class="qc-sample-row__idx">1</span>
  749. <span class="qc-sample-row__code">${MOCK.sampleCode}</span>
  750. <span class="qc-sample-row__sub">${MOCK.subtitle}</span>
  751. <span class="qc-sample-row__badge">${MOCK.workOrders.length} 条质检</span>
  752. </div>`
  753. }
  754. function cardPlaceholder(wo) {
  755. return `
  756. <div class="qc-card-placeholder">
  757. <div class="qc-card-placeholder__head">
  758. <span class="qc-card-placeholder__no">${wo.no}</span>
  759. <span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
  760. <span class="tag tag--${wo.source}">${wo.label}</span>
  761. </div>
  762. <div class="qc-card-placeholder__meta">
  763. <span><strong>触发来源</strong>${wo.label}</span>
  764. <span><strong>细胞类型</strong>间充质干细胞</span>
  765. <span><strong>创建时间</strong>2025-06-18 14:32</span>
  766. </div>
  767. <div class="qc-card-placeholder__tasks">▼ 质检任务列表(${wo.label})</div>
  768. </div>`
  769. }
  770. function wrap(inner, activeWo) {
  771. return `<article class="qc-sample-row">${ctxHeader()}${inner}${cardPlaceholder(activeWo)}</article>`
  772. }
  773. function renderN1(id) {
  774. const active = MOCK.workOrders.find((w) => w.id === id)
  775. const [a, b] = MOCK.workOrders
  776. return wrap(
  777. `<div class="n1-switcher-wrap">
  778. <div class="n1-switcher" role="tablist">
  779. <button type="button" class="n1-half n1-half--reception tab-btn ${a.id === id ? 'is-active' : ''}"
  780. data-id="${a.id}" role="tab">
  781. <span class="n1-half__title">${a.emoji} ${a.label}</span>
  782. <span class="n1-half__sub">${a.no}</span>
  783. </button>
  784. <button type="button" class="n1-half n1-half--production tab-btn ${b.id === id ? 'is-active' : ''}"
  785. data-id="${b.id}" role="tab">
  786. <span class="n1-half__title">${b.emoji} ${b.label}</span>
  787. <span class="n1-half__sub">${b.no}</span>
  788. </button>
  789. </div>
  790. <div class="n1-divider"></div>
  791. </div>`,
  792. active
  793. )
  794. }
  795. function renderN2(id) {
  796. const active = MOCK.workOrders.find((w) => w.id === id)
  797. const rows = MOCK.workOrders.map(
  798. (wo) => `
  799. <button type="button" class="n2-row tab-btn ${wo.id === id ? 'is-active' : ''}"
  800. data-id="${wo.id}" data-source="${wo.source}" role="tab">
  801. <span class="n2-row__radio"></span>
  802. <span class="n2-row__main">
  803. <div class="n2-row__type">${wo.label}</div>
  804. <div class="n2-row__no">${wo.no}</div>
  805. </span>
  806. <span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
  807. <span class="n2-row__action">${wo.id === id ? '正在查看' : '切换至此'}</span>
  808. </button>`
  809. ).join('')
  810. return wrap(
  811. `<div class="n2-list">
  812. <div class="n2-list__head">请选择要查看的质检工单(共 ${MOCK.workOrders.length} 条)</div>
  813. ${rows}
  814. </div>`,
  815. active
  816. )
  817. }
  818. function renderN3(id) {
  819. const active = MOCK.workOrders.find((w) => w.id === id)
  820. const btns = MOCK.workOrders.map(
  821. (wo) => `
  822. <button type="button" class="n3-toggle__btn tab-btn ${wo.id === id ? 'is-active' : ''}"
  823. data-id="${wo.id}" data-source="${wo.source}" role="tab">
  824. <span class="n3-toggle__icon">${wo.emoji}</span>
  825. <span>${wo.label}</span>
  826. ${wo.id !== id ? '<span class="n3-toggle__hint">点击切换</span>' : ''}
  827. </button>`
  828. ).join('')
  829. return wrap(
  830. `<div class="n3-zone">
  831. <div class="n3-label">本样本有两条质检工单,请选择查看:</div>
  832. <div class="n3-toggle" role="tablist">${btns}</div>
  833. <div class="n3-current">当前:${active.no} · ${active.statusLabel}</div>
  834. </div>`,
  835. active
  836. )
  837. }
  838. function renderN4(id) {
  839. const active = MOCK.workOrders.find((w) => w.id === id)
  840. const tiles = MOCK.workOrders.map(
  841. (wo) => `
  842. <button type="button" class="n4-tile tab-btn ${wo.id === id ? 'is-active' : ''}"
  843. data-id="${wo.id}" data-source="${wo.source}" role="tab">
  844. <span class="n4-tile__icon">${ICONS[wo.source]}</span>
  845. <span class="n4-tile__name">${wo.label}</span>
  846. <span class="n4-tile__meta">${wo.no}</span>
  847. <span class="tag ${wo.statusClass}">${wo.statusLabel}</span>
  848. <span class="n4-tile__cta">${wo.id === id ? '✓ 当前查看' : '点击查看'}</span>
  849. </button>`
  850. ).join('')
  851. return wrap(
  852. `<div class="n4-zone">
  853. <div class="n4-head">
  854. <div class="n4-head__title">选择质检工单类型</div>
  855. <div class="n4-head__sub">虚线边框 = 可点击切换 · 实线 = 当前查看</div>
  856. </div>
  857. <div class="n4-grid">${tiles}</div>
  858. </div>`,
  859. active
  860. )
  861. }
  862. function renderN5(id) {
  863. const active = MOCK.workOrders.find((w) => w.id === id)
  864. const chips = MOCK.workOrders.map(
  865. (wo) => `
  866. <button type="button" class="n5-chip tab-btn ${wo.id === id ? 'is-active' : ''}"
  867. data-id="${wo.id}" data-source="${wo.source}" role="tab">
  868. <span class="n5-chip__dot"></span>
  869. <span class="n5-chip__body">
  870. <div class="n5-chip__type">${wo.label}</div>
  871. <div class="n5-chip__status">${wo.no} · ${wo.statusLabel}</div>
  872. </span>
  873. <span class="n5-chip__switch">${wo.id === id ? '查看中' : '切换'}</span>
  874. </button>`
  875. ).join('')
  876. return wrap(
  877. `<div class="n5-zone">
  878. <div class="n5-banner">
  879. 💡 本样本有 <strong>2 条</strong>质检工单(样本接收 + 生产过程),请点击下方切换查看
  880. </div>
  881. <div class="n5-strip">${chips}</div>
  882. </div>`,
  883. active
  884. )
  885. }
  886. function renderN6(id) {
  887. const active = MOCK.workOrders.find((w) => w.id === id)
  888. const nav = MOCK.workOrders.map(
  889. (wo) => `
  890. <button type="button" class="n6-nav__btn tab-btn ${wo.id === id ? 'is-active' : ''}"
  891. data-id="${wo.id}" data-source="${wo.source}" role="tab">
  892. <span class="n6-nav__type">${wo.label}</span>
  893. <span class="n6-nav__no">${wo.no}</span>
  894. <span class="tag ${wo.statusClass}" style="margin-top:4px;height:20px;padding:0 6px;font-size:10px">${wo.statusLabel}</span>
  895. </button>`
  896. ).join('')
  897. return wrap(
  898. `<div class="n6-layout">
  899. <nav class="n6-nav">
  900. <div class="n6-nav__label">工单列表</div>
  901. ${nav}
  902. </nav>
  903. <div class="n6-preview">
  904. <div class="n6-preview__label">当前选中</div>
  905. <div class="n6-preview__title">${active.label}</div>
  906. <div class="n6-preview__no">${active.no}</div>
  907. <span class="tag ${active.statusClass}">${active.statusLabel}</span>
  908. <div class="n6-preview__hint">← 点击左侧另一条工单可切换</div>
  909. </div>
  910. </div>`,
  911. active
  912. )
  913. }
  914. const VARIANTS = {
  915. n1: renderN1,
  916. n2: renderN2,
  917. n3: renderN3,
  918. n4: renderN4,
  919. n5: renderN5,
  920. n6: renderN6
  921. }
  922. const state = Object.fromEntries(Object.keys(VARIANTS).map((k) => [k, 'wo-reception']))
  923. function mount(key) {
  924. const el = document.getElementById(`block-${key}`)
  925. el.innerHTML = VARIANTS[key](state[key])
  926. el.querySelectorAll('[data-id]').forEach((btn) => {
  927. btn.addEventListener('click', () => {
  928. state[key] = btn.dataset.id
  929. mount(key)
  930. })
  931. })
  932. }
  933. Object.keys(VARIANTS).forEach(mount)
  934. </script>
  935. </body>
  936. </html>