zkxw-mobile-control-style-demos.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  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" />
  6. <title>mobile-h5 · 控件样式 Demo(输入 / 下拉 / Tag / Tab)</title>
  7. <style>
  8. :root {
  9. --ink: #1f2937;
  10. --muted: #6b7280;
  11. --line: #e5e7eb;
  12. --page: #f3f4f6;
  13. --brand: #22c55e;
  14. --brand-ink: #166534;
  15. }
  16. * { box-sizing: border-box; }
  17. html, body { margin: 0; min-height: 100%; }
  18. body {
  19. font-family: "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", -apple-system, system-ui, sans-serif;
  20. color: var(--ink);
  21. background: linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%);
  22. line-height: 1.45;
  23. }
  24. .wrap {
  25. max-width: 1240px;
  26. margin: 0 auto;
  27. padding: 28px 16px 80px;
  28. }
  29. h1 { margin: 0 0 8px; font-size: 24px; letter-spacing: -0.02em; }
  30. .sub { margin: 0; color: var(--muted); font-size: 14px; max-width: 760px; }
  31. .note {
  32. margin: 14px 0 22px;
  33. padding: 10px 12px;
  34. border-radius: 10px;
  35. background: #fff;
  36. border: 1px solid var(--line);
  37. font-size: 13px;
  38. color: var(--muted);
  39. }
  40. .note strong { color: var(--brand-ink); }
  41. .scheme-tabs {
  42. display: flex;
  43. flex-wrap: wrap;
  44. gap: 8px;
  45. margin-bottom: 18px;
  46. }
  47. .scheme-tab {
  48. appearance: none;
  49. border: 1px solid var(--line);
  50. background: #fff;
  51. border-radius: 999px;
  52. padding: 9px 14px;
  53. font-size: 13px;
  54. font-weight: 650;
  55. color: #475569;
  56. cursor: pointer;
  57. }
  58. .scheme-tab.is-on {
  59. border-color: #94a3b8;
  60. background: #0f172a;
  61. color: #fff;
  62. }
  63. .scheme-tab .rec {
  64. margin-left: 6px;
  65. font-size: 10px;
  66. padding: 1px 6px;
  67. border-radius: 999px;
  68. background: #ecfdf5;
  69. color: #166534;
  70. }
  71. .scheme-tab.is-on .rec {
  72. background: rgba(255,255,255,.18);
  73. color: #fff;
  74. }
  75. .grid {
  76. display: grid;
  77. grid-template-columns: 1fr 390px;
  78. gap: 18px;
  79. align-items: start;
  80. }
  81. @media (max-width: 960px) {
  82. .grid { grid-template-columns: 1fr; }
  83. }
  84. .panel {
  85. background: #fff;
  86. border: 1px solid var(--line);
  87. border-radius: 16px;
  88. padding: 16px;
  89. box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
  90. }
  91. .panel h2 {
  92. margin: 0 0 4px;
  93. font-size: 15px;
  94. }
  95. .panel .desc {
  96. margin: 0 0 14px;
  97. font-size: 12px;
  98. color: var(--muted);
  99. }
  100. .block { margin-bottom: 22px; }
  101. .block:last-child { margin-bottom: 0; }
  102. .block-label {
  103. margin: 0 0 10px;
  104. font-size: 11px;
  105. font-weight: 700;
  106. letter-spacing: 0.06em;
  107. text-transform: uppercase;
  108. color: #94a3b8;
  109. }
  110. /* ========== 手机预览壳 ========== */
  111. .phone {
  112. width: 100%;
  113. max-width: 390px;
  114. margin: 0 auto;
  115. background: #111827;
  116. border-radius: 36px;
  117. padding: 10px;
  118. box-shadow: 0 24px 50px rgba(15, 23, 42, 0.18);
  119. }
  120. .phone-screen {
  121. border-radius: 28px;
  122. overflow: hidden;
  123. background: #fff;
  124. min-height: 720px;
  125. }
  126. .phone-nav {
  127. height: 48px;
  128. display: flex;
  129. align-items: center;
  130. justify-content: center;
  131. font-size: 15px;
  132. font-weight: 650;
  133. border-bottom: 1px solid #f1f5f9;
  134. background: #fff;
  135. }
  136. /* ========== 方案公共预览区 ========== */
  137. .preview-pad { padding: 12px 14px 20px; background: #fff; }
  138. .preview-stack { display: grid; gap: 16px; }
  139. /* —— 控件原子(由 scheme class 覆盖) —— */
  140. .ui-search {
  141. display: flex;
  142. align-items: center;
  143. gap: 8px;
  144. height: 40px;
  145. padding: 0 12px;
  146. width: 100%;
  147. }
  148. .ui-search input {
  149. flex: 1;
  150. border: 0;
  151. outline: none;
  152. background: transparent;
  153. font-size: 14px;
  154. color: var(--ink);
  155. min-width: 0;
  156. font-family: inherit;
  157. }
  158. .ui-search input::placeholder { color: #b0b7c3; }
  159. .ui-ico { color: #9ca3af; font-size: 14px; flex-shrink: 0; }
  160. .ui-select {
  161. position: relative;
  162. width: 100%;
  163. }
  164. .ui-select-trigger {
  165. width: 100%;
  166. height: 40px;
  167. display: flex;
  168. align-items: center;
  169. justify-content: space-between;
  170. gap: 8px;
  171. padding: 0 12px;
  172. font-size: 14px;
  173. cursor: pointer;
  174. background: #fff;
  175. font-family: inherit;
  176. color: var(--ink);
  177. }
  178. .ui-select-trigger .ph { color: #b0b7c3; }
  179. .ui-select-trigger .chev {
  180. color: #9ca3af;
  181. font-size: 12px;
  182. transition: transform .15s;
  183. }
  184. .ui-select.open .ui-select-trigger .chev { transform: rotate(180deg); }
  185. .ui-dropdown {
  186. display: none;
  187. position: absolute;
  188. left: 0; right: 0;
  189. top: calc(100% + 6px);
  190. z-index: 20;
  191. background: #fff;
  192. overflow: hidden;
  193. max-height: 260px;
  194. }
  195. .ui-select.open .ui-dropdown { display: block; }
  196. .ui-dropdown .dd-search {
  197. display: flex;
  198. align-items: center;
  199. gap: 8px;
  200. margin: 10px;
  201. height: 34px;
  202. padding: 0 10px;
  203. }
  204. .ui-dropdown .dd-search input {
  205. flex: 1;
  206. border: 0;
  207. outline: none;
  208. background: transparent;
  209. font-size: 13px;
  210. font-family: inherit;
  211. }
  212. .ui-dropdown .dd-list {
  213. max-height: 180px;
  214. overflow: auto;
  215. padding: 4px 0 8px;
  216. }
  217. .ui-dropdown .dd-item {
  218. padding: 10px 14px;
  219. font-size: 13px;
  220. cursor: pointer;
  221. color: var(--ink);
  222. }
  223. .ui-dropdown .dd-item:hover { background: #f8fafc; }
  224. .ui-dropdown .dd-item.is-on { font-weight: 650; }
  225. .ui-tags {
  226. display: flex;
  227. flex-wrap: wrap;
  228. gap: 8px;
  229. }
  230. .ui-tag {
  231. display: inline-flex;
  232. align-items: center;
  233. height: 28px;
  234. padding: 0 12px;
  235. font-size: 12px;
  236. cursor: pointer;
  237. user-select: none;
  238. border: 0;
  239. font-family: inherit;
  240. }
  241. .ui-tag.is-on { font-weight: 650; }
  242. .ui-tabs {
  243. display: flex;
  244. align-items: stretch;
  245. gap: 0;
  246. overflow-x: auto;
  247. -webkit-overflow-scrolling: touch;
  248. }
  249. .ui-tab {
  250. position: relative;
  251. flex-shrink: 0;
  252. height: 40px;
  253. padding: 0 14px;
  254. display: inline-flex;
  255. align-items: center;
  256. justify-content: center;
  257. font-size: 14px;
  258. color: #6b7280;
  259. cursor: pointer;
  260. background: transparent;
  261. border: 0;
  262. font-family: inherit;
  263. }
  264. .ui-tab.is-on {
  265. color: var(--ink);
  266. font-weight: 650;
  267. }
  268. /* ========== A 极简线框 ========== */
  269. .scheme-a .ui-search {
  270. border: 1px solid #e5e7eb;
  271. border-radius: 10px;
  272. background: #fff;
  273. }
  274. .scheme-a .ui-select-trigger {
  275. border: 1px solid #e5e7eb;
  276. border-radius: 10px;
  277. }
  278. .scheme-a .ui-dropdown {
  279. border: 1px solid #e5e7eb;
  280. border-radius: 12px;
  281. box-shadow: 0 10px 28px rgba(15,23,42,.08);
  282. }
  283. .scheme-a .ui-dropdown .dd-search {
  284. border: 1px solid #e5e7eb;
  285. border-radius: 8px;
  286. background: #fff;
  287. }
  288. .scheme-a .ui-dropdown .dd-item.is-on { color: #111827; background: #f3f4f6; }
  289. .scheme-a .ui-tag {
  290. border: 1px solid #e5e7eb;
  291. border-radius: 8px;
  292. background: #fff;
  293. color: #4b5563;
  294. }
  295. .scheme-a .ui-tag.is-on {
  296. border-color: #111827;
  297. color: #111827;
  298. background: #fff;
  299. }
  300. .scheme-a .ui-tabs {
  301. border-bottom: 1px solid #eee;
  302. }
  303. .scheme-a .ui-tab.is-on::after {
  304. content: "";
  305. position: absolute;
  306. left: 14px; right: 14px; bottom: 0;
  307. height: 2px;
  308. background: #111827;
  309. border-radius: 2px 2px 0 0;
  310. }
  311. /* ========== B 浅灰填充 ========== */
  312. .scheme-b .ui-search {
  313. border: 0;
  314. border-radius: 12px;
  315. background: #f3f4f6;
  316. }
  317. .scheme-b .ui-select-trigger {
  318. border: 0;
  319. border-radius: 12px;
  320. background: #f3f4f6;
  321. }
  322. .scheme-b .ui-dropdown {
  323. border: 0;
  324. border-radius: 14px;
  325. box-shadow: 0 12px 32px rgba(15,23,42,.12);
  326. }
  327. .scheme-b .ui-dropdown .dd-search {
  328. border: 0;
  329. border-radius: 10px;
  330. background: #f3f4f6;
  331. }
  332. .scheme-b .ui-dropdown .dd-item.is-on { background: #eef2ff; color: #3730a3; }
  333. .scheme-b .ui-tag {
  334. border-radius: 999px;
  335. background: #f3f4f6;
  336. color: #6b7280;
  337. }
  338. .scheme-b .ui-tag.is-on {
  339. background: #e5e7eb;
  340. color: #111827;
  341. }
  342. .scheme-b .ui-tabs {
  343. gap: 8px;
  344. padding: 2px 0;
  345. }
  346. .scheme-b .ui-tab {
  347. height: 32px;
  348. border-radius: 999px;
  349. background: #f3f4f6;
  350. padding: 0 14px;
  351. font-size: 13px;
  352. }
  353. .scheme-b .ui-tab.is-on {
  354. background: #e5e7eb;
  355. color: #111827;
  356. }
  357. /* ========== C 分段控件 ========== */
  358. .scheme-c .ui-search {
  359. border: 0;
  360. border-radius: 999px;
  361. background: #f5f5f7;
  362. padding: 0 14px;
  363. }
  364. .scheme-c .ui-select-trigger {
  365. border: 0;
  366. border-radius: 12px;
  367. background: #f5f5f7;
  368. }
  369. .scheme-c .ui-dropdown {
  370. border-radius: 16px;
  371. box-shadow: 0 16px 40px rgba(15,23,42,.14);
  372. border: 1px solid rgba(15,23,42,.06);
  373. }
  374. .scheme-c .ui-dropdown .dd-search {
  375. border: 0;
  376. border-radius: 999px;
  377. background: #f5f5f7;
  378. }
  379. .scheme-c .ui-dropdown .dd-item.is-on { color: #007aff; background: #f0f7ff; }
  380. .scheme-c .ui-tag {
  381. border-radius: 8px;
  382. background: #f5f5f7;
  383. color: #6b7280;
  384. }
  385. .scheme-c .ui-tag.is-on {
  386. background: #111827;
  387. color: #fff;
  388. }
  389. .scheme-c .ui-tabs {
  390. background: #f5f5f7;
  391. border-radius: 10px;
  392. padding: 3px;
  393. gap: 2px;
  394. }
  395. .scheme-c .ui-tab {
  396. flex: 1;
  397. height: 34px;
  398. border-radius: 8px;
  399. font-size: 13px;
  400. justify-content: center;
  401. }
  402. .scheme-c .ui-tab.is-on {
  403. background: #fff;
  404. color: #111827;
  405. box-shadow: 0 1px 3px rgba(15,23,42,.08);
  406. }
  407. /* ========== D 清爽描边 + 轻主色(推荐) ========== */
  408. .scheme-d .ui-search {
  409. border: 1px solid #e8eaed;
  410. border-radius: 10px;
  411. background: #fafafa;
  412. }
  413. .scheme-d .ui-search:focus-within {
  414. border-color: #cbd5e1;
  415. background: #fff;
  416. box-shadow: 0 0 0 3px rgba(148, 163, 184, 0.18);
  417. }
  418. .scheme-d .ui-select-trigger {
  419. border: 1px solid #e8eaed;
  420. border-radius: 10px;
  421. background: #fafafa;
  422. }
  423. .scheme-d .ui-select.open .ui-select-trigger {
  424. border-color: #cbd5e1;
  425. background: #fff;
  426. }
  427. .scheme-d .ui-dropdown {
  428. border: 1px solid #e8eaed;
  429. border-radius: 12px;
  430. box-shadow: 0 12px 28px rgba(15,23,42,.1);
  431. }
  432. .scheme-d .ui-dropdown .dd-search {
  433. border: 1px solid #e8eaed;
  434. border-radius: 8px;
  435. background: #fafafa;
  436. }
  437. .scheme-d .ui-dropdown .dd-item.is-on {
  438. color: var(--brand-ink);
  439. background: #f0fdf4;
  440. }
  441. .scheme-d .ui-tag {
  442. border: 1px solid #e8eaed;
  443. border-radius: 999px;
  444. background: #fff;
  445. color: #64748b;
  446. }
  447. .scheme-d .ui-tag.is-on {
  448. border-color: #bbf7d0;
  449. background: #f0fdf4;
  450. color: var(--brand-ink);
  451. }
  452. .scheme-d .ui-tabs {
  453. border-bottom: 1px solid #f1f5f9;
  454. }
  455. .scheme-d .ui-tab.is-on {
  456. color: var(--brand-ink);
  457. }
  458. .scheme-d .ui-tab.is-on::after {
  459. content: "";
  460. position: absolute;
  461. left: 14px; right: 14px; bottom: 0;
  462. height: 2px;
  463. background: var(--brand);
  464. border-radius: 2px 2px 0 0;
  465. }
  466. .phone-list {
  467. padding: 12px 14px 20px;
  468. }
  469. .phone-card {
  470. display: flex;
  471. align-items: center;
  472. gap: 10px;
  473. padding: 12px;
  474. border: 1px solid #e8eaed;
  475. border-radius: 12px;
  476. margin-bottom: 10px;
  477. background: #fff;
  478. }
  479. .phone-card .ico {
  480. width: 36px; height: 36px;
  481. border-radius: 8px;
  482. background: #f1f5f9;
  483. display: grid; place-items: center;
  484. font-size: 12px; font-weight: 700; color: #64748b;
  485. }
  486. .phone-card .name { font-size: 14px; font-weight: 650; }
  487. .phone-card .meta { font-size: 12px; color: #94a3b8; margin-top: 2px; }
  488. .phone-card .go {
  489. margin-left: auto;
  490. font-size: 12px;
  491. font-weight: 650;
  492. color: #fff;
  493. background: var(--brand);
  494. border-radius: 8px;
  495. padding: 6px 10px;
  496. }
  497. .token-list {
  498. display: grid;
  499. gap: 6px;
  500. margin-top: 10px;
  501. font-size: 12px;
  502. color: #64748b;
  503. }
  504. .token-list code {
  505. font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  506. background: #f8fafc;
  507. border: 1px solid #eef2f7;
  508. border-radius: 6px;
  509. padding: 1px 5px;
  510. color: #0f766e;
  511. }
  512. .compare {
  513. margin-top: 22px;
  514. display: grid;
  515. grid-template-columns: repeat(4, 1fr);
  516. gap: 12px;
  517. }
  518. @media (max-width: 1100px) {
  519. .compare { grid-template-columns: repeat(2, 1fr); }
  520. }
  521. @media (max-width: 640px) {
  522. .compare { grid-template-columns: 1fr; }
  523. }
  524. .compare .mini {
  525. background: #fff;
  526. border: 1px solid var(--line);
  527. border-radius: 14px;
  528. padding: 12px;
  529. cursor: pointer;
  530. transition: border-color .15s, box-shadow .15s;
  531. }
  532. .compare .mini:hover,
  533. .compare .mini.is-on {
  534. border-color: #94a3b8;
  535. box-shadow: 0 0 0 3px rgba(148,163,184,.2);
  536. }
  537. .compare .mini h3 {
  538. margin: 0 0 8px;
  539. font-size: 13px;
  540. }
  541. </style>
  542. </head>
  543. <body>
  544. <div class="wrap">
  545. <h1>控件样式 Demo · 输入 / 下拉 / Tag / Tab</h1>
  546. <p class="sub">
  547. 针对「发起审批」顶部检索与分类,以及表单里常见的下拉选择。以下 4 套均为中性清爽风格,主色只作轻量点缀(或完全不用)。
  548. 点选方案后,右侧手机预览会同步切换。
  549. </p>
  550. <div class="note">
  551. 文件路径:<strong>mobile-ui/zkxw-mobile-control-style-demos.html</strong>
  552. · 本地直接打开即可。选定后告诉我方案代号(A/B/C/D),再落到工程。
  553. </div>
  554. <div class="scheme-tabs" id="schemeTabs"></div>
  555. <div class="grid">
  556. <aside class="panel" id="specPanel">
  557. <h2 id="schemeTitle">—</h2>
  558. <p class="desc" id="schemeDesc">—</p>
  559. <div class="block">
  560. <div class="block-label">1. 搜索输入框</div>
  561. <div id="demoSearch"></div>
  562. </div>
  563. <div class="block">
  564. <div class="block-label">2. 下拉框</div>
  565. <div id="demoSelect"></div>
  566. </div>
  567. <div class="block">
  568. <div class="block-label">3. 下拉框(内嵌检索)</div>
  569. <div id="demoSelectSearch"></div>
  570. </div>
  571. <div class="block">
  572. <div class="block-label">4. Tag 标签</div>
  573. <div id="demoTags"></div>
  574. </div>
  575. <div class="block">
  576. <div class="block-label">5. Tab 栏</div>
  577. <div id="demoTabs"></div>
  578. </div>
  579. <div class="token-list" id="tokenList"></div>
  580. </aside>
  581. <section>
  582. <div class="phone">
  583. <div class="phone-screen" id="phoneRoot">
  584. <div class="phone-nav">发起审批</div>
  585. <div class="preview-pad" id="phoneToolbar"></div>
  586. <div class="phone-list">
  587. <div class="phone-card">
  588. <div class="ico">假</div>
  589. <div>
  590. <div class="name">请假申请</div>
  591. <div class="meta">人事 · 通用</div>
  592. </div>
  593. <div class="go">发起</div>
  594. </div>
  595. <div class="phone-card">
  596. <div class="ico">差</div>
  597. <div>
  598. <div class="name">出差申请</div>
  599. <div class="meta">行政</div>
  600. </div>
  601. <div class="go">发起</div>
  602. </div>
  603. <div class="phone-card">
  604. <div class="ico">报</div>
  605. <div>
  606. <div class="name">费用报销</div>
  607. <div class="meta">财务</div>
  608. </div>
  609. <div class="go">发起</div>
  610. </div>
  611. </div>
  612. </div>
  613. </div>
  614. </section>
  615. </div>
  616. <div class="compare" id="compare"></div>
  617. </div>
  618. <script>
  619. const SCHEMES = {
  620. a: {
  621. id: 'a',
  622. name: 'A · 极简线框',
  623. desc: '白底细线框,Tab 用下划线。最克制,适合信息密度高的列表页。完全不用主题绿。',
  624. tokens: [
  625. '搜索:白底 + #e5e7eb 描边,圆角 10',
  626. '下拉:同搜索线框;展开层白底阴影',
  627. 'Tag:线框方圆角;选中加深描边',
  628. 'Tab:文字 + 黑色下划线'
  629. ]
  630. },
  631. b: {
  632. id: 'b',
  633. name: 'B · 浅灰填充',
  634. desc: '控件用浅灰底、无描边。Tag / Tab 都是胶囊。柔和,但选中对比偏弱。',
  635. tokens: [
  636. '搜索 / 下拉:#f3f4f6 填充,圆角 12',
  637. '内嵌检索:同浅灰底',
  638. 'Tag / Tab:灰色胶囊,选中更深灰'
  639. ]
  640. },
  641. c: {
  642. id: 'c',
  643. name: 'C · 分段控件',
  644. desc: '搜索做成胶囊;分类用 iOS 分段控件。适合分类少(≤5)的场景。',
  645. tokens: [
  646. '搜索:圆角胶囊 #f5f5f7',
  647. 'Tag:选中黑底白字',
  648. 'Tab:整条灰底,选中白块浮起'
  649. ]
  650. },
  651. d: {
  652. id: 'd',
  653. name: 'D · 清爽描边',
  654. badge: '推荐',
  655. desc: '浅灰底 + 细描边,聚焦时微阴影。Tag/Tab 选中才用极浅绿点缀,主色不抢戏。最适合当前「明亮但不花」目标。',
  656. tokens: [
  657. '搜索:#fafafa + #e8eaed 边,聚焦提亮',
  658. '下拉检索:同风格;选中项浅绿底',
  659. 'Tag:白底描边胶囊;选中浅绿',
  660. 'Tab:下划线用主色,文字深绿'
  661. ]
  662. }
  663. }
  664. const GROUPS = ['全部', '人事', '行政', '财务', '其他']
  665. const OPTIONS = ['请假申请', '出差申请', '加班申请', '费用报销', '用章申请', '采购申请']
  666. let active = 'd'
  667. let tabOn = 0
  668. let tagOn = 0
  669. let selectVal = ''
  670. let selectSearchVal = ''
  671. let openSelect = '' // 'plain' | 'search' | ''
  672. function el(html) {
  673. const t = document.createElement('template')
  674. t.innerHTML = html.trim()
  675. return t.content.firstElementChild
  676. }
  677. function renderSchemeTabs() {
  678. const root = document.getElementById('schemeTabs')
  679. root.innerHTML = ''
  680. Object.values(SCHEMES).forEach((s) => {
  681. const b = el(`<button class="scheme-tab ${s.id === active ? 'is-on' : ''}" type="button">
  682. ${s.name}${s.badge ? `<span class="rec">${s.badge}</span>` : ''}
  683. </button>`)
  684. b.onclick = () => { active = s.id; openSelect = ''; renderAll() }
  685. root.appendChild(b)
  686. })
  687. }
  688. function renderCompare() {
  689. const root = document.getElementById('compare')
  690. root.innerHTML = ''
  691. Object.values(SCHEMES).forEach((s) => {
  692. const card = el(`<div class="mini ${s.id === active ? 'is-on' : ''} scheme-${s.id}" data-choice="${s.id}">
  693. <h3>${s.name}</h3>
  694. <div class="ui-search" style="margin-bottom:8px"><span class="ui-ico">⌕</span><input placeholder="搜索审批单" readonly /></div>
  695. <div class="ui-tags" style="margin-bottom:8px">
  696. <button class="ui-tag is-on" type="button">全部</button>
  697. <button class="ui-tag" type="button">人事</button>
  698. </div>
  699. <div class="ui-tabs">
  700. <button class="ui-tab is-on" type="button">全部</button>
  701. <button class="ui-tab" type="button">人事</button>
  702. <button class="ui-tab" type="button">行政</button>
  703. </div>
  704. </div>`)
  705. card.onclick = () => { active = s.id; openSelect = ''; renderAll() }
  706. root.appendChild(card)
  707. })
  708. }
  709. function searchHtml() {
  710. return `<div class="ui-search"><span class="ui-ico">⌕</span><input placeholder="搜索审批单" /><span class="ui-ico">×</span></div>`
  711. }
  712. function selectHtml(kind) {
  713. const isOpen = openSelect === kind
  714. const val = kind === 'plain' ? selectVal : selectSearchVal
  715. const label = val || '<span class="ph">请选择审批类型</span>'
  716. const items = OPTIONS.map((o) =>
  717. `<div class="dd-item ${o === val ? 'is-on' : ''}" data-v="${o}">${o}</div>`
  718. ).join('')
  719. const searchBar = kind === 'search'
  720. ? `<div class="dd-search"><span class="ui-ico">⌕</span><input placeholder="输入关键词筛选" /></div>`
  721. : ''
  722. return `<div class="ui-select ${isOpen ? 'open' : ''}" data-kind="${kind}">
  723. <button type="button" class="ui-select-trigger"><span>${label}</span><span class="chev">▾</span></button>
  724. <div class="ui-dropdown">${searchBar}<div class="dd-list">${items}</div></div>
  725. </div>`
  726. }
  727. function tagsHtml() {
  728. return `<div class="ui-tags">${GROUPS.map((g, i) =>
  729. `<button type="button" class="ui-tag ${i === tagOn ? 'is-on' : ''}" data-i="${i}">${g}</button>`
  730. ).join('')}</div>`
  731. }
  732. function tabsHtml() {
  733. return `<div class="ui-tabs">${GROUPS.map((g, i) =>
  734. `<button type="button" class="ui-tab ${i === tabOn ? 'is-on' : ''}" data-i="${i}">${g}</button>`
  735. ).join('')}</div>`
  736. }
  737. function bindSelect(root) {
  738. root.querySelectorAll('.ui-select').forEach((box) => {
  739. const kind = box.dataset.kind
  740. box.querySelector('.ui-select-trigger').onclick = (e) => {
  741. e.stopPropagation()
  742. openSelect = openSelect === kind ? '' : kind
  743. renderAll()
  744. }
  745. box.querySelectorAll('.dd-item').forEach((item) => {
  746. item.onclick = (e) => {
  747. e.stopPropagation()
  748. if (kind === 'plain') selectVal = item.dataset.v
  749. else selectSearchVal = item.dataset.v
  750. openSelect = ''
  751. renderAll()
  752. }
  753. })
  754. })
  755. }
  756. function bindTagsTabs(root) {
  757. root.querySelectorAll('.ui-tag').forEach((t) => {
  758. t.onclick = () => { tagOn = Number(t.dataset.i); renderAll() }
  759. })
  760. root.querySelectorAll('.ui-tab').forEach((t) => {
  761. t.onclick = () => { tabOn = Number(t.dataset.i); renderAll() }
  762. })
  763. }
  764. function renderAll() {
  765. const s = SCHEMES[active]
  766. document.getElementById('schemeTitle').textContent = s.name
  767. document.getElementById('schemeDesc').textContent = s.desc
  768. document.getElementById('tokenList').innerHTML = s.tokens.map((t) => `<div>· ${t}</div>`).join('')
  769. const panel = document.getElementById('specPanel')
  770. panel.className = `panel scheme-${active}`
  771. document.getElementById('demoSearch').innerHTML = searchHtml()
  772. document.getElementById('demoSelect').innerHTML = selectHtml('plain')
  773. document.getElementById('demoSelectSearch').innerHTML = selectHtml('search')
  774. document.getElementById('demoTags').innerHTML = tagsHtml()
  775. document.getElementById('demoTabs').innerHTML = tabsHtml()
  776. bindSelect(panel)
  777. bindTagsTabs(panel)
  778. const phone = document.getElementById('phoneRoot')
  779. phone.className = `phone-screen scheme-${active}`
  780. document.getElementById('phoneToolbar').innerHTML =
  781. `<div class="preview-stack">${searchHtml()}${tabsHtml()}</div>`
  782. bindTagsTabs(document.getElementById('phoneToolbar'))
  783. renderSchemeTabs()
  784. renderCompare()
  785. }
  786. document.addEventListener('click', () => {
  787. if (!openSelect) return
  788. openSelect = ''
  789. renderAll()
  790. })
  791. renderAll()
  792. </script>
  793. </body>
  794. </html>