zkxw-mobile-vant-ui-demo.html 107 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676
  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, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
  6. <title>中科兴皖 · 移动端 UI Demo(Vant)</title>
  7. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@4.9.21/lib/index.css" />
  8. <style>
  9. :root {
  10. --brand-green: #0a9f6d;
  11. --brand-green-dark: #087f57;
  12. --brand-green-soft: #e9f9f2;
  13. --barley-yellow: #d6a343;
  14. --barley-yellow-soft: #fff7e8;
  15. --bg-page: #f5f8f7;
  16. --surface: #ffffff;
  17. --text-primary: #1f2a26;
  18. --text-muted: #6b7b74;
  19. --border: #dcebe4;
  20. --danger: #d14343;
  21. --warning: #cf8a19;
  22. --success: #189b65;
  23. --radius-card: 14px;
  24. --radius-lg: 20px;
  25. --shadow-soft: 0 4px 16px rgba(8, 127, 87, 0.08);
  26. --van-primary-color: #0a9f6d;
  27. --van-success-color: #189b65;
  28. --van-warning-color: #cf8a19;
  29. --van-danger-color: #d14343;
  30. --van-border-color: #dcebe4;
  31. --van-background: #f5f8f7;
  32. --van-background-2: #ffffff;
  33. --van-text-color: #1f2a26;
  34. --van-text-color-2: #6b7b74;
  35. --van-button-primary-background: #0a9f6d;
  36. --van-button-primary-border-color: #0a9f6d;
  37. --van-button-radius: 10px;
  38. --van-cell-group-inset-padding: 12px;
  39. --van-cell-group-inset-radius: 14px;
  40. --van-dialog-confirm-button-text-color: #0a9f6d;
  41. --van-nav-bar-icon-color: #ffffff;
  42. --van-nav-bar-text-color: #ffffff;
  43. --van-nav-bar-title-text-color: #ffffff;
  44. --van-nav-bar-background: transparent;
  45. --van-tabbar-item-active-color: #0a9f6d;
  46. --van-tag-radius: 6px;
  47. }
  48. * { box-sizing: border-box; }
  49. html, body {
  50. margin: 0;
  51. padding: 0;
  52. min-height: 100%;
  53. font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
  54. color: var(--text-primary);
  55. background:
  56. radial-gradient(900px 420px at 10% -10%, rgba(10, 159, 109, 0.14), transparent 55%),
  57. radial-gradient(700px 380px at 90% 0%, rgba(214, 163, 67, 0.12), transparent 50%),
  58. linear-gradient(180deg, #eef6f2 0%, #f7faf9 40%, #f3f6f5 100%);
  59. }
  60. body {
  61. display: flex;
  62. justify-content: center;
  63. padding: 24px 16px 40px;
  64. }
  65. #app {
  66. width: 100%;
  67. max-width: 1120px;
  68. display: grid;
  69. grid-template-columns: 280px 1fr;
  70. gap: 24px;
  71. align-items: start;
  72. }
  73. .doc-panel {
  74. position: sticky;
  75. top: 24px;
  76. background: rgba(255, 255, 255, 0.92);
  77. border: 1px solid var(--border);
  78. border-radius: 18px;
  79. padding: 20px 18px;
  80. box-shadow: var(--shadow-soft);
  81. backdrop-filter: blur(8px);
  82. }
  83. .doc-panel h1 {
  84. margin: 0 0 6px;
  85. font-size: 20px;
  86. line-height: 1.3;
  87. color: var(--brand-green-dark);
  88. }
  89. .doc-panel .sub {
  90. margin: 0 0 16px;
  91. font-size: 12px;
  92. color: var(--text-muted);
  93. line-height: 1.5;
  94. }
  95. .doc-panel .section-title {
  96. margin: 16px 0 8px;
  97. font-size: 12px;
  98. font-weight: 600;
  99. color: var(--text-muted);
  100. letter-spacing: 0.04em;
  101. text-transform: uppercase;
  102. }
  103. .screen-nav {
  104. display: flex;
  105. flex-direction: column;
  106. gap: 8px;
  107. }
  108. .screen-nav button {
  109. appearance: none;
  110. border: 1px solid var(--border);
  111. background: #fff;
  112. border-radius: 12px;
  113. padding: 12px 14px;
  114. text-align: left;
  115. cursor: pointer;
  116. font-size: 14px;
  117. color: var(--text-primary);
  118. transition: border-color .2s, background .2s, color .2s;
  119. }
  120. .screen-nav button:hover {
  121. border-color: #a7e0c8;
  122. background: var(--brand-green-soft);
  123. }
  124. .screen-nav button.active {
  125. border-color: var(--brand-green);
  126. background: var(--brand-green-soft);
  127. color: var(--brand-green-dark);
  128. font-weight: 600;
  129. }
  130. .token-row {
  131. display: flex;
  132. flex-wrap: wrap;
  133. gap: 8px;
  134. }
  135. .token-chip {
  136. display: inline-flex;
  137. align-items: center;
  138. gap: 6px;
  139. font-size: 11px;
  140. color: var(--text-muted);
  141. background: #fff;
  142. border: 1px solid var(--border);
  143. border-radius: 999px;
  144. padding: 4px 8px 4px 4px;
  145. }
  146. .token-chip i {
  147. width: 14px;
  148. height: 14px;
  149. border-radius: 50%;
  150. display: block;
  151. }
  152. .notes {
  153. margin: 12px 0 0;
  154. padding: 0;
  155. list-style: none;
  156. font-size: 12px;
  157. color: var(--text-muted);
  158. line-height: 1.55;
  159. }
  160. .notes li {
  161. position: relative;
  162. padding-left: 12px;
  163. margin-bottom: 6px;
  164. }
  165. .notes li::before {
  166. content: "";
  167. position: absolute;
  168. left: 0;
  169. top: 7px;
  170. width: 5px;
  171. height: 5px;
  172. border-radius: 50%;
  173. background: var(--brand-green);
  174. }
  175. .phone-stage {
  176. display: flex;
  177. justify-content: center;
  178. }
  179. .phone {
  180. width: 390px;
  181. height: 844px;
  182. background: #111;
  183. border-radius: 42px;
  184. padding: 12px;
  185. box-shadow:
  186. 0 24px 60px rgba(15, 40, 30, 0.18),
  187. inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  188. position: relative;
  189. }
  190. .phone::before {
  191. content: "";
  192. position: absolute;
  193. top: 18px;
  194. left: 50%;
  195. transform: translateX(-50%);
  196. width: 118px;
  197. height: 28px;
  198. background: #0a0a0a;
  199. border-radius: 20px;
  200. z-index: 5;
  201. }
  202. .phone-screen {
  203. width: 100%;
  204. height: 100%;
  205. border-radius: 32px;
  206. overflow: hidden;
  207. background: var(--bg-page);
  208. position: relative;
  209. display: flex;
  210. flex-direction: column;
  211. }
  212. .screen-scroll {
  213. flex: 1;
  214. overflow-y: auto;
  215. -webkit-overflow-scrolling: touch;
  216. padding-bottom: 16px;
  217. }
  218. /* ========== 首页 ========== */
  219. .home-header {
  220. background: linear-gradient(145deg, var(--brand-green-dark), var(--brand-green));
  221. color: #fff;
  222. padding: 54px 16px 28px;
  223. border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  224. position: relative;
  225. overflow: hidden;
  226. }
  227. .home-header::after {
  228. content: "";
  229. position: absolute;
  230. right: -40px;
  231. top: -30px;
  232. width: 160px;
  233. height: 160px;
  234. border-radius: 50%;
  235. background: rgba(255, 255, 255, 0.08);
  236. }
  237. .home-header .brand {
  238. font-size: 12px;
  239. opacity: 0.88;
  240. letter-spacing: 0.04em;
  241. margin-bottom: 8px;
  242. }
  243. .home-header h2 {
  244. margin: 0;
  245. font-size: 22px;
  246. font-weight: 700;
  247. letter-spacing: 0.02em;
  248. }
  249. .home-header .meta {
  250. margin-top: 10px;
  251. display: flex;
  252. align-items: center;
  253. gap: 8px;
  254. font-size: 12px;
  255. opacity: 0.92;
  256. }
  257. .home-header .meta .dot {
  258. width: 4px;
  259. height: 4px;
  260. border-radius: 50%;
  261. background: rgba(255,255,255,.7);
  262. }
  263. .home-body {
  264. padding: 14px 12px 0;
  265. }
  266. .panel {
  267. background: var(--surface);
  268. border: 1px solid var(--border);
  269. border-radius: var(--radius-card);
  270. box-shadow: var(--shadow-soft);
  271. padding: 14px 12px;
  272. margin-bottom: 12px;
  273. }
  274. .panel-hd {
  275. display: flex;
  276. align-items: center;
  277. justify-content: space-between;
  278. margin-bottom: 12px;
  279. padding: 0 4px;
  280. }
  281. .panel-hd h3 {
  282. margin: 0;
  283. font-size: 15px;
  284. font-weight: 650;
  285. }
  286. .panel-hd .hint {
  287. font-size: 11px;
  288. color: var(--text-muted);
  289. }
  290. .menu-grid {
  291. display: grid;
  292. grid-template-columns: repeat(4, 1fr);
  293. gap: 10px 6px;
  294. }
  295. .menu-card {
  296. display: flex;
  297. flex-direction: column;
  298. align-items: center;
  299. justify-content: flex-start;
  300. gap: 8px;
  301. min-height: 84px;
  302. padding: 10px 4px 6px;
  303. border: none;
  304. background: transparent;
  305. cursor: pointer;
  306. border-radius: 12px;
  307. transition: background .18s, transform .18s;
  308. -webkit-tap-highlight-color: transparent;
  309. }
  310. .menu-card:active {
  311. background: var(--brand-green-soft);
  312. transform: scale(0.97);
  313. }
  314. .menu-icon {
  315. width: 48px;
  316. height: 48px;
  317. border-radius: 14px;
  318. display: flex;
  319. align-items: center;
  320. justify-content: center;
  321. background: var(--brand-green-soft);
  322. color: var(--brand-green-dark);
  323. font-size: 24px;
  324. box-shadow: inset 0 0 0 1px rgba(10, 159, 109, 0.08);
  325. }
  326. .menu-icon.tone-yellow {
  327. background: var(--barley-yellow-soft);
  328. color: #a67b20;
  329. }
  330. .menu-icon.tone-slate {
  331. background: #eef2f1;
  332. color: #3d524a;
  333. }
  334. .menu-title {
  335. font-size: 12px;
  336. line-height: 1.25;
  337. color: var(--text-primary);
  338. text-align: center;
  339. max-width: 72px;
  340. word-break: break-all;
  341. }
  342. .perm-banner {
  343. display: flex;
  344. gap: 8px;
  345. align-items: flex-start;
  346. padding: 10px 12px;
  347. border-radius: 12px;
  348. background: var(--barley-yellow-soft);
  349. border: 1px solid #f0e0b8;
  350. font-size: 12px;
  351. color: #8a6520;
  352. margin-bottom: 12px;
  353. line-height: 1.45;
  354. }
  355. /* ========== 待办摘要 ========== */
  356. .todo-panel {
  357. padding: 14px 12px 12px;
  358. background:
  359. linear-gradient(165deg, rgba(233, 249, 242, 0.95) 0%, #fff 42%),
  360. #fff;
  361. }
  362. .todo-stats {
  363. display: grid;
  364. grid-template-columns: repeat(3, 1fr);
  365. gap: 8px;
  366. margin-bottom: 12px;
  367. }
  368. .todo-stat {
  369. position: relative;
  370. border: none;
  371. border-radius: 12px;
  372. padding: 12px 10px 10px;
  373. text-align: left;
  374. cursor: pointer;
  375. background: #fff;
  376. box-shadow: 0 2px 10px rgba(8, 127, 87, 0.06);
  377. border: 1px solid rgba(220, 235, 228, 0.9);
  378. transition: transform .18s, box-shadow .18s;
  379. -webkit-tap-highlight-color: transparent;
  380. overflow: hidden;
  381. }
  382. .todo-stat:active {
  383. transform: scale(0.97);
  384. }
  385. .todo-stat::before {
  386. content: "";
  387. position: absolute;
  388. left: 0;
  389. top: 0;
  390. bottom: 0;
  391. width: 3px;
  392. border-radius: 3px 0 0 3px;
  393. }
  394. .todo-stat.tone-warn::before { background: var(--warning); }
  395. .todo-stat.tone-pay::before { background: var(--barley-yellow); }
  396. .todo-stat.tone-danger::before { background: var(--danger); }
  397. .todo-stat.tone-warn { background: linear-gradient(180deg, #fffaf0 0%, #fff 70%); }
  398. .todo-stat.tone-pay { background: linear-gradient(180deg, #fff8ec 0%, #fff 70%); }
  399. .todo-stat.tone-danger { background: linear-gradient(180deg, #fff5f5 0%, #fff 70%); }
  400. .todo-stat .num {
  401. display: block;
  402. font-size: 22px;
  403. font-weight: 700;
  404. line-height: 1.1;
  405. letter-spacing: -0.02em;
  406. color: var(--text-primary);
  407. font-variant-numeric: tabular-nums;
  408. }
  409. .todo-stat.tone-warn .num { color: #b77412; }
  410. .todo-stat.tone-pay .num { color: #9a7424; }
  411. .todo-stat.tone-danger .num { color: var(--danger); }
  412. .todo-stat .label {
  413. display: block;
  414. margin-top: 4px;
  415. font-size: 11px;
  416. color: var(--text-muted);
  417. }
  418. .todo-stat .stat-icon {
  419. position: absolute;
  420. right: 8px;
  421. top: 8px;
  422. width: 26px;
  423. height: 26px;
  424. border-radius: 8px;
  425. display: flex;
  426. align-items: center;
  427. justify-content: center;
  428. font-size: 14px;
  429. opacity: 0.9;
  430. }
  431. .todo-stat.tone-warn .stat-icon {
  432. background: rgba(207, 138, 25, 0.12);
  433. color: var(--warning);
  434. }
  435. .todo-stat.tone-pay .stat-icon {
  436. background: rgba(214, 163, 67, 0.16);
  437. color: #a67b20;
  438. }
  439. .todo-stat.tone-danger .stat-icon {
  440. background: rgba(209, 67, 67, 0.12);
  441. color: var(--danger);
  442. }
  443. .todo-list {
  444. display: flex;
  445. flex-direction: column;
  446. gap: 8px;
  447. }
  448. .todo-item {
  449. display: flex;
  450. align-items: center;
  451. gap: 10px;
  452. width: 100%;
  453. padding: 10px 10px;
  454. border: 1px solid var(--border);
  455. border-radius: 12px;
  456. background: #fff;
  457. cursor: pointer;
  458. text-align: left;
  459. transition: background .15s, border-color .15s;
  460. }
  461. .todo-item:active {
  462. background: var(--brand-green-soft);
  463. border-color: #b7e5d0;
  464. }
  465. .todo-item .ti-icon {
  466. width: 36px;
  467. height: 36px;
  468. border-radius: 10px;
  469. display: flex;
  470. align-items: center;
  471. justify-content: center;
  472. flex-shrink: 0;
  473. font-size: 18px;
  474. }
  475. .todo-item .ti-icon.i-approve {
  476. background: #fff3e0;
  477. color: var(--warning);
  478. }
  479. .todo-item .ti-icon.i-pay {
  480. background: var(--barley-yellow-soft);
  481. color: #a67b20;
  482. }
  483. .todo-item .ti-icon.i-alert {
  484. background: #fdecec;
  485. color: var(--danger);
  486. }
  487. .todo-item .ti-body {
  488. flex: 1;
  489. min-width: 0;
  490. }
  491. .todo-item .ti-title {
  492. font-size: 13px;
  493. font-weight: 600;
  494. color: var(--text-primary);
  495. line-height: 1.3;
  496. }
  497. .todo-item .ti-desc {
  498. margin-top: 2px;
  499. font-size: 11px;
  500. color: var(--text-muted);
  501. white-space: nowrap;
  502. overflow: hidden;
  503. text-overflow: ellipsis;
  504. }
  505. .todo-item .ti-right {
  506. display: flex;
  507. flex-direction: column;
  508. align-items: flex-end;
  509. gap: 4px;
  510. flex-shrink: 0;
  511. }
  512. .todo-item .ti-time {
  513. font-size: 10px;
  514. color: #9aada4;
  515. }
  516. /* ========== 列表 ========== */
  517. .page-nav {
  518. background: linear-gradient(145deg, var(--brand-green-dark), var(--brand-green));
  519. padding: 44px 8px 10px;
  520. }
  521. .page-nav .van-nav-bar {
  522. background: transparent;
  523. }
  524. .page-nav .van-hairline--bottom::after {
  525. display: none;
  526. }
  527. .list-page {
  528. flex: 1;
  529. display: flex;
  530. flex-direction: column;
  531. min-height: 0;
  532. background:
  533. radial-gradient(420px 220px at 100% 0%, rgba(214, 163, 67, 0.16), transparent 55%),
  534. radial-gradient(380px 240px at 0% 30%, rgba(10, 159, 109, 0.10), transparent 50%),
  535. linear-gradient(180deg, #e8f5ef 0%, #f3f7f5 38%, #f7f9f8 100%);
  536. }
  537. .list-page .screen-scroll {
  538. background: transparent;
  539. }
  540. .list-hero {
  541. background:
  542. radial-gradient(220px 140px at 92% 18%, rgba(255, 255, 255, 0.18), transparent 60%),
  543. linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
  544. padding: 44px 14px 22px;
  545. border-radius: 0 0 28px 28px;
  546. position: relative;
  547. overflow: hidden;
  548. }
  549. .list-hero::after {
  550. content: "";
  551. position: absolute;
  552. width: 180px;
  553. height: 180px;
  554. right: -50px;
  555. bottom: -70px;
  556. border-radius: 50%;
  557. background: rgba(255, 255, 255, 0.08);
  558. pointer-events: none;
  559. }
  560. .list-hero .van-nav-bar {
  561. background: transparent;
  562. margin: 0 -6px;
  563. }
  564. .list-hero .van-hairline--bottom::after {
  565. display: none;
  566. }
  567. .list-hero-copy {
  568. position: relative;
  569. z-index: 1;
  570. margin: 4px 2px 14px;
  571. color: #fff;
  572. }
  573. .list-hero-copy .eyebrow {
  574. font-size: 11px;
  575. letter-spacing: 0.08em;
  576. opacity: 0.82;
  577. margin-bottom: 4px;
  578. }
  579. .list-hero-copy h2 {
  580. margin: 0;
  581. font-size: 22px;
  582. font-weight: 700;
  583. letter-spacing: 0.01em;
  584. }
  585. .list-hero-copy .subline {
  586. margin-top: 6px;
  587. font-size: 12px;
  588. opacity: 0.88;
  589. display: flex;
  590. align-items: center;
  591. gap: 8px;
  592. }
  593. .list-hero-copy .subline em {
  594. font-style: normal;
  595. font-weight: 700;
  596. background: rgba(255, 255, 255, 0.18);
  597. border-radius: 999px;
  598. padding: 1px 8px;
  599. }
  600. .list-search {
  601. position: relative;
  602. z-index: 1;
  603. }
  604. .list-search .van-search {
  605. padding: 0;
  606. }
  607. .list-search .van-search__content {
  608. background: rgba(255, 255, 255, 0.96);
  609. border-radius: 14px;
  610. box-shadow: 0 8px 24px rgba(4, 70, 48, 0.18);
  611. }
  612. .list-filter-dock {
  613. margin: -14px 12px 0;
  614. position: relative;
  615. z-index: 3;
  616. background: rgba(255, 255, 255, 0.92);
  617. border: 1px solid rgba(220, 235, 228, 0.95);
  618. border-radius: 18px;
  619. padding: 10px;
  620. box-shadow: 0 10px 28px rgba(15, 55, 40, 0.08);
  621. backdrop-filter: blur(10px);
  622. }
  623. .seg-tabs {
  624. display: grid;
  625. grid-template-columns: repeat(4, 1fr);
  626. gap: 4px;
  627. padding: 4px;
  628. border-radius: 14px;
  629. background: #eef6f2;
  630. }
  631. .seg-tab {
  632. border: none;
  633. background: transparent;
  634. border-radius: 11px;
  635. padding: 8px 4px;
  636. cursor: pointer;
  637. color: #6b7b74;
  638. display: flex;
  639. flex-direction: column;
  640. align-items: center;
  641. gap: 2px;
  642. transition: all .2s;
  643. -webkit-tap-highlight-color: transparent;
  644. }
  645. .seg-tab .lab {
  646. font-size: 12px;
  647. line-height: 1.2;
  648. }
  649. .seg-tab .count {
  650. font-size: 11px;
  651. font-weight: 700;
  652. font-variant-numeric: tabular-nums;
  653. color: #9aada4;
  654. }
  655. .seg-tab.active {
  656. background: #fff;
  657. color: var(--brand-green-dark);
  658. box-shadow: 0 4px 12px rgba(8, 127, 87, 0.14);
  659. font-weight: 650;
  660. }
  661. .seg-tab.active .count {
  662. color: var(--brand-green);
  663. }
  664. .list-wrap {
  665. padding: 16px 12px 0;
  666. }
  667. .biz-card {
  668. position: relative;
  669. border: none;
  670. border-radius: 20px;
  671. padding: 0;
  672. margin-bottom: 14px;
  673. overflow: hidden;
  674. box-shadow: 0 10px 28px rgba(20, 55, 40, 0.08);
  675. transition: transform .16s, box-shadow .16s;
  676. isolation: isolate;
  677. }
  678. .biz-card:active {
  679. transform: translateY(1px) scale(0.995);
  680. box-shadow: 0 6px 16px rgba(20, 55, 40, 0.07);
  681. }
  682. .biz-card::before {
  683. content: "";
  684. position: absolute;
  685. inset: 0;
  686. z-index: 0;
  687. pointer-events: none;
  688. }
  689. .biz-card::after {
  690. content: "";
  691. position: absolute;
  692. width: 140px;
  693. height: 140px;
  694. right: -36px;
  695. top: -48px;
  696. border-radius: 50%;
  697. z-index: 0;
  698. pointer-events: none;
  699. opacity: 0.55;
  700. }
  701. .biz-card.status-warning {
  702. background: linear-gradient(145deg, #fff9ef 0%, #ffffff 46%, #fffaf3 100%);
  703. }
  704. .biz-card.status-warning::before {
  705. background: linear-gradient(90deg, rgba(214, 163, 67, 0.22), transparent 28%);
  706. }
  707. .biz-card.status-warning::after {
  708. background: radial-gradient(circle, rgba(214, 163, 67, 0.28), transparent 68%);
  709. }
  710. .biz-card.status-primary {
  711. background: linear-gradient(145deg, #eaf8f1 0%, #ffffff 48%, #f3fbf7 100%);
  712. }
  713. .biz-card.status-primary::before {
  714. background: linear-gradient(90deg, rgba(10, 159, 109, 0.18), transparent 28%);
  715. }
  716. .biz-card.status-primary::after {
  717. background: radial-gradient(circle, rgba(10, 159, 109, 0.22), transparent 68%);
  718. }
  719. .biz-card.status-danger {
  720. background: linear-gradient(145deg, #fff1f1 0%, #ffffff 48%, #fff7f7 100%);
  721. }
  722. .biz-card.status-danger::before {
  723. background: linear-gradient(90deg, rgba(209, 67, 67, 0.16), transparent 28%);
  724. }
  725. .biz-card.status-danger::after {
  726. background: radial-gradient(circle, rgba(209, 67, 67, 0.18), transparent 68%);
  727. }
  728. .biz-card.status-success {
  729. background: linear-gradient(145deg, #eef8f3 0%, #ffffff 50%, #f6fbf8 100%);
  730. }
  731. .biz-card.status-success::before {
  732. background: linear-gradient(90deg, rgba(24, 155, 101, 0.16), transparent 28%);
  733. }
  734. .biz-card.status-success::after {
  735. background: radial-gradient(circle, rgba(24, 155, 101, 0.18), transparent 68%);
  736. }
  737. .biz-card .card-inner {
  738. position: relative;
  739. z-index: 1;
  740. padding: 14px 14px 12px;
  741. }
  742. .biz-card .row-top {
  743. display: flex;
  744. align-items: flex-start;
  745. gap: 10px;
  746. margin-bottom: 12px;
  747. }
  748. .biz-card .type-icon {
  749. width: 44px;
  750. height: 44px;
  751. border-radius: 14px;
  752. display: flex;
  753. align-items: center;
  754. justify-content: center;
  755. flex-shrink: 0;
  756. font-size: 22px;
  757. box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.65);
  758. }
  759. .biz-card.status-warning .type-icon {
  760. background: linear-gradient(145deg, #ffe8b8, #f6d089);
  761. color: #7a5610;
  762. }
  763. .biz-card.status-primary .type-icon {
  764. background: linear-gradient(145deg, #8fe0bf, #0a9f6d);
  765. color: #fff;
  766. }
  767. .biz-card.status-danger .type-icon {
  768. background: linear-gradient(145deg, #ffb4b4, #e85a5a);
  769. color: #fff;
  770. }
  771. .biz-card.status-success .type-icon {
  772. background: linear-gradient(145deg, #b8efd4, #189b65);
  773. color: #fff;
  774. }
  775. .biz-card .title-wrap {
  776. flex: 1;
  777. min-width: 0;
  778. padding-top: 1px;
  779. }
  780. .biz-card .type-chip {
  781. display: inline-flex;
  782. align-items: center;
  783. gap: 4px;
  784. font-size: 10px;
  785. font-weight: 650;
  786. letter-spacing: 0.04em;
  787. padding: 2px 7px;
  788. border-radius: 999px;
  789. margin-bottom: 5px;
  790. }
  791. .biz-card.status-warning .type-chip {
  792. background: rgba(214, 163, 67, 0.16);
  793. color: #8f6718;
  794. }
  795. .biz-card.status-primary .type-chip {
  796. background: rgba(10, 159, 109, 0.12);
  797. color: #087f57;
  798. }
  799. .biz-card.status-danger .type-chip {
  800. background: rgba(209, 67, 67, 0.12);
  801. color: #b13535;
  802. }
  803. .biz-card.status-success .type-chip {
  804. background: rgba(24, 155, 101, 0.12);
  805. color: #147a50;
  806. }
  807. .biz-card .title {
  808. font-size: 15px;
  809. font-weight: 700;
  810. line-height: 1.35;
  811. color: #173128;
  812. letter-spacing: -0.01em;
  813. }
  814. .biz-card .status-pill {
  815. flex-shrink: 0;
  816. margin-top: 2px;
  817. font-size: 11px;
  818. font-weight: 700;
  819. padding: 4px 9px;
  820. border-radius: 999px;
  821. letter-spacing: 0.02em;
  822. }
  823. .biz-card.status-warning .status-pill {
  824. background: #fff;
  825. color: #a67b20;
  826. box-shadow: 0 2px 8px rgba(214, 163, 67, 0.22);
  827. }
  828. .biz-card.status-primary .status-pill {
  829. background: #0a9f6d;
  830. color: #fff;
  831. box-shadow: 0 4px 12px rgba(10, 159, 109, 0.28);
  832. }
  833. .biz-card.status-danger .status-pill {
  834. background: #d14343;
  835. color: #fff;
  836. box-shadow: 0 4px 12px rgba(209, 67, 67, 0.25);
  837. }
  838. .biz-card.status-success .status-pill {
  839. background: rgba(24, 155, 101, 0.12);
  840. color: #147a50;
  841. }
  842. .biz-card .body-row {
  843. display: flex;
  844. gap: 12px;
  845. align-items: stretch;
  846. }
  847. .biz-card .desc-box {
  848. flex: 1;
  849. margin: 0;
  850. padding: 10px 11px;
  851. border-radius: 12px;
  852. background: rgba(255, 255, 255, 0.72);
  853. border: 1px solid rgba(255, 255, 255, 0.9);
  854. font-size: 12px;
  855. color: #3f564c;
  856. line-height: 1.5;
  857. backdrop-filter: blur(2px);
  858. }
  859. .biz-card .side-metric {
  860. width: 86px;
  861. flex-shrink: 0;
  862. border-radius: 12px;
  863. padding: 8px 8px;
  864. display: flex;
  865. flex-direction: column;
  866. justify-content: center;
  867. align-items: flex-end;
  868. text-align: right;
  869. background: rgba(255, 255, 255, 0.65);
  870. border: 1px solid rgba(255, 255, 255, 0.95);
  871. }
  872. .biz-card .side-metric .amt-label {
  873. font-size: 10px;
  874. color: #8aa198;
  875. }
  876. .biz-card .side-metric .amt {
  877. margin-top: 2px;
  878. font-size: 15px;
  879. font-weight: 800;
  880. color: #173128;
  881. letter-spacing: -0.02em;
  882. font-variant-numeric: tabular-nums;
  883. line-height: 1.15;
  884. }
  885. .biz-card.status-warning .side-metric .amt { color: #9a7424; }
  886. .biz-card.status-primary .side-metric .amt { color: #087f57; }
  887. .biz-card.status-danger .side-metric .amt { color: #b13535; }
  888. .biz-card .side-metric .time {
  889. margin-top: 6px;
  890. font-size: 10px;
  891. color: #8aa198;
  892. }
  893. .biz-card .meta-row {
  894. display: flex;
  895. flex-wrap: wrap;
  896. gap: 8px;
  897. margin-top: 12px;
  898. }
  899. .biz-card .meta-row .mi {
  900. display: inline-flex;
  901. align-items: center;
  902. gap: 4px;
  903. font-size: 11px;
  904. color: #5d746a;
  905. background: rgba(255, 255, 255, 0.7);
  906. border-radius: 999px;
  907. padding: 3px 8px;
  908. }
  909. .biz-card .meta-row .van-icon {
  910. font-size: 12px;
  911. color: #7f968c;
  912. }
  913. .biz-card .code-pill {
  914. font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  915. font-size: 10px;
  916. letter-spacing: 0.02em;
  917. }
  918. .biz-card .footer {
  919. margin-top: 12px;
  920. padding-top: 10px;
  921. border-top: 1px dashed rgba(140, 170, 155, 0.28);
  922. display: flex;
  923. justify-content: space-between;
  924. align-items: center;
  925. gap: 8px;
  926. }
  927. .biz-card .footer .left-hint {
  928. font-size: 11px;
  929. color: #7f968c;
  930. display: inline-flex;
  931. align-items: center;
  932. gap: 4px;
  933. }
  934. .biz-card .footer .left-hint i {
  935. width: 6px;
  936. height: 6px;
  937. border-radius: 50%;
  938. display: inline-block;
  939. background: currentColor;
  940. opacity: 0.75;
  941. }
  942. .biz-card.status-warning .footer .left-hint { color: #a67b20; }
  943. .biz-card.status-primary .footer .left-hint { color: #0a9f6d; }
  944. .biz-card.status-danger .footer .left-hint { color: #d14343; }
  945. .biz-card.status-success .footer .left-hint { color: #189b65; }
  946. .biz-card .footer .ops {
  947. display: flex;
  948. gap: 8px;
  949. }
  950. .biz-card .footer .van-button {
  951. min-width: 64px;
  952. border-radius: 999px;
  953. height: 30px;
  954. }
  955. .biz-card .footer .van-button--primary {
  956. background: linear-gradient(135deg, #12b37a, #087f57);
  957. border: none;
  958. box-shadow: 0 4px 12px rgba(8, 127, 87, 0.28);
  959. }
  960. .list-end {
  961. padding: 8px 0 28px;
  962. text-align: center;
  963. font-size: 11px;
  964. color: #8aa198;
  965. letter-spacing: 0.08em;
  966. }
  967. .list-end span {
  968. display: inline-flex;
  969. align-items: center;
  970. gap: 10px;
  971. }
  972. .list-end span::before,
  973. .list-end span::after {
  974. content: "";
  975. width: 28px;
  976. height: 1px;
  977. background: linear-gradient(90deg, transparent, #c5d8ce);
  978. }
  979. .list-end span::after {
  980. background: linear-gradient(90deg, #c5d8ce, transparent);
  981. }
  982. .list-empty-pad {
  983. padding: 40px 0;
  984. }
  985. /* ========== 详情 ========== */
  986. .detail-page {
  987. flex: 1;
  988. display: flex;
  989. flex-direction: column;
  990. min-height: 0;
  991. background:
  992. radial-gradient(380px 200px at 100% 8%, rgba(214, 163, 67, 0.14), transparent 55%),
  993. linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 42%, #f7f9f8 100%);
  994. }
  995. .detail-page .screen-scroll {
  996. background: transparent;
  997. padding-bottom: 88px;
  998. }
  999. .detail-hero {
  1000. position: relative;
  1001. overflow: hidden;
  1002. padding: 44px 14px 28px;
  1003. border-radius: 0 0 28px 28px;
  1004. color: #fff;
  1005. }
  1006. .detail-hero.status-warning {
  1007. background:
  1008. radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.2), transparent 60%),
  1009. linear-gradient(155deg, #a67b20 0%, #d6a343 45%, #e8c06a 100%);
  1010. }
  1011. .detail-hero.status-primary {
  1012. background:
  1013. radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.18), transparent 60%),
  1014. linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
  1015. }
  1016. .detail-hero.status-danger {
  1017. background:
  1018. radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.16), transparent 60%),
  1019. linear-gradient(155deg, #9c2f2f 0%, #d14343 50%, #e36a6a 100%);
  1020. }
  1021. .detail-hero.status-success {
  1022. background:
  1023. radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.16), transparent 60%),
  1024. linear-gradient(155deg, #0f6b48 0%, #189b65 52%, #3bb883 100%);
  1025. }
  1026. .detail-hero::after {
  1027. content: "";
  1028. position: absolute;
  1029. width: 180px;
  1030. height: 180px;
  1031. right: -48px;
  1032. bottom: -70px;
  1033. border-radius: 50%;
  1034. background: rgba(255, 255, 255, 0.1);
  1035. pointer-events: none;
  1036. }
  1037. .detail-hero .van-nav-bar {
  1038. background: transparent;
  1039. margin: 0 -6px;
  1040. }
  1041. .detail-hero .van-hairline--bottom::after {
  1042. display: none;
  1043. }
  1044. .detail-hero-body {
  1045. position: relative;
  1046. z-index: 1;
  1047. margin-top: 6px;
  1048. }
  1049. .detail-hero .type-line {
  1050. display: flex;
  1051. align-items: center;
  1052. gap: 8px;
  1053. margin-bottom: 10px;
  1054. }
  1055. .detail-hero .type-chip {
  1056. font-size: 11px;
  1057. font-weight: 650;
  1058. letter-spacing: 0.04em;
  1059. padding: 3px 9px;
  1060. border-radius: 999px;
  1061. background: rgba(255, 255, 255, 0.2);
  1062. color: #fff;
  1063. }
  1064. .detail-hero .status-chip {
  1065. font-size: 11px;
  1066. font-weight: 700;
  1067. padding: 3px 9px;
  1068. border-radius: 999px;
  1069. background: #fff;
  1070. color: #173128;
  1071. }
  1072. .detail-hero.status-warning .status-chip { color: #8f6718; }
  1073. .detail-hero.status-primary .status-chip { color: #087f57; }
  1074. .detail-hero.status-danger .status-chip { color: #b13535; }
  1075. .detail-hero.status-success .status-chip { color: #147a50; }
  1076. .detail-hero h2 {
  1077. margin: 0;
  1078. font-size: 20px;
  1079. font-weight: 750;
  1080. line-height: 1.35;
  1081. letter-spacing: -0.01em;
  1082. }
  1083. .detail-hero .code {
  1084. margin-top: 8px;
  1085. font-size: 12px;
  1086. opacity: 0.92;
  1087. font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  1088. letter-spacing: 0.02em;
  1089. }
  1090. .detail-metric-dock {
  1091. margin: -18px 12px 0;
  1092. position: relative;
  1093. z-index: 3;
  1094. display: grid;
  1095. grid-template-columns: repeat(3, 1fr);
  1096. gap: 8px;
  1097. padding: 12px;
  1098. border-radius: 18px;
  1099. background: rgba(255, 255, 255, 0.94);
  1100. border: 1px solid rgba(220, 235, 228, 0.95);
  1101. box-shadow: 0 12px 30px rgba(15, 55, 40, 0.1);
  1102. backdrop-filter: blur(10px);
  1103. }
  1104. .detail-metric {
  1105. text-align: center;
  1106. padding: 4px 2px;
  1107. }
  1108. .detail-metric .lab {
  1109. display: block;
  1110. font-size: 10px;
  1111. color: #8aa198;
  1112. margin-bottom: 4px;
  1113. }
  1114. .detail-metric .val {
  1115. display: block;
  1116. font-size: 14px;
  1117. font-weight: 750;
  1118. color: #173128;
  1119. line-height: 1.25;
  1120. word-break: break-all;
  1121. }
  1122. .detail-metric .val.money {
  1123. color: #9a7424;
  1124. font-variant-numeric: tabular-nums;
  1125. }
  1126. .detail-body {
  1127. padding: 16px 12px 0;
  1128. }
  1129. .detail-section {
  1130. background: #fff;
  1131. border-radius: 18px;
  1132. border: 1px solid rgba(220, 235, 228, 0.9);
  1133. box-shadow: 0 8px 22px rgba(20, 55, 40, 0.05);
  1134. padding: 14px 14px 12px;
  1135. margin-bottom: 12px;
  1136. overflow: hidden;
  1137. }
  1138. .detail-section .sec-hd {
  1139. display: flex;
  1140. align-items: center;
  1141. justify-content: space-between;
  1142. margin-bottom: 12px;
  1143. }
  1144. .detail-section .sec-hd h3 {
  1145. margin: 0;
  1146. font-size: 14px;
  1147. font-weight: 700;
  1148. color: #173128;
  1149. display: inline-flex;
  1150. align-items: center;
  1151. gap: 8px;
  1152. }
  1153. .detail-section .sec-hd h3::before {
  1154. content: "";
  1155. width: 4px;
  1156. height: 14px;
  1157. border-radius: 999px;
  1158. background: linear-gradient(180deg, #12b37a, #087f57);
  1159. }
  1160. .detail-section .sec-hd .more {
  1161. border: none;
  1162. background: transparent;
  1163. color: var(--brand-green);
  1164. font-size: 12px;
  1165. cursor: pointer;
  1166. padding: 0;
  1167. }
  1168. .kv-list {
  1169. display: flex;
  1170. flex-direction: column;
  1171. gap: 10px;
  1172. }
  1173. .kv-row {
  1174. display: flex;
  1175. align-items: flex-start;
  1176. gap: 12px;
  1177. font-size: 13px;
  1178. line-height: 1.45;
  1179. }
  1180. .kv-row .k {
  1181. width: 72px;
  1182. flex-shrink: 0;
  1183. color: #8aa198;
  1184. }
  1185. .kv-row .v {
  1186. flex: 1;
  1187. color: #173128;
  1188. font-weight: 550;
  1189. word-break: break-all;
  1190. }
  1191. .detail-note {
  1192. margin: 0;
  1193. padding: 10px 12px;
  1194. border-radius: 12px;
  1195. background: linear-gradient(135deg, #fff8ec, #fffdf8);
  1196. border: 1px solid #f0e0b8;
  1197. color: #8a6520;
  1198. font-size: 12px;
  1199. line-height: 1.5;
  1200. }
  1201. /* 详情 · 流转记录 */
  1202. .flow-list {
  1203. display: flex;
  1204. flex-direction: column;
  1205. gap: 0;
  1206. position: relative;
  1207. }
  1208. .flow-item {
  1209. position: relative;
  1210. display: flex;
  1211. gap: 12px;
  1212. padding-bottom: 14px;
  1213. }
  1214. .flow-item:last-child {
  1215. padding-bottom: 0;
  1216. }
  1217. .flow-item:not(:last-child) .flow-rail::after {
  1218. content: "";
  1219. position: absolute;
  1220. left: 50%;
  1221. top: 36px;
  1222. bottom: -14px;
  1223. width: 2px;
  1224. transform: translateX(-50%);
  1225. background: linear-gradient(180deg, rgba(10, 159, 109, 0.45), rgba(220, 235, 228, 0.9));
  1226. border-radius: 2px;
  1227. }
  1228. .flow-rail {
  1229. position: relative;
  1230. width: 36px;
  1231. flex-shrink: 0;
  1232. display: flex;
  1233. justify-content: center;
  1234. }
  1235. .flow-dot {
  1236. width: 36px;
  1237. height: 36px;
  1238. border-radius: 12px;
  1239. display: flex;
  1240. align-items: center;
  1241. justify-content: center;
  1242. font-size: 16px;
  1243. color: #fff;
  1244. box-shadow: 0 6px 14px rgba(8, 127, 87, 0.2);
  1245. position: relative;
  1246. z-index: 1;
  1247. }
  1248. .flow-item.done .flow-dot {
  1249. background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
  1250. }
  1251. .flow-item.warn .flow-dot {
  1252. background: linear-gradient(145deg, #ecc06a, #d6a343);
  1253. box-shadow: 0 6px 14px rgba(214, 163, 67, 0.28);
  1254. }
  1255. .flow-item.danger .flow-dot {
  1256. background: linear-gradient(145deg, #ef7a7a, #d14343);
  1257. box-shadow: 0 6px 14px rgba(209, 67, 67, 0.25);
  1258. }
  1259. .flow-card {
  1260. flex: 1;
  1261. min-width: 0;
  1262. border-radius: 14px;
  1263. padding: 11px 12px;
  1264. background: linear-gradient(160deg, #f7fbf9 0%, #ffffff 55%);
  1265. border: 1px solid #e4eee9;
  1266. box-shadow: 0 4px 12px rgba(20, 55, 40, 0.04);
  1267. }
  1268. .flow-item.warn .flow-card {
  1269. background: linear-gradient(160deg, #fff9ef 0%, #ffffff 60%);
  1270. border-color: #f0e0b8;
  1271. }
  1272. .flow-item.danger .flow-card {
  1273. background: linear-gradient(160deg, #fff4f4 0%, #ffffff 60%);
  1274. border-color: #f3d0d0;
  1275. }
  1276. .flow-card .flow-top {
  1277. display: flex;
  1278. align-items: flex-start;
  1279. justify-content: space-between;
  1280. gap: 8px;
  1281. margin-bottom: 4px;
  1282. }
  1283. .flow-card .flow-title {
  1284. font-size: 13px;
  1285. font-weight: 700;
  1286. color: #173128;
  1287. line-height: 1.35;
  1288. }
  1289. .flow-card .flow-badge {
  1290. flex-shrink: 0;
  1291. font-size: 10px;
  1292. font-weight: 700;
  1293. padding: 2px 7px;
  1294. border-radius: 999px;
  1295. letter-spacing: 0.02em;
  1296. }
  1297. .flow-item.done .flow-badge {
  1298. background: rgba(10, 159, 109, 0.12);
  1299. color: #087f57;
  1300. }
  1301. .flow-item.warn .flow-badge {
  1302. background: rgba(214, 163, 67, 0.16);
  1303. color: #8f6718;
  1304. }
  1305. .flow-item.danger .flow-badge {
  1306. background: rgba(209, 67, 67, 0.12);
  1307. color: #b13535;
  1308. }
  1309. .flow-card .flow-desc {
  1310. font-size: 12px;
  1311. color: #5d746a;
  1312. line-height: 1.5;
  1313. }
  1314. .flow-card .flow-meta {
  1315. margin-top: 8px;
  1316. display: flex;
  1317. flex-wrap: wrap;
  1318. gap: 8px;
  1319. align-items: center;
  1320. }
  1321. .flow-card .flow-meta .chip {
  1322. display: inline-flex;
  1323. align-items: center;
  1324. gap: 4px;
  1325. font-size: 11px;
  1326. color: #6b7b74;
  1327. background: rgba(255, 255, 255, 0.9);
  1328. border: 1px solid #e8f0ec;
  1329. border-radius: 999px;
  1330. padding: 2px 8px;
  1331. }
  1332. .flow-card .flow-meta .van-icon {
  1333. font-size: 12px;
  1334. color: #8aa198;
  1335. }
  1336. .pay-row {
  1337. display: flex;
  1338. align-items: center;
  1339. justify-content: space-between;
  1340. gap: 10px;
  1341. padding: 10px 12px;
  1342. border-radius: 12px;
  1343. background: #f7faf9;
  1344. margin-bottom: 8px;
  1345. }
  1346. .pay-row:last-child {
  1347. margin-bottom: 0;
  1348. }
  1349. .pay-row .left .name {
  1350. font-size: 13px;
  1351. font-weight: 650;
  1352. color: #173128;
  1353. }
  1354. .pay-row .left .sub {
  1355. margin-top: 2px;
  1356. font-size: 11px;
  1357. color: #8aa198;
  1358. }
  1359. .pay-row .right {
  1360. text-align: right;
  1361. }
  1362. .pay-row .right .amt {
  1363. font-size: 14px;
  1364. font-weight: 750;
  1365. color: #9a7424;
  1366. font-variant-numeric: tabular-nums;
  1367. }
  1368. /* 详情 · 附件 */
  1369. .d-attach-list {
  1370. display: flex;
  1371. flex-direction: column;
  1372. gap: 10px;
  1373. }
  1374. .d-attach-upload {
  1375. width: 100%;
  1376. border: 1.5px dashed #9ad9be;
  1377. border-radius: 14px;
  1378. background:
  1379. linear-gradient(135deg, rgba(233, 249, 242, 0.95), rgba(255, 255, 255, 0.95)),
  1380. #e9f9f2;
  1381. padding: 14px 12px;
  1382. display: flex;
  1383. align-items: center;
  1384. gap: 12px;
  1385. cursor: pointer;
  1386. text-align: left;
  1387. transition: transform .15s, border-color .15s;
  1388. }
  1389. .d-attach-upload:active {
  1390. transform: scale(0.99);
  1391. border-color: #0a9f6d;
  1392. }
  1393. .d-attach-upload .up-icon {
  1394. width: 44px;
  1395. height: 44px;
  1396. border-radius: 12px;
  1397. background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
  1398. color: #fff;
  1399. display: flex;
  1400. align-items: center;
  1401. justify-content: center;
  1402. font-size: 22px;
  1403. box-shadow: 0 6px 14px rgba(10, 159, 109, 0.28);
  1404. flex-shrink: 0;
  1405. }
  1406. .d-attach-upload .up-text {
  1407. flex: 1;
  1408. min-width: 0;
  1409. }
  1410. .d-attach-upload .up-title {
  1411. font-size: 13px;
  1412. font-weight: 700;
  1413. color: #087f57;
  1414. }
  1415. .d-attach-upload .up-sub {
  1416. margin-top: 2px;
  1417. font-size: 11px;
  1418. color: #6b7b74;
  1419. line-height: 1.4;
  1420. }
  1421. .d-attach-card {
  1422. display: flex;
  1423. align-items: center;
  1424. gap: 12px;
  1425. padding: 12px;
  1426. border-radius: 14px;
  1427. background: #fff;
  1428. border: 1px solid #e4eee9;
  1429. box-shadow: 0 4px 14px rgba(20, 55, 40, 0.05);
  1430. position: relative;
  1431. overflow: hidden;
  1432. }
  1433. .d-attach-card::before {
  1434. content: "";
  1435. position: absolute;
  1436. left: 0;
  1437. top: 0;
  1438. bottom: 0;
  1439. width: 3px;
  1440. background: var(--file-accent, #0a9f6d);
  1441. }
  1442. .d-attach-card.ft-pdf { --file-accent: #d14343; }
  1443. .d-attach-card.ft-img { --file-accent: #0a9f6d; }
  1444. .d-attach-card.ft-xls { --file-accent: #189b65; }
  1445. .d-attach-card.ft-doc { --file-accent: #3b82f6; }
  1446. .d-attach-card.ft-other { --file-accent: #d6a343; }
  1447. .d-attach-card .file-icon {
  1448. width: 46px;
  1449. height: 46px;
  1450. border-radius: 13px;
  1451. display: flex;
  1452. flex-direction: column;
  1453. align-items: center;
  1454. justify-content: center;
  1455. gap: 1px;
  1456. flex-shrink: 0;
  1457. color: #fff;
  1458. font-weight: 800;
  1459. box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
  1460. }
  1461. .d-attach-card.ft-pdf .file-icon {
  1462. background: linear-gradient(145deg, #f07171, #d14343);
  1463. }
  1464. .d-attach-card.ft-img .file-icon {
  1465. background: linear-gradient(145deg, #3dca96, #0a9f6d);
  1466. }
  1467. .d-attach-card.ft-xls .file-icon {
  1468. background: linear-gradient(145deg, #4fbf8a, #189b65);
  1469. }
  1470. .d-attach-card.ft-doc .file-icon {
  1471. background: linear-gradient(145deg, #6ea8ff, #3b82f6);
  1472. }
  1473. .d-attach-card.ft-other .file-icon {
  1474. background: linear-gradient(145deg, #e4bc6a, #d6a343);
  1475. }
  1476. .d-attach-card .file-icon .ext {
  1477. font-size: 10px;
  1478. letter-spacing: 0.04em;
  1479. line-height: 1;
  1480. text-transform: uppercase;
  1481. }
  1482. .d-attach-card .file-icon .van-icon {
  1483. font-size: 16px;
  1484. opacity: 0.95;
  1485. }
  1486. .d-attach-card .file-body {
  1487. flex: 1;
  1488. min-width: 0;
  1489. }
  1490. .d-attach-card .file-name {
  1491. font-size: 13px;
  1492. font-weight: 650;
  1493. color: #173128;
  1494. line-height: 1.35;
  1495. white-space: nowrap;
  1496. overflow: hidden;
  1497. text-overflow: ellipsis;
  1498. }
  1499. .d-attach-card .file-meta {
  1500. margin-top: 4px;
  1501. display: flex;
  1502. flex-wrap: wrap;
  1503. gap: 8px;
  1504. font-size: 11px;
  1505. color: #8aa198;
  1506. }
  1507. .d-attach-card .file-ops {
  1508. display: flex;
  1509. flex-direction: column;
  1510. gap: 6px;
  1511. flex-shrink: 0;
  1512. }
  1513. .d-attach-card .file-ops button {
  1514. border: none;
  1515. cursor: pointer;
  1516. width: 32px;
  1517. height: 32px;
  1518. border-radius: 10px;
  1519. display: flex;
  1520. align-items: center;
  1521. justify-content: center;
  1522. font-size: 16px;
  1523. transition: transform .12s;
  1524. }
  1525. .d-attach-card .file-ops button:active {
  1526. transform: scale(0.94);
  1527. }
  1528. .d-attach-card .file-ops .btn-preview {
  1529. background: var(--brand-green-soft);
  1530. color: var(--brand-green-dark);
  1531. }
  1532. .d-attach-card .file-ops .btn-download {
  1533. background: #f0f5f3;
  1534. color: #4d635a;
  1535. }
  1536. .d-attach-card .file-ops .icon-download {
  1537. width: 14px;
  1538. height: 14px;
  1539. display: block;
  1540. }
  1541. .detail-footer {
  1542. position: absolute;
  1543. left: 0;
  1544. right: 0;
  1545. bottom: 0;
  1546. display: flex;
  1547. gap: 10px;
  1548. padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
  1549. background: rgba(255, 255, 255, 0.96);
  1550. border-top: 1px solid var(--border);
  1551. backdrop-filter: blur(10px);
  1552. z-index: 4;
  1553. }
  1554. .detail-footer .van-button {
  1555. flex: 1;
  1556. height: 44px;
  1557. border-radius: 999px;
  1558. }
  1559. .detail-footer .van-button--primary {
  1560. background: linear-gradient(135deg, #12b37a, #087f57);
  1561. border: none;
  1562. box-shadow: 0 6px 16px rgba(8, 127, 87, 0.28);
  1563. }
  1564. /* ========== 表单 ========== */
  1565. .form-page {
  1566. padding-bottom: 88px;
  1567. }
  1568. .form-section-title {
  1569. margin: 14px 16px 8px;
  1570. font-size: 13px;
  1571. font-weight: 600;
  1572. color: var(--text-muted);
  1573. }
  1574. .form-tip {
  1575. margin: 8px 16px 0;
  1576. font-size: 11px;
  1577. color: var(--text-muted);
  1578. line-height: 1.45;
  1579. }
  1580. .form-footer {
  1581. position: absolute;
  1582. left: 0;
  1583. right: 0;
  1584. bottom: 0;
  1585. display: flex;
  1586. gap: 10px;
  1587. padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
  1588. background: rgba(255, 255, 255, 0.96);
  1589. border-top: 1px solid var(--border);
  1590. backdrop-filter: blur(8px);
  1591. z-index: 3;
  1592. }
  1593. .form-footer .van-button {
  1594. flex: 1;
  1595. height: 44px;
  1596. }
  1597. /* ========== 组件展示 ========== */
  1598. .comps-page {
  1599. flex: 1;
  1600. display: flex;
  1601. flex-direction: column;
  1602. min-height: 0;
  1603. background:
  1604. radial-gradient(400px 220px at 100% 0%, rgba(214, 163, 67, 0.14), transparent 55%),
  1605. radial-gradient(360px 200px at 0% 20%, rgba(10, 159, 109, 0.1), transparent 50%),
  1606. linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 40%, #f7f9f8 100%);
  1607. }
  1608. .comps-page .screen-scroll {
  1609. background: transparent;
  1610. }
  1611. .comps-hero {
  1612. background:
  1613. radial-gradient(220px 140px at 90% 15%, rgba(255, 255, 255, 0.18), transparent 60%),
  1614. linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
  1615. padding: 44px 14px 22px;
  1616. border-radius: 0 0 28px 28px;
  1617. color: #fff;
  1618. position: relative;
  1619. overflow: hidden;
  1620. }
  1621. .comps-hero::after {
  1622. content: "";
  1623. position: absolute;
  1624. width: 160px;
  1625. height: 160px;
  1626. right: -40px;
  1627. bottom: -60px;
  1628. border-radius: 50%;
  1629. background: rgba(255, 255, 255, 0.08);
  1630. pointer-events: none;
  1631. }
  1632. .comps-hero .van-nav-bar {
  1633. background: transparent;
  1634. margin: 0 -6px;
  1635. }
  1636. .comps-hero .van-hairline--bottom::after {
  1637. display: none;
  1638. }
  1639. .comps-hero-copy {
  1640. position: relative;
  1641. z-index: 1;
  1642. margin: 4px 2px 0;
  1643. }
  1644. .comps-hero-copy .eyebrow {
  1645. font-size: 11px;
  1646. letter-spacing: 0.08em;
  1647. opacity: 0.82;
  1648. margin-bottom: 4px;
  1649. }
  1650. .comps-hero-copy h2 {
  1651. margin: 0;
  1652. font-size: 22px;
  1653. font-weight: 750;
  1654. }
  1655. .comps-hero-copy .subline {
  1656. margin-top: 6px;
  1657. font-size: 12px;
  1658. opacity: 0.9;
  1659. line-height: 1.45;
  1660. }
  1661. .comp-section {
  1662. padding: 14px 12px 28px;
  1663. }
  1664. .comp-block {
  1665. position: relative;
  1666. background: #fff;
  1667. border: 1px solid rgba(220, 235, 228, 0.95);
  1668. border-radius: 18px;
  1669. padding: 0;
  1670. margin-bottom: 12px;
  1671. box-shadow: 0 8px 22px rgba(20, 55, 40, 0.05);
  1672. overflow: hidden;
  1673. }
  1674. .comp-block .comp-hd {
  1675. display: flex;
  1676. align-items: center;
  1677. gap: 10px;
  1678. padding: 12px 14px;
  1679. background: linear-gradient(135deg, #f3faf6 0%, #ffffff 70%);
  1680. border-bottom: 1px solid #eef4f1;
  1681. }
  1682. .comp-block .comp-hd .hd-icon {
  1683. width: 32px;
  1684. height: 32px;
  1685. border-radius: 10px;
  1686. display: flex;
  1687. align-items: center;
  1688. justify-content: center;
  1689. font-size: 16px;
  1690. color: #fff;
  1691. background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
  1692. box-shadow: 0 4px 10px rgba(10, 159, 109, 0.22);
  1693. flex-shrink: 0;
  1694. }
  1695. .comp-block .comp-hd .hd-icon.tone-amber {
  1696. background: linear-gradient(145deg, #ecc06a, #d6a343);
  1697. box-shadow: 0 4px 10px rgba(214, 163, 67, 0.25);
  1698. }
  1699. .comp-block .comp-hd .hd-icon.tone-rose {
  1700. background: linear-gradient(145deg, #f07171, #d14343);
  1701. box-shadow: 0 4px 10px rgba(209, 67, 67, 0.22);
  1702. }
  1703. .comp-block .comp-hd .hd-icon.tone-slate {
  1704. background: linear-gradient(145deg, #8aa198, #5d746a);
  1705. box-shadow: 0 4px 10px rgba(93, 116, 106, 0.2);
  1706. }
  1707. .comp-block .comp-hd h4 {
  1708. margin: 0;
  1709. font-size: 14px;
  1710. font-weight: 750;
  1711. color: #173128;
  1712. line-height: 1.2;
  1713. }
  1714. .comp-block .comp-hd .hd-sub {
  1715. margin-top: 2px;
  1716. font-size: 11px;
  1717. color: #8aa198;
  1718. }
  1719. .comp-block .comp-bd {
  1720. padding: 14px;
  1721. }
  1722. .comp-row {
  1723. display: flex;
  1724. flex-wrap: wrap;
  1725. gap: 8px;
  1726. align-items: center;
  1727. margin-bottom: 10px;
  1728. }
  1729. .comp-row:last-child { margin-bottom: 0; }
  1730. .label-muted {
  1731. font-size: 11px;
  1732. color: #8aa198;
  1733. width: 100%;
  1734. margin-bottom: 2px;
  1735. }
  1736. .comps-page .van-button--primary {
  1737. background: linear-gradient(135deg, #12b37a, #087f57);
  1738. border: none;
  1739. box-shadow: 0 4px 12px rgba(8, 127, 87, 0.22);
  1740. }
  1741. .comps-page .van-button--primary.van-button--plain {
  1742. background: #fff;
  1743. box-shadow: none;
  1744. color: #0a9f6d;
  1745. border-color: #0a9f6d;
  1746. }
  1747. .comps-page .van-button--round {
  1748. border-radius: 999px;
  1749. }
  1750. .tag-grid {
  1751. display: flex;
  1752. flex-wrap: wrap;
  1753. gap: 8px;
  1754. }
  1755. .demo-tag {
  1756. display: inline-flex;
  1757. align-items: center;
  1758. gap: 5px;
  1759. padding: 5px 10px;
  1760. border-radius: 999px;
  1761. font-size: 12px;
  1762. font-weight: 650;
  1763. }
  1764. .demo-tag i {
  1765. width: 6px;
  1766. height: 6px;
  1767. border-radius: 50%;
  1768. background: currentColor;
  1769. }
  1770. .demo-tag.t-warn {
  1771. background: #fff3e0;
  1772. color: #a67b20;
  1773. }
  1774. .demo-tag.t-primary {
  1775. background: #0a9f6d;
  1776. color: #fff;
  1777. }
  1778. .demo-tag.t-success {
  1779. background: rgba(24, 155, 101, 0.12);
  1780. color: #147a50;
  1781. }
  1782. .demo-tag.t-danger {
  1783. background: #fdecec;
  1784. color: #d14343;
  1785. }
  1786. .demo-tag.t-muted {
  1787. background: #eef2f1;
  1788. color: #6b7b74;
  1789. }
  1790. .comp-cell-list {
  1791. display: flex;
  1792. flex-direction: column;
  1793. gap: 8px;
  1794. }
  1795. .comp-cell-item {
  1796. display: flex;
  1797. align-items: center;
  1798. gap: 12px;
  1799. padding: 12px 12px;
  1800. border-radius: 14px;
  1801. background: linear-gradient(160deg, #f7fbf9 0%, #ffffff 55%);
  1802. border: 1px solid #e4eee9;
  1803. box-shadow: 0 4px 12px rgba(20, 55, 40, 0.04);
  1804. cursor: pointer;
  1805. text-align: left;
  1806. width: 100%;
  1807. transition: transform .15s, border-color .15s;
  1808. }
  1809. .comp-cell-item:active {
  1810. transform: scale(0.99);
  1811. border-color: #b7e5d0;
  1812. }
  1813. .comp-cell-item .ci-icon {
  1814. width: 40px;
  1815. height: 40px;
  1816. border-radius: 12px;
  1817. display: flex;
  1818. align-items: center;
  1819. justify-content: center;
  1820. font-size: 18px;
  1821. color: #fff;
  1822. flex-shrink: 0;
  1823. background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
  1824. box-shadow: 0 4px 10px rgba(10, 159, 109, 0.2);
  1825. }
  1826. .comp-cell-item .ci-icon.tone-amber {
  1827. background: linear-gradient(145deg, #ecc06a, #d6a343);
  1828. box-shadow: 0 4px 10px rgba(214, 163, 67, 0.22);
  1829. }
  1830. .comp-cell-item .ci-icon.tone-slate {
  1831. background: linear-gradient(145deg, #8aa198, #5d746a);
  1832. box-shadow: 0 4px 10px rgba(93, 116, 106, 0.18);
  1833. }
  1834. .comp-cell-item .ci-body {
  1835. flex: 1;
  1836. min-width: 0;
  1837. }
  1838. .comp-cell-item .ci-title {
  1839. font-size: 14px;
  1840. font-weight: 700;
  1841. color: #173128;
  1842. line-height: 1.3;
  1843. }
  1844. .comp-cell-item .ci-label {
  1845. margin-top: 2px;
  1846. font-size: 11px;
  1847. color: #8aa198;
  1848. line-height: 1.4;
  1849. }
  1850. .comp-cell-item .ci-right {
  1851. display: flex;
  1852. align-items: center;
  1853. gap: 6px;
  1854. flex-shrink: 0;
  1855. color: #8aa198;
  1856. font-size: 13px;
  1857. font-weight: 550;
  1858. }
  1859. .comp-cell-item .ci-right .val {
  1860. color: #4d635a;
  1861. }
  1862. .comp-feedback-grid {
  1863. display: grid;
  1864. grid-template-columns: repeat(3, 1fr);
  1865. gap: 8px;
  1866. }
  1867. .comp-feedback-grid .fb-btn {
  1868. border: 1px solid #e4eee9;
  1869. background: linear-gradient(160deg, #f7fbf9, #fff);
  1870. border-radius: 14px;
  1871. padding: 14px 8px;
  1872. display: flex;
  1873. flex-direction: column;
  1874. align-items: center;
  1875. gap: 8px;
  1876. cursor: pointer;
  1877. color: #173128;
  1878. font-size: 12px;
  1879. font-weight: 650;
  1880. transition: transform .15s;
  1881. }
  1882. .comp-feedback-grid .fb-btn:active {
  1883. transform: scale(0.97);
  1884. }
  1885. .comp-feedback-grid .fb-btn .fb-icon {
  1886. width: 40px;
  1887. height: 40px;
  1888. border-radius: 12px;
  1889. display: flex;
  1890. align-items: center;
  1891. justify-content: center;
  1892. font-size: 20px;
  1893. color: #fff;
  1894. }
  1895. .comp-feedback-grid .fb-btn.fb-toast .fb-icon {
  1896. background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
  1897. }
  1898. .comp-feedback-grid .fb-btn.fb-dialog .fb-icon {
  1899. background: linear-gradient(145deg, #ecc06a, #d6a343);
  1900. }
  1901. .comp-feedback-grid .fb-btn.fb-sheet .fb-icon {
  1902. background: linear-gradient(145deg, #8aa198, #5d746a);
  1903. }
  1904. .comp-steps-wrap {
  1905. padding: 8px 4px 0;
  1906. border-radius: 12px;
  1907. background: #f7faf9;
  1908. }
  1909. .comp-seg {
  1910. display: grid;
  1911. grid-template-columns: repeat(3, 1fr);
  1912. gap: 4px;
  1913. padding: 4px;
  1914. border-radius: 14px;
  1915. background: #eef6f2;
  1916. margin-bottom: 12px;
  1917. }
  1918. .comp-seg button {
  1919. border: none;
  1920. background: transparent;
  1921. border-radius: 11px;
  1922. padding: 8px 4px;
  1923. font-size: 12px;
  1924. color: #6b7b74;
  1925. cursor: pointer;
  1926. }
  1927. .comp-seg button.active {
  1928. background: #fff;
  1929. color: #087f57;
  1930. font-weight: 700;
  1931. box-shadow: 0 4px 12px rgba(8, 127, 87, 0.12);
  1932. }
  1933. .comp-notice-stack {
  1934. display: flex;
  1935. flex-direction: column;
  1936. gap: 12px;
  1937. }
  1938. .comp-notice-bar {
  1939. border-radius: 14px;
  1940. overflow: hidden;
  1941. border: 1px solid #c7e6d6;
  1942. box-shadow: 0 4px 12px rgba(10, 159, 109, 0.08);
  1943. min-height: 44px;
  1944. }
  1945. .comp-empty-box {
  1946. border-radius: 16px;
  1947. background: linear-gradient(165deg, #eef9f3 0%, #ffffff 55%);
  1948. border: 1.5px dashed #b7e5d0;
  1949. padding: 28px 16px 24px;
  1950. min-height: 220px;
  1951. display: flex;
  1952. flex-direction: column;
  1953. align-items: center;
  1954. justify-content: center;
  1955. box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8);
  1956. }
  1957. .comp-empty-box .van-empty {
  1958. padding: 0;
  1959. }
  1960. .comp-empty-box .van-empty__description {
  1961. margin-top: 12px;
  1962. color: #5d746a;
  1963. font-size: 13px;
  1964. }
  1965. .comp-empty-box .empty-tip {
  1966. margin-top: 4px;
  1967. font-size: 11px;
  1968. color: #8aa198;
  1969. text-align: center;
  1970. line-height: 1.45;
  1971. }
  1972. .comp-loading-box {
  1973. display: flex;
  1974. flex-direction: column;
  1975. align-items: center;
  1976. justify-content: center;
  1977. gap: 12px;
  1978. padding: 28px 16px;
  1979. min-height: 120px;
  1980. border-radius: 16px;
  1981. background: linear-gradient(165deg, #f3faf6 0%, #ffffff 60%);
  1982. border: 1px solid #e4eee9;
  1983. color: #087f57;
  1984. font-size: 13px;
  1985. font-weight: 650;
  1986. box-shadow: 0 4px 12px rgba(20, 55, 40, 0.04);
  1987. }
  1988. .comp-loading-box .load-sub {
  1989. font-size: 11px;
  1990. font-weight: 500;
  1991. color: #8aa198;
  1992. }
  1993. .comps-page .comp-attach-list {
  1994. display: flex;
  1995. flex-direction: column;
  1996. gap: 10px;
  1997. width: 100%;
  1998. }
  1999. .comps-page .comp-upload-tile {
  2000. width: 100%;
  2001. border: 1.5px dashed #9ad9be;
  2002. border-radius: 16px;
  2003. background:
  2004. radial-gradient(120px 80px at 12% 30%, rgba(46, 207, 143, 0.16), transparent 70%),
  2005. linear-gradient(145deg, #e9f9f2 0%, #ffffff 55%);
  2006. padding: 16px 14px;
  2007. display: flex;
  2008. align-items: center;
  2009. gap: 12px;
  2010. cursor: pointer;
  2011. text-align: left;
  2012. transition: transform .15s, border-color .15s, box-shadow .15s;
  2013. box-shadow: 0 6px 16px rgba(10, 159, 109, 0.08);
  2014. }
  2015. .comps-page .comp-upload-tile:active {
  2016. transform: scale(0.99);
  2017. border-color: #0a9f6d;
  2018. box-shadow: 0 4px 12px rgba(10, 159, 109, 0.12);
  2019. }
  2020. .comps-page .comp-upload-tile .up-icon {
  2021. width: 48px;
  2022. height: 48px;
  2023. border-radius: 14px;
  2024. background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
  2025. color: #fff;
  2026. display: flex;
  2027. align-items: center;
  2028. justify-content: center;
  2029. font-size: 24px;
  2030. box-shadow: 0 6px 14px rgba(10, 159, 109, 0.28);
  2031. flex-shrink: 0;
  2032. }
  2033. .comps-page .comp-upload-tile .up-text {
  2034. flex: 1;
  2035. min-width: 0;
  2036. }
  2037. .comps-page .comp-upload-tile .up-title {
  2038. display: block;
  2039. font-size: 14px;
  2040. font-weight: 750;
  2041. color: #087f57;
  2042. }
  2043. .comps-page .comp-upload-tile .up-sub {
  2044. display: block;
  2045. margin-top: 3px;
  2046. font-size: 11px;
  2047. color: #6b7b74;
  2048. line-height: 1.45;
  2049. }
  2050. .comps-page .comp-file-card {
  2051. display: flex;
  2052. align-items: center;
  2053. gap: 12px;
  2054. padding: 12px;
  2055. border-radius: 14px;
  2056. background: #fff;
  2057. border: 1px solid #e4eee9;
  2058. box-shadow: 0 4px 14px rgba(20, 55, 40, 0.05);
  2059. position: relative;
  2060. overflow: hidden;
  2061. }
  2062. .comps-page .comp-file-card::before {
  2063. content: "";
  2064. position: absolute;
  2065. left: 0;
  2066. top: 0;
  2067. bottom: 0;
  2068. width: 3px;
  2069. background: #3b82f6;
  2070. }
  2071. .comps-page .comp-file-card .file-icon {
  2072. width: 46px;
  2073. height: 46px;
  2074. border-radius: 13px;
  2075. display: flex;
  2076. flex-direction: column;
  2077. align-items: center;
  2078. justify-content: center;
  2079. gap: 1px;
  2080. flex-shrink: 0;
  2081. color: #fff;
  2082. font-weight: 800;
  2083. background: linear-gradient(145deg, #6ea8ff, #3b82f6);
  2084. }
  2085. .comps-page .comp-file-card .file-icon .ext {
  2086. font-size: 10px;
  2087. letter-spacing: 0.04em;
  2088. line-height: 1;
  2089. text-transform: uppercase;
  2090. }
  2091. .comps-page .comp-file-card .file-icon .van-icon {
  2092. font-size: 16px;
  2093. }
  2094. .comps-page .comp-file-card .file-body {
  2095. flex: 1;
  2096. min-width: 0;
  2097. }
  2098. .comps-page .comp-file-card .file-name {
  2099. font-size: 13px;
  2100. font-weight: 650;
  2101. color: #173128;
  2102. line-height: 1.35;
  2103. white-space: nowrap;
  2104. overflow: hidden;
  2105. text-overflow: ellipsis;
  2106. }
  2107. .comps-page .comp-file-card .file-meta {
  2108. margin-top: 4px;
  2109. display: flex;
  2110. flex-wrap: wrap;
  2111. gap: 8px;
  2112. font-size: 11px;
  2113. color: #8aa198;
  2114. }
  2115. .comps-page .comp-file-card .file-ops {
  2116. display: flex;
  2117. flex-direction: column;
  2118. gap: 6px;
  2119. flex-shrink: 0;
  2120. }
  2121. .comps-page .comp-file-card .file-ops button {
  2122. border: none;
  2123. cursor: pointer;
  2124. width: 32px;
  2125. height: 32px;
  2126. border-radius: 10px;
  2127. display: flex;
  2128. align-items: center;
  2129. justify-content: center;
  2130. font-size: 16px;
  2131. }
  2132. .comps-page .comp-file-card .file-ops .btn-preview {
  2133. background: var(--brand-green-soft);
  2134. color: var(--brand-green-dark);
  2135. }
  2136. .comps-page .comp-file-card .file-ops .btn-del {
  2137. background: #fdecec;
  2138. color: #d14343;
  2139. }
  2140. /* 表单页等仍可能用到的旧附件样式 */
  2141. .upload-card {
  2142. width: 88px;
  2143. height: 88px;
  2144. border: 1.5px dashed #9ad9be;
  2145. border-radius: 14px;
  2146. display: flex;
  2147. flex-direction: column;
  2148. align-items: center;
  2149. justify-content: center;
  2150. gap: 6px;
  2151. color: #087f57;
  2152. background: linear-gradient(145deg, #e9f9f2, #fff);
  2153. font-size: 11px;
  2154. font-weight: 650;
  2155. }
  2156. .attach-card {
  2157. width: 140px;
  2158. min-height: 88px;
  2159. border: 1px solid #e4eee9;
  2160. border-radius: 14px;
  2161. padding: 10px 12px;
  2162. background: #fff;
  2163. display: flex;
  2164. flex-direction: column;
  2165. justify-content: space-between;
  2166. font-size: 11px;
  2167. }
  2168. .attach-card .name {
  2169. color: #173128;
  2170. font-weight: 650;
  2171. word-break: break-all;
  2172. line-height: 1.35;
  2173. }
  2174. .attach-card .ops {
  2175. display: flex;
  2176. gap: 10px;
  2177. color: #0a9f6d;
  2178. font-weight: 650;
  2179. }
  2180. .demo-badge {
  2181. display: inline-flex;
  2182. align-items: center;
  2183. gap: 4px;
  2184. padding: 2px 8px;
  2185. border-radius: 999px;
  2186. font-size: 11px;
  2187. background: var(--brand-green-soft);
  2188. color: var(--brand-green-dark);
  2189. }
  2190. @media (max-width: 900px) {
  2191. #app {
  2192. grid-template-columns: 1fr;
  2193. }
  2194. .doc-panel {
  2195. position: static;
  2196. }
  2197. .phone {
  2198. width: min(390px, 100%);
  2199. height: min(844px, calc(100vh - 120px));
  2200. min-height: 640px;
  2201. }
  2202. }
  2203. @media (prefers-reduced-motion: reduce) {
  2204. * { transition: none !important; }
  2205. }
  2206. </style>
  2207. </head>
  2208. <body>
  2209. <div id="app">
  2210. <aside class="doc-panel">
  2211. <h1>移动端 UI Demo</h1>
  2212. <p class="sub">
  2213. Vant 4 + 三墙 Token(#0a9f6d)。首页为权限菜单卡片;列表 / 新增编辑 / 组件风格可在下方切换预览。
  2214. </p>
  2215. <div class="section-title">页面预览</div>
  2216. <div class="screen-nav">
  2217. <button
  2218. v-for="s in screens"
  2219. :key="s.key"
  2220. :class="{ active: screen === s.key }"
  2221. type="button"
  2222. @click="goScreen(s.key)"
  2223. >{{ s.label }}</button>
  2224. </div>
  2225. <div class="section-title">设计 Token</div>
  2226. <div class="token-row">
  2227. <span class="token-chip"><i style="background:#0a9f6d"></i>#0a9f6d</span>
  2228. <span class="token-chip"><i style="background:#087f57"></i>#087f57</span>
  2229. <span class="token-chip"><i style="background:#e9f9f2"></i>#e9f9f2</span>
  2230. <span class="token-chip"><i style="background:#d6a343"></i>#d6a343</span>
  2231. <span class="token-chip"><i style="background:#f5f8f7"></i>#f5f8f7</span>
  2232. </div>
  2233. <div class="section-title">权限说明</div>
  2234. <ul class="notes">
  2235. <li>菜单来自「菜单管理 → 移动端菜单」树</li>
  2236. <li>仅展示当前登录人角色/职位拥有的菜单权限</li>
  2237. <li>卡片:上 Icon、下标题;建议 4 列宫格</li>
  2238. <li>本 Demo 用右侧开关模拟「无某菜单权限」</li>
  2239. </ul>
  2240. <div class="section-title" v-if="screen === 'home'">权限模拟</div>
  2241. <div v-if="screen === 'home'" style="display:flex;flex-direction:column;gap:8px;">
  2242. <van-checkbox
  2243. v-for="m in allMenus"
  2244. :key="m.id"
  2245. v-model="grantedMap[m.id]"
  2246. shape="square"
  2247. icon-size="16px"
  2248. >{{ m.title }}</van-checkbox>
  2249. </div>
  2250. </aside>
  2251. <div class="phone-stage">
  2252. <div class="phone">
  2253. <div class="phone-screen">
  2254. <!-- ===== 首页 ===== -->
  2255. <template v-if="screen === 'home'">
  2256. <div class="screen-scroll">
  2257. <header class="home-header">
  2258. <div class="brand">中科兴皖 · 移动工作台</div>
  2259. <h2>你好,{{ userName }}</h2>
  2260. <div class="meta">
  2261. <span>{{ roleName }}</span>
  2262. <span class="dot"></span>
  2263. <span>{{ deptName }}</span>
  2264. </div>
  2265. </header>
  2266. <div class="home-body">
  2267. <div class="perm-banner">
  2268. 菜单卡片按角色权限动态渲染;无权限的功能入口不会出现在宫格中。
  2269. </div>
  2270. <section class="panel">
  2271. <div class="panel-hd">
  2272. <h3>功能菜单</h3>
  2273. <span class="hint">共 {{ visibleMenus.length }} 项</span>
  2274. </div>
  2275. <div v-if="visibleMenus.length" class="menu-grid">
  2276. <button
  2277. v-for="m in visibleMenus"
  2278. :key="m.id"
  2279. type="button"
  2280. class="menu-card"
  2281. @click="onMenuTap(m)"
  2282. >
  2283. <div class="menu-icon" :class="m.tone">
  2284. <van-icon :name="m.icon" />
  2285. </div>
  2286. <span class="menu-title">{{ m.title }}</span>
  2287. </button>
  2288. </div>
  2289. <div v-else class="list-empty-pad">
  2290. <van-empty description="暂无可用菜单权限" image-size="80" />
  2291. </div>
  2292. </section>
  2293. <section class="panel todo-panel">
  2294. <div class="panel-hd">
  2295. <h3>待办摘要</h3>
  2296. <button type="button" class="demo-badge" style="border:none;cursor:pointer;" @click="screen = 'list'">
  2297. 查看全部
  2298. <van-icon name="arrow" style="font-size:11px;" />
  2299. </button>
  2300. </div>
  2301. <div class="todo-stats">
  2302. <button type="button" class="todo-stat tone-warn" @click="goListTab(2)">
  2303. <span class="stat-icon"><van-icon name="passed" /></span>
  2304. <span class="num">{{ todoStats.approve }}</span>
  2305. <span class="label">待审批</span>
  2306. </button>
  2307. <button type="button" class="todo-stat tone-pay" @click="goListTab(2)">
  2308. <span class="stat-icon"><van-icon name="balance-pay" /></span>
  2309. <span class="num">{{ todoStats.pay }}</span>
  2310. <span class="label">待确认缴费</span>
  2311. </button>
  2312. <button type="button" class="todo-stat tone-danger" @click="goListTab(0)">
  2313. <span class="stat-icon"><van-icon name="warning-o" /></span>
  2314. <span class="num">{{ todoStats.alert }}</span>
  2315. <span class="label">异常提醒</span>
  2316. </button>
  2317. </div>
  2318. <div class="todo-list">
  2319. <button
  2320. v-for="t in todoItems"
  2321. :key="t.id"
  2322. type="button"
  2323. class="todo-item"
  2324. @click="screen = 'list'"
  2325. >
  2326. <div class="ti-icon" :class="t.iconClass">
  2327. <van-icon :name="t.icon" />
  2328. </div>
  2329. <div class="ti-body">
  2330. <div class="ti-title">{{ t.title }}</div>
  2331. <div class="ti-desc">{{ t.desc }}</div>
  2332. </div>
  2333. <div class="ti-right">
  2334. <van-tag :type="t.tagType" plain size="medium">{{ t.tag }}</van-tag>
  2335. <span class="ti-time">{{ t.time }}</span>
  2336. </div>
  2337. </button>
  2338. </div>
  2339. </section>
  2340. </div>
  2341. </div>
  2342. </template>
  2343. <!-- ===== 列表 ===== -->
  2344. <template v-else-if="screen === 'list'">
  2345. <div class="list-page">
  2346. <div class="screen-scroll">
  2347. <header class="list-hero">
  2348. <van-nav-bar left-arrow @click-left="screen = 'home'">
  2349. <template #title>
  2350. <span style="font-weight:600;">业务列表</span>
  2351. </template>
  2352. <template #right>
  2353. <van-icon name="plus" size="20" @click="openForm('create')" />
  2354. </template>
  2355. </van-nav-bar>
  2356. <div class="list-hero-copy">
  2357. <div class="eyebrow">WORKSPACE</div>
  2358. <h2>今日待办一览</h2>
  2359. <div class="subline">
  2360. <span>当前展示</span>
  2361. <em>{{ filteredList.length }} 条</em>
  2362. <span v-if="keyword">· 已筛选</span>
  2363. </div>
  2364. </div>
  2365. <div class="list-search">
  2366. <van-search
  2367. v-model="keyword"
  2368. shape="round"
  2369. background="transparent"
  2370. placeholder="搜索单号 / 名称 / 负责人"
  2371. />
  2372. </div>
  2373. </header>
  2374. <div class="list-filter-dock">
  2375. <div class="seg-tabs">
  2376. <button
  2377. v-for="(tab, idx) in listTabs"
  2378. :key="tab.key"
  2379. type="button"
  2380. class="seg-tab"
  2381. :class="{ active: listTab === idx }"
  2382. @click="listTab = idx"
  2383. >
  2384. <span class="lab">{{ tab.label }}</span>
  2385. <span class="count">{{ tabCount(idx) }}</span>
  2386. </button>
  2387. </div>
  2388. </div>
  2389. <div class="list-wrap">
  2390. <article
  2391. v-for="item in filteredList"
  2392. :key="item.id"
  2393. class="biz-card"
  2394. :class="'status-' + item.tagType"
  2395. >
  2396. <div class="card-inner">
  2397. <div class="row-top">
  2398. <div class="type-icon">
  2399. <van-icon :name="item.icon" />
  2400. </div>
  2401. <div class="title-wrap">
  2402. <div class="type-chip">{{ item.typeLabel }}</div>
  2403. <div class="title">{{ item.title }}</div>
  2404. </div>
  2405. <span class="status-pill">{{ item.status }}</span>
  2406. </div>
  2407. <div class="body-row">
  2408. <p class="desc-box">{{ item.desc }}</p>
  2409. <div class="side-metric">
  2410. <template v-if="item.amount">
  2411. <span class="amt-label">金额</span>
  2412. <span class="amt">¥{{ item.amount }}</span>
  2413. </template>
  2414. <template v-else>
  2415. <span class="amt-label">进度</span>
  2416. <span class="amt" style="font-size:13px;">{{ item.progress || '—' }}</span>
  2417. </template>
  2418. <span class="time">{{ item.time }}</span>
  2419. </div>
  2420. </div>
  2421. <div class="meta-row">
  2422. <span class="mi code-pill">{{ item.code }}</span>
  2423. <span class="mi"><van-icon name="manager-o" />{{ item.owner }}</span>
  2424. </div>
  2425. <div class="footer">
  2426. <span class="left-hint"><i></i>{{ item.hint || '点击操作处理' }}</span>
  2427. <div class="ops">
  2428. <van-button size="small" plain hairline type="default" @click="openDetail(item)">查看</van-button>
  2429. <van-button size="small" type="primary" @click="openForm('edit', item)">处理</van-button>
  2430. </div>
  2431. </div>
  2432. </div>
  2433. </article>
  2434. <div v-if="!filteredList.length" class="list-empty-pad">
  2435. <van-empty description="暂无数据" />
  2436. </div>
  2437. <div v-else class="list-end"><span>已经到底了</span></div>
  2438. </div>
  2439. </div>
  2440. </div>
  2441. </template>
  2442. <!-- ===== 详情 ===== -->
  2443. <template v-else-if="screen === 'detail'">
  2444. <div class="detail-page" v-if="detail">
  2445. <div class="screen-scroll">
  2446. <header class="detail-hero" :class="'status-' + detail.tagType">
  2447. <van-nav-bar left-arrow @click-left="screen = 'list'">
  2448. <template #title>
  2449. <span style="font-weight:600;">业务详情</span>
  2450. </template>
  2451. <template #right>
  2452. <van-icon name="ellipsis" size="20" @click="showAction = true" />
  2453. </template>
  2454. </van-nav-bar>
  2455. <div class="detail-hero-body">
  2456. <div class="type-line">
  2457. <span class="type-chip">{{ detail.typeLabel }}</span>
  2458. <span class="status-chip">{{ detail.status }}</span>
  2459. </div>
  2460. <h2>{{ detail.title }}</h2>
  2461. <div class="code">{{ detail.code }}</div>
  2462. </div>
  2463. </header>
  2464. <div class="detail-metric-dock">
  2465. <div class="detail-metric">
  2466. <span class="lab">{{ detail.amount ? '金额' : '进度' }}</span>
  2467. <span class="val" :class="{ money: !!detail.amount }">
  2468. {{ detail.amount ? ('¥' + detail.amount) : (detail.progress || '—') }}
  2469. </span>
  2470. </div>
  2471. <div class="detail-metric">
  2472. <span class="lab">负责人</span>
  2473. <span class="val">{{ detail.owner }}</span>
  2474. </div>
  2475. <div class="detail-metric">
  2476. <span class="lab">更新时间</span>
  2477. <span class="val">{{ detail.time }}</span>
  2478. </div>
  2479. </div>
  2480. <div class="detail-body">
  2481. <section class="detail-section">
  2482. <div class="sec-hd"><h3>基础信息</h3></div>
  2483. <div class="kv-list">
  2484. <div class="kv-row"><span class="k">业务类型</span><span class="v">{{ detail.typeLabel }}</span></div>
  2485. <div class="kv-row"><span class="k">所属部门</span><span class="v">{{ detail.dept || deptName }}</span></div>
  2486. <div class="kv-row"><span class="k">客户/对象</span><span class="v">{{ detail.customer || '—' }}</span></div>
  2487. <div class="kv-row"><span class="k">创建时间</span><span class="v">{{ detail.createdAt || '—' }}</span></div>
  2488. </div>
  2489. <p class="detail-note" style="margin-top:12px;">{{ detail.desc }}</p>
  2490. </section>
  2491. <section class="detail-section">
  2492. <div class="sec-hd"><h3>业务进度</h3></div>
  2493. <van-steps :active="detail.stepActive" active-color="#0a9f6d" inactive-color="#dcebe4">
  2494. <van-step v-for="s in detail.steps" :key="s">{{ s }}</van-step>
  2495. </van-steps>
  2496. </section>
  2497. <section class="detail-section" v-if="detail.payments && detail.payments.length">
  2498. <div class="sec-hd">
  2499. <h3>缴费明细</h3>
  2500. <button type="button" class="more" @click="toast('查看全部缴费')">全部</button>
  2501. </div>
  2502. <div
  2503. v-for="p in detail.payments"
  2504. :key="p.id"
  2505. class="pay-row"
  2506. >
  2507. <div class="left">
  2508. <div class="name">{{ p.name }}</div>
  2509. <div class="sub">{{ p.sub }}</div>
  2510. </div>
  2511. <div class="right">
  2512. <div class="amt">¥{{ p.amount }}</div>
  2513. <van-tag :type="p.tagType" plain size="medium" style="margin-top:4px;">{{ p.status }}</van-tag>
  2514. </div>
  2515. </div>
  2516. </section>
  2517. <section class="detail-section">
  2518. <div class="sec-hd">
  2519. <h3>附件</h3>
  2520. <span style="font-size:11px;color:#8aa198;">{{ detail.attachments.length }} 个文件</span>
  2521. </div>
  2522. <div class="d-attach-list">
  2523. <button type="button" class="d-attach-upload" @click="toast('上传附件')">
  2524. <span class="up-icon"><van-icon name="plus" /></span>
  2525. <span class="up-text">
  2526. <span class="up-title">添加附件</span>
  2527. <span class="up-sub">支持 PDF / 图片 / Office,可多选上传</span>
  2528. </span>
  2529. <van-icon name="arrow" color="#0a9f6d" />
  2530. </button>
  2531. <div
  2532. v-for="a in detail.attachments"
  2533. :key="a.name"
  2534. class="d-attach-card"
  2535. :class="fileTypeClass(a.name)"
  2536. >
  2537. <div class="file-icon">
  2538. <van-icon :name="fileTypeIcon(a.name)" />
  2539. <span class="ext">{{ fileExt(a.name) }}</span>
  2540. </div>
  2541. <div class="file-body">
  2542. <div class="file-name">{{ a.name }}</div>
  2543. <div class="file-meta">
  2544. <span>{{ a.size || '—' }}</span>
  2545. <span>{{ a.time || '—' }}</span>
  2546. </div>
  2547. </div>
  2548. <div class="file-ops">
  2549. <button type="button" class="btn-preview" title="预览" @click="toast('预览 ' + a.name)">
  2550. <van-icon name="eye-o" />
  2551. </button>
  2552. <button type="button" class="btn-download" title="下载" @click="toast('下载 ' + a.name)">
  2553. <svg class="icon-download" viewBox="0 0 24 24" aria-hidden="true">
  2554. <path fill="currentColor" d="M12 3a1 1 0 0 1 1 1v8.59l2.3-2.3a1 1 0 1 1 1.4 1.42l-4 4a1 1 0 0 1-1.4 0l-4-4a1 1 0 1 1 1.4-1.42L11 12.59V4a1 1 0 0 1 1-1Z"/>
  2555. <path fill="currentColor" d="M5 15a1 1 0 0 1 1 1v2h12v-2a1 1 0 1 1 2 0v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2a1 1 0 0 1 1-1Z"/>
  2556. </svg>
  2557. </button>
  2558. </div>
  2559. </div>
  2560. </div>
  2561. </section>
  2562. <section class="detail-section">
  2563. <div class="sec-hd">
  2564. <h3>流转记录</h3>
  2565. <span style="font-size:11px;color:#8aa198;">{{ detail.logs.length }} 步</span>
  2566. </div>
  2567. <div class="flow-list">
  2568. <div
  2569. v-for="(log, idx) in detail.logs"
  2570. :key="idx"
  2571. class="flow-item"
  2572. :class="log.tone"
  2573. >
  2574. <div class="flow-rail">
  2575. <div class="flow-dot">
  2576. <van-icon :name="log.icon || 'checked'" />
  2577. </div>
  2578. </div>
  2579. <div class="flow-card">
  2580. <div class="flow-top">
  2581. <div class="flow-title">{{ log.title }}</div>
  2582. <span class="flow-badge">{{ log.badge || (log.tone === 'warn' ? '进行中' : '已完成') }}</span>
  2583. </div>
  2584. <div class="flow-desc">{{ log.desc }}</div>
  2585. <div class="flow-meta">
  2586. <span class="chip" v-if="log.actor">
  2587. <van-icon name="manager-o" />{{ log.actor }}
  2588. </span>
  2589. <span class="chip">
  2590. <van-icon name="clock-o" />{{ log.time }}
  2591. </span>
  2592. </div>
  2593. </div>
  2594. </div>
  2595. </div>
  2596. </section>
  2597. </div>
  2598. </div>
  2599. <div class="detail-footer">
  2600. <van-button plain hairline type="default" @click="screen = 'list'">返回</van-button>
  2601. <van-button type="primary" @click="openForm('edit', detail)">编辑处理</van-button>
  2602. </div>
  2603. </div>
  2604. </template>
  2605. <!-- ===== 新增 / 编辑 ===== -->
  2606. <template v-else-if="screen === 'form'">
  2607. <div class="page-nav">
  2608. <van-nav-bar
  2609. :title="formMode === 'create' ? '新增业务' : '编辑业务'"
  2610. left-arrow
  2611. @click-left="screen = 'list'"
  2612. />
  2613. </div>
  2614. <div class="screen-scroll form-page">
  2615. <div class="form-section-title">基础信息</div>
  2616. <van-form @submit="onSubmit">
  2617. <van-cell-group inset>
  2618. <van-field
  2619. v-model="form.name"
  2620. name="name"
  2621. label="名称"
  2622. placeholder="请输入名称"
  2623. :rules="[{ required: true, message: '请填写名称' }]"
  2624. />
  2625. <van-field
  2626. v-model="form.code"
  2627. name="code"
  2628. label="业务单号"
  2629. placeholder="系统自动生成或手工录入"
  2630. />
  2631. <van-field
  2632. v-model="form.typeLabel"
  2633. is-link
  2634. readonly
  2635. name="type"
  2636. label="类型"
  2637. placeholder="请选择类型"
  2638. @click="showTypePicker = true"
  2639. />
  2640. <van-field name="urgent" label="加急">
  2641. <template #input>
  2642. <van-switch v-model="form.urgent" size="20px" active-color="#0a9f6d" />
  2643. </template>
  2644. </van-field>
  2645. </van-cell-group>
  2646. <div class="form-section-title">业务详情</div>
  2647. <van-cell-group inset>
  2648. <van-field
  2649. v-model="form.owner"
  2650. name="owner"
  2651. label="负责人"
  2652. placeholder="请输入负责人"
  2653. />
  2654. <van-field
  2655. v-model="form.date"
  2656. is-link
  2657. readonly
  2658. name="date"
  2659. label="业务日期"
  2660. placeholder="选择日期"
  2661. @click="showDate = true"
  2662. />
  2663. <van-field
  2664. v-model="form.amount"
  2665. type="number"
  2666. name="amount"
  2667. label="金额"
  2668. placeholder="0.00"
  2669. >
  2670. <template #right-icon>元</template>
  2671. </van-field>
  2672. <van-field
  2673. v-model="form.remark"
  2674. rows="3"
  2675. autosize
  2676. type="textarea"
  2677. maxlength="200"
  2678. name="remark"
  2679. label="备注"
  2680. placeholder="选填,最多 200 字"
  2681. show-word-limit
  2682. />
  2683. </van-cell-group>
  2684. <div class="form-section-title">附件</div>
  2685. <van-cell-group inset>
  2686. <van-cell title="协议附件" label="支持多附件,逗号分隔存储">
  2687. <template #value>
  2688. <div style="display:flex;gap:8px;justify-content:flex-end;">
  2689. <div class="upload-card">
  2690. <van-icon name="plus" size="20" />
  2691. <span>上传</span>
  2692. </div>
  2693. </div>
  2694. </template>
  2695. </van-cell>
  2696. <van-cell>
  2697. <div style="display:flex;gap:8px;flex-wrap:wrap;">
  2698. <div class="attach-card">
  2699. <div class="name">协议扫描件.pdf</div>
  2700. <div class="ops">
  2701. <span>预览</span>
  2702. <span>删除</span>
  2703. </div>
  2704. </div>
  2705. </div>
  2706. </van-cell>
  2707. </van-cell-group>
  2708. <p class="form-tip">表单:label 在左、控件在右(Vant Field 默认);必填项校验失败时 Toast / 字段红字提示。底栏固定「取消 / 保存」。</p>
  2709. <div class="form-footer">
  2710. <van-button plain hairline type="default" @click="screen = 'list'">取消</van-button>
  2711. <van-button round block type="primary" native-type="submit">保存</van-button>
  2712. </div>
  2713. </van-form>
  2714. </div>
  2715. <van-popup v-model:show="showTypePicker" position="bottom" round>
  2716. <van-picker
  2717. :columns="typeColumns"
  2718. @confirm="onTypeConfirm"
  2719. @cancel="showTypePicker = false"
  2720. />
  2721. </van-popup>
  2722. <van-calendar
  2723. v-model:show="showDate"
  2724. @confirm="onDateConfirm"
  2725. color="#0a9f6d"
  2726. />
  2727. </template>
  2728. <!-- ===== 组件风格 ===== -->
  2729. <template v-else>
  2730. <div class="comps-page">
  2731. <div class="screen-scroll">
  2732. <header class="comps-hero">
  2733. <van-nav-bar left-arrow @click-left="screen = 'home'">
  2734. <template #title>
  2735. <span style="font-weight:600;">组件风格</span>
  2736. </template>
  2737. </van-nav-bar>
  2738. <div class="comps-hero-copy">
  2739. <div class="eyebrow">DESIGN SYSTEM</div>
  2740. <h2>组件合集</h2>
  2741. <div class="subline">按钮 · 状态 · 反馈 · 步骤 · 附件等基础样式规范</div>
  2742. </div>
  2743. </header>
  2744. <div class="comp-section">
  2745. <section class="comp-block">
  2746. <div class="comp-hd">
  2747. <div class="hd-icon"><van-icon name="font-o" /></div>
  2748. <div>
  2749. <h4>按钮 Button</h4>
  2750. <div class="hd-sub">每组仅一个实心主色行动</div>
  2751. </div>
  2752. </div>
  2753. <div class="comp-bd">
  2754. <div class="comp-row">
  2755. <span class="label-muted">常用尺寸</span>
  2756. <van-button type="primary" size="small" round>主按钮</van-button>
  2757. <van-button type="primary" size="small" plain hairline round>描边主色</van-button>
  2758. <van-button type="default" size="small" plain hairline round>次要</van-button>
  2759. <van-button type="danger" size="small" plain hairline round>危险</van-button>
  2760. </div>
  2761. <div class="comp-row">
  2762. <van-button type="primary" block round style="height:44px;">通栏主按钮(44px)</van-button>
  2763. </div>
  2764. </div>
  2765. </section>
  2766. <section class="comp-block status-demo">
  2767. <div class="comp-hd">
  2768. <div class="hd-icon tone-amber"><van-icon name="label-o" /></div>
  2769. <div>
  2770. <h4>状态 Tag</h4>
  2771. <div class="hd-sub">语义色:待处理 / 进行中 / 完成 / 异常</div>
  2772. </div>
  2773. </div>
  2774. <div class="comp-bd">
  2775. <div class="tag-grid">
  2776. <span class="demo-tag t-warn"><i></i>待处理</span>
  2777. <span class="demo-tag t-primary"><i></i>进行中</span>
  2778. <span class="demo-tag t-success"><i></i>已完成</span>
  2779. <span class="demo-tag t-danger"><i></i>异常</span>
  2780. <span class="demo-tag t-muted"><i></i>已作废</span>
  2781. </div>
  2782. <div class="comp-row" style="margin-top:12px;">
  2783. <span class="label-muted">Vant Tag 兼容</span>
  2784. <van-tag type="warning" plain round>待处理</van-tag>
  2785. <van-tag type="primary" round>进行中</van-tag>
  2786. <van-tag type="success" plain round>已完成</van-tag>
  2787. <van-tag type="danger" plain round>异常</van-tag>
  2788. </div>
  2789. </div>
  2790. </section>
  2791. <section class="comp-block">
  2792. <div class="comp-hd">
  2793. <div class="hd-icon tone-slate"><van-icon name="orders-o" /></div>
  2794. <div>
  2795. <h4>单元格 Cell</h4>
  2796. <div class="hd-sub">分组列表与跳转行</div>
  2797. </div>
  2798. </div>
  2799. <div class="comp-bd">
  2800. <div class="comp-cell-list">
  2801. <button type="button" class="comp-cell-item" @click="toast('查看内容')">
  2802. <span class="ci-icon"><van-icon name="orders-o" /></span>
  2803. <span class="ci-body">
  2804. <span class="ci-title">标题</span>
  2805. <span class="ci-label">基础信息展示</span>
  2806. </span>
  2807. <span class="ci-right"><span class="val">内容</span></span>
  2808. </button>
  2809. <button type="button" class="comp-cell-item" @click="toast('跳转详情')">
  2810. <span class="ci-icon tone-amber"><van-icon name="guide-o" /></span>
  2811. <span class="ci-body">
  2812. <span class="ci-title">可跳转</span>
  2813. <span class="ci-label">点击进入下一页</span>
  2814. </span>
  2815. <span class="ci-right">
  2816. <span class="val">详情</span>
  2817. <van-icon name="arrow" />
  2818. </span>
  2819. </button>
  2820. <button type="button" class="comp-cell-item" @click="toast('状态:启用')">
  2821. <span class="ci-icon tone-slate"><van-icon name="manager-o" /></span>
  2822. <span class="ci-body">
  2823. <span class="ci-title">带标签</span>
  2824. <span class="ci-label">辅助说明文字</span>
  2825. </span>
  2826. <span class="ci-right">
  2827. <span class="demo-tag t-success" style="padding:3px 8px;font-size:11px;"><i></i>启用</span>
  2828. </span>
  2829. </button>
  2830. </div>
  2831. </div>
  2832. </section>
  2833. <section class="comp-block">
  2834. <div class="comp-hd">
  2835. <div class="hd-icon"><van-icon name="volume-o" /></div>
  2836. <div>
  2837. <h4>通知 / 空态 / 加载</h4>
  2838. <div class="hd-sub">提示条与占位反馈</div>
  2839. </div>
  2840. </div>
  2841. <div class="comp-bd">
  2842. <div class="comp-notice-stack">
  2843. <div class="comp-notice-bar">
  2844. <van-notice-bar
  2845. left-icon="volume-o"
  2846. text="移动端菜单在「菜单管理 → 移动端菜单」下配置,按角色授权后展示。"
  2847. background="#e9f9f2"
  2848. color="#087f57"
  2849. />
  2850. </div>
  2851. <div class="comp-empty-box">
  2852. <van-empty description="暂无相关数据" image-size="110" />
  2853. <div class="empty-tip">可下拉刷新,或点击上方按钮新增一条记录</div>
  2854. </div>
  2855. <div class="comp-loading-box">
  2856. <van-loading type="spinner" color="#0a9f6d" size="28px" />
  2857. <div>内容加载中…</div>
  2858. <div class="load-sub">正在拉取最新业务数据</div>
  2859. </div>
  2860. </div>
  2861. </div>
  2862. </section>
  2863. <section class="comp-block">
  2864. <div class="comp-hd">
  2865. <div class="hd-icon tone-amber"><van-icon name="comment-o" /></div>
  2866. <div>
  2867. <h4>反馈 Feedback</h4>
  2868. <div class="hd-sub">Toast / Dialog / ActionSheet</div>
  2869. </div>
  2870. </div>
  2871. <div class="comp-bd">
  2872. <div class="comp-feedback-grid">
  2873. <button type="button" class="fb-btn fb-toast" @click="toast('操作成功')">
  2874. <span class="fb-icon"><van-icon name="comment-o" /></span>
  2875. Toast
  2876. </button>
  2877. <button type="button" class="fb-btn fb-dialog" @click="showDialog">
  2878. <span class="fb-icon"><van-icon name="info-o" /></span>
  2879. Dialog
  2880. </button>
  2881. <button type="button" class="fb-btn fb-sheet" @click="showAction = true">
  2882. <span class="fb-icon"><van-icon name="wap-nav" /></span>
  2883. Sheet
  2884. </button>
  2885. </div>
  2886. </div>
  2887. </section>
  2888. <section class="comp-block">
  2889. <div class="comp-hd">
  2890. <div class="hd-icon"><van-icon name="cluster-o" /></div>
  2891. <div>
  2892. <h4>分段 / 步骤</h4>
  2893. <div class="hd-sub">筛选分段与流程进度</div>
  2894. </div>
  2895. </div>
  2896. <div class="comp-bd">
  2897. <div class="comp-seg">
  2898. <button type="button" :class="{ active: compTab === 0 }" @click="compTab = 0">资料</button>
  2899. <button type="button" :class="{ active: compTab === 1 }" @click="compTab = 1">进度</button>
  2900. <button type="button" :class="{ active: compTab === 2 }" @click="compTab = 2">附件</button>
  2901. </div>
  2902. <div class="comp-steps-wrap">
  2903. <van-steps :active="compTab === 2 ? 2 : 1" active-color="#0a9f6d" inactive-color="#dcebe4">
  2904. <van-step>提交</van-step>
  2905. <van-step>审批中</van-step>
  2906. <van-step>完成</van-step>
  2907. </van-steps>
  2908. </div>
  2909. </div>
  2910. </section>
  2911. <section class="comp-block">
  2912. <div class="comp-hd">
  2913. <div class="hd-icon tone-rose"><van-icon name="description" /></div>
  2914. <div>
  2915. <h4>附件卡片</h4>
  2916. <div class="hd-sub">上传入口与文件摘要卡</div>
  2917. </div>
  2918. </div>
  2919. <div class="comp-bd">
  2920. <div class="comp-attach-list">
  2921. <button type="button" class="comp-upload-tile" @click="toast('上传附件')">
  2922. <span class="up-icon"><van-icon name="plus" /></span>
  2923. <span class="up-text">
  2924. <span class="up-title">添加附件</span>
  2925. <span class="up-sub">支持 PDF / 图片 / Office,点按即可上传</span>
  2926. </span>
  2927. <van-icon name="arrow" color="#0a9f6d" />
  2928. </button>
  2929. <div class="comp-file-card">
  2930. <div class="file-icon">
  2931. <van-icon name="records" />
  2932. <span class="ext">DOCX</span>
  2933. </div>
  2934. <div class="file-body">
  2935. <div class="file-name">质检报告.docx</div>
  2936. <div class="file-meta">
  2937. <span>1.2 MB</span>
  2938. <span>08-10 10:20</span>
  2939. </div>
  2940. </div>
  2941. <div class="file-ops">
  2942. <button type="button" class="btn-preview" @click="toast('预览')">
  2943. <van-icon name="eye-o" />
  2944. </button>
  2945. <button type="button" class="btn-del" @click="toast('删除')">
  2946. <van-icon name="delete-o" />
  2947. </button>
  2948. </div>
  2949. </div>
  2950. </div>
  2951. </div>
  2952. </section>
  2953. </div>
  2954. </div>
  2955. </div>
  2956. </template>
  2957. <van-action-sheet
  2958. v-model:show="showAction"
  2959. :actions="actions"
  2960. cancel-text="取消"
  2961. close-on-click-action
  2962. @select="onActionSelect"
  2963. />
  2964. </div>
  2965. </div>
  2966. </div>
  2967. </div>
  2968. <script src="https://cdn.jsdelivr.net/npm/vue@3.5.13/dist/vue.global.prod.js"></script>
  2969. <script src="https://cdn.jsdelivr.net/npm/vant@4.9.21/lib/vant.min.js"></script>
  2970. <script>
  2971. const { createApp, computed, reactive, ref } = Vue
  2972. createApp({
  2973. setup() {
  2974. const screen = ref('home')
  2975. const screens = [
  2976. { key: 'home', label: '1. 首页 · 权限菜单卡片' },
  2977. { key: 'list', label: '2. 列表界面' },
  2978. { key: 'detail', label: '3. 详情界面' },
  2979. { key: 'form', label: '4. 新增 / 编辑表单' },
  2980. { key: 'comps', label: '5. 组件风格合集' }
  2981. ]
  2982. const userName = '张三'
  2983. const roleName = '业务专员'
  2984. const deptName = '样本运营部'
  2985. const allMenus = [
  2986. { id: 'm1', title: '待办审批', icon: 'todo-list-o', tone: '', path: 'list' },
  2987. { id: 'm2', title: '采集协议', icon: 'orders-o', tone: '', path: 'list' },
  2988. { id: 'm3', title: '缴费确认', icon: 'balance-pay', tone: 'tone-yellow', path: 'list' },
  2989. { id: 'm4', title: '样本接收', icon: 'logistics', tone: '', path: 'list' },
  2990. { id: 'm5', title: '质检工单', icon: 'certificate', tone: 'tone-slate', path: 'list' },
  2991. { id: 'm6', title: '生产任务', icon: 'cluster-o', tone: '', path: 'list' },
  2992. { id: 'm7', title: '库位出库', icon: 'send-gift-o', tone: 'tone-yellow', path: 'list' },
  2993. { id: 'm8', title: '我的', icon: 'user-o', tone: 'tone-slate', path: 'comps' }
  2994. ]
  2995. const grantedMap = reactive({
  2996. m1: true, m2: true, m3: true, m4: true,
  2997. m5: true, m6: false, m7: true, m8: true
  2998. })
  2999. const visibleMenus = computed(() =>
  3000. allMenus.filter((m) => grantedMap[m.id])
  3001. )
  3002. const todoStats = { approve: 3, pay: 1, alert: 1 }
  3003. const todoItems = [
  3004. {
  3005. id: 't1',
  3006. title: '采集协议审批',
  3007. desc: '王女士 · 分期首期待复核',
  3008. tag: '待审批',
  3009. tagType: 'warning',
  3010. time: '09:20',
  3011. icon: 'todo-list-o',
  3012. iconClass: 'i-approve'
  3013. },
  3014. {
  3015. id: 't2',
  3016. title: '缴费确认',
  3017. desc: 'XY20260810001 · ¥12,800',
  3018. tag: '待确认',
  3019. tagType: 'warning',
  3020. time: '昨天',
  3021. icon: 'balance-pay',
  3022. iconClass: 'i-pay'
  3023. },
  3024. {
  3025. id: 't3',
  3026. title: '质检异常',
  3027. desc: 'QC-8821 · 结论待录入',
  3028. tag: '异常',
  3029. tagType: 'danger',
  3030. time: '08-08',
  3031. icon: 'warning-o',
  3032. iconClass: 'i-alert'
  3033. }
  3034. ]
  3035. const keyword = ref('')
  3036. const listTab = ref(0)
  3037. const listTabs = [
  3038. { key: 'all', label: '全部' },
  3039. { key: 'doing', label: '进行中' },
  3040. { key: 'pending', label: '待处理' },
  3041. { key: 'done', label: '已完成' }
  3042. ]
  3043. const listData = [
  3044. {
  3045. id: 1, title: '脐带血采集协议-王女士', code: 'XY20260810001',
  3046. typeLabel: '采集协议', icon: 'orders-o',
  3047. owner: '李四', time: '08-10 09:20', amount: '12,800', progress: '',
  3048. desc: '分期缴费 · 待财务确认首期款项',
  3049. status: '待处理', tagType: 'warning', hint: '需尽快确认',
  3050. customer: '王女士', dept: '样本运营部', createdAt: '2026-08-08 14:20',
  3051. stepActive: 1,
  3052. steps: ['建档', '缴费确认', '样本采集', '入库'],
  3053. payments: [
  3054. { id: 'p1', name: '第 1 期(首期)', sub: '应缴 08-10 · 微信转账', amount: '6,400', status: '待确认', tagType: 'warning' },
  3055. { id: 'p2', name: '第 2 期', sub: '应缴 09-10 · 未到期', amount: '6,400', status: '待缴费', tagType: 'primary' }
  3056. ],
  3057. attachments: [
  3058. { name: '协议扫描件.pdf', size: '2.4 MB', time: '08-08 14:18' },
  3059. { name: '身份证明.jpg', size: '860 KB', time: '08-08 14:19' }
  3060. ],
  3061. logs: [
  3062. { title: '提交审批', desc: '提交采集协议,等待财务确认首期', time: '08-08 14:22', tone: 'done', actor: '李四', icon: 'edit', badge: '已完成' },
  3063. { title: '流转至财务', desc: '系统自动分配至财务确认岗', time: '08-08 14:23', tone: 'done', actor: '系统', icon: 'cluster-o', badge: '已完成' },
  3064. { title: '待确认缴费', desc: '首期 ¥6,400 等待财务核对附件', time: '08-10 09:20', tone: 'warn', actor: '财务岗', icon: 'balance-pay', badge: '进行中' }
  3065. ]
  3066. },
  3067. {
  3068. id: 2, title: '样本接收单-批次 A12', code: 'YS20260809018',
  3069. typeLabel: '样本接收', icon: 'logistics',
  3070. owner: '王五', time: '08-09 16:40', amount: '', progress: '在途',
  3071. desc: '冷链在途 · 预计今日 18:00 前到达库房',
  3072. status: '进行中', tagType: 'primary', hint: '跟踪物流',
  3073. customer: '批次 A12', dept: '仓储物流部', createdAt: '2026-08-09 09:10',
  3074. stepActive: 2,
  3075. steps: ['发运', '在途', '到库签收', '入库'],
  3076. payments: [],
  3077. attachments: [{ name: '冷链温度记录.pdf', size: '1.1 MB', time: '08-09 11:28' }],
  3078. logs: [
  3079. { title: '创建接收单', desc: '创建样本接收单并绑定批次 A12', time: '08-09 09:10', tone: 'done', actor: '王五', icon: 'orders-o', badge: '已完成' },
  3080. { title: '冷链发出', desc: '承运方已取件,全程温控开启', time: '08-09 11:30', tone: 'done', actor: '承运方', icon: 'logistics', badge: '已完成' },
  3081. { title: '运输中', desc: '当前位置:城东中转站', time: '08-09 16:40', tone: 'warn', actor: '物流跟踪', icon: 'location-o', badge: '进行中' }
  3082. ]
  3083. },
  3084. {
  3085. id: 3, title: '质检工单-QC-8821', code: 'ZJ20260808007',
  3086. typeLabel: '质检工单', icon: 'certificate',
  3087. owner: '赵六', time: '08-08 11:05', amount: '', progress: '阻塞',
  3088. desc: '检测结论待录入,阻塞下游入库流程',
  3089. status: '异常', tagType: 'danger', hint: '优先处理',
  3090. customer: '样本 S-8821', dept: '质检中心', createdAt: '2026-08-07 16:40',
  3091. stepActive: 1,
  3092. steps: ['取样', '检测', '结论录入', '关闭'],
  3093. payments: [],
  3094. attachments: [{ name: '初检原始数据.xlsx', size: '428 KB', time: '08-08 10:48' }],
  3095. logs: [
  3096. { title: '工单创建', desc: '系统根据接收单自动生成质检工单', time: '08-07 16:40', tone: 'done', actor: '系统', icon: 'add-o', badge: '已完成' },
  3097. { title: '检测完成', desc: '完成实验操作,待录入结论', time: '08-08 10:50', tone: 'done', actor: '赵六', icon: 'certificate', badge: '已完成' },
  3098. { title: '异常挂起', desc: '结论超时未录入,已阻塞入库', time: '08-08 11:05', tone: 'danger', actor: '质控规则', icon: 'warning-o', badge: '异常' }
  3099. ]
  3100. },
  3101. {
  3102. id: 4, title: '出库申请-库位 B-03', code: 'CK20260807002',
  3103. typeLabel: '库位出库', icon: 'send-gift-o',
  3104. owner: '张三', time: '08-07 14:22', amount: '', progress: '完结',
  3105. desc: '复核已完成,可归档关闭',
  3106. status: '已完成', tagType: 'success', hint: '已完结',
  3107. customer: '库位 B-03', dept: '仓储物流部', createdAt: '2026-08-06 10:00',
  3108. stepActive: 3,
  3109. steps: ['申请', '复核', '出库', '归档'],
  3110. payments: [],
  3111. attachments: [{ name: '出库单签收.pdf', size: '980 KB', time: '08-07 14:18' }],
  3112. logs: [
  3113. { title: '提交申请', desc: '发起出库申请,指定库位 B-03', time: '08-06 10:00', tone: 'done', actor: '张三', icon: 'edit', badge: '已完成' },
  3114. { title: '复核通过', desc: '库管复核通过,允许出库', time: '08-07 11:10', tone: 'done', actor: '库管', icon: 'passed', badge: '已完成' },
  3115. { title: '出库完成', desc: '实物已交接,单据归档', time: '08-07 14:22', tone: 'done', actor: '张三', icon: 'checked', badge: '已完成' }
  3116. ]
  3117. }
  3118. ]
  3119. const detail = ref(null)
  3120. function openDetail(item) {
  3121. detail.value = item || listData[0]
  3122. screen.value = 'detail'
  3123. }
  3124. function goScreen(key) {
  3125. if (key === 'detail' && !detail.value) detail.value = listData[0]
  3126. screen.value = key
  3127. }
  3128. function fileExt(name) {
  3129. const i = (name || '').lastIndexOf('.')
  3130. return i >= 0 ? name.slice(i + 1).slice(0, 4) : 'FILE'
  3131. }
  3132. function fileTypeClass(name) {
  3133. const ext = fileExt(name).toLowerCase()
  3134. if (ext === 'pdf') return 'ft-pdf'
  3135. if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) return 'ft-img'
  3136. if (['xls', 'xlsx', 'csv'].includes(ext)) return 'ft-xls'
  3137. if (['doc', 'docx'].includes(ext)) return 'ft-doc'
  3138. return 'ft-other'
  3139. }
  3140. function fileTypeIcon(name) {
  3141. const cls = fileTypeClass(name)
  3142. if (cls === 'ft-pdf') return 'description'
  3143. if (cls === 'ft-img') return 'photo-o'
  3144. if (cls === 'ft-xls') return 'bar-chart-o'
  3145. if (cls === 'ft-doc') return 'records'
  3146. return 'bag-o'
  3147. }
  3148. function rowsByTab(idx) {
  3149. if (idx === 1) return listData.filter((r) => r.status === '进行中')
  3150. if (idx === 2) return listData.filter((r) => r.status === '待处理')
  3151. if (idx === 3) return listData.filter((r) => r.status === '已完成')
  3152. return listData
  3153. }
  3154. function tabCount(idx) {
  3155. return rowsByTab(idx).length
  3156. }
  3157. const filteredList = computed(() => {
  3158. const kw = keyword.value.trim()
  3159. let rows = rowsByTab(listTab.value)
  3160. if (!kw) return rows
  3161. return rows.filter((r) =>
  3162. r.title.includes(kw) || r.code.includes(kw) || r.owner.includes(kw)
  3163. )
  3164. })
  3165. function goListTab(idx) {
  3166. listTab.value = idx
  3167. screen.value = 'list'
  3168. }
  3169. const formMode = ref('create')
  3170. const form = reactive({
  3171. name: '',
  3172. code: '',
  3173. typeLabel: '',
  3174. urgent: false,
  3175. owner: '',
  3176. date: '',
  3177. amount: '',
  3178. remark: ''
  3179. })
  3180. const showTypePicker = ref(false)
  3181. const showDate = ref(false)
  3182. const typeColumns = [
  3183. { text: '采集协议', value: 'protocol' },
  3184. { text: '样本接收', value: 'sample' },
  3185. { text: '质检工单', value: 'qc' },
  3186. { text: '出库申请', value: 'outbound' }
  3187. ]
  3188. function resetForm() {
  3189. form.name = ''
  3190. form.code = ''
  3191. form.typeLabel = ''
  3192. form.urgent = false
  3193. form.owner = userName
  3194. form.date = ''
  3195. form.amount = ''
  3196. form.remark = ''
  3197. }
  3198. function openForm(mode, item) {
  3199. formMode.value = mode
  3200. if (mode === 'edit' && item) {
  3201. form.name = item.title
  3202. form.code = item.code
  3203. form.typeLabel = '采集协议'
  3204. form.urgent = item.tagType === 'danger'
  3205. form.owner = item.owner
  3206. form.date = '2026-08-10'
  3207. form.amount = '12800'
  3208. form.remark = item.desc
  3209. } else {
  3210. resetForm()
  3211. }
  3212. screen.value = 'form'
  3213. }
  3214. function onTypeConfirm({ selectedOptions }) {
  3215. form.typeLabel = selectedOptions[0]?.text || ''
  3216. showTypePicker.value = false
  3217. }
  3218. function onDateConfirm(val) {
  3219. const d = val
  3220. const y = d.getFullYear()
  3221. const m = String(d.getMonth() + 1).padStart(2, '0')
  3222. const day = String(d.getDate()).padStart(2, '0')
  3223. form.date = `${y}-${m}-${day}`
  3224. showDate.value = false
  3225. }
  3226. function onSubmit() {
  3227. vant.showToast({ message: formMode.value === 'create' ? '已保存(新增)' : '已保存(编辑)', type: 'success' })
  3228. screen.value = 'list'
  3229. }
  3230. function onMenuTap(m) {
  3231. if (m.path === 'list') screen.value = 'list'
  3232. else if (m.path === 'comps') screen.value = 'comps'
  3233. else vant.showToast(m.title)
  3234. }
  3235. function toast(msg) {
  3236. vant.showToast(msg)
  3237. }
  3238. function showDialog() {
  3239. vant.showConfirmDialog({
  3240. title: '确认提交',
  3241. message: '提交后将进入审批流程,是否继续?',
  3242. confirmButtonText: '确认',
  3243. confirmButtonColor: '#0a9f6d'
  3244. }).then(() => {
  3245. vant.showToast('已确认')
  3246. }).catch(() => {})
  3247. }
  3248. const showAction = ref(false)
  3249. const actions = [
  3250. { name: '查看', color: '#1f2a26' },
  3251. { name: '编辑', color: '#0a9f6d' },
  3252. { name: '作废', color: '#d14343' }
  3253. ]
  3254. const compTab = ref(0)
  3255. function onActionSelect(action) {
  3256. vant.showToast(action.name)
  3257. }
  3258. return {
  3259. screen, screens, goScreen, userName, roleName, deptName,
  3260. allMenus, grantedMap, visibleMenus,
  3261. todoStats, todoItems, goListTab,
  3262. keyword, listTab, listTabs, tabCount, filteredList,
  3263. detail, openDetail, fileExt, fileTypeClass, fileTypeIcon,
  3264. formMode, form, showTypePicker, showDate, typeColumns,
  3265. openForm, onTypeConfirm, onDateConfirm, onSubmit, onMenuTap,
  3266. toast, showDialog, showAction, actions, onActionSelect, compTab
  3267. }
  3268. }
  3269. }).use(vant).mount('#app')
  3270. </script>
  3271. </body>
  3272. </html>