zkxw-mobile-oa-detail-actions-demo.html 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  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>待办详情 · 底栏操作折叠 & 默认备注 Demo</title>
  7. <style>
  8. :root {
  9. --ink: #0f172a;
  10. --muted: #64748b;
  11. --line: #e2e8f0;
  12. --brand: #22c55e;
  13. --brand-dark: #16a34a;
  14. --brand-deep: #15803d;
  15. --brand-soft: #ecfdf5;
  16. --danger: #ef4444;
  17. --warn: #d97706;
  18. }
  19. * { box-sizing: border-box; }
  20. html, body { margin: 0; min-height: 100%; }
  21. body {
  22. font-family: "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", -apple-system, system-ui, sans-serif;
  23. color: var(--ink);
  24. background:
  25. radial-gradient(900px 420px at 8% -8%, rgba(52, 211, 153, 0.16), transparent 55%),
  26. radial-gradient(700px 380px at 92% 0%, rgba(148, 163, 184, 0.14), transparent 50%),
  27. linear-gradient(180deg, #f8fffc 0%, #f4f7fb 50%, #f7f8fa 100%);
  28. line-height: 1.45;
  29. }
  30. .wrap {
  31. max-width: 1180px;
  32. margin: 0 auto;
  33. padding: 28px 18px 96px;
  34. }
  35. h1 {
  36. margin: 0 0 8px;
  37. font-size: 24px;
  38. letter-spacing: -0.02em;
  39. font-weight: 720;
  40. }
  41. .sub {
  42. margin: 0;
  43. max-width: 760px;
  44. color: var(--muted);
  45. font-size: 14px;
  46. }
  47. .note {
  48. margin: 14px 0 22px;
  49. padding: 10px 12px;
  50. border-radius: 10px;
  51. background: #fff;
  52. border: 1px solid var(--line);
  53. font-size: 13px;
  54. color: var(--muted);
  55. }
  56. .note strong { color: var(--brand-deep); }
  57. .layout {
  58. display: grid;
  59. grid-template-columns: 1fr minmax(300px, 360px);
  60. gap: 22px;
  61. align-items: start;
  62. }
  63. @media (max-width: 920px) {
  64. .layout { grid-template-columns: 1fr; }
  65. }
  66. .schemes { display: grid; gap: 12px; }
  67. .scheme {
  68. appearance: none;
  69. width: 100%;
  70. text-align: left;
  71. border: 1px solid var(--line);
  72. background: #fff;
  73. border-radius: 16px;
  74. padding: 14px 16px;
  75. cursor: pointer;
  76. transition: border-color .15s, box-shadow .15s, background .15s;
  77. font-family: inherit;
  78. color: inherit;
  79. }
  80. .scheme:hover {
  81. border-color: #cbd5e1;
  82. box-shadow: 0 8px 24px rgba(15, 23, 42, 0.05);
  83. }
  84. .scheme.is-on {
  85. border-color: var(--brand);
  86. background: linear-gradient(180deg, #f0fdf4, #fff);
  87. box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
  88. }
  89. .scheme-top {
  90. display: flex;
  91. align-items: center;
  92. gap: 10px;
  93. margin-bottom: 6px;
  94. }
  95. .letter {
  96. width: 28px; height: 28px;
  97. border-radius: 8px;
  98. display: grid; place-items: center;
  99. font-size: 13px; font-weight: 750;
  100. background: #f1f5f9;
  101. color: #334155;
  102. flex-shrink: 0;
  103. }
  104. .scheme.is-on .letter {
  105. background: var(--brand-soft);
  106. color: var(--brand-deep);
  107. }
  108. .scheme h2 {
  109. margin: 0;
  110. font-size: 15px;
  111. font-weight: 680;
  112. }
  113. .scheme p {
  114. margin: 0 0 10px;
  115. font-size: 12.5px;
  116. color: var(--muted);
  117. padding-left: 38px;
  118. }
  119. .tag {
  120. display: inline-block;
  121. margin: 0 0 0 38px;
  122. padding: 2px 8px;
  123. border-radius: 999px;
  124. font-size: 11px;
  125. font-weight: 650;
  126. background: #f1f5f9;
  127. color: #475569;
  128. }
  129. .scheme.is-on .tag {
  130. background: var(--brand-soft);
  131. color: var(--brand-deep);
  132. }
  133. .tag.rec {
  134. background: #ecfdf5;
  135. color: #047857;
  136. }
  137. .defaults {
  138. margin-top: 18px;
  139. padding: 14px 16px;
  140. border-radius: 16px;
  141. background: #fff;
  142. border: 1px solid var(--line);
  143. }
  144. .defaults h3 {
  145. margin: 0 0 10px;
  146. font-size: 14px;
  147. font-weight: 680;
  148. }
  149. .defaults table {
  150. width: 100%;
  151. border-collapse: collapse;
  152. font-size: 12.5px;
  153. }
  154. .defaults th,
  155. .defaults td {
  156. text-align: left;
  157. padding: 8px 6px;
  158. border-bottom: 1px solid #f1f5f9;
  159. vertical-align: top;
  160. }
  161. .defaults th {
  162. color: var(--muted);
  163. font-weight: 600;
  164. font-size: 11px;
  165. }
  166. .defaults code {
  167. display: inline-block;
  168. padding: 2px 6px;
  169. border-radius: 6px;
  170. background: #f8fafc;
  171. border: 1px solid #e2e8f0;
  172. font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  173. font-size: 12px;
  174. color: #0f172a;
  175. }
  176. .defaults .hint {
  177. margin: 10px 0 0;
  178. font-size: 12px;
  179. color: var(--muted);
  180. }
  181. /* —— Phone preview —— */
  182. .preview-col { position: sticky; top: 16px; }
  183. .phone {
  184. width: 100%;
  185. max-width: 360px;
  186. margin: 0 auto;
  187. border-radius: 28px;
  188. border: 1px solid #cbd5e1;
  189. background: #0f172a;
  190. padding: 10px;
  191. box-shadow: 0 24px 60px rgba(15, 23, 42, 0.18);
  192. }
  193. .phone-inner {
  194. position: relative;
  195. height: 640px;
  196. border-radius: 20px;
  197. overflow: hidden;
  198. background: #fff;
  199. display: flex;
  200. flex-direction: column;
  201. }
  202. .status-bar {
  203. display: flex;
  204. justify-content: space-between;
  205. padding: 8px 16px 4px;
  206. font-size: 11px;
  207. font-weight: 650;
  208. color: #334155;
  209. background: #fff;
  210. }
  211. .nav {
  212. display: flex;
  213. align-items: center;
  214. gap: 8px;
  215. padding: 8px 12px 10px;
  216. border-bottom: 1px solid #f1f5f9;
  217. background: #fff;
  218. }
  219. .nav .back {
  220. width: 28px; height: 28px;
  221. border-radius: 8px;
  222. display: grid; place-items: center;
  223. background: #f1f5f9;
  224. color: #475569;
  225. font-size: 14px;
  226. }
  227. .nav h4 {
  228. margin: 0;
  229. font-size: 15px;
  230. font-weight: 700;
  231. }
  232. .scroll {
  233. flex: 1;
  234. overflow: auto;
  235. padding: 12px 12px 108px;
  236. background: #f8fafc;
  237. -webkit-overflow-scrolling: touch;
  238. }
  239. .card {
  240. background: #fff;
  241. border-radius: 14px;
  242. border: 1px solid #eef2f6;
  243. padding: 12px 14px;
  244. margin-bottom: 10px;
  245. }
  246. .card .title-row {
  247. display: flex;
  248. align-items: center;
  249. gap: 8px;
  250. margin-bottom: 10px;
  251. }
  252. .card h5 {
  253. margin: 0;
  254. font-size: 15px;
  255. font-weight: 700;
  256. }
  257. .pill {
  258. padding: 2px 8px;
  259. border-radius: 6px;
  260. font-size: 11px;
  261. font-weight: 650;
  262. color: var(--warn);
  263. background: #fffbeb;
  264. }
  265. .field {
  266. display: flex;
  267. justify-content: space-between;
  268. gap: 12px;
  269. padding: 8px 0;
  270. border-top: 1px solid #f1f5f9;
  271. font-size: 13px;
  272. }
  273. .field:first-of-type { border-top: none; }
  274. .field .k { color: var(--muted); }
  275. .field .v { color: #0f172a; font-weight: 560; text-align: right; }
  276. .timeline {
  277. font-size: 12.5px;
  278. color: #334155;
  279. }
  280. .timeline .item {
  281. display: grid;
  282. grid-template-columns: 14px 1fr;
  283. gap: 8px;
  284. margin-bottom: 10px;
  285. }
  286. .dot {
  287. width: 10px; height: 10px;
  288. margin-top: 4px;
  289. border-radius: 50%;
  290. background: var(--brand);
  291. box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
  292. }
  293. .dot.mute { background: #94a3b8; box-shadow: none; }
  294. .timeline .t { font-weight: 650; }
  295. .timeline .m { color: var(--muted); margin-top: 2px; }
  296. .ft {
  297. position: absolute;
  298. left: 0; right: 0; bottom: 0;
  299. z-index: 5;
  300. display: flex;
  301. align-items: center;
  302. gap: 8px;
  303. padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
  304. background: #fff;
  305. border-top: 1px solid #eef2f6;
  306. box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.04);
  307. }
  308. .btn {
  309. appearance: none;
  310. border: none;
  311. height: 40px;
  312. border-radius: 12px;
  313. font: inherit;
  314. font-size: 14px;
  315. font-weight: 650;
  316. cursor: pointer;
  317. padding: 0 12px;
  318. transition: opacity .12s, transform .12s;
  319. }
  320. .btn:active { opacity: 0.88; transform: scale(0.98); }
  321. .btn-close {
  322. flex: 0 0 auto;
  323. min-width: 56px;
  324. background: #f1f5f9;
  325. color: #334155;
  326. }
  327. .btn-ghost {
  328. flex: 1 1 0;
  329. min-width: 0;
  330. border: 1px solid #e2e8f0;
  331. background: #fff;
  332. color: #334155;
  333. }
  334. .btn-primary {
  335. flex: 1.2 1 0;
  336. min-width: 0;
  337. background: var(--brand);
  338. color: #fff;
  339. font-weight: 750;
  340. }
  341. .btn-more {
  342. flex: 0 0 auto;
  343. width: 44px;
  344. padding: 0;
  345. border: 1px solid #e2e8f0;
  346. background: #fff;
  347. color: #334155;
  348. font-size: 18px;
  349. letter-spacing: 1px;
  350. line-height: 1;
  351. }
  352. .btn-more.wide {
  353. width: auto;
  354. min-width: 72px;
  355. font-size: 14px;
  356. letter-spacing: 0;
  357. }
  358. /* before (crowded) */
  359. .ft.before .btn {
  360. flex: 1 1 auto;
  361. min-width: 0;
  362. padding: 0 6px;
  363. font-size: 12px;
  364. }
  365. .ft.before .btn-primary { flex: 1.1 1 auto; }
  366. /* overlay */
  367. .mask {
  368. position: absolute;
  369. inset: 0;
  370. background: rgba(15, 23, 42, 0.42);
  371. z-index: 10;
  372. opacity: 0;
  373. pointer-events: none;
  374. transition: opacity .18s;
  375. }
  376. .mask.is-on {
  377. opacity: 1;
  378. pointer-events: auto;
  379. }
  380. .sheet {
  381. position: absolute;
  382. left: 0; right: 0; bottom: 0;
  383. z-index: 12;
  384. background: #fff;
  385. border-radius: 18px 18px 0 0;
  386. transform: translateY(110%);
  387. transition: transform .22s ease;
  388. padding: 8px 0 calc(12px + env(safe-area-inset-bottom, 0px));
  389. box-shadow: 0 -8px 28px rgba(15, 23, 42, 0.12);
  390. }
  391. .sheet.is-on { transform: translateY(0); }
  392. .sheet .handle {
  393. width: 36px; height: 4px;
  394. border-radius: 999px;
  395. background: #e2e8f0;
  396. margin: 4px auto 10px;
  397. }
  398. .sheet .row {
  399. display: flex;
  400. align-items: center;
  401. gap: 12px;
  402. width: 100%;
  403. padding: 14px 18px;
  404. border: none;
  405. background: transparent;
  406. font: inherit;
  407. font-size: 15px;
  408. font-weight: 600;
  409. color: #0f172a;
  410. cursor: pointer;
  411. text-align: left;
  412. }
  413. .sheet .row:active { background: #f8fafc; }
  414. .sheet .row .ico {
  415. width: 32px; height: 32px;
  416. border-radius: 10px;
  417. display: grid; place-items: center;
  418. background: #f1f5f9;
  419. color: #475569;
  420. font-size: 14px;
  421. }
  422. .sheet .row.danger { color: var(--danger); }
  423. .sheet .row.danger .ico { background: #fef2f2; color: var(--danger); }
  424. .sheet .cancel {
  425. margin: 8px 12px 0;
  426. height: 44px;
  427. border-radius: 12px;
  428. border: none;
  429. background: #f1f5f9;
  430. font: inherit;
  431. font-size: 15px;
  432. font-weight: 650;
  433. color: #334155;
  434. cursor: pointer;
  435. width: calc(100% - 24px);
  436. }
  437. .popover {
  438. position: absolute;
  439. right: 12px;
  440. bottom: 62px;
  441. z-index: 12;
  442. min-width: 148px;
  443. background: #fff;
  444. border: 1px solid #e2e8f0;
  445. border-radius: 12px;
  446. box-shadow: 0 12px 32px rgba(15, 23, 42, 0.14);
  447. padding: 6px;
  448. opacity: 0;
  449. pointer-events: none;
  450. transform: translateY(6px);
  451. transition: opacity .15s, transform .15s;
  452. }
  453. .popover.is-on {
  454. opacity: 1;
  455. pointer-events: auto;
  456. transform: translateY(0);
  457. }
  458. .popover button {
  459. display: block;
  460. width: 100%;
  461. text-align: left;
  462. border: none;
  463. background: transparent;
  464. padding: 10px 12px;
  465. border-radius: 8px;
  466. font: inherit;
  467. font-size: 13.5px;
  468. font-weight: 600;
  469. color: #0f172a;
  470. cursor: pointer;
  471. }
  472. .popover button:active { background: #f8fafc; }
  473. .popover button.danger { color: var(--danger); }
  474. .dialog {
  475. position: absolute;
  476. left: 50%;
  477. top: 42%;
  478. z-index: 14;
  479. width: calc(100% - 48px);
  480. max-width: 300px;
  481. transform: translate(-50%, -50%) scale(0.96);
  482. background: #fff;
  483. border-radius: 16px;
  484. padding: 16px 16px 12px;
  485. box-shadow: 0 20px 48px rgba(15, 23, 42, 0.2);
  486. opacity: 0;
  487. pointer-events: none;
  488. transition: opacity .15s, transform .15s;
  489. }
  490. .dialog.is-on {
  491. opacity: 1;
  492. pointer-events: auto;
  493. transform: translate(-50%, -50%) scale(1);
  494. }
  495. .dialog h3 {
  496. margin: 0 0 10px;
  497. text-align: center;
  498. font-size: 16px;
  499. font-weight: 720;
  500. }
  501. .dialog textarea {
  502. width: 100%;
  503. min-height: 84px;
  504. resize: none;
  505. border: 1px solid #e2e8f0;
  506. border-radius: 10px;
  507. padding: 10px 12px;
  508. font: inherit;
  509. font-size: 14px;
  510. color: #0f172a;
  511. background: #f8fafc;
  512. outline: none;
  513. }
  514. .dialog textarea:focus {
  515. border-color: #86efac;
  516. box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15);
  517. background: #fff;
  518. }
  519. .dialog .tip {
  520. margin: 8px 0 0;
  521. font-size: 11.5px;
  522. color: var(--muted);
  523. }
  524. .dialog .acts {
  525. display: grid;
  526. grid-template-columns: 1fr 1fr;
  527. margin-top: 12px;
  528. border-top: 1px solid #f1f5f9;
  529. }
  530. .dialog .acts button {
  531. border: none;
  532. background: transparent;
  533. height: 42px;
  534. font: inherit;
  535. font-size: 15px;
  536. font-weight: 650;
  537. cursor: pointer;
  538. }
  539. .dialog .acts .cancel { color: #64748b; border-right: 1px solid #f1f5f9; }
  540. .dialog .acts .ok { color: var(--brand-deep); font-weight: 750; }
  541. .toast {
  542. position: absolute;
  543. left: 50%;
  544. bottom: 88px;
  545. z-index: 20;
  546. transform: translateX(-50%) translateY(8px);
  547. background: rgba(15, 23, 42, 0.88);
  548. color: #fff;
  549. padding: 8px 14px;
  550. border-radius: 999px;
  551. font-size: 12.5px;
  552. font-weight: 600;
  553. opacity: 0;
  554. pointer-events: none;
  555. transition: opacity .18s, transform .18s;
  556. white-space: nowrap;
  557. max-width: 90%;
  558. overflow: hidden;
  559. text-overflow: ellipsis;
  560. }
  561. .toast.is-on {
  562. opacity: 1;
  563. transform: translateX(-50%) translateY(0);
  564. }
  565. .actions {
  566. margin-top: 14px;
  567. display: flex;
  568. flex-wrap: wrap;
  569. gap: 8px;
  570. justify-content: center;
  571. }
  572. .chip {
  573. appearance: none;
  574. border: 1px solid var(--line);
  575. background: #fff;
  576. border-radius: 999px;
  577. padding: 8px 12px;
  578. font: inherit;
  579. font-size: 12px;
  580. font-weight: 650;
  581. color: #475569;
  582. cursor: pointer;
  583. }
  584. .chip:hover { border-color: #94a3b8; }
  585. </style>
  586. </head>
  587. <body>
  588. <div class="wrap">
  589. <h1>待办详情 · 底栏操作折叠 & 默认备注</h1>
  590. <p class="sub">
  591. 对齐现网 <code>mobile-oa-h5</code> 审批详情底栏:默认只露出「同意 / 打回」,其余收入「更多」;
  592. 需要意见的操作弹窗预填默认备注,用户可改。
  593. </p>
  594. <div class="note">
  595. <strong>推荐方案 A</strong>:底栏「⋯ / 打回 / 同意」;更多用底部 ActionSheet。
  596. 右侧手机可点击交互。默认备注见下方表格,可在弹窗内修改。
  597. </div>
  598. <div class="layout">
  599. <div>
  600. <div class="schemes" id="schemes">
  601. <button type="button" class="scheme is-on" data-scheme="A">
  602. <div class="scheme-top">
  603. <span class="letter">A</span>
  604. <h2>⋯ + 打回 + 同意(推荐)</h2>
  605. </div>
  606. <p>主路径最清晰:主操作同意、次操作打回常显;拒绝 / 加签 / 转交 / 打印 / 关闭进更多(底部 ActionSheet)。</p>
  607. <span class="tag rec">推荐 · 对齐「默认只展示同意、打回」</span>
  608. </button>
  609. <button type="button" class="scheme" data-scheme="B">
  610. <div class="scheme-top">
  611. <span class="letter">B</span>
  612. <h2>关闭常显 + ⋯ + 打回 + 同意</h2>
  613. </div>
  614. <p>保留「关闭」快捷退出;更多里放拒绝 / 加签 / 转交 / 打印。比 A 多一颗常驻按钮。</p>
  615. <span class="tag">可选 · 退出路径更短</span>
  616. </button>
  617. <button type="button" class="scheme" data-scheme="C">
  618. <div class="scheme-top">
  619. <span class="letter">C</span>
  620. <h2>⋯文字「更多」+ 气泡菜单</h2>
  621. </div>
  622. <p>底栏文案「更多」替代三点;点击后右下角 Popover 列出次要操作,不遮挡整屏。</p>
  623. <span class="tag">可选 · 轻量气泡</span>
  624. </button>
  625. <button type="button" class="scheme" data-scheme="before">
  626. <div class="scheme-top">
  627. <span class="letter">旧</span>
  628. <h2>现状:全部平铺</h2>
  629. </div>
  630. <p>关闭 / 打印 / 拒绝 / 打回 / 加签 / 转交 / 同意 一排挤开,字号被迫缩小。</p>
  631. <span class="tag">对比</span>
  632. </button>
  633. </div>
  634. <div class="defaults">
  635. <h3>默认备注文案(弹窗预填,可改)</h3>
  636. <table>
  637. <thead>
  638. <tr><th>操作</th><th>默认备注</th><th>说明</th></tr>
  639. </thead>
  640. <tbody>
  641. <tr><td>同意</td><td><code>同意</code></td><td>意见可选,预填后可直接确定</td></tr>
  642. <tr><td>打回</td><td><code>请修改后重新提交</code></td><td>意见必填;预填满足必填,可改</td></tr>
  643. <tr><td>拒绝</td><td><code>拒绝</code></td><td>意见可选</td></tr>
  644. <tr><td>加签</td><td><code>加签</code></td><td>说明可选;选人后仍可改备注</td></tr>
  645. <tr><td>转交</td><td><code>转交</code></td><td>说明可选;选人后仍可改备注</td></tr>
  646. </tbody>
  647. </table>
  648. <p class="hint">点右侧「同意 / 打回 / 拒绝」可看预填弹窗;加签 / 转交在本 Demo 仅 Toast 示意(真实页仍为选人面板 + 预填说明)。</p>
  649. </div>
  650. </div>
  651. <div class="preview-col">
  652. <div class="phone">
  653. <div class="phone-inner" id="phone">
  654. <div class="status-bar"><span>9:41</span><span>●●●</span></div>
  655. <div class="nav">
  656. <span class="back">‹</span>
  657. <h4>审批详情</h4>
  658. </div>
  659. <div class="scroll">
  660. <div class="card">
  661. <div class="title-row">
  662. <h5>请假申请 · 张三</h5>
  663. <span class="pill">处理中</span>
  664. </div>
  665. <div class="field"><span class="k">请假类型</span><span class="v">年假</span></div>
  666. <div class="field"><span class="k">开始时间</span><span class="v">2026-08-18 09:00</span></div>
  667. <div class="field"><span class="k">结束时间</span><span class="v">2026-08-19 18:00</span></div>
  668. <div class="field"><span class="k">时长</span><span class="v">2 天</span></div>
  669. </div>
  670. <div class="card timeline">
  671. <div class="item"><span class="dot"></span><div><div class="t">发起人 · 张三</div><div class="m">已提交 · 08-12 10:02</div></div></div>
  672. <div class="item"><span class="dot mute"></span><div><div class="t">审批人 · 我</div><div class="m">待处理</div></div></div>
  673. </div>
  674. </div>
  675. <footer class="ft" id="ft"></footer>
  676. <div class="mask" id="mask"></div>
  677. <div class="sheet" id="sheet">
  678. <div class="handle"></div>
  679. <div id="sheetRows"></div>
  680. <button type="button" class="cancel" id="sheetCancel">取消</button>
  681. </div>
  682. <div class="popover" id="popover"></div>
  683. <div class="dialog" id="dialog">
  684. <h3 id="dialogTitle">同意</h3>
  685. <textarea id="dialogText" maxlength="500"></textarea>
  686. <p class="tip" id="dialogTip">可修改默认备注后确定</p>
  687. <div class="acts">
  688. <button type="button" class="cancel" id="dialogCancel">取消</button>
  689. <button type="button" class="ok" id="dialogOk">确定</button>
  690. </div>
  691. </div>
  692. <div class="toast" id="toast"></div>
  693. </div>
  694. </div>
  695. <div class="actions">
  696. <button type="button" class="chip" data-demo="approve">演示 · 同意弹窗</button>
  697. <button type="button" class="chip" data-demo="return">演示 · 打回弹窗</button>
  698. <button type="button" class="chip" data-demo="reject">演示 · 拒绝弹窗</button>
  699. <button type="button" class="chip" data-demo="more">演示 · 打开更多</button>
  700. </div>
  701. </div>
  702. </div>
  703. </div>
  704. <script>
  705. const DEFAULTS = {
  706. approve: { title: '同意', text: '同意', tip: '意见可选,已预填默认备注', required: false },
  707. return: { title: '打回', text: '请修改后重新提交', tip: '意见必填;已预填,可直接确定或修改', required: true },
  708. reject: { title: '拒绝', text: '拒绝', tip: '意见可选,已预填默认备注', required: false },
  709. addSign: { title: '加签', text: '加签', tip: '说明可选', required: false },
  710. transfer: { title: '转交', text: '转交', tip: '说明可选', required: false }
  711. }
  712. const MORE_ITEMS = [
  713. { key: 'reject', label: '拒绝', danger: true, ico: '✕' },
  714. { key: 'addSign', label: '加签', ico: '+' },
  715. { key: 'transfer', label: '转交', ico: '⇄' },
  716. { key: 'print', label: '打印', ico: '🖨' },
  717. { key: 'close', label: '关闭', ico: '◁' }
  718. ]
  719. let scheme = 'A'
  720. let commentKind = 'approve'
  721. let toastTimer = null
  722. const ft = document.getElementById('ft')
  723. const mask = document.getElementById('mask')
  724. const sheet = document.getElementById('sheet')
  725. const sheetRows = document.getElementById('sheetRows')
  726. const popover = document.getElementById('popover')
  727. const dialog = document.getElementById('dialog')
  728. const dialogTitle = document.getElementById('dialogTitle')
  729. const dialogText = document.getElementById('dialogText')
  730. const dialogTip = document.getElementById('dialogTip')
  731. const toastEl = document.getElementById('toast')
  732. function toast(msg) {
  733. toastEl.textContent = msg
  734. toastEl.classList.add('is-on')
  735. clearTimeout(toastTimer)
  736. toastTimer = setTimeout(() => toastEl.classList.remove('is-on'), 1600)
  737. }
  738. function closeOverlays() {
  739. mask.classList.remove('is-on')
  740. sheet.classList.remove('is-on')
  741. popover.classList.remove('is-on')
  742. dialog.classList.remove('is-on')
  743. }
  744. function openComment(kind) {
  745. const cfg = DEFAULTS[kind]
  746. if (!cfg) return
  747. commentKind = kind
  748. closeOverlays()
  749. dialogTitle.textContent = cfg.title
  750. dialogText.value = cfg.text
  751. dialogTip.textContent = cfg.tip
  752. mask.classList.add('is-on')
  753. dialog.classList.add('is-on')
  754. setTimeout(() => {
  755. dialogText.focus()
  756. dialogText.select()
  757. }, 50)
  758. }
  759. function onMoreAction(key) {
  760. closeOverlays()
  761. if (key === 'reject') {
  762. openComment('reject')
  763. return
  764. }
  765. if (key === 'addSign') {
  766. toast('加签:选人面板 + 默认备注「加签」')
  767. return
  768. }
  769. if (key === 'transfer') {
  770. toast('转交:选人面板 + 默认备注「转交」')
  771. return
  772. }
  773. if (key === 'print') {
  774. toast('打印')
  775. return
  776. }
  777. if (key === 'close') {
  778. toast('关闭详情')
  779. }
  780. }
  781. function renderMoreContent(includeClose) {
  782. const items = MORE_ITEMS.filter((x) => includeClose || x.key !== 'close')
  783. sheetRows.innerHTML = items.map((it) => `
  784. <button type="button" class="row ${it.danger ? 'danger' : ''}" data-more="${it.key}">
  785. <span class="ico">${it.ico}</span>
  786. <span>${it.label}</span>
  787. </button>
  788. `).join('')
  789. popover.innerHTML = items.map((it) => `
  790. <button type="button" class="${it.danger ? 'danger' : ''}" data-more="${it.key}">${it.label}</button>
  791. `).join('')
  792. }
  793. function openMore() {
  794. dialog.classList.remove('is-on')
  795. if (scheme === 'C') {
  796. mask.classList.add('is-on')
  797. popover.classList.add('is-on')
  798. sheet.classList.remove('is-on')
  799. return
  800. }
  801. mask.classList.add('is-on')
  802. sheet.classList.add('is-on')
  803. popover.classList.remove('is-on')
  804. }
  805. function renderFooter() {
  806. closeOverlays()
  807. ft.className = 'ft'
  808. if (scheme === 'before') {
  809. ft.classList.add('before')
  810. ft.innerHTML = `
  811. <button type="button" class="btn btn-close" data-act="close">关闭</button>
  812. <button type="button" class="btn btn-ghost" data-act="print">打印</button>
  813. <button type="button" class="btn btn-ghost" data-act="reject">拒绝</button>
  814. <button type="button" class="btn btn-ghost" data-act="return">打回</button>
  815. <button type="button" class="btn btn-ghost" data-act="addSign">加签</button>
  816. <button type="button" class="btn btn-ghost" data-act="transfer">转交</button>
  817. <button type="button" class="btn btn-primary" data-act="approve">同意</button>
  818. `
  819. renderMoreContent(true)
  820. return
  821. }
  822. if (scheme === 'A') {
  823. ft.innerHTML = `
  824. <button type="button" class="btn btn-more" data-act="more" aria-label="更多">⋯</button>
  825. <button type="button" class="btn btn-ghost" data-act="return">打回</button>
  826. <button type="button" class="btn btn-primary" data-act="approve">同意</button>
  827. `
  828. renderMoreContent(true)
  829. return
  830. }
  831. if (scheme === 'B') {
  832. ft.innerHTML = `
  833. <button type="button" class="btn btn-close" data-act="close">关闭</button>
  834. <button type="button" class="btn btn-more" data-act="more" aria-label="更多">⋯</button>
  835. <button type="button" class="btn btn-ghost" data-act="return">打回</button>
  836. <button type="button" class="btn btn-primary" data-act="approve">同意</button>
  837. `
  838. renderMoreContent(false)
  839. return
  840. }
  841. if (scheme === 'C') {
  842. ft.innerHTML = `
  843. <button type="button" class="btn btn-more wide" data-act="more">更多</button>
  844. <button type="button" class="btn btn-ghost" data-act="return">打回</button>
  845. <button type="button" class="btn btn-primary" data-act="approve">同意</button>
  846. `
  847. renderMoreContent(true)
  848. }
  849. }
  850. document.getElementById('schemes').addEventListener('click', (e) => {
  851. const btn = e.target.closest('.scheme')
  852. if (!btn) return
  853. document.querySelectorAll('.scheme').forEach((el) => el.classList.toggle('is-on', el === btn))
  854. scheme = btn.dataset.scheme
  855. renderFooter()
  856. })
  857. ft.addEventListener('click', (e) => {
  858. const btn = e.target.closest('[data-act]')
  859. if (!btn) return
  860. const act = btn.dataset.act
  861. if (act === 'more') {
  862. openMore()
  863. return
  864. }
  865. if (act === 'approve' || act === 'return' || act === 'reject') {
  866. openComment(act)
  867. return
  868. }
  869. if (act === 'addSign') {
  870. toast('加签:选人面板 + 默认备注「加签」')
  871. return
  872. }
  873. if (act === 'transfer') {
  874. toast('转交:选人面板 + 默认备注「转交」')
  875. return
  876. }
  877. if (act === 'print') {
  878. toast('打印')
  879. return
  880. }
  881. if (act === 'close') {
  882. toast('关闭详情')
  883. }
  884. })
  885. sheetRows.addEventListener('click', (e) => {
  886. const btn = e.target.closest('[data-more]')
  887. if (!btn) return
  888. onMoreAction(btn.dataset.more)
  889. })
  890. popover.addEventListener('click', (e) => {
  891. const btn = e.target.closest('[data-more]')
  892. if (!btn) return
  893. onMoreAction(btn.dataset.more)
  894. })
  895. mask.addEventListener('click', closeOverlays)
  896. document.getElementById('sheetCancel').addEventListener('click', closeOverlays)
  897. document.getElementById('dialogCancel').addEventListener('click', closeOverlays)
  898. document.getElementById('dialogOk').addEventListener('click', () => {
  899. const cfg = DEFAULTS[commentKind]
  900. const text = String(dialogText.value || '').trim()
  901. if (cfg?.required && !text) {
  902. toast('请填写意见')
  903. return
  904. }
  905. closeOverlays()
  906. toast(`${cfg.title} · ${text || '(无备注)'}`)
  907. })
  908. document.querySelector('.actions').addEventListener('click', (e) => {
  909. const chip = e.target.closest('[data-demo]')
  910. if (!chip) return
  911. const d = chip.dataset.demo
  912. if (d === 'more') {
  913. if (scheme === 'before') {
  914. toast('请先切到方案 A/B/C')
  915. return
  916. }
  917. openMore()
  918. return
  919. }
  920. openComment(d)
  921. })
  922. renderFooter()
  923. </script>
  924. </body>
  925. </html>