CMS-UI完整设计.html 345 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113
  1. <!DOCTYPE html>
  2. <!--
  3. 三端 CMS · 完整 UI 设计稿(基于 2026-10-08 第一批分项选型结果:基础视觉 F 晴空渐变、框架 A 经典三段、弹窗 J 卡片分区、抽屉 E 卡片+描述表;登录页按 10-07 追加的分项选型登录页 C 右侧浮卡·全幅底图)
  4. 换肤:浅色 F + 深色 B 石墨灰(10-08 从三版候选里选定;顶栏「皮肤」切换;地址 #p/<页面>/<端>/<变体>/t=db)
  5. 本文件由 src/build.py 生成,单文件自包含,离线双击即可打开。请改 src/ 下的源文件后重新生成。
  6. -->
  7. <html lang="zh-CN">
  8. <head>
  9. <meta charset="UTF-8">
  10. <meta name="viewport" content="width=device-width, initial-scale=1">
  11. <title>三端 CMS · 完整 UI 设计稿</title>
  12. <style>
  13. @font-face{font-family:"Inter";font-style:normal;font-weight:400;font-display:block;src:url(data:font/woff2;base64,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) format("woff2")}
  14. @font-face{font-family:"Inter";font-style:normal;font-weight:500;font-display:block;src:url(data:font/woff2;base64,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) format("woff2")}
  15. @font-face{font-family:"Inter";font-style:normal;font-weight:600;font-display:block;src:url(data:font/woff2;base64,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) format("woff2")}
  16. /* ============ 设计稿外壳(不参与设计,保持中性) ============ */
  17. :root { --bk-bg: #F3F4F6; --bk-card: #FFFFFF; --bk-t1: #1F2329; --bk-t2: #4E5561; --bk-t3: #8A919C; --bk-bd: #E3E5E9; --bk-acc: #2F7CF6; --bk-link: #1D5FD0; --bk-acc-bg: #EAF2FF; --bk-warn: #B45309; --bk-warn-bg: #FFF4E5; }
  18. * { box-sizing: border-box; }
  19. html, body { margin: 0; }
  20. body { min-width: 1100px; background: var(--bk-bg); color: var(--bk-t1); font: 14px/22px -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; -webkit-font-smoothing: antialiased; }
  21. body.bk-lock { overflow: hidden; }
  22. button { font: inherit; cursor: pointer; }
  23. [hidden] { display: none !important; }
  24. a { color: var(--bk-link); text-decoration: none; }
  25. .bk-hd { position: sticky; top: 0; z-index: 50; display: flex; align-items: center; gap: 16px; height: 56px; padding: 0 24px; background: var(--bk-card); border-bottom: 1px solid var(--bk-bd); }
  26. .bk-hd b { font-size: 16px; white-space: nowrap; }
  27. .bk-hd .bk-picks { display: flex; gap: 6px; flex-wrap: wrap; font-size: 12px; color: var(--bk-t2); }
  28. .bk-hd .bk-picks span { padding: 2px 10px; border-radius: 12px; background: var(--bk-acc-bg); color: var(--bk-link); white-space: nowrap; }
  29. .bk-hd .sp { flex: 1; }
  30. .bk-hd .bk-date { font-size: 12px; color: var(--bk-t3); white-space: nowrap; }
  31. .bk-theme { display: flex; align-items: center; gap: 8px; flex: none; }
  32. .bk-theme-nm { min-width: 56px; font-size: 12px; color: var(--bk-t2); white-space: nowrap; }
  33. .bk-btn.sm { height: 28px; padding: 0 12px; font-size: 13px; }
  34. .bk-body { display: grid; grid-template-columns: 236px minmax(0, 1fr); gap: 16px; padding: 16px 24px 40px; align-items: start; }
  35. .bk-nav { position: sticky; top: 72px; display: flex; flex-direction: column; gap: 10px; max-height: calc(100vh - 88px); overflow: auto; }
  36. .bk-grp { background: var(--bk-card); border: 1px solid var(--bk-bd); border-radius: 10px; padding: 6px; }
  37. .bk-grp-t { padding: 6px 10px 4px; font-size: 12px; font-weight: 600; color: var(--bk-t3); }
  38. .bk-item { display: flex; flex-direction: column; gap: 1px; padding: 7px 10px; border-radius: 8px; color: var(--bk-t1); }
  39. .bk-item:hover { background: var(--bk-bg); }
  40. .bk-item.on { background: var(--bk-acc-bg); }
  41. .bk-item b { font-weight: 500; font-size: 13px; }
  42. .bk-item.on b { color: var(--bk-link); font-weight: 600; }
  43. .bk-item small { font-size: 11px; line-height: 15px; color: var(--bk-t3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  44. .bk-main { min-width: 0; }
  45. .bk-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
  46. .bk-bar-l, .bk-bar-r { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  47. .bk-ttl { font-size: 17px; }
  48. .bk-sub { font-size: 13px; color: var(--bk-t3); }
  49. .bk-lbl { font-size: 12px; color: var(--bk-t3); }
  50. .bk-seg { display: inline-flex; padding: 2px; background: #E7E9ED; border-radius: 8px; }
  51. .bk-seg button { height: 28px; padding: 0 12px; border: 0; border-radius: 6px; background: none; color: var(--bk-t2); font-size: 13px; white-space: nowrap; }
  52. .bk-seg button:hover { color: var(--bk-t1); }
  53. .bk-seg button.on { background: var(--bk-card); color: var(--bk-t1); font-weight: 500; box-shadow: 0 1px 2px rgba(0,0,0,.08); }
  54. .bk-btn { height: 32px; padding: 0 14px; border: 1px solid var(--bk-bd); border-radius: 6px; background: var(--bk-card); color: var(--bk-t1); white-space: nowrap; }
  55. .bk-btn:hover { border-color: #C9CDD4; }
  56. .bk-host { position: relative; overflow: hidden; background: #fff; }
  57. #bkStage { border-radius: 8px; box-shadow: 0 0 0 1px var(--bk-bd), 0 4px 16px rgba(15,23,42,.06); }
  58. #bkStage.is-kit { height: auto !important; }
  59. .bk-notes { display: grid; grid-template-columns: 1fr 1.4fr 1fr; gap: 12px; margin-top: 12px; }
  60. .bk-note { padding: 12px 16px; background: var(--bk-card); border: 1px solid var(--bk-bd); border-radius: 10px; font-size: 13px; line-height: 20px; color: var(--bk-t2); }
  61. .bk-note h4 { margin: 0 0 6px; font-size: 13px; color: var(--bk-t1); }
  62. .bk-note p { margin: 0; word-break: break-all; }
  63. .bk-note ul { margin: 0; padding-left: 18px; }
  64. .bk-note li + li { margin-top: 4px; }
  65. .bk-note.todo { border-color: #F3D9A4; background: var(--bk-warn-bg); }
  66. .bk-note.todo h4 { color: var(--bk-warn); }
  67. /* 规范摘要 */
  68. .spec { max-width: 1100px; display: flex; flex-direction: column; gap: 14px; }
  69. .spec section { padding: 18px 22px; background: var(--bk-card); border: 1px solid var(--bk-bd); border-radius: 10px; }
  70. .spec h2 { margin: 0 0 10px; font-size: 16px; }
  71. .spec p { margin: 0 0 8px; color: var(--bk-t2); }
  72. .spec code { padding: 0 4px; border-radius: 4px; background: var(--bk-bg); font-size: 12px; }
  73. .sp-picks { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  74. .sp-pick:last-child:nth-child(odd) { grid-column: 1 / -1; }
  75. .sp-pick { padding: 12px 14px; border: 1px solid var(--bk-bd); border-radius: 8px; }
  76. .sp-pick-hd { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
  77. .sp-pick p { font-size: 13px; line-height: 20px; }
  78. .rv-letter { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; flex: none; border-radius: 6px; background: var(--bk-acc-bg); color: var(--bk-link); font-size: 12px; font-weight: 600; }
  79. .sp-go { display: inline-block; margin: 2px 8px 2px 0; padding: 0 8px; border-radius: 10px; background: var(--bk-acc-bg); color: var(--bk-link); font-size: 12px; line-height: 20px; white-space: nowrap; }
  80. .sp-tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
  81. .sp-tbl th, .sp-tbl td { padding: 8px 10px; border-bottom: 1px solid var(--bk-bd); text-align: left; vertical-align: top; }
  82. .sp-tbl th { width: 110px; color: var(--bk-t3); font-weight: 500; white-space: nowrap; }
  83. .sp-tbl.map thead th { color: var(--bk-t1); font-weight: 600; width: auto; }
  84. .sp-grp { margin-bottom: 12px; }
  85. .sp-grp h4 { margin: 0 0 6px; font-size: 12px; color: var(--bk-t3); font-weight: 500; }
  86. .sp-sw { display: flex; flex-wrap: wrap; gap: 8px; }
  87. .sp-swc { width: 112px; font-size: 11px; line-height: 15px; }
  88. .sp-swc i { display: block; height: 36px; border-radius: 4px; margin-bottom: 4px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.06); }
  89. .sp-swc b { display: block; color: var(--bk-t1); font-weight: 500; }
  90. .sp-swc span { color: var(--bk-t3); font-family: Inter, sans-serif; }
  91. .sp-h2-sub { margin-left: 10px; font-size: 12px; font-weight: 400; color: var(--bk-t3); }
  92. .sp-themes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-bottom: 14px; }
  93. .sp-theme { display: flex; flex-direction: column; padding: 12px 14px; border: 1px solid var(--bk-bd); border-radius: 8px; font-size: 13px; line-height: 20px; }
  94. .sp-theme.on { border-color: var(--bk-acc); box-shadow: 0 0 0 2px var(--bk-acc-bg); }
  95. .sp-theme p { margin: 8px 0 6px; color: var(--bk-t2); }
  96. .sp-theme ul { margin: 0 0 10px; padding-left: 16px; color: var(--bk-t2); font-size: 12px; line-height: 18px; }
  97. .sp-theme li + li { margin-top: 3px; }
  98. .sp-theme .bk-btn { margin-top: auto; align-self: flex-start; }
  99. .sp-cur { margin-left: auto; padding: 0 8px; border-radius: 10px; background: var(--bk-acc-bg); color: var(--bk-link); font-size: 11px; line-height: 18px; }
  100. .rv-letter.dk { background: #1F2937; color: #fff; }
  101. .sp-strip { display: flex; gap: 4px; height: 36px; padding: 6px; border-radius: 6px; margin-top: 2px; }
  102. .sp-strip i { flex: 1; border-radius: 3px; box-shadow: inset 0 0 0 1px rgba(255,255,255,.14); }
  103. .sp-tbl.cmp thead th { color: var(--bk-t1); font-weight: 600; width: auto; }
  104. .sp-tbl.cmp tbody th { width: 110px; }
  105. .sp-todo { margin: 0; padding-left: 18px; color: var(--bk-t2); }
  106. .sp-todo > li { margin-bottom: 8px; }
  107. .sp-todo ul { margin: 2px 0 0; padding-left: 18px; }
  108. /* 全屏 */
  109. .bk-full { position: fixed; inset: 0; z-index: 100; display: flex; flex-direction: column; align-items: center; background: #0F172A; }
  110. .bk-full-bar { display: flex; align-items: center; gap: 12px; width: 100%; height: 48px; padding: 0 16px; color: #fff; }
  111. .bk-full-bar .bk-ttl { color: #fff; }
  112. .bk-full-bar .sp { flex: 1; }
  113. .bk-full-bar .bk-seg { background: rgba(255,255,255,.12); }
  114. .bk-full-bar .bk-seg button { color: rgba(255,255,255,.75); }
  115. .bk-full-bar .bk-seg button.on { background: #fff; color: var(--bk-t1); }
  116. .bk-k { font-size: 12px; color: rgba(255,255,255,.55); }
  117. .bk-x { width: 32px; height: 32px; border: 0; border-radius: 6px; background: rgba(255,255,255,.12); color: #fff; }
  118. #bkFullStage { margin-top: 8px; border-radius: 6px; }
  119. #bkFullStage.is-kit { overflow-y: auto; overflow-x: hidden; }
  120. /* 单独模式 */
  121. body.bk-solo { background: #fff; min-width: 0; }
  122. body.bk-solo .bk-host { width: 1440px; }
  123. /* ============ 画布内的组件样式:全部读 .cv[data-v] 上的令牌,5 套视觉共用一份结构 ============ */
  124. .cv {
  125. position: relative;
  126. width: 1440px;
  127. font-family: var(--font);
  128. font-size: 14px;
  129. line-height: 22px;
  130. color: var(--t2);
  131. background: var(--canvas, var(--bg-page));
  132. -webkit-font-smoothing: antialiased;
  133. text-align: left;
  134. }
  135. .cv *, .cv *::before, .cv *::after { box-sizing: border-box; }
  136. .cv button, .cv input { font: inherit; color: inherit; }
  137. .cv .num { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
  138. .cv .ic { width: 16px; height: 16px; flex: none; }
  139. .cv .t1 { color: var(--t1); }
  140. .cv .t3 { color: var(--t3); }
  141. .cv .t4 { color: var(--t4); }
  142. .cv a, .cv .lnk { color: var(--c-link); text-decoration: none; cursor: pointer; }
  143. .cv .buy { color: var(--buy); }
  144. .cv .sell { color: var(--sell); }
  145. /* ---------- 按钮 ---------- */
  146. .cv .btn {
  147. display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  148. height: 32px; padding: 0 15px; border-radius: var(--r-ctl);
  149. border: 1px solid var(--btn-bd); background: var(--btn-bg); color: var(--btn-t);
  150. box-shadow: var(--btn-sh); font-size: 14px; font-weight: var(--btn-fw);
  151. white-space: nowrap; cursor: pointer; transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
  152. }
  153. .cv .btn .ic { width: 14px; height: 14px; }
  154. .cv .btn:hover, .cv .btn.is-hover { border-color: var(--btn-bd-h); background: var(--btn-bg-h); color: var(--btn-t-h); box-shadow: var(--btn-sh-h); }
  155. .cv .btn.pri { background: var(--pri-bg); background-origin: border-box; border-color: var(--pri-bd); color: var(--pri-t); box-shadow: var(--btn-pri-sh); }
  156. .cv .btn.pri:hover, .cv .btn.pri.is-hover { background: var(--pri-bg-h); background-origin: border-box; border-color: var(--pri-bd-h); color: var(--pri-t); }
  157. .cv .btn.dng { color: var(--dang-t); }
  158. .cv .btn.dng:hover { color: var(--dang); }
  159. .cv .btn.dng-pri { background: var(--dang); border-color: var(--dang); color: #fff; box-shadow: none; }
  160. .cv .btn.txt { height: auto; padding: 0 2px; border-color: transparent; background: none; box-shadow: none; color: var(--c-link); }
  161. .cv .btn.txt:hover { color: var(--c-primary-hover); background: none; }
  162. .cv .btn.txt.dng { color: var(--dang-t); }
  163. .cv .btn.sm { height: 24px; padding: 0 8px; font-size: 13px; }
  164. .cv .btn.icon { width: 32px; padding: 0; }
  165. .cv .btn[disabled], .cv .btn.is-dis { opacity: .45; cursor: not-allowed; }
  166. .cv .btns { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
  167. /* ---------- 输入类 ---------- */
  168. .cv .ipt {
  169. display: flex; align-items: center; gap: 6px; min-width: 0;
  170. height: 32px; padding: 0 11px; border-radius: var(--r-ctl);
  171. background: var(--ipt-bg); border: 1px solid var(--ipt-bd); box-shadow: var(--ipt-sh);
  172. color: var(--t1); font-size: 14px; transition: background .15s, border-color .15s, box-shadow .15s; cursor: text;
  173. }
  174. .cv .ipt:hover { background: var(--ipt-bg-h); border-color: var(--ipt-bd-h); }
  175. .cv .ipt.is-focus, .cv .ipt:focus-within { background: var(--bg-ctl); border-color: var(--focus-bd); box-shadow: var(--ipt-sh), var(--focus); }
  176. .cv[data-v="e"] .ipt.is-focus, .cv[data-v="e"] .ipt:focus-within { box-shadow: var(--focus); }
  177. .cv .ipt.is-err { border-color: var(--dang); background: var(--bg-ctl); }
  178. .cv[data-v="e"] .ipt.is-err { box-shadow: 0 0 0 1px var(--dang), 0 0 0 4px rgba(223,27,65,.14); }
  179. .cv .ipt.is-dis { background: var(--fill-1); color: var(--t4); cursor: not-allowed; }
  180. .cv[data-v="a"] .ipt.is-dis { background: var(--fill-1); }
  181. .cv .ipt .ph { color: var(--t4); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  182. .cv .ipt .val { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  183. .cv .ipt .ic { width: 14px; height: 14px; color: var(--t3); }
  184. .cv .ipt input { flex: 1; min-width: 0; border: 0; outline: 0; background: none; padding: 0; height: 30px; color: var(--t1); }
  185. .cv .ipt input::placeholder { color: var(--t4); }
  186. .cv .ipt .sep { color: var(--t3); padding: 0 4px; }
  187. .cv .ipt .sfx { color: var(--t3); font-size: 13px; }
  188. .cv .ipt .kbd { font-size: 11px; line-height: 16px; padding: 0 5px; border-radius: 4px; color: var(--t3); border: 1px solid var(--bd); background: var(--bg-card); font-family: var(--font-num); }
  189. .cv .fld-err { margin-top: 4px; font-size: 12px; line-height: 18px; color: var(--dang-t); display: flex; gap: 4px; align-items: center; }
  190. .cv .fld-err .ic { width: 12px; height: 12px; }
  191. .cv .ck {
  192. width: 16px; height: 16px; flex: none; border-radius: var(--ck-r);
  193. border: 1px solid var(--ck-bd); background: var(--bg-ctl); display: inline-flex; align-items: center; justify-content: center;
  194. cursor: pointer; transition: .15s; vertical-align: middle;
  195. }
  196. .cv[data-v="e"] .ck { border-color: transparent; box-shadow: 0 0 0 1px rgba(60,66,87,.2), 0 1px 1px rgba(0,0,0,.06); }
  197. .cv .ck svg { width: 12px; height: 12px; color: #fff; opacity: 0; }
  198. .cv .ck.on { background: var(--c-primary); border-color: var(--c-primary); box-shadow: none; }
  199. .cv .ck.on svg { opacity: 1; }
  200. .cv .ck.half { background: var(--c-primary); border-color: var(--c-primary); }
  201. .cv .ck.half::after { content: ""; width: 8px; height: 2px; background: #fff; border-radius: 1px; }
  202. .cv .rd { width: 16px; height: 16px; border-radius: 50%; border: 1px solid var(--ck-bd); background: var(--bg-ctl); display: inline-flex; align-items: center; justify-content: center; flex: none; }
  203. .cv .rd.on { border-color: var(--c-primary); border-width: 5px; }
  204. .cv .sw { width: 36px; height: 20px; border-radius: 10px; background: var(--sw-off); position: relative; flex: none; }
  205. .cv .sw::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.2); transition: .15s; }
  206. .cv .sw.on { background: var(--c-primary); }
  207. .cv .sw.on::after { left: 18px; }
  208. .cv[data-v="c"] .sw, .cv[data-v="a"] .sw { border-radius: 10px; }
  209. .cv .chk-l { display: inline-flex; align-items: center; gap: 8px; color: var(--t1); cursor: pointer; }
  210. /* ---------- 标签(状态、字典) ---------- */
  211. .cv .tg {
  212. --x: var(--info); --x-bg: var(--info-bg); --x-t: var(--info-t); --x-bd: var(--info-bd);
  213. display: inline-flex; align-items: center; gap: 6px; height: var(--tag-h); padding: 0 8px;
  214. border-radius: var(--r-tag); border: 1px solid var(--x-bd); background: var(--x-bg); color: var(--x-t);
  215. font-size: var(--tag-fs); font-weight: var(--tag-fw); line-height: 1; white-space: nowrap; vertical-align: middle;
  216. }
  217. .cv .tg-primary { --x: var(--c-primary); --x-bg: var(--prim-bg); --x-t: var(--prim-t); --x-bd: var(--prim-bd); }
  218. .cv .tg-success { --x: var(--succ); --x-bg: var(--succ-bg); --x-t: var(--succ-t); --x-bd: var(--succ-bd); }
  219. .cv .tg-warning { --x: var(--warn); --x-bg: var(--warn-bg); --x-t: var(--warn-t); --x-bd: var(--warn-bd); }
  220. .cv .tg-danger { --x: var(--dang); --x-bg: var(--dang-bg); --x-t: var(--dang-t); --x-bd: var(--dang-bd); }
  221. /* C:圆点+文字,不用底色;异常文字变红 */
  222. .cv[data-v="c"] .tg { background: none; border: 0; padding: 0; color: var(--td-t); gap: 7px; }
  223. .cv[data-v="c"] .tg-danger { color: var(--dang-t); }
  224. /* D:白底描边小标签,颜色只在圆点 */
  225. .cv[data-v="d"] .tg { background: #fff; color: var(--t2); padding: 0 7px; }
  226. .cv[data-v="c"] .tg::before, .cv[data-v="d"] .tg::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--x); flex: none; }
  227. /* E:胶囊 */
  228. .cv[data-v="e"] .tg { padding: 0 9px; }
  229. /* ---------- 卡片 ---------- */
  230. .cv .card { background: var(--bg-card); border: var(--card-bd); border-radius: var(--r-card); box-shadow: var(--card-sh); backdrop-filter: var(--card-bf); min-width: 0; }
  231. .cv .card-hd {
  232. display: flex; align-items: center; gap: 12px; height: var(--card-hd-h); padding: 0 16px;
  233. border-bottom: 1px solid var(--card-hd-bd); background: var(--card-hd-bg);
  234. border-radius: var(--r-card) var(--r-card) 0 0;
  235. color: var(--t1); font-size: 15px; font-weight: var(--fw-title);
  236. }
  237. .cv .card-hd .sub { font-size: 12px; font-weight: 400; color: var(--t3); }
  238. .cv .card-hd .sp { flex: 1; }
  239. .cv .card-bd { padding: 16px; }
  240. .cv[data-v="c"] .card-hd { font-size: 14px; }
  241. /* ---------- 键值 ---------- */
  242. .cv .kv { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 24px; margin: 0; }
  243. .cv .kv > div { display: flex; gap: 12px; min-width: 0; }
  244. .cv .kv dt { flex: none; width: 84px; color: var(--t3); }
  245. .cv .kv dd { margin: 0; color: var(--t1); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  246. /* ---------- 表格 ---------- */
  247. .cv .tbl-wrap { overflow: auto; min-height: 0; position: relative; }
  248. .cv .tbl { border-collapse: separate; border-spacing: 0; width: max-content; min-width: 100%; font-size: 14px; }
  249. .cv .tbl th {
  250. position: sticky; top: 0; z-index: 2; height: 40px; padding: 0 12px;
  251. background: var(--th-bg); color: var(--th-t); font-weight: var(--th-fw); font-size: var(--th-fs);
  252. text-align: left; white-space: nowrap; border-bottom: 1px solid var(--row-bd);
  253. }
  254. .cv .tbl td { height: 40px; padding: 0 12px; color: var(--td-t); white-space: nowrap; border-bottom: 1px solid var(--row-bd); background: var(--td-bg); }
  255. .cv .tbl tbody tr:nth-child(even) td { background: var(--row-stripe); }
  256. .cv .tbl tbody tr:hover td, .cv .tbl tbody tr.is-hover td { background: var(--row-hover); }
  257. .cv .tbl tbody tr.is-sel td { background: var(--row-sel); }
  258. .cv .tbl .r { text-align: right; }
  259. .cv .tbl .c { text-align: center; }
  260. .cv .tbl .fx { position: sticky; z-index: 1; }
  261. .cv .tbl th.fx { z-index: 3; }
  262. .cv .tbl .fx-end { box-shadow: var(--fx-sh); }
  263. .cv .tbl .ck { vertical-align: middle; }
  264. .cv .tbl .ellip { max-width: 180px; overflow: hidden; text-overflow: ellipsis; }
  265. /* B:表头列之间的短分隔线 */
  266. .cv[data-v="b"] .tbl th:not(:last-child) { background-image: linear-gradient(#F0F0F0, #F0F0F0); background-size: 1px 22px; background-position: right center; background-repeat: no-repeat; }
  267. /* C:全网格 */
  268. .cv[data-v="c"] .tbl th, .cv[data-v="c"] .tbl td { border-right: 1px solid var(--row-bd); }
  269. .cv[data-v="c"] .tbl th:last-child, .cv[data-v="c"] .tbl td:last-child { border-right: 0; }
  270. /* D / E:表头小字 */
  271. .cv[data-v="e"] .tbl th { letter-spacing: .02em; }
  272. /* ---------- 分页 ---------- */
  273. .cv .pager { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding: 12px 16px; font-size: 14px; color: var(--t2); }
  274. .cv .pager .total { margin-right: auto; }
  275. .cv .pg-b {
  276. min-width: 32px; height: 32px; padding: 0 6px; display: inline-flex; align-items: center; justify-content: center;
  277. border-radius: var(--r-ctl); background: var(--pg-bg); border: 1px solid var(--pg-bd); color: var(--t2); cursor: pointer;
  278. }
  279. .cv[data-v="e"] .pg-b { box-shadow: 0 0 0 1px rgba(60,66,87,.12); }
  280. .cv .pg-b.on { background: var(--pg-on-bg); color: var(--pg-on-t); border-color: var(--pg-on-bd); font-weight: 500; }
  281. .cv[data-v="e"] .pg-b.on { box-shadow: 0 0 0 1px rgba(64,128,255,.35); }
  282. .cv .pg-b.dis { color: var(--t4); cursor: not-allowed; }
  283. .cv .pg-b .ic { width: 14px; height: 14px; }
  284. .cv .pager .ipt { height: 32px; }
  285. /* ---------- 弹层 ---------- */
  286. .cv .pop { background: var(--bg-pop); backdrop-filter: var(--pop-bf); border-radius: var(--r-pop); box-shadow: var(--sh-pop); border: var(--pop-bd); padding: 4px; min-width: 160px; color: var(--t1); }
  287. .cv .pop-i { display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 12px; border-radius: calc(var(--r-pop) - 2px); cursor: pointer; white-space: nowrap; }
  288. .cv .pop-i:hover, .cv .pop-i.is-hover { background: var(--fill-1); }
  289. .cv .pop-i.on { color: var(--c-link); font-weight: 500; }
  290. .cv .pop-i .ic { width: 14px; height: 14px; color: var(--t3); }
  291. .cv .pop-i.on .ic { color: var(--c-link); }
  292. .cv .pop-sep { height: 1px; margin: 4px 0; background: var(--bd); }
  293. .cv .pop-t { padding: 6px 12px 2px; font-size: 12px; color: var(--t3); }
  294. .cv .toast .ic.ok { color: var(--succ); }
  295. .cv .toast .ic.no { color: var(--dang); }
  296. .cv .toast { display: inline-flex; align-items: center; gap: 8px; padding: 9px 16px; background: var(--bg-pop); backdrop-filter: var(--pop-bf); border-radius: var(--r-pop); box-shadow: var(--sh-pop); border: var(--pop-bd); color: var(--t1); }
  297. .cv .tip { display: inline-block; padding: 6px 10px; font-size: 12px; line-height: 18px; color: var(--tip-t); background: var(--tip-bg); border-radius: calc(var(--r-ctl) + 2px); }
  298. /* ---------- 提示条 ---------- */
  299. .cv .alt {
  300. --x: var(--info); --x-bg: var(--info-bg); --x-bd: var(--info-bd);
  301. display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px;
  302. border-radius: var(--r-ctl); background: var(--x-bg); border: 1px solid var(--x-bd); color: var(--t1); font-size: 14px;
  303. }
  304. .cv .alt .ic { width: 16px; height: 16px; color: var(--x); margin-top: 3px; }
  305. .cv .alt-info { --x: var(--c-primary); --x-bg: var(--prim-bg); --x-bd: var(--prim-bd); }
  306. .cv .alt-success { --x: var(--succ); --x-bg: var(--succ-bg); --x-bd: var(--succ-bd); }
  307. .cv .alt-warning { --x: var(--warn); --x-bg: var(--warn-bg); --x-bd: var(--warn-bd); }
  308. .cv .alt-danger { --x: var(--dang); --x-bg: var(--dang-bg); --x-bd: var(--dang-bd); }
  309. .cv[data-v="c"] .alt { border: 0; border-left: 3px solid var(--x); border-radius: 0 var(--r-ctl) var(--r-ctl) 0; }
  310. .cv[data-v="d"] .alt { background: #fff; border-color: var(--bd-ctl); }
  311. .cv[data-v="e"] .alt { border: 0; border-radius: 8px; }
  312. .cv[data-v="e"] .alt-info { background: #EEF4FF; }
  313. .cv[data-v="e"] .alt-success { background: #EFFBEA; }
  314. .cv[data-v="e"] .alt-warning { background: #FEF7E0; }
  315. .cv[data-v="e"] .alt-danger { background: #FFF0F5; }
  316. /* ---------- 页签(仅视觉层面,结构在第三批定) ---------- */
  317. .cv .tabs { display: flex; gap: 28px; border-bottom: 1px solid var(--bd); }
  318. .cv .tab { position: relative; height: 40px; display: inline-flex; align-items: center; color: var(--t2); cursor: pointer; }
  319. .cv .tab.on { color: var(--c-link); font-weight: var(--fw-strong); }
  320. .cv .tab.on::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--c-primary); border-radius: 1px; }
  321. .cv[data-v="d"] .tab.on { color: var(--t1); }
  322. .cv[data-v="d"] .tab.on::after { background: var(--t1); }
  323. .cv[data-v="e"] .tab.on::after { height: 3px; border-radius: 3px 3px 0 0; }
  324. .cv .tab .cnt { margin-left: 6px; font-size: 12px; color: var(--t3); font-family: var(--font-num); }
  325. /* ---------- 弹窗外观(布局在第二批定) ---------- */
  326. .cv .dlg { width: 480px; background: var(--bg-dlg); backdrop-filter: var(--dlg-bf); border-radius: var(--r-dlg); box-shadow: var(--sh-dlg); overflow: hidden; }
  327. .cv .dlg-hd { display: flex; align-items: center; gap: 8px; height: 56px; padding: 0 20px; font-size: 16px; font-weight: var(--fw-title); color: var(--t1); border-bottom: 1px solid var(--bd); }
  328. .cv[data-v="d"] .dlg-hd, .cv[data-v="e"] .dlg-hd { border-bottom: 0; }
  329. .cv .dlg-hd .x { margin-left: auto; color: var(--t3); cursor: pointer; }
  330. .cv .dlg-bd { padding: 20px; color: var(--t1); }
  331. .cv[data-v="d"] .dlg-bd, .cv[data-v="e"] .dlg-bd { padding-top: 4px; }
  332. .cv .dlg-ft { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--bd); }
  333. .cv[data-v="d"] .dlg-ft { border-top: 0; padding-bottom: 20px; }
  334. .cv[data-v="e"] .dlg-ft { border-top: 0; background: var(--fill-1); }
  335. /* ---------- 空状态 ---------- */
  336. .cv .empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 24px; color: var(--t3); font-size: 13px; }
  337. .cv .empty svg { width: 64px; height: 48px; flex: none; }
  338. /* ============ 组件总览(基础视觉的第一视图) ============ */
  339. .cv.kit { padding: 24px 32px 40px; min-height: 900px; }
  340. .kit-top { display: flex; align-items: baseline; gap: 12px; margin-bottom: 20px; }
  341. .kit-top h1 { margin: 0; font-size: 20px; line-height: 28px; font-weight: var(--fw-title); color: var(--t1); }
  342. .kit-top span { color: var(--t3); font-size: 13px; }
  343. .kit-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; }
  344. .kit-grid > .card { grid-column: span 12; }
  345. .kit-grid > .s6 { grid-column: span 6; }
  346. .kit-grid > .s4 { grid-column: span 4; }
  347. .kit-grid > .s8 { grid-column: span 8; }
  348. .kit-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
  349. .kit-row + .kit-row { margin-top: 16px; }
  350. .kit-lbl { width: 72px; flex: none; font-size: 12px; color: var(--t3); }
  351. .sw-group { display: flex; flex-wrap: wrap; gap: 10px; }
  352. .sw-group + .sw-group { margin-top: 14px; }
  353. .swc { width: 112px; font-size: 12px; line-height: 16px; }
  354. .swc i { display: block; height: 40px; border-radius: var(--r-ctl); margin-bottom: 6px; box-shadow: inset 0 0 0 1px var(--sw-ring); }
  355. .swc b { display: block; color: var(--t1); font-weight: 500; }
  356. .swc span { color: var(--t3); font-family: var(--font-num); }
  357. .ty-row { display: flex; align-items: baseline; gap: 16px; padding: 8px 0; border-bottom: 1px dashed var(--bd); }
  358. .ty-row:last-child { border-bottom: 0; }
  359. .ty-row .spec { width: 132px; flex: none; font-size: 12px; color: var(--t3); font-family: var(--font-num); }
  360. .kit-fields { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px 20px; }
  361. .kit-fields .f-l { font-size: 13px; color: var(--t2); margin-bottom: 6px; }
  362. .kit-mask { position: relative; padding: 28px; border-radius: var(--r-card); background: var(--mask); display: flex; justify-content: center; align-items: center; min-height: 320px; overflow: hidden; }
  363. .kit-mask::before { content: ""; position: absolute; inset: 0; background: repeating-linear-gradient(0deg, rgba(255,255,255,.05) 0 40px, transparent 40px 41px); }
  364. .kit-mask .dlg { position: relative; }
  365. /* ============ 整体框架 A「经典三段」(取自分项选型 frames.css 的通用部分 + A,去掉其他框架) ============ */
  366. .cv.shell { height: 900px; overflow: hidden; background: var(--canvas, var(--frame-outer)); }
  367. .sh { display: flex; height: 100%; }
  368. .mn { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  369. .pa { flex: 1; min-height: 0; overflow: auto; padding: 16px; background: var(--pa-bg, var(--bg-page)); }
  370. .sp { flex: 1; }
  371. /* 侧栏色调全部读皮肤的 sd-* 令牌:浅色 F 是白底微透的浅色侧栏(2026-10-08 按分项选型框架 E 的配色),深色 B 是近黑侧栏 */
  372. .sd { position: relative; z-index: 5; flex: none; display: flex; flex-direction: column; width: 220px; transition: width .2s; }
  373. .sd { --m-bg: var(--sd-bg); --m-sub: var(--sd-sub); --m-t: var(--sd-t); --m-t-dim: var(--sd-t-dim); --m-act-bg: var(--sd-act-bg); --m-act-t: var(--sd-act-t); --m-hover: var(--sd-hover); --m-hi: var(--sd-hi); --m-fly: var(--sd-fly); }
  374. .sd { background: var(--m-bg); color: var(--m-t); backdrop-filter: var(--sd-bf); border-right: var(--sd-bd); }
  375. .brand { flex: none; display: flex; align-items: center; gap: 10px; height: 52px; padding: 0 16px; white-space: nowrap; overflow: hidden; }
  376. .brand .logo { width: 28px; height: 28px; flex: none; border-radius: calc(var(--r-ctl) + 4px); background: var(--logo-bg); color: var(--logo-t); font-size: 14px; font-weight: 600; display: flex; align-items: center; justify-content: center; }
  377. .brand .nm { font-size: 15px; font-weight: 600; color: var(--m-hi); letter-spacing: .01em; }
  378. .sd .brand .logo { background: var(--sd-logo-bg); color: var(--sd-logo-t); }
  379. .menu { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 6px 8px 12px; scrollbar-width: thin; }
  380. .mg { position: relative; }
  381. .mg-t, .mi-top {
  382. display: flex; align-items: center; gap: 10px; height: 40px; padding: 0 12px; margin: 2px 0;
  383. border-radius: var(--r-ctl); cursor: pointer; color: var(--m-t); white-space: nowrap; transition: background .15s, color .15s;
  384. }
  385. .mg-t:hover, .mi-top:hover, .mi:hover { background: var(--m-hover); color: var(--m-hi); }
  386. .mg-t .ic, .mi-top .ic { width: 16px; height: 16px; }
  387. .mg-t .arr { margin-left: auto; width: 14px; height: 14px; opacity: .6; transition: transform .2s; }
  388. .mg.is-open > .mg-t .arr { transform: rotate(180deg); }
  389. .mg.has-act > .mg-t { color: var(--m-hi); }
  390. .mg-c { display: none; }
  391. .mg.is-open > .mg-c { display: block; }
  392. .mi { display: flex; align-items: center; height: 36px; padding: 0 12px 0 38px; margin: 2px 0; border-radius: var(--r-ctl); cursor: pointer; color: var(--m-t); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: background .15s, color .15s; }
  393. .mi.is-act, .mi-top.is-act { background: var(--m-act-bg); color: var(--m-act-t); font-weight: 500; }
  394. .sec-t { padding: 14px 12px 4px; font-size: 12px; color: var(--m-t-dim); white-space: nowrap; }
  395. /* 收起态:图标栏 + 悬停弹出二级菜单 */
  396. .sh.is-col .sd { width: 64px; }
  397. .sh.is-col .sd .txt, .sh.is-col .sd .arr, .sh.is-col .sec-t, .sh.is-col .brand .nm { display: none; }
  398. .sh.is-col .brand { justify-content: center; padding: 0; }
  399. .sh.is-col .menu { overflow: visible; padding: 6px 10px; }
  400. .sh.is-col .mg-t, .sh.is-col .mi-top { justify-content: center; padding: 0; }
  401. .sh.is-col .mg.has-act > .mg-t { background: var(--m-act-bg); color: var(--m-act-t); }
  402. .sh.is-col .mg > .mg-c { display: none !important; }
  403. .sh.is-col .mg:hover > .mg-c {
  404. display: block !important; position: absolute; left: calc(100% + 10px); top: 0; min-width: 180px; padding: 4px;
  405. background: var(--m-fly); border-radius: var(--r-pop); box-shadow: var(--sh-pop); border: var(--pop-bd); z-index: 30;
  406. }
  407. .sh.is-col .mg:hover > .mg-c::before { content: ""; position: absolute; left: -12px; top: 0; bottom: 0; width: 12px; }
  408. .sh.is-col .mg-c .mi { padding-left: 12px; }
  409. .sh.is-col .mg-c .fly-t { display: block; padding: 6px 12px 2px; font-size: 12px; color: var(--m-t-dim); }
  410. .fly-t { display: none; }
  411. /* 通用小部件 */
  412. .icb { position: relative; width: 32px; height: 32px; flex: none; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--r-ctl); cursor: pointer; color: var(--t2); transition: background .15s; }
  413. .icb:hover { background: var(--fill-2); color: var(--t1); }
  414. .icb .ic { width: 18px; height: 18px; }
  415. .icb .dot { position: absolute; top: 6px; right: 7px; width: 7px; height: 7px; border-radius: 50%; background: var(--dang); box-shadow: 0 0 0 2px var(--bg-card); }
  416. .anchor { position: relative; display: inline-flex; align-items: center; }
  417. .user { display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 6px; border-radius: var(--r-ctl); cursor: pointer; color: var(--t1); white-space: nowrap; }
  418. .user:hover { background: var(--fill-2); }
  419. .ava { width: 26px; height: 26px; flex: none; border-radius: 50%; background: var(--c-primary-bg); color: var(--c-link); font-size: 12px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; }
  420. .user .ic { width: 12px; height: 12px; color: var(--t3); }
  421. .td { display: flex; align-items: center; gap: 16px; font-size: 13px; color: var(--t3); white-space: nowrap; }
  422. .td b { color: var(--t1); font-weight: 500; font-family: var(--font-num); font-variant-numeric: tabular-nums; margin-left: 6px; }
  423. .td .rf { width: 14px; height: 14px; cursor: pointer; color: var(--t3); }
  424. .td .rf.spin, .tdb .rf.spin { animation: cv-spin .6s linear; }
  425. @keyframes cv-spin { to { transform: rotate(360deg); } }
  426. .vsep { width: 1px; height: 16px; background: var(--bd); flex: none; }
  427. /* 弹出层(用户菜单、通知、菜单搜索、标签右键) */
  428. .fl { position: absolute; z-index: 40; }
  429. .fl.below-r { top: calc(100% + 6px); right: 0; }
  430. .fl.below-l { top: calc(100% + 6px); left: 0; }
  431. .fl.above-l { bottom: calc(100% + 6px); left: 0; }
  432. .fl.right-b { left: calc(100% + 10px); bottom: 0; }
  433. .bell-pop { width: 320px; padding: 0; }
  434. .bell-pop .pop-hd { display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 16px; border-bottom: 1px solid var(--bd); font-weight: 500; color: var(--t1); }
  435. .bell-pop .pop-hd .cnt { font-size: 12px; color: var(--t3); font-weight: 400; }
  436. .bell-pop .pop-hd .btn.txt { margin-left: auto; font-size: 12px; }
  437. .bell-i { display: flex; flex-direction: column; gap: 2px; padding: 10px 16px; border-bottom: 1px solid var(--bd-light); cursor: pointer; }
  438. .bell-i:hover { background: var(--fill-1); }
  439. .bell-i .r { display: flex; align-items: center; gap: 8px; }
  440. .bell-i .r span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--t1); }
  441. .bell-i .r small { font-size: 12px; color: var(--t3); font-family: var(--font-num); }
  442. .bell-i p { margin: 0; font-size: 12px; color: var(--t3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  443. .bell-ft { display: flex; justify-content: space-between; padding: 8px 16px; font-size: 12px; }
  444. .ms-pop { width: 380px; padding: 0; overflow: hidden; }
  445. .ms-pop .ipt { margin: 8px; }
  446. .ms-list { max-height: 300px; overflow: auto; padding: 0 4px 4px; }
  447. .ms-list .pop-i { justify-content: space-between; }
  448. .ms-list .grp { color: var(--t3); font-size: 12px; }
  449. .ms-list mark { background: none; color: var(--c-link); font-weight: 600; }
  450. .ms-empty { padding: 20px; text-align: center; color: var(--t3); font-size: 13px; }
  451. .ctx { position: absolute; top: calc(100% - 2px); z-index: 45; min-width: 128px; }
  452. /* 标签页基础 */
  453. .tv { position: relative; z-index: 4; flex: none; display: flex; align-items: center; }
  454. .tvi { display: inline-flex; align-items: center; gap: 6px; flex: none; cursor: pointer; white-space: nowrap; font-size: 13px; color: var(--t2); transition: color .15s, background .15s; }
  455. .tvi:hover { color: var(--t1); }
  456. .tvi .x { width: 14px; height: 14px; padding: 2px; border-radius: 50%; color: var(--t3); }
  457. .tvi .x:hover { background: var(--fill-3); color: var(--t1); }
  458. .tvi .ic.hm { width: 14px; height: 14px; }
  459. /* 页面内容:列表页与占位页 */
  460. .pg-list { display: flex; flex-direction: column; gap: 12px; height: 100%; min-height: 0; }
  461. .sf { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px 24px; padding: 16px 20px; }
  462. .sf-i { display: flex; align-items: center; gap: 12px; min-width: 0; }
  463. .sf-i > label { flex: none; text-align: right; color: var(--t2); white-space: nowrap; }
  464. .sf-i > .ipt { flex: 1; }
  465. .sf-act { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
  466. .sf-act .btn.txt { margin-left: 4px; }
  467. .sf-act .btn.txt .ic { transition: transform .2s; }
  468. .sf-act .btn.txt.up .ic { transform: rotate(180deg); }
  469. .tb-card { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
  470. .tb-card .tbl-wrap { flex: 1; }
  471. .tb-card .pager { border-top: 1px solid var(--row-bd); flex: none; }
  472. .tb-bar { flex: none; display: flex; align-items: center; gap: 8px; min-height: 48px; padding: 0 16px; border-bottom: 1px solid var(--card-hd-bd); }
  473. .tb-bar .ttl { font-size: 14px; font-weight: var(--fw-title); color: var(--t1); }
  474. .selbar { flex: none; display: flex; align-items: center; gap: 12px; height: 40px; padding: 0 16px; background: var(--c-primary-bg); color: var(--t1); font-size: 13px; }
  475. .selbar b { color: var(--c-link); font-family: var(--font-num); }
  476. .skel { position: relative; display: flex; flex-direction: column; gap: 12px; height: 100%; }
  477. .skel .card { padding: 16px 20px; }
  478. .skel .bar { height: 12px; border-radius: 6px; background: var(--fill-2); }
  479. .skel .rowb { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px 24px; }
  480. .skel .rowb .bar { height: 32px; border-radius: var(--r-ctl); }
  481. .skel .lines { flex: 1; display: flex; flex-direction: column; gap: 18px; }
  482. .skel-note { position: absolute; left: 50%; top: 46%; transform: translate(-50%, -50%); display: flex; align-items: center; gap: 8px; padding: 12px 18px; background: var(--bg-card); border-radius: var(--r-pop); box-shadow: var(--sh-pop); border: var(--pop-bd); color: var(--t2); font-size: 14px; white-space: nowrap; }
  483. .skel-note .ic { color: var(--c-primary); }
  484. /* ---------- A 经典三段:侧栏通高(颜色看皮肤的 sd-* 令牌);白色顶栏放折叠按钮、面包屑、交易日和工具;下面一行卡片式标签页 ---------- */
  485. .fa .sd { width: 220px; }
  486. .fa .brand { height: 52px; }
  487. .fa .brand .nm { color: var(--m-hi); }
  488. .fa .nb { flex: none; position: relative; z-index: 6; display: flex; align-items: center; gap: 8px; height: 52px; padding: 0 12px 0 8px; background: var(--bg-card); border-bottom: 1px solid var(--bd); }
  489. .fa .nb .td { margin-right: 8px; }
  490. .fa .tv { height: 38px; gap: 6px; padding: 0 12px; background: var(--bg-card); border-bottom: 1px solid var(--bd); }
  491. .fa .tvi { height: 28px; padding: 0 10px; border: 1px solid var(--bd); border-radius: var(--r-ctl); background: var(--bg-card); }
  492. .fa .tvi.is-act { background: var(--c-primary-bg); border-color: var(--c-primary-border); color: var(--c-link); }
  493. .fa .tvi.is-act::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--c-primary); }
  494. .fa .tvi.is-act .x { color: var(--c-link); }
  495. /* ============ 弹窗 J「卡片分区」与抽屉 E「卡片+描述表」(取自分项选型 overlay.css / overlay2.css 的相关部分) ============ */
  496. .ovl { position: absolute; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; background: var(--mask); }
  497. .ovl.dr { justify-content: flex-end; align-items: stretch; }
  498. .ovl.lv2 { z-index: 120; }
  499. .ovl.lv3 { z-index: 140; }
  500. /* ---------- 弹窗骨架 ---------- */
  501. .dg { display: flex; flex-direction: column; max-height: 820px; background: var(--bg-dlg); backdrop-filter: var(--dlg-bf); border-radius: var(--r-dlg); box-shadow: var(--sh-dlg); overflow: hidden; color: var(--t1); }
  502. .dg-hd { flex: none; display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 0 16px 0 24px; border-bottom: 1px solid var(--bd); }
  503. .dg-hd .ttl { font-size: 16px; line-height: 24px; font-weight: var(--fw-title); color: var(--t1); white-space: nowrap; }
  504. .dg-hd .sub { font-size: 13px; color: var(--t3); white-space: nowrap; }
  505. /* 标题和副标题(账户、模式、操作对象)同一行,基线对齐(2026-10-07 用户要求「一行展示」) */
  506. .dg-hd .row { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
  507. .dg-hd .icb { margin-left: auto; }
  508. .dg-hd .chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
  509. .chip { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 10px; border-radius: var(--r-ctl); background: var(--fill-2); color: var(--t2); font-size: 13px; white-space: nowrap; }
  510. .chip b { font-weight: 500; color: var(--t1); font-family: var(--font-num); font-variant-numeric: tabular-nums; }
  511. .dg-bd { flex: 1; min-height: 0; overflow: auto; padding: 20px 24px; position: relative; }
  512. .dg-ft { flex: none; display: flex; align-items: center; gap: 8px; padding: 12px 24px; border-top: 1px solid var(--bd); }
  513. .dg .hint { font-size: 13px; color: var(--t3); display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
  514. .dg .hint.warn { color: var(--warn-t); }
  515. .dg .hint .ic { width: 14px; height: 14px; }
  516. .dg .hint b { font-family: var(--font-num); font-weight: 600; }
  517. /* ---------- 表单项 ---------- */
  518. .req { color: var(--dang); font-style: normal; margin-right: 4px; }
  519. .fg + .fg { margin-top: 24px; }
  520. .fg-t { display: flex; align-items: baseline; gap: 8px; margin-bottom: 14px; font-size: 15px; line-height: 22px; font-weight: var(--fw-title); color: var(--t1); }
  521. .fg-t small { font-size: 12px; font-weight: 400; color: var(--t3); }
  522. .fgrid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px 24px; }
  523. .fgrid.c2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  524. .fgrid.c3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  525. .fgrid .full { grid-column: 1 / -1; }
  526. .fi { display: flex; min-width: 0; }
  527. .fi > label { flex: none; color: var(--t2); line-height: 32px; white-space: nowrap; }
  528. .fi .fc { flex: 1; min-width: 0; position: relative; }
  529. .fi-l > label { text-align: right; padding-right: 12px; }
  530. .fi-l.al > label { text-align: left; }
  531. .fi-t { flex-direction: column; }
  532. .fi-t > label { line-height: 20px; margin-bottom: 6px; font-size: 13px; }
  533. .ro { display: flex; align-items: center; gap: 6px; min-height: 32px; color: var(--t1); }
  534. .ro .u { color: var(--t3); font-size: 13px; }
  535. .combo { display: flex; gap: 8px; }
  536. .checks { display: flex; align-items: center; gap: 24px; min-height: 32px; }
  537. .fc > .alt { margin-top: 8px; font-size: 13px; }
  538. .fc .fl { left: 0; right: 0; top: 36px; }
  539. .fc .fl .pop { min-width: 0; }
  540. .upl { display: flex; flex-direction: column; gap: 8px; }
  541. .upl-r { display: flex; align-items: center; gap: 12px; }
  542. .upl-tip { font-size: 12px; color: var(--t3); }
  543. .upl-f { display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px; border-radius: var(--r-ctl); background: var(--fill-1); color: var(--t1); font-size: 13px; }
  544. .upl-f .ic { width: 14px; height: 14px; color: var(--t3); }
  545. .upl-f .sz { color: var(--t3); font-family: var(--font-num); }
  546. .upl-f .x { margin-left: auto; cursor: pointer; }
  547. .gst { margin-left: 8px; font-size: 12px; font-weight: 400; color: var(--t3); font-family: var(--font-num); white-space: nowrap; }
  548. .gst.ok { color: var(--succ-t); }
  549. .gst.bad { color: var(--dang-t); }
  550. /* 两段切换 */
  551. .segc { display: inline-flex; padding: 2px; border-radius: var(--r-ctl); background: var(--fill-2); }
  552. .segc span { min-width: 80px; height: 28px; padding: 0 14px; display: inline-flex; align-items: center; justify-content: center; border-radius: calc(var(--r-ctl) - 1px); color: var(--t2); cursor: pointer; white-space: nowrap; }
  553. .segc span.on { background: var(--bg-ctl); color: var(--c-link); font-weight: 500; box-shadow: 0 1px 2px rgba(0,0,0,.12); }
  554. /* ---------- 弹窗 J:卡片分区 ---------- */
  555. .dg-j .dg-bd { background: var(--bg-page); padding: 16px; }
  556. .dgc { background: var(--bg-card); border: var(--card-bd); border-radius: var(--r-card); box-shadow: var(--card-sh); }
  557. .dgc + .dgc { margin-top: 12px; }
  558. .dgc-hd { display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 20px; border-bottom: 1px solid var(--bd-light); }
  559. .dgc-hd b { font-size: 15px; font-weight: var(--fw-title); color: var(--t1); }
  560. .dgc-hd small { font-size: 12px; color: var(--t3); }
  561. .dgc-hd .gst { margin-left: 0; }
  562. .dgc-bd { padding: 16px 20px 20px; }
  563. .calcrow { display: flex; flex-direction: column; gap: 10px; }
  564. .calcrow > div { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; color: var(--t2); }
  565. .calcrow small { margin-left: 10px; font-size: 12px; color: var(--t3); font-family: var(--font-num); }
  566. .calcrow b { font-size: 16px; font-weight: var(--fw-title); color: var(--t1); font-family: var(--font-num); font-variant-numeric: tabular-nums; white-space: nowrap; }
  567. .calcrow b i { font-style: normal; font-size: 13px; font-weight: 400; color: var(--t3); margin-left: 6px; }
  568. /* ---------- 抽屉骨架 ---------- */
  569. .dw { display: flex; flex-direction: column; height: 100%; background: var(--bg-dlg); backdrop-filter: var(--dlg-bf); box-shadow: var(--sh-dlg); color: var(--t1); }
  570. .dw-hd { flex: none; display: flex; align-items: center; gap: 12px; height: 56px; padding: 0 16px 0 24px; border-bottom: 1px solid var(--bd); }
  571. .dw-hd .ttl { font-size: 16px; font-weight: var(--fw-title); color: var(--t1); white-space: nowrap; }
  572. .dw-hd .no { font-size: 15px; font-weight: 500; color: var(--t1); font-family: var(--font-num); font-variant-numeric: tabular-nums; }
  573. .dw-hd .tags { display: flex; gap: 6px; align-items: center; }
  574. .dw-bd { flex: 1; min-height: 0; overflow: auto; padding: 20px 24px; position: relative; }
  575. .dw-bd.gray { background: var(--bg-page); padding: 16px; }
  576. .dw-ft { flex: none; display: flex; align-items: center; gap: 8px; height: 56px; padding: 0 24px; border-top: 1px solid var(--bd); }
  577. .sec + .sec { margin-top: 28px; }
  578. .sec-hd { display: flex; align-items: center; gap: 8px; min-height: 32px; margin-bottom: 14px; }
  579. .sec-hd b { font-size: 15px; font-weight: var(--fw-title); color: var(--t1); }
  580. .sec-hd small { font-size: 12px; color: var(--t3); }
  581. .sec > .alt, .dw .alt { font-size: 13px; }
  582. .sec .tbl-wrap, .dw .tbl-wrap, .card .tbl-wrap.bd { border: 1px solid var(--row-bd); border-radius: var(--r-ctl); }
  583. .sec .tbl-wrap .tbl tr:last-child td, .dw .tbl-wrap .tbl tr:last-child td { border-bottom: 0; }
  584. .dw .gap { height: 12px; }
  585. .dw-hd .facts, .facts { display: flex; align-items: center; gap: 20px; color: var(--t3); font-size: 13px; white-space: nowrap; }
  586. .dw-hd .facts b, .facts b { font-weight: 400; color: var(--t1); margin-left: 6px; font-family: var(--font-num); font-variant-numeric: tabular-nums; }
  587. /* 键值排布 */
  588. .kvg { display: grid; gap: 12px 24px; margin: 0; }
  589. .kvg > div { display: flex; gap: 12px; min-width: 0; line-height: 22px; }
  590. .kvg dt { flex: none; color: var(--t3); white-space: nowrap; }
  591. .kvg dd { margin: 0; min-width: 0; color: var(--t1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  592. .kvg.l3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  593. .kvg.l3 dt { width: 98px; }
  594. .kvg .s2 { grid-column: span 2; }
  595. .kvg.t4 { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px 24px; }
  596. .kvg.t4 > div { flex-direction: column; gap: 2px; }
  597. .kvg.t4 dt { font-size: 12px; line-height: 18px; }
  598. .kvg.t4.c3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  599. .kvg.t4.c2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  600. .kvg.bd3, .kvg.bd2, .kvg.bd1 { gap: 0; border-top: 1px solid var(--bd); border-left: 1px solid var(--bd); }
  601. .kvg.bd3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  602. .kvg.bd2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  603. .kvg.bd1 { grid-template-columns: minmax(0, 1fr); }
  604. .kvg.bd3 > div, .kvg.bd2 > div, .kvg.bd1 > div { gap: 0; border-right: 1px solid var(--bd); border-bottom: 1px solid var(--bd); min-height: 40px; }
  605. .kvg.bd3 dt, .kvg.bd2 dt, .kvg.bd1 dt { width: 116px; padding: 9px 12px; background: var(--fill-1); border-right: 1px solid var(--bd); color: var(--t2); }
  606. .kvg.bd2 dt { width: 190px; }
  607. .kvg.bd1 dt { width: 132px; }
  608. .kvg.bd3 dd, .kvg.bd2 dd, .kvg.bd1 dd { flex: 1; padding: 9px 12px; }
  609. .kvg.bd3 .s2 { grid-column: span 2; }
  610. .kvg .up { color: var(--buy); }
  611. .kvg .dn { color: var(--sell); }
  612. /* 数字卡片 */
  613. .tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
  614. .tile { padding: 12px 14px; border-radius: var(--r-card); background: var(--fill-1); min-width: 0; }
  615. .tile .l { font-size: 12px; line-height: 18px; color: var(--t3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  616. .tile .v { margin-top: 2px; font-size: 18px; line-height: 26px; font-weight: var(--fw-title); color: var(--t1); font-family: var(--font-num); font-variant-numeric: tabular-nums; white-space: nowrap; }
  617. .tile .v i { font-style: normal; font-size: 12px; font-weight: 400; color: var(--t3); margin-left: 4px; }
  618. .tile .bar { height: 4px; margin-top: 8px; border-radius: 2px; background: var(--fill-3); overflow: hidden; }
  619. .tile .bar i { display: block; height: 100%; background: var(--c-primary); }
  620. /* ---------- 抽屉 E:卡片+描述表 ---------- */
  621. .dw-e .dw-hd { height: auto; padding-top: 12px; padding-bottom: 12px; align-items: flex-start; }
  622. .dw-e .dw-hd .idc { display: flex; flex-direction: column; gap: 6px; }
  623. .dw-e .dw-hd .idr { display: flex; align-items: center; gap: 12px; }
  624. .dw-e .dw-hd .no { font-size: 16px; font-weight: var(--fw-title); }
  625. .dw-e .dw-hd .hbtns { display: flex; gap: 8px; align-items: center; margin-top: 4px; }
  626. .dw-e .card { padding: 0 16px 16px; }
  627. .dw-e .card + .card { margin-top: 12px; }
  628. .dw-e .sec-hd { min-height: 48px; margin-bottom: 0; }
  629. .dw-e .sec-hd.sub { min-height: 36px; margin-top: 4px; }
  630. .dw-e .sec-hd.sub b { font-size: 13px; color: var(--t2); }
  631. .dw-e .sec-hd.sub::before { display: none; }
  632. .dw-e .dw-tabs { flex: none; display: flex; gap: 28px; padding: 0 24px; border-bottom: 1px solid var(--bd); background: var(--bg-card); }
  633. .dw-e .dw-tabs .tab { height: 42px; }
  634. .kvline { display: flex; flex-wrap: wrap; gap: 6px 28px; color: var(--t3); }
  635. .kvline span { white-space: nowrap; }
  636. .kvline b { margin-left: 8px; font-weight: 400; color: var(--t1); font-family: var(--font-num); font-variant-numeric: tabular-nums; }
  637. .sumkv { margin: 0; display: flex; flex-direction: column; gap: 8px; font-size: 13px; line-height: 20px; }
  638. .sumkv > div { display: flex; justify-content: space-between; gap: 12px; }
  639. .sumkv dt { flex: none; color: var(--t3); white-space: nowrap; }
  640. .sumkv dd { margin: 0; min-width: 0; color: var(--t1); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  641. /* ============ 完整设计稿新增页面的样式(首页、页内页签、清算、TRS 下单、票据详情、确认框、个人中心、404/401、登录、反馈类) ============ */
  642. /* 多选的值显示成小标签 */
  643. .cv .ipt .chips { display: flex; gap: 4px; flex: 1; min-width: 0; overflow: hidden; }
  644. .cv .chipv { display: inline-flex; align-items: center; height: 22px; padding: 0 6px; border-radius: var(--r-tag); background: var(--fill-2); color: var(--t2); font-size: 12px; white-space: nowrap; }
  645. .cv .btn.ok-pri { background: var(--sell); border-color: var(--sell); color: #fff; box-shadow: none; }
  646. .cv .btn.dng-pri, .cv .btn.ok-pri { box-shadow: var(--btn-pri-sh); }
  647. .cv .btn.is-loading { opacity: .7; cursor: progress; }
  648. .cv .spin { display: inline-block; width: 14px; height: 14px; border-radius: 50%; border: 2px solid currentColor; border-right-color: transparent; animation: cv-spin .8s linear infinite; }
  649. .cv .spin.big { width: 24px; height: 24px; color: var(--c-primary); }
  650. .cv .icb.sm { width: 24px; height: 24px; }
  651. .cv .icb.sm .ic { width: 14px; height: 14px; }
  652. .cv .ic.gender { width: 14px; height: 14px; color: var(--c-link); margin-left: 6px; vertical-align: -2px; }
  653. .cv .fld-tip { margin-top: 4px; font-size: 12px; line-height: 18px; color: var(--t3); }
  654. .cv .chip b { margin-left: 6px; }
  655. .cv .chip .tg { margin-left: 6px; }
  656. /* ---------- 页内页签 ---------- */
  657. .ptabs { display: flex; gap: 28px; flex: none; padding: 0 4px; border-bottom: 1px solid var(--bd); }
  658. .ptabs .tab { height: 40px; font-size: 14px; }
  659. .ctabs { flex: none; display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 0 16px; border-bottom: 1px solid var(--card-hd-bd); }
  660. .ctabs .tabs { border-bottom: 0; align-self: stretch; }
  661. .ctabs .tab { height: 48px; }
  662. .ctabs .tab.on::after { bottom: 0; }
  663. .cv .tab .cnt { margin-left: 6px; font-size: 12px; color: var(--t3); font-family: var(--font-num); }
  664. .cv .tab.on .cnt { color: var(--c-link); }
  665. /* ---------- 首页现状版(2026-10-08 按现状内容重画,默认):只有一张问候卡;工作台版的样式在下一段,整套保留 ---------- */
  666. .hm-now { display: flex; flex-direction: column; gap: 12px; }
  667. .hm-hello { display: flex; align-items: center; gap: 20px; padding: 24px 28px; }
  668. .hm-hello-t { min-width: 0; }
  669. .hm-hello-t b { display: block; font-size: 20px; line-height: 28px; font-weight: 600; color: var(--t1); }
  670. .hm-hello-t p { margin: 6px 0 0; font-size: 14px; line-height: 22px; color: var(--t2); }
  671. /* ---------- 首页工作台(保留,变体 work) ---------- */
  672. .hm { display: grid; grid-template-columns: minmax(0, 1fr) 352px; grid-template-rows: auto minmax(0, 1fr); gap: 12px; height: 100%; min-height: 0; }
  673. .hm .card-hd .sub b { color: var(--t1); font-weight: 600; }
  674. .hm .card-hd .sub b.hot { color: var(--c-link); }
  675. .hm-st { grid-column: 1; grid-row: 1; }
  676. .hm-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 10px; padding: 16px; }
  677. .hm-tile { display: flex; flex-direction: column; gap: 4px; padding: 10px 12px; border: 1px solid var(--bd); border-radius: var(--r-card); background: var(--fill-1); cursor: pointer; min-width: 0; transition: border-color .15s, background .15s; }
  678. .hm-tile:hover { border-color: var(--c-primary); background: var(--bg-card); }
  679. .hm-tile .l { font-size: 12px; line-height: 18px; color: var(--t3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  680. .hm-tile b { font-size: 22px; line-height: 28px; font-weight: 600; color: var(--t1); }
  681. .hm-tile.hot { background: var(--bg-card); border-color: var(--c-primary-border); box-shadow: inset 3px 0 0 var(--c-primary); }
  682. .hm-tile.hot b { color: var(--c-link); }
  683. .hm-tile.zero b { color: var(--t4); }
  684. .hm-sc-card { grid-column: 2; grid-row: 1; }
  685. .hm-scs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; padding: 16px; }
  686. .hm-sc { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 12px 6px 10px; border: 1px solid var(--bd); border-radius: var(--r-card); background: var(--fill-1); cursor: pointer; text-align: center; min-width: 0; transition: border-color .15s, background .15s; }
  687. .hm-sc:hover { border-color: var(--c-primary-border); background: var(--c-primary-bg); }
  688. .hm-sc .ic { width: 20px; height: 20px; color: var(--c-primary); margin-bottom: 2px; }
  689. .hm-sc b { font-size: 13px; font-weight: 500; color: var(--t1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  690. .hm-sc small { font-size: 11px; color: var(--t3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  691. .hm-tb { grid-column: 1 / -1; grid-row: 2; }
  692. /* ---------- 清算 ---------- */
  693. .clr { display: flex; flex-direction: column; height: 100%; min-height: 0; }
  694. .clr > .card-bd { flex: 1; min-height: 0; overflow: auto; }
  695. .clr-info { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
  696. .cv .tbl tbody tr.is-sel td { font-weight: 500; }
  697. /* ---------- TRS 交易下单页 ---------- */
  698. .trs-acct { display: flex; align-items: center; gap: 8px; padding: 12px 16px; }
  699. .trs-acct .sf-i > label { color: var(--t2); }
  700. /* ---------- 短表单弹窗(2026-10-08 用户给的下单弹窗布局):标签在左、单列;顶部高亮信息块;底部灰色结果块 ---------- */
  701. .dg-s .dg-hd { border-bottom: 0; padding-top: 4px; }
  702. .dg-s .dg-bd { padding: 8px 24px 24px; display: flex; gap: 20px; }
  703. .dg-s .sf-main { flex: 1; min-width: 0; }
  704. .sf-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-bottom: 20px; }
  705. .sf-tile { padding: 12px 16px; border-radius: var(--r-card); background: var(--c-primary-bg); min-width: 0; }
  706. .sf-tile .l { display: block; font-size: 13px; color: var(--t2); }
  707. .sf-tile b { display: block; margin-top: 2px; font-size: 20px; line-height: 28px; font-weight: 600; color: var(--c-link); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  708. .sf-tile b i { font-style: normal; font-size: 13px; font-weight: 400; color: var(--c-link); opacity: .7; margin-left: 4px; }
  709. .sf-rows { display: flex; flex-direction: column; gap: 18px; }
  710. .sf-row { display: flex; gap: 16px; align-items: flex-start; }
  711. .sf-row > label { flex: none; width: var(--lw); text-align: right; line-height: 32px; color: var(--t2); white-space: nowrap; }
  712. .sf-c { flex: 1; min-width: 0; }
  713. /* 标签在上:标签独占一行,结果块不再空出标签列 */
  714. .dg-s.lt .sf-rows { gap: 16px; }
  715. .dg-s.lt .sf-row { flex-direction: column; align-items: stretch; gap: 6px; }
  716. .dg-s.lt .sf-row > label { width: auto; text-align: left; line-height: 20px; font-size: 13px; }
  717. .dg-s.lt .sf-row > label:empty { display: none; }
  718. .dg-s.lt .sf-res { margin-top: 6px; }
  719. .sf-res { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-top: 2px; }
  720. .sf-row + .sf-row .sf-res { margin-top: 0; }
  721. .sf-rt { padding: 12px 16px; background: var(--fill-1); border-radius: var(--r-card); min-width: 0; }
  722. .sf-rt .l { display: block; font-size: 12px; color: var(--t3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  723. .sf-rt > small { display: block; margin-top: 2px; font-size: 12px; color: var(--t4); }
  724. .sf-rt b { display: block; margin-top: 2px; font-size: 18px; line-height: 26px; font-weight: 600; color: var(--t1); white-space: nowrap; }
  725. .sf-rt b i { font-style: normal; font-size: 12px; font-weight: 400; color: var(--t3); margin-left: 4px; }
  726. .cv .stp { display: flex; align-items: center; height: 32px; border: 1px solid var(--ipt-bd); border-radius: var(--r-ctl); background: var(--ipt-bg); overflow: hidden; }
  727. .cv .stp:hover { border-color: var(--ipt-bd-h); }
  728. .cv .stp.is-err { border-color: var(--dang); }
  729. .cv .stp-b { flex: none; width: 32px; align-self: stretch; display: inline-flex; align-items: center; justify-content: center; background: var(--fill-1); color: var(--t2); font-size: 16px; cursor: pointer; user-select: none; }
  730. .cv .stp-b:first-child { border-right: 1px solid var(--bd); }
  731. .cv .stp-b:last-child { border-left: 1px solid var(--bd); }
  732. .cv .stp-b:hover { background: var(--fill-2); color: var(--c-link); }
  733. .cv .stp .val { flex: 1; text-align: center; font-weight: 600; color: var(--t1); }
  734. .cv .stp .sfx { padding-right: 10px; color: var(--t3); font-size: 13px; }
  735. .qchips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
  736. .qchip { display: inline-flex; align-items: center; gap: 4px; height: 26px; padding: 0 10px; border: 1px solid var(--bd-ctl); border-radius: var(--r-ctl); background: var(--bg-card); font-size: 12px; color: var(--t2); cursor: pointer; white-space: nowrap; }
  737. .qchip b { font-weight: 500; color: var(--t1); }
  738. .qchip:hover { border-color: var(--c-primary-hover); color: var(--c-link); }
  739. .qchip.on { border-color: var(--c-primary); background: var(--c-primary-bg); color: var(--c-link); }
  740. .qchip.on b { color: var(--c-link); }
  741. /* 右侧五档行情 */
  742. .qpanel { flex: none; width: 280px; display: flex; flex-direction: column; padding: 14px 16px 10px; background: var(--fill-1); border: 1px solid var(--bd); border-radius: var(--r-card); }
  743. .qname { display: flex; align-items: baseline; gap: 8px; }
  744. .qname b { font-size: 16px; font-weight: 600; color: var(--t1); }
  745. .qname .code { font-size: 13px; color: var(--t3); }
  746. .qname .live { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--t3); white-space: nowrap; }
  747. .qname .live::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--succ); }
  748. .qprice { display: flex; align-items: baseline; gap: 10px; margin-top: 4px; }
  749. .qprice .last { font-size: 26px; line-height: 32px; font-weight: 600; }
  750. .qprice .chg { font-size: 13px; }
  751. .qsub { margin: 2px 0 8px; font-size: 12px; color: var(--t3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  752. .qsub b { font-weight: 400; color: var(--t2); }
  753. .ql { display: grid; grid-template-columns: 32px 56px minmax(0, 1fr); gap: 8px; align-items: center; height: 28px; padding: 0 6px; border-radius: 4px; font-size: 13px; cursor: pointer; }
  754. .ql:hover { background: var(--fill-2); }
  755. .ql.on { background: var(--c-primary-bg); }
  756. .ql > span:first-child { color: var(--t3); }
  757. .ql b { font-weight: 500; }
  758. .ql .vol { position: relative; text-align: right; height: 20px; line-height: 20px; }
  759. .ql .vol em { position: absolute; right: 0; top: 2px; bottom: 2px; border-radius: 3px; opacity: .16; }
  760. .ql.ask .vol em { background: var(--sell); }
  761. .ql.bid .vol em { background: var(--buy); }
  762. .ql .vol i { position: relative; font-style: normal; color: var(--t1); padding-right: 4px; }
  763. .qmid { display: flex; align-items: baseline; gap: 8px; margin: 6px 0; padding: 6px 6px; border-top: 1px dashed var(--bd-ctl); border-bottom: 1px dashed var(--bd-ctl); }
  764. .qmid b { font-size: 16px; font-weight: 600; }
  765. .qmid span { margin-left: auto; font-size: 13px; }
  766. /* ---------- 票据详情抽屉 ---------- */
  767. .dw-e .dw-hd .hbtns { display: flex; align-items: center; gap: 8px; margin-top: 2px; }
  768. .dw-e .dw-hd .hbtns.col { flex-direction: column; align-items: flex-end; gap: 6px; margin-top: 0; }
  769. .dw-e .dw-hd .hbtns .r { display: flex; align-items: center; gap: 8px; }
  770. .cv .tbl td.wrap { white-space: normal; line-height: 20px; padding-top: 9px; padding-bottom: 9px; }
  771. .cv .kvg.bd2.n dt { width: 116px; }
  772. .dw-e .card .tbl-wrap + .sec-hd.sub { margin-top: 12px; }
  773. .dw-e .card .kvg + .sec-hd.sub { margin-top: 10px; }
  774. /* ---------- 确认框 ---------- */
  775. .cfm { width: 420px; background: var(--bg-dlg); border-radius: var(--r-dlg); box-shadow: var(--sh-dlg); color: var(--t1); }
  776. /* 状态图标和标题同一行,下面的说明和描述表与图标左对齐,不缩进 */
  777. .cfm-bd { padding: 22px 24px 18px; }
  778. .cfm-hd { display: flex; align-items: center; gap: 10px; }
  779. .cfm-hd b { font-size: 16px; line-height: 24px; font-weight: var(--fw-title); color: var(--t1); }
  780. .cfm-ic { flex: none; width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; }
  781. .cfm-ic .ic { width: 24px; height: 24px; }
  782. .cfm-ic.danger { color: var(--dang); }
  783. .cfm-ic.warning { color: var(--warn); }
  784. .cfm-ic.info { color: var(--c-primary); }
  785. .cfm-bd > p { margin: 10px 0 0; color: var(--t2); }
  786. .cfm-kv { margin: 12px 0 0; display: flex; flex-direction: column; gap: 0; border-top: 1px solid var(--bd); }
  787. .cfm-kv > div { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; font-size: 13px; border-bottom: 1px solid var(--bd-light); }
  788. .cfm-kv dt { color: var(--t3); white-space: nowrap; }
  789. .cfm-kv dd { margin: 0; color: var(--t1); text-align: right; }
  790. .cfm-ft { display: flex; justify-content: flex-end; gap: 8px; padding: 0 24px 20px; }
  791. /* ---------- 个人中心 ---------- */
  792. .prof { display: grid; grid-template-columns: 360px minmax(0, 1fr); gap: 12px; align-items: start; }
  793. .prof-id { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 36px 24px; }
  794. .prof-ava { position: relative; cursor: pointer; }
  795. .cv .ava.big { width: 88px; height: 88px; font-size: 32px; }
  796. .prof-cam { position: absolute; right: -2px; bottom: 2px; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; background: var(--bg-card); border: 1px solid var(--bd-ctl); color: var(--t2); }
  797. .prof-cam .ic { width: 14px; height: 14px; }
  798. .prof-nm { display: flex; align-items: center; gap: 4px; margin-top: 8px; }
  799. .prof-nm b { font-size: 18px; font-weight: 600; color: var(--t1); }
  800. .prof-role { font-size: 13px; color: var(--t3); }
  801. .prof-r { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
  802. .prof-sec { display: flex; flex-direction: column; }
  803. .prof-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 16px; border-bottom: 1px solid var(--bd); }
  804. .prof-row:last-child { border-bottom: 0; }
  805. .prof-row b { display: block; font-weight: 500; color: var(--t1); }
  806. .prof-row small { display: block; margin-top: 2px; font-size: 12px; color: var(--t3); }
  807. /* ---------- 404 / 401 ---------- */
  808. .errp { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; height: 100%; min-height: 560px; }
  809. .err-art { width: 420px; height: 160px; border-radius: var(--r-card); background: var(--bg-card); box-shadow: var(--card-sh); }
  810. .err-t { text-align: center; }
  811. .err-t b { display: block; font-size: 20px; line-height: 28px; font-weight: 600; color: var(--t1); }
  812. .err-t p { margin: 8px 0 20px; color: var(--t2); }
  813. /* ---------- 登录页:分项选型 C「右侧浮卡·全幅底图」(表单卡片靠右浮着,左侧一列是系统名、业务模块、三端入口) ---------- */
  814. .cv.login { height: 900px; overflow: hidden; }
  815. .lg { position: relative; height: 100%; display: flex; align-items: center; justify-content: center; background: var(--bg-page); color: var(--t2); }
  816. /* 左列和浮卡成一组:组宽按 1440 基准(左 72 + 列 440 + 间距 392 + 卡 440 + 右 96),窗口更宽时整组居中、两块间距不变(2026-10-08 定) */
  817. .lg-stage { position: relative; z-index: 1; display: flex; align-items: center; justify-content: flex-end; width: 100%; max-width: 1440px; padding: 0 96px 0 72px; }
  818. .lg-actions { position: absolute; top: 20px; right: 28px; z-index: 5; display: flex; gap: 4px; }
  819. .lg-actions .icb { color: var(--t3); }
  820. .lg-deco { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--c-primary); pointer-events: none; }
  821. .lg-deco path, .lg-deco circle { fill: none; stroke: currentColor; stroke-width: 1.5; }
  822. .lg-logo { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 36px; height: 36px; border-radius: calc(var(--r-ctl) + 4px); background: var(--logo-bg); color: var(--logo-t); font-size: 17px; font-weight: 600; }
  823. .lg-logo.sm { width: 28px; height: 28px; font-size: 14px; border-radius: calc(var(--r-ctl) + 2px); }
  824. .lg-brand { display: flex; align-items: center; gap: 12px; white-space: nowrap; }
  825. .lg-brand b { font-size: 20px; font-weight: 600; color: var(--t1); letter-spacing: .02em; }
  826. .lg-brand.sm b { font-size: 15px; }
  827. /* 左侧一列:垂直居中,和右边的卡片对齐 */
  828. .lg-c-left { position: absolute; left: 72px; top: 0; bottom: 0; width: 440px; display: flex; flex-direction: column; justify-content: center; gap: 32px; }
  829. .lg-c-sys { display: block; font-size: 32px; line-height: 40px; font-weight: 600; color: var(--t1); letter-spacing: .02em; }
  830. .lg-c-ver { margin-top: 4px; font-size: 12px; color: var(--t3); font-family: var(--font-num); }
  831. /* 右侧浮卡 */
  832. .lg-card { position: relative; width: 440px; padding: 36px 40px 32px; background: var(--bg-dlg); border-radius: var(--r-dlg); box-shadow: var(--sh-dlg); color: var(--t1); }
  833. .lg-card .lg-brand { margin-bottom: 24px; }
  834. .lg-ttl { font-size: 22px; line-height: 30px; font-weight: 600; color: var(--t1); margin-bottom: 20px; }
  835. .lg-p { margin: -12px 0 20px; font-size: 13px; line-height: 20px; color: var(--t3); }
  836. .lg-fi { margin-bottom: 18px; }
  837. .lg-fi > label { display: block; margin-bottom: 6px; font-size: 13px; line-height: 18px; color: var(--t2); }
  838. .cv .lg-ipt { height: 40px; padding: 0 12px; }
  839. .cv .lg-ipt > .ic { width: 16px; height: 16px; }
  840. .cv .lg-ipt .val { color: var(--t1); }
  841. .lg-eye { display: inline-flex; margin-left: auto; cursor: pointer; color: var(--t3); }
  842. .lg-eye:hover { color: var(--t1); }
  843. .lg-eye .ic { width: 16px; height: 16px; }
  844. .lg-cap-row { display: flex; gap: 10px; align-items: center; }
  845. .lg-cap { flex: none; width: 120px; height: 40px; border: 1px solid var(--bd-ctl); border-radius: var(--r-ctl); background: var(--fill-2); cursor: pointer; overflow: hidden; }
  846. .lg-cap:hover { border-color: var(--ipt-bd-h); }
  847. .lg-cap svg { display: block; width: 100%; height: 100%; }
  848. .lg-cap text { font-family: var(--font-num); }
  849. .lg-row { display: flex; align-items: center; justify-content: space-between; margin: -2px 0 20px; font-size: 13px; }
  850. .lg-row .chk-l { font-size: 13px; }
  851. .lg-row.center { justify-content: center; gap: 6px; margin: 16px 0 0; }
  852. .cv .lg-btn { width: 100%; height: 40px; font-size: 15px; }
  853. /* ---------- 反馈类总览 ---------- */
  854. .fb-note { margin-top: 12px; font-size: 12px; line-height: 18px; color: var(--t3); }
  855. .fb-msgs { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 8px 0; }
  856. .cv .toast { max-width: 100%; }
  857. .cv .toast .x { margin-left: 8px; color: var(--t3); cursor: pointer; display: inline-flex; }
  858. .cv .toast .x .ic { width: 14px; height: 14px; }
  859. .cv .toast .ic.success { color: var(--succ); }
  860. .cv .toast .ic.error { color: var(--dang); }
  861. .cv .toast .ic.warning { color: var(--warn); }
  862. .cv .toast .ic.info { color: var(--c-primary); }
  863. .ntf { width: 360px; margin: 0 0 0 auto; padding: 14px 16px; background: var(--bg-pop); border: var(--pop-bd); box-shadow: var(--sh-pop); border-radius: var(--r-pop); }
  864. .ntf-hd { display: flex; align-items: center; gap: 8px; }
  865. .ntf-hd .ic { width: 18px; height: 18px; }
  866. .ntf-hd .ic.ok { color: var(--succ); }
  867. .ntf-hd b { flex: 1; font-size: 14px; font-weight: 600; color: var(--t1); }
  868. .ntf-hd small { font-size: 12px; color: var(--t3); font-family: var(--font-num); }
  869. .ntf-hd .x { color: var(--t3); cursor: pointer; display: inline-flex; }
  870. .ntf-hd .x .ic { width: 14px; height: 14px; }
  871. .ntf p { margin: 8px 0 6px; font-size: 13px; line-height: 20px; color: var(--t2); }
  872. .ntf .lnk { font-size: 13px; display: inline-flex; align-items: center; gap: 2px; }
  873. .ntf .lnk .ic { width: 12px; height: 12px; }
  874. .fb-alts { display: flex; flex-direction: column; gap: 10px; }
  875. .cv .alt.titled { align-items: flex-start; }
  876. .cv .alt.titled > div { flex: 1; }
  877. .cv .alt.titled b { display: block; font-weight: 600; }
  878. .cv .alt.titled p { margin: 2px 0 0; font-size: 13px; color: var(--t2); }
  879. .cv .alt .x { margin-left: auto; color: var(--t3); cursor: pointer; display: inline-flex; margin-top: 3px; }
  880. .cv .alt .x .ic { width: 14px; height: 14px; color: var(--t3); margin: 0; }
  881. .fb-empties { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
  882. .fb-lbl { font-size: 12px; color: var(--t3); margin-bottom: 6px; }
  883. .cv .empty.box { height: 200px; justify-content: center; border: 1px solid var(--bd); border-radius: var(--r-card); background: var(--fill-1); }
  884. .cv .empty small { font-size: 12px; color: var(--t4); }
  885. .cv .empty .btn.sm { margin-top: 4px; }
  886. .fb-load { display: flex; gap: 20px; }
  887. .fb-load .tbl-wrap { flex: 1; min-width: 0; }
  888. .fb-load-r { flex: none; width: 240px; }
  889. .cv .loading { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; background: var(--veil); color: var(--t2); font-size: 13px; z-index: 5; }
  890. .fb-results { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
  891. .result { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 28px 16px; border: 1px solid var(--bd); border-radius: var(--r-card); background: var(--fill-1); }
  892. .res-ic { width: 56px; height: 56px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; border: 2px solid currentColor; margin-bottom: 14px; }
  893. .res-ic .ic { width: 30px; height: 30px; }
  894. .res-ic.success { color: var(--succ); }
  895. .res-ic.error { color: var(--dang); }
  896. .result b { font-size: 18px; line-height: 26px; font-weight: 600; color: var(--t1); }
  897. .result p { margin: 6px 0 18px; font-size: 13px; color: var(--t2); max-width: 360px; }
  898. /* ============ 皮肤之外的结构差异(取自分项选型 themes.css 的 F 段,改成读令牌,四套皮肤共用)。放在最后 ============ */
  899. .cv .tg { padding: 0 9px; }
  900. .cv .ck.on, .cv .ck.half { background: var(--grad); border-color: transparent; }
  901. .cv .tab.on::after { height: 3px; border-radius: 3px 3px 0 0; background: var(--grad); }
  902. .cv .alt { border: 0; border-radius: 10px; }
  903. .cv .tile .bar i { background: var(--grad); }
  904. .cv .dlg-hd, .cv .dlg-ft { border-color: var(--bd-light); }
  905. .cv .hm-tile b { font-weight: 600; }
  906. .cv .lg { background: var(--canvas, var(--bg-page)); }
  907. /* 深色皮肤:单选选中后圆点用白色,深色底上才看得见;标签文字用「亮色字」令牌 */
  908. .cv[data-dark] .rd.on { background: #fff; }
  909. .cv[data-dark] .kit-mask::before { background: repeating-linear-gradient(0deg, rgba(255,255,255,.03) 0 40px, transparent 40px 41px); }
  910. /* 深色 B 的提示条、登录页模块标签只描边:描边令牌是半透明彩色,底色透明 */
  911. .cv[data-v="db"] .alt { border: 1px solid var(--x-bd); background: var(--fill-1); }
  912. </style>
  913. </head>
  914. <body>
  915. <header class="bk-hd"><b>三端 CMS · 完整 UI 设计稿</b><div class="bk-picks"><span>基础视觉 F 晴空渐变</span><span>框架 A 经典三段</span><span>长表单 J 卡片分区</span><span>抽屉 E 卡片+描述表</span><span>登录页 C 右侧浮卡</span><span>深色皮肤 B 石墨灰</span></div><span class="sp"></span><div class="bk-theme" id="bkTheme"></div><span class="bk-date">按 2026-10-08 第一批评审结果绘制 · 1440×900 · 三端一致</span></header>
  916. <div class="bk-body">
  917. <nav class="bk-nav" id="bkNav"></nav>
  918. <main class="bk-main">
  919. <div class="bk-bar" id="bkBar"></div>
  920. <div class="bk-host" id="bkStage"></div>
  921. <div id="bkSpec" hidden></div>
  922. <section class="bk-notes" id="bkNotes"></section>
  923. </main>
  924. </div>
  925. <div class="bk-full" id="bkFull" hidden>
  926. <div class="bk-full-bar" id="bkFullBar"></div>
  927. <div class="bk-host" id="bkFullStage"></div>
  928. </div>
  929. <script>
  930. /* 各批方案共用的内容数据(沿用第五轮从代码提取的内容,只作内容来源,不含任何设计)。
  931. * 字段、顺序、文案取自三端前端代码与 ddl 字典 seed;表内数值是按字段格式编写的示例值,未按业务公式计算。
  932. * 2026-09-30 同步代码:TRS 委托状态「已过期」改为「过期」。
  933. * 修改后执行:python3 build.py
  934. */
  935. window.SPEC_DATA = (function () {
  936. // 字典:文案 -> Element Plus tag 类型(取自 ddl/<sys>/public/数据字典/seed.sql)
  937. const dict = {
  938. direction: { 买入: "danger", 卖出: "success" },
  939. op_type: { 正常: "primary", 强平: "danger", 到期: "warning" },
  940. process_mode: { 自动: "primary", 人工: "warning" },
  941. hedge_progress: { 未开始: "info", 对冲中: "primary", 已结束: "success" },
  942. note_order_status: {
  943. 待处理: "info", 执行中: "primary", 待撤: "warning", 撤单中: "warning", 确认中: "primary",
  944. 已完成: "success", 已结算: "success", 已撤: "info", 已拒绝: "danger",
  945. },
  946. note_type: { "Delta A": "primary", "Delta B": "warning" },
  947. order_source: { CRM: "primary", IOS: "info", Android: "info", H5: "info", 发行端: "warning" },
  948. // 销售端下单方式的文案取自代码注释;标签颜色在 seed 里未核对,这里按「本端 primary、其余 info」示意
  949. sales_order_source: { 销售端: "primary", iOS: "info", Android: "info", H5: "info" },
  950. hedge_system: { TRS: "primary", 自定义: "info" },
  951. hedge_order_type: { 普通单: "primary", 算法TWAP: "warning", 算法POV: "warning", "算法E-Amount": "warning" },
  952. trs_order_status: {
  953. 未报: "info", 待报: "info", 已报: "primary", 部成: "warning", 待撤: "warning", 已撤: "info",
  954. 部撤: "warning", 已成: "success", 废单: "danger", 过期: "info", 部废: "danger",
  955. },
  956. trade_channel: { QFII: "primary", 深沪港通: "primary", 港股自营: "info" },
  957. price_req_type: { 限价: "primary", 市价: "warning" },
  958. order_type: { 正常下单: "primary", 后台下单: "info", 算法下单: "warning", 后台平仓: "danger", 后台补单: "info", 条件单: "success" },
  959. order_mode: { Windows: "primary", iOS: "primary", Android: "primary", FIX: "warning", 电话: "info", 邮件: "info", CRM: "info", OpenAPI: "warning", H5: "info" },
  960. };
  961. const menus = {
  962. issue: [
  963. ["报表管理", "chart", ["共赢票据交易确认书", "日常报表"]],
  964. ["风控管理", "shield", ["白名单管理", "共赢票据额度管理", "共赢票据风险监控"]],
  965. ["共赢票据管理", "ticket", ["订单管理", "票据管理", "票据日常对冲统计", "票据展期管理", "询价管理"]],
  966. ["汇率管理", "coin", ["账面汇率"]],
  967. ["清算管理", "check", ["共赢票据成交对账", "共赢票据持仓对账", "共赢票据清算", "营业日日切"]],
  968. ["系统设置", "gear", ["对冲账户管理", "费用管理", "交易时间片设置", "平台参数", "日历配置", "自动化配置"]],
  969. ["常驻工作进程", "cpu", ["工作进程配置", "实例与分片监控", "操作记录"]],
  970. ["销售机构管理", "building", ["机构管理", "平台参数"]],
  971. ["计划任务中心", "clock", ["计划任务配置", "任务运行记录"]],
  972. ["通知和告警管理", "mail", ["通知设置", "邮件发送记录", "短信发送记录", "飞书发送记录"]],
  973. ],
  974. sales: [
  975. ["报表管理", "chart", ["共赢票据交易确认书", "日常报表", "日结单", "月结单"]],
  976. ["费用管理", "coin", ["费用串列表", "投资者费用管理"]],
  977. ["风控管理", "shield", ["共赢票据风险监控"]],
  978. ["共赢票据管理", "ticket", ["订单管理", "票据管理", "票据展期管理", "询价管理"]],
  979. ["机构系统设置", "gear", ["帮助中心", "机构参数", "日历配置"]],
  980. ["清算管理", "check", ["共赢票据成交对账", "共赢票据持仓对账", "共赢票据清算"]],
  981. ["投资者管理", "user", ["开户审核", "投资者信息"]],
  982. ["资金管理", "card", ["出金", "汇率", "票据转外部", "入金", "透支额度管理", "外部转票据", "BizCode管理"]],
  983. ["常驻工作进程", "cpu", ["工作进程配置", "实例与分片监控", "操作记录"]],
  984. ["计划任务中心", "clock", ["计划任务配置", "任务运行记录"]],
  985. ["通知和告警管理", "mail", ["通知设置", "邮件发送记录", "短信发送记录", "飞书发送记录"]],
  986. ],
  987. trs: [
  988. ["报表管理", "chart", ["TRS 报表"]],
  989. ["财务管理", "coin", ["收取利息"]],
  990. ["对账管理", "scale", ["成交对账", "持仓对账"]],
  991. ["风险监控", "shield", ["成交数据统计", "股票融资额度监控", "平台风险监控", "通道风险监控", "用户风险监控", "用户融资额度管理"]],
  992. ["清算管理", "check", ["公司行动", "清算"]],
  993. ["通道管理", "sliders", ["通道连接", "通道账户"]],
  994. ["系统设置", "gear", ["交易补单", "交易下单", "算法下单", "系统参数", "下单风控设置", "交易日历"]],
  995. ["用户管理", "user", ["成交查询", "清算群组管理", "委托查询", "用户通道优先级", "用户信息"]],
  996. ["资金管理", "card", ["出金", "汇率", "亏损交收", "流水 bizcode 维护", "入金"]],
  997. ["常驻工作进程", "cpu", ["工作进程配置", "实例与分片监控", "操作记录"]],
  998. ["计划任务中心", "clock", ["计划任务配置", "任务运行记录"]],
  999. ["通知和告警管理", "mail", ["通知设置", "邮件发送记录", "短信发送记录", "飞书发送记录"]],
  1000. ],
  1001. };
  1002. const sysName = { issue: "发行管理系统", sales: "销售管理系统", trs: "TRS管理系统" };
  1003. const tradeDays = [["营业日", "2026-09-25"], ["A股交易日 (QFII)", "2026-09-25"], ["A股交易日 (深沪港通)", "2026-09-25"]];
  1004. /* ---------- 发行端 · 订单管理(issue_frontend/src/views/issue/order/index.vue) ---------- */
  1005. const issueSearch = [
  1006. { label: "主订单编号", type: "input", ph: "主订单编号" },
  1007. { label: "子订单编号", type: "input", ph: "子订单编号" },
  1008. { label: "订单方向", type: "select", ph: "全部" },
  1009. { label: "订单状态", type: "select", ph: "全部", multiple: true },
  1010. { label: "票据类型", type: "select", ph: "全部" },
  1011. { label: "操作类型", type: "select", ph: "全部", multiple: true },
  1012. { label: "处理方式", type: "select", ph: "全部" },
  1013. { label: "投资者", type: "input", ph: "投资者编号/账户/名称" },
  1014. { label: "机构名称", type: "select", ph: "全部" },
  1015. { label: "证券代码/名称", type: "input", ph: "证券代码/名称" },
  1016. { label: "交易日期", type: "daterange", value: ["2026-09-25", "2026-09-25"], sep: "~" },
  1017. ];
  1018. const issueActions = [["查找", "primary", "search"], ["重置", "", "refresh"], ["数据导出", "", "download"]];
  1019. // merge: 按主订单跨行合并;group: 激进套用的列分组
  1020. const issueColumns = [
  1021. { key: "no", title: "主订单编号", w: 200, merge: true, fixed: true, mono: true, group: "订单" },
  1022. { key: "direction", title: "订单方向", w: 90, tag: "direction", merge: true, group: "订单" },
  1023. { key: "op", title: "操作类型", w: 100, tag: "op_type", merge: true, group: "订单" },
  1024. { key: "mode", title: "处理方式", w: 90, tag: "process_mode", merge: true, group: "订单" },
  1025. { key: "hedge", title: "对冲进度", w: 90, tag: "hedge_progress", merge: true, group: "订单" },
  1026. { key: "status", title: "订单状态", w: 90, tag: "note_order_status", group: "订单" },
  1027. { key: "noteType", title: "票据类型", w: 90, tag: "note_type", group: "订单" },
  1028. { key: "invNo", title: "投资者编号", w: 150, mono: true, group: "投资者" },
  1029. { key: "invAcct", title: "投资者账户", w: 120, mono: true, group: "投资者" },
  1030. { key: "invName", title: "投资者名称", w: 110, group: "投资者" },
  1031. { key: "fundAcct", title: "资金账户", w: 150, mono: true, group: "投资者" },
  1032. { key: "org", title: "机构名称", w: 160, group: "投资者" },
  1033. { key: "stock", title: "证券代码/名称", w: 200, group: "标的与条款" },
  1034. { key: "noteDir", title: "票据方向", w: 90, group: "标的与条款" },
  1035. { key: "period", title: "周期", w: 90, group: "标的与条款" },
  1036. { key: "notional", title: "订单名义本金(交易币种)", w: 200, align: "right", num: true, group: "金额与成交" },
  1037. { key: "priceReq", title: "价格要求(交易币种)", w: 170, align: "center", priceReq: true, group: "金额与成交" },
  1038. { key: "execNotional", title: "执行名义本金(交易币种)", w: 200, align: "right", num: true, group: "金额与成交" },
  1039. { key: "avgPrice", title: "订单成交均价(交易币种)", w: 200, align: "right", num: true, group: "金额与成交" },
  1040. { key: "execQty", title: "执行订单股数", w: 170, align: "right", num: true, group: "金额与成交" },
  1041. { key: "hedgeQty", title: "对冲股数", w: 110, align: "right", num: true, group: "金额与成交" },
  1042. { key: "tradeCcy", title: "交易币种", w: 90, group: "金额与成交" },
  1043. { key: "settleCcy", title: "结算币种", w: 90, group: "金额与成交" },
  1044. { key: "orderTime", title: "下单时间", w: 165, mono: true, group: "时间" },
  1045. { key: "tradeDate", title: "交易日", w: 110, mono: true, group: "时间" },
  1046. { key: "settleDate", title: "预计交收日", w: 110, mono: true, group: "时间" },
  1047. { key: "subNo", title: "子订单编号", w: 200, mono: true, group: "关联" },
  1048. { key: "noteNo", title: "关联主票据编号", w: 200, link: true, mono: true, group: "关联" },
  1049. { key: "subNoteNo", title: "关联子票据编号", w: 200, link: true, mono: true, group: "关联" },
  1050. { key: "source", title: "下单方式", w: 100, tag: "order_source", group: "关联" },
  1051. { key: "remark", title: "备注", w: 140, group: "关联" },
  1052. ];
  1053. const people = [
  1054. ["张明远", "明德资产管理"], ["李婉清", "远航财富"], ["王子豪", "明德资产管理"], ["陈思雨", "安衡证券"],
  1055. ["刘建国", "远航财富"], ["赵雅琳", "启原投资"], ["孙浩然", "明德资产管理"], ["周晓彤", "安衡证券"],
  1056. ["吴俊杰", "远航财富"], ["郑可欣", "启原投资"], ["黄一鸣", "安衡证券"], ["林书瑶", "远航财富"],
  1057. ];
  1058. const stocks = ["600519 贵州茅台", "300750 宁德时代", "000858 五粮液", "601318 中国平安", "002594 比亚迪", "600036 招商银行",
  1059. "601899 紫金矿业", "600900 长江电力", "688981 中芯国际", "000333 美的集团", "601012 隆基绿能", "600276 恒瑞医药"];
  1060. // [方向, 操作类型, 处理方式, 对冲进度, [子单状态...], 周期, 名义本金, 价格要求, 下单方式]
  1061. const plan = [
  1062. ["买入", "正常", "自动", "未开始", ["待处理"], "3M", 3000000, "限价:1,688.00", "CRM"],
  1063. ["买入", "正常", "人工", "对冲中", ["执行中"], "6M", 2000000, "限价:245.30", "IOS"],
  1064. ["卖出", "正常", "自动", "对冲中", ["执行中", "执行中"], "", 0, "市价", "CRM"],
  1065. ["买入", "正常", "自动", "已结束", ["确认中"], "3M", 1500000, "限价:52.18", "Android"],
  1066. ["卖出", "强平", "人工", "已结束", ["已完成"], "", 0, "市价", "发行端"],
  1067. ["买入", "正常", "自动", "已结束", ["已完成", "已完成"], "6M", 1000000, "限价:35.60", "CRM"],
  1068. ["买入", "正常", "自动", "未开始", ["待撤"], "1M", 800000, "限价:16.42", "H5"],
  1069. ["卖出", "到期", "自动", "已结束", ["已结算"], "", 0, "市价", "发行端"],
  1070. ["买入", "正常", "自动", "未开始", ["已拒绝"], "3M", 500000, "限价:88.20", "CRM"],
  1071. ["买入", "正常", "人工", "对冲中", ["撤单中"], "3M", 1200000, "限价:61.75", "IOS"],
  1072. ["卖出", "正常", "自动", "已结束", ["已完成", "已完成"], "", 0, "限价:1,692.50", "CRM"],
  1073. ["买入", "正常", "自动", "未开始", ["已撤"], "1M", 600000, "限价:21.06", "Android"],
  1074. ];
  1075. const fmt = (n, d = 2) => Number(n).toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
  1076. const issueRows = [];
  1077. plan.forEach((p, i) => {
  1078. const seq = String(31 - i).padStart(4, "0");
  1079. const no = "ORD00120260925" + seq;
  1080. const [name, org] = people[i];
  1081. const acct = String(1100151 - i);
  1082. const done = ["确认中", "已完成", "已结算"].includes(p[4][0]);
  1083. const price = p[7].startsWith("限价") ? Number(p[7].slice(3).replace(/,/g, "")) : [168.4, 245.1, 1690.2][i % 3];
  1084. const qty = [1700, 8100, 12400, 28700, 5200, 28000, 48700, 9300, 5600, 19400, 800, 28400][i];
  1085. p[4].forEach((st, j) => {
  1086. issueRows.push({
  1087. master: i, sub: j, span: j === 0 ? p[4].length : 0,
  1088. no, direction: p[0], op: p[1], mode: p[2], hedge: p[3], status: st,
  1089. noteType: j === 0 ? "Delta A" : "Delta B",
  1090. invNo: "INV00" + acct, invAcct: "HH1002" + String(3431 - i), invName: name, fundAcct: "FA00" + acct, org,
  1091. stock: stocks[i], noteDir: p[0] === "买入" ? "CALL" : "--", period: p[5] || "--",
  1092. notional: p[6] ? fmt(p[6]) : "--", priceReq: p[7],
  1093. execNotional: done && p[6] ? fmt(p[6] * 0.998) : "--",
  1094. avgPrice: done ? fmt(price) : "--", execQty: done ? fmt(qty, 0) : "--", hedgeQty: p[3] === "未开始" ? "--" : fmt(qty, 0),
  1095. tradeCcy: "CNY", settleCcy: "HKD",
  1096. orderTime: "2026-09-25 " + ["09:31:07", "09:42:55", "10:03:18", "10:15:42", "10:28:09", "10:47:31", "11:02:26", "13:05:44", "13:21:10", "13:48:37", "14:06:52", "14:33:15"][i],
  1097. tradeDate: "2026-09-25", settleDate: done ? "2026-09-29" : "--",
  1098. subNo: no + (j === 0 ? "A" : "B"),
  1099. noteNo: done || p[0] === "卖出" ? "NT00120260" + String(8120 + i * 7) : "--",
  1100. subNoteNo: done || p[0] === "卖出" ? "NT00120260" + String(8120 + i * 7) + (j === 0 ? "A" : "B") : "--",
  1101. source: p[8], remark: i === 4 ? "触发平仓线,风控发起" : i === 8 ? "额度不足" : "",
  1102. });
  1103. });
  1104. });
  1105. /* ---------- 发行端 · 订单详情抽屉(OrderDetailDrawer 及子组件),以一笔人工买入单为例 ---------- */
  1106. const issueDetail = {
  1107. title: "订单详情",
  1108. toolbar: "刷新数据",
  1109. master: {
  1110. title: "主订单信息",
  1111. buttons: [["转为自动处理", ""]],
  1112. fields: [
  1113. ["主订单编号", "ORD001202609250030", "mono"], ["订单方向", "买入", "tag:direction"], ["操作类型", "正常", "tag:op_type"], ["处理方式", "人工", "tag:process_mode"],
  1114. ["证券代码/名称", "300750 宁德时代"], ["票据方向", "CALL"], ["周期", "6M"], ["价格要求", "限价:245.30", "priceReq"],
  1115. ["交易币种", "CNY"], ["结算币种", "HKD"], ["下单时间", "2026-09-25 09:42:55", "mono"], ["状态", "执行中", "tag:note_order_status"],
  1116. ["对冲进度", "对冲中", "tag:hedge_progress"], ["关联主票据编号", "--", "span2"],
  1117. ],
  1118. },
  1119. subA: {
  1120. title: "A类订单信息",
  1121. fields: [
  1122. ["子订单编号", "ORD001202609250030A", "mono"], ["订单状态", "执行中", "tag:note_order_status"], ["交易日", "2026-09-25", "mono"], ["预计交收日", "2026-09-29", "mono"],
  1123. ["投资者编号", "INV001100150", "mono"], ["投资者账户", "HH10023430", "mono"], ["投资者名称", "李婉清"], ["资金账户", "FA001100150", "mono"],
  1124. ["机构名称", "远航财富"], ["订单名义本金", "2,000,000.00 CNY", "num"], ["报价", "12.50%", "num"], ["A类收益比例", "80.00%", "num"],
  1125. ["票据面值", "273,437.50 HKD", "num"], ["发行费用", "2,734.38 HKD", "num"], ["票据发行价", "276,171.88 HKD", "num"],
  1126. ],
  1127. },
  1128. hedge: {
  1129. title: "对冲执行",
  1130. buttons: [["直接下单", "success"], ["补录", "success"], ["完成对冲", "primary"]],
  1131. refresh: "刷新",
  1132. suggest: "对冲建议:使用TRS系统 hedge01/发行对冲一号-880012 对冲,建议建仓比例(100.00%),建议建仓股数(8,100股)",
  1133. suggestParts: { system: "TRS", account: "hedge01/发行对冲一号-880012", ratio: "100.00%", qty: "8,100股" },
  1134. columns: [
  1135. ["操作", 110], ["对冲类型", 100], ["对冲系统", 100], ["对冲账号", 130], ["资金号", 110], ["下单类型", 110], ["委托编号", 180], ["状态", 168], ["方向", 80],
  1136. ["委托股数", 110, "r"], ["委托价格(交易币种)", 180, "r"], ["成交股数", 110, "r"], ["成交均价(交易币种)", 180, "r"], ["发生金额(交易币种)", 180, "r"], ["发生金额(结算币种)", 180, "r"],
  1137. ],
  1138. rows: [
  1139. { ops: [["撤单", "danger"]], type: "直接下单", sys: "TRS", acct: "hedge01", fund: "880012", otype: "算法TWAP", no: "ALG202609250007", status: ["执行中", "primary", "已成(执行中)"], dir: "买入", qty: "5,000", price: "245.30", fqty: "3,200", fprice: "245.12", amt: "784,384.00 CNY", samt: "858,429.85 HKD" },
  1140. { ops: [], type: "直接下单", sys: "TRS", acct: "hedge01", fund: "880012", otype: "普通单", no: "T2026092500418", status: ["已成", "success"], dir: "买入", qty: "2,000", price: "245.20", fqty: "2,000", fprice: "245.18", amt: "490,360.00 CNY", samt: "536,649.98 HKD" },
  1141. { ops: [["修改", "primary"], ["删除", "danger"]], type: "补录", sys: "自定义", acct: "--", fund: "--", otype: "普通单", no: "--", status: ["已成", "success"], dir: "买入", qty: "--", price: "--", fqty: "1,100", fprice: "245.26", amt: "269,786.00 CNY", samt: "295,253.80 HKD" },
  1142. ],
  1143. summary: [
  1144. ["累计有效委托数量", "8,100"], ["累计成交数量", "6,300"], ["对冲含费成交均价", "245.2112 CNY"], ["对冲比例", "77.78%"],
  1145. ["累计发生金额(交易币种)", "1,544,530.00 CNY"], ["累计发生金额(结算币种)", "1,690,333.63 HKD"], ["结算汇率", "1.0944"],
  1146. ],
  1147. },
  1148. };
  1149. /* ---------- 发行端 · 补录弹窗(HedgeFillDialog.vue),对冲系统选「自定义」 ---------- */
  1150. const fill = {
  1151. title: "补录",
  1152. common: [
  1153. { label: "交易日", type: "select", value: "2026-09-25", ph: "请选择" },
  1154. { label: "对冲系统", type: "select", value: "自定义" },
  1155. ],
  1156. custom: [
  1157. { label: "方向", type: "tag", value: "买入", tag: "direction" },
  1158. { label: "成交股数", type: "amount", value: "1,100", unit: "股" },
  1159. { label: "成交均价", type: "amount", value: "245.260000", unit: "CNY" },
  1160. { label: "发生金额(交易币种)", type: "text", value: "269,786.00", unit: "CNY" },
  1161. { label: "结算币种", type: "text", value: "HKD" },
  1162. { label: "发生金额(结算币种)", type: "amount", value: "", ph: "请输入", unit: "HKD", error: "请输入发生金额(HKD)" },
  1163. ],
  1164. trs: [
  1165. { label: "对冲账号", type: "select", value: "hedge01" },
  1166. { label: "资金号", type: "select", value: "880012", ph: "请选择" },
  1167. { label: "方向", type: "tag", value: "买入", tag: "direction" },
  1168. { label: "下单类型", type: "select", value: "普通单" },
  1169. { label: "委托编号/算法母单编号", type: "input", value: "", ph: "请输入委托编号" },
  1170. ],
  1171. footer: [["确定", "primary"]],
  1172. };
  1173. /* ---------- TRS · 下单详情页 + 下单弹窗(trs/config/order-place/detail) ---------- */
  1174. const trsTrade = {
  1175. host: {
  1176. title: "下单详情", account: "ceshi1", user: "李四测",
  1177. accountCols: ["币种", "可用金额", "冻结金额", "持仓市值", "总资产", "应付利息(汇总)", "净资产", "预警线", "平仓线"],
  1178. accountRows: [["HKD", "1,286,430.52", "127,760.00", "2,418,905.00", "3,833,095.52", "1,204.66", "3,831,890.86", "130.00%", "110.00%"]],
  1179. posCols: [["股票代码", 100], ["股票名称", 110], ["交易渠道", 100], ["持仓数量", 110, "r"], ["可用数量", 110, "r"], ["参考成本价", 110, "r"], ["最新价格", 100, "r"],
  1180. ["持仓市值(交易币种)", 170, "r"], ["结算币种", 90], ["持仓市值(结算币种)", 170, "r"], ["浮动盈亏(结算币种)", 170, "r"], ["盈亏比例", 100, "r"]],
  1181. posRows: [
  1182. ["000007", "全新好", "QFII", "12,000", "12,000", "11.20", "11.64", "139,680.00", "HKD", "152,865.79", "+5,778.43", "+3.93%"],
  1183. ["600519", "贵州茅台", "深沪港通", "800", "500", "1,702.40", "1,688.00", "1,350,400.00", "HKD", "1,477,877.76", "-12,607.49", "-0.85%"],
  1184. ["300750", "宁德时代", "QFII", "3,000", "3,000", "238.15", "245.30", "735,900.00", "HKD", "805,368.96", "+23,474.88", "+3.00%"],
  1185. ],
  1186. orderCols: [["委托编号", 180], ["股票代码", 100], ["股票名称", 110], ["交易渠道", 100], ["委托时间", 160], ["委托方向", 90], ["委托数量", 110, "r"], ["委托价格", 100, "r"],
  1187. ["成交数量", 110, "r"], ["成交价格", 100, "r"], ["委托状态", 100], ["备注", 140]],
  1188. orderRows: [
  1189. ["T2026092500431", "000007", "全新好", "QFII", "2026-09-25 10:42:16", "买入", "2,000", "11.62", "0", "--", "已报", ""],
  1190. ["T2026092500418", "300750", "宁德时代", "QFII", "2026-09-25 10:15:03", "买入", "2,000", "245.20", "2,000", "245.18", "已成", ""],
  1191. ["T2026092500377", "600519", "贵州茅台", "深沪港通", "2026-09-25 09:48:51", "卖出", "300", "1,690.00", "100", "1,690.00", "部成", ""],
  1192. ],
  1193. },
  1194. dialog: {
  1195. account: "ceshi1", user: "李四测",
  1196. strip: [["可用余额", "1,286,430.52", "HKD"], ["最大可买股数", "100,900", "股"]],
  1197. channels: ["QFII", "深沪港通"],
  1198. stock: { code: "000007", name: "全新好", mkt: "SZ" },
  1199. price: "11.65", priceCcy: "CNY", quick: [["卖一", "11.65"], ["最新", "11.64"], ["买一", "11.64"]],
  1200. qty: "1000", qtyQuick: ["1/4", "1/3", "1/2", "全部可买"], qtyHelp: "买入须为 100 股整数倍",
  1201. amounts: [["买入金额(交易币种)", "11,650.00", "CNY"], ["买入金额(结算币种)", "12,749.76", "HKD"]],
  1202. quote: {
  1203. last: "11.64", chg: "0.03", pct: "0.26%", prev: "11.61", source: "实时推送", hint: "点击任一档位填入委托价",
  1204. asks: [["卖五", "11.70", 254], ["卖四", "11.69", 20], ["卖三", "11.67", 16], ["卖二", "11.66", 38], ["卖一", "11.65", 20]],
  1205. bids: [["买一", "11.64", 102], ["买二", "11.60", 11], ["买三", "11.59", 22], ["买四", "11.57", 5], ["买五", "11.56", 50]],
  1206. },
  1207. footHint: "提交后可在「当日委托」中查看与撤单",
  1208. footer: [["取消", ""], ["确认买入 1,000 股", "primary"]],
  1209. },
  1210. };
  1211. /* ---------- TRS · 委托查询(trs/order/index.vue) ---------- */
  1212. const trsSearch = [
  1213. { label: "用户ID", type: "input", ph: "精确搜索" }, { label: "手机号", type: "input", ph: "精确搜索" }, { label: "委托编号", type: "input", ph: "精确搜索" },
  1214. { label: "账户", type: "input", ph: "模糊搜索" }, { label: "用户名称", type: "input", ph: "模糊搜索" }, { label: "股票代码/名称", type: "input", ph: "模糊搜索" },
  1215. { label: "委托状态", type: "select", ph: "全部", multiple: true }, { label: "下单类型", type: "select", ph: "全部" }, { label: "下单方式", type: "select", ph: "全部" },
  1216. { label: "委托日期", type: "daterange", value: ["2026-09-25", "2026-09-25"], sep: "至" },
  1217. ];
  1218. const trsActions = [["查找", "primary", "search"], ["重置", "", "refresh"], ["导出", "", "download"]];
  1219. /* ---------- 销售端 · 新建投资者(sales/investor/components/InvestorCreateDialog.vue) ---------- */
  1220. const salesForm = {
  1221. title: "新建投资者",
  1222. mode: "手动新建",
  1223. host: { title: "投资者信息", button: "新建投资者" },
  1224. groups: [
  1225. {
  1226. title: "基础信息", cols: 2,
  1227. fields: [
  1228. { label: "投资者账户", type: "input", req: true, value: "lwq_2026", ph: "请输入投资者账户" },
  1229. { label: "投资者类别", type: "select", req: true, value: "PI", ph: "请选择" },
  1230. { label: "投资者名称", type: "input", req: true, value: "李婉清", ph: "请输入投资者名称" },
  1231. { label: "投资者英文名", type: "input", req: true, value: "LI WANQING", ph: "请输入投资者英文名" },
  1232. { label: "E-mail", type: "input", req: true, value: "wanqing.li@", ph: "请输入E-mail", error: "邮箱格式不正确" },
  1233. { label: "手机号码", type: "combo", req: true, parts: [{ w: 92, value: "+86", ph: "区号" }, { value: "13800138000", ph: "请输入手机号码" }] },
  1234. { label: "联系地址", type: "combo", req: true, parts: [{ w: 150, value: "CN", ph: "国别代码" }, { value: "", ph: "请输入详细地址" }] },
  1235. { label: "证件号码", type: "combo", req: true, parts: [{ w: 150, value: "", ph: "证件类型", select: true }, { value: "", ph: "请输入证件号码" }] },
  1236. { label: "投资者状态", type: "select", req: true, value: "正常", ph: "请选择" },
  1237. { label: "状态说明", type: "input", value: "", ph: "请输入状态说明" },
  1238. { label: "A.E. Code", type: "input", value: "", ph: "请输入A.E. Code" },
  1239. ],
  1240. },
  1241. {
  1242. title: "问卷信息", cols: 2,
  1243. fields: [
  1244. { label: "投资者风险等级", type: "select", req: true, value: "3-增长型", ph: "请选择" },
  1245. { label: "投资者年龄", type: "select", req: true, value: "35~50岁", ph: "请选择" },
  1246. { label: "投资年限", type: "select", req: true, value: "5-10年", ph: "请选择" },
  1247. { label: "投资者目标", type: "select", req: true, value: "", ph: "请选择" },
  1248. { label: "场外衍生品经验", type: "select", req: true, value: "", ph: "请选择" },
  1249. { label: "资产净值(HKD)", type: "amount", req: true, value: "", ph: "请输入大于0的数,最多2位小数" },
  1250. { label: "问卷有效期", type: "date", req: true, value: "", ph: "请选择日期" },
  1251. { label: "衍生产品问卷", type: "select", req: true, value: "", ph: "请选择" },
  1252. ],
  1253. },
  1254. {
  1255. title: "交易品类", cols: 1,
  1256. fields: [{ label: "可交易品类", type: "checks", req: true, options: [["共赢票据(A类)", false], ["共赢票据(B类)", false]] }],
  1257. alert: "未启用共赢票据(A类),请确认该投资者当前无需A类交易权限。",
  1258. },
  1259. {
  1260. title: "其它信息", cols: 1,
  1261. uploads: [
  1262. { label: "开户文件", files: [["开户申请表-李婉清.pdf", "2.4M"]] },
  1263. { label: "其它附件", files: [] },
  1264. ],
  1265. uploadBtn: "上传附件", uploadTip: "PNG、JPEG、PDF;单个30M;单次100M;最多20个",
  1266. },
  1267. ],
  1268. footer: [["取消", ""], ["保存", "primary"]],
  1269. };
  1270. // 委托查询表格列(trs/order/index.vue)。align 为代码现状;group 为激进套的分组
  1271. const trsColumns = [
  1272. { key: "uid", title: "用户ID", w: 90, group: "用户" }, { key: "acct", title: "账户", w: 120, group: "用户" },
  1273. { key: "uname", title: "用户名称", w: 110, group: "用户" }, { key: "phone", title: "手机号", w: 120, group: "用户" },
  1274. { key: "channel", title: "交易渠道", w: 100, tag: "trade_channel", group: "委托" },
  1275. { key: "code", title: "股票代码", w: 100, group: "委托" }, { key: "sname", title: "股票名称", w: 110, group: "委托" },
  1276. { key: "dir", title: "委托方向", w: 90, tag: "direction", group: "委托" }, { key: "ptype", title: "报价方式", w: 90, tag: "price_req_type", group: "委托" },
  1277. { key: "status", title: "委托状态", w: 180, tag: "trs_order_status", group: "委托" },
  1278. { key: "qty", title: "委托数量", w: 110, num: true, group: "委托" }, { key: "price", title: "委托价格", w: 100, num: true, group: "委托" },
  1279. { key: "otime", title: "委托时间", w: 160, group: "委托" },
  1280. { key: "fqty", title: "成交数量", w: 110, num: true, group: "成交" }, { key: "fprice", title: "成交价格", w: 100, num: true, group: "成交" },
  1281. { key: "ccy", title: "交易币种", w: 90, group: "成交" }, { key: "famt", title: "成交金额", w: 130, num: true, group: "成交" },
  1282. { key: "ftime", title: "成交时间", w: 160, group: "成交" },
  1283. { key: "comm", title: "佣金", w: 100, num: true, group: "费用" }, { key: "stamp", title: "印花税", w: 100, num: true, group: "费用" },
  1284. { key: "fee", title: "规费", w: 100, num: true, group: "费用" }, { key: "total", title: "交易总费用", w: 110, num: true, group: "费用" },
  1285. { key: "amt", title: "发生金额(交易币种)", w: 170, num: true, group: "结算" }, { key: "sccy", title: "结算币种", w: 90, group: "结算" },
  1286. { key: "samt", title: "发生金额(结算币种)", w: 170, num: true, group: "结算" },
  1287. { key: "no", title: "委托编号", w: 200, group: "来源" }, { key: "operator", title: "操作方", w: 120, group: "来源" },
  1288. { key: "otype", title: "下单类型", w: 100, tag: "order_type", group: "来源" }, { key: "omode", title: "下单方式", w: 100, tag: "order_mode", group: "来源" },
  1289. { key: "ip", title: "IP", w: 130, group: "来源" }, { key: "lbs", title: "LBS", w: 130, group: "来源" },
  1290. { key: "vpn", title: "是否用VPN", w: 100, group: "来源" }, { key: "remark", title: "备注", w: 140, group: "来源" },
  1291. ];
  1292. // [用户, 渠道, 股票, 方向, 报价, 状态, 数量, 价格, 成交数量, 成交价, 下单类型, 下单方式, 子单数]
  1293. const trsPlan = [
  1294. [0, "QFII", 0, "买入", "限价", "已报", 2000, 11.62, 0, 0, "正常下单", "iOS", 0],
  1295. [1, "QFII", 1, "买入", "限价", "已成", 2000, 245.2, 2000, 245.18, "后台下单", "CRM", 2],
  1296. [2, "深沪港通", 2, "卖出", "限价", "部成", 300, 1690, 100, 1690, "正常下单", "Android", 0],
  1297. [3, "QFII", 3, "买入", "市价", "已成", 5000, 52.2, 5000, 52.18, "算法下单", "OpenAPI", 0],
  1298. [4, "深沪港通", 4, "卖出", "限价", "待撤", 1200, 35.6, 0, 0, "正常下单", "Windows", 0],
  1299. [5, "QFII", 5, "买入", "限价", "已撤", 800, 16.42, 0, 0, "正常下单", "iOS", 0],
  1300. [0, "QFII", 6, "卖出", "限价", "废单", 600, 88.2, 0, 0, "后台平仓", "CRM", 0],
  1301. [1, "深沪港通", 7, "买入", "限价", "部撤", 3000, 61.75, 1200, 61.74, "正常下单", "H5", 0],
  1302. [2, "QFII", 8, "买入", "限价", "已成", 1000, 21.06, 1000, 21.05, "条件单", "Android", 0],
  1303. [3, "QFII", 9, "卖出", "市价", "已成", 400, 245.3, 400, 245.31, "后台补单", "CRM", 0],
  1304. ];
  1305. const trsUsers = [["10021", "ceshi1", "李四测"], ["10034", "zhangmy", "张明远"], ["10058", "wangzh", "王子豪"], ["10077", "chensy", "陈思雨"], ["10092", "liujg", "刘建国"], ["10105", "zhaoyl", "赵雅琳"]];
  1306. const trsStocks = [["000007", "全新好"], ["300750", "宁德时代"], ["600519", "贵州茅台"], ["601318", "中国平安"], ["600036", "招商银行"], ["601899", "紫金矿业"], ["688981", "中芯国际"], ["000333", "美的集团"], ["601012", "隆基绿能"], ["002594", "比亚迪"]];
  1307. const trsRows = [];
  1308. trsPlan.forEach((p, i) => {
  1309. const u = trsUsers[p[0]], s = trsStocks[p[2]], t = ["10:42:16", "10:15:03", "09:48:51", "09:41:27", "09:38:02", "09:35:44", "09:33:10", "09:31:58", "09:31:05", "09:30:12"][i];
  1310. const famt = p[8] * p[9], comm = famt * 0.0003, stamp = p[3] === "卖出" ? famt * 0.0005 : 0, fee = famt * 0.0000687, total = comm + stamp + fee;
  1311. const amt = famt ? (p[3] === "买入" ? -(famt + total) : famt - total) : 0;
  1312. const mk = (sub, qty, fq) => ({
  1313. sub, kids: sub ? 0 : p[12], uid: u[0], acct: u[1], uname: u[2], phone: "1380000" + String(1001 + p[0]), channel: p[1],
  1314. code: s[0], sname: s[1], dir: p[3], ptype: p[4], status: p[5], qty: fmt(qty, 0), price: fmt(p[7]), otime: "2026-09-25 " + t,
  1315. fqty: fmt(fq, 0), fprice: fq ? fmt(p[9]) : "--", ccy: "CNY", famt: fq ? fmt(fq * p[9]) : "--", ftime: fq ? "2026-09-25 " + t.slice(0, 6) + "59" : "--",
  1316. comm: fq ? fmt(comm * fq / (p[8] || 1)) : "--", stamp: fq ? fmt(stamp * fq / (p[8] || 1)) : "--", fee: fq ? fmt(fee * fq / (p[8] || 1)) : "--", total: fq ? fmt(total * fq / (p[8] || 1)) : "--",
  1317. amt: fq ? fmt(amt * fq / (p[8] || 1)) : "--", sccy: sub ? "" : "HKD", samt: fq ? fmt(amt * fq / (p[8] || 1) * 1.0944) : "--",
  1318. no: "T20260925" + String(431 - i * 9).padStart(5, "0") + (sub ? "-" + sub : ""), operator: sub ? "" : p[10] === "正常下单" ? "--" : "admin",
  1319. otype: sub ? "" : p[10], omode: sub ? "" : p[11], ip: "203.0.113." + (20 + i), lbs: ["上海", "深圳", "香港", "北京"][i % 4], vpn: i === 3 ? "是" : "否",
  1320. remark: p[5] === "废单" ? "超出持仓可卖数量" : "", route: sub ? "通道" + sub : "",
  1321. cancel: p[5] === "待撤" ? ["撤单结果未知", "danger", "上轮撤单未取得明确结果,可人工再次撤单"] : null,
  1322. });
  1323. trsRows.push(mk(0, p[6], p[8]));
  1324. for (let k = 1; k <= p[12]; k++) trsRows.push(mk(k, p[6] / p[12], p[8] / p[12]));
  1325. });
  1326. /* ---------- 销售端 · 订单管理(sales/order/components/OrderSearchForm.vue、OrderTable.vue,management 模式) ---------- */
  1327. const salesSearch = [
  1328. { label: "主订单编号", type: "input", ph: "主订单编号" },
  1329. { label: "子订单编号", type: "input", ph: "子订单编号" },
  1330. { label: "订单方向", type: "select", ph: "全部" },
  1331. { label: "操作类型", type: "select", ph: "全部", multiple: true },
  1332. { label: "订单状态", type: "select", ph: "全部", multiple: true },
  1333. { label: "票据类型", type: "select", ph: "全部", multiple: true },
  1334. { label: "投资者", type: "input", ph: "编号/账户/名称" },
  1335. { label: "证券代码/名称", type: "input", ph: "证券代码/名称" },
  1336. { label: "交易日期", type: "daterange", value: ["2026-09-25", "2026-09-25"], sep: "~" },
  1337. ];
  1338. const salesStatuses = ["未报", "待报", ...Object.keys(dict.note_order_status)];
  1339. const SW = { direction: 95, op: 90, invAcct: 110, invName: 100, period: 70, execQty: 120, tradeCcy: 85, settleCcy: 85, tradeDate: 105, settleDate: 105, source: 90 };
  1340. const salesColumns = issueColumns.filter((c) => !["mode", "hedge", "org", "hedgeQty"].includes(c.key)).map((c) => ({
  1341. ...c, w: SW[c.key] || c.w, merge: ["no", "direction", "op"].includes(c.key),
  1342. title: c.key === "execQty" ? "订单执行股数" : c.title, tag: c.key === "source" ? "sales_order_source" : c.tag,
  1343. }));
  1344. const srcMap = { CRM: "销售端", IOS: "iOS", Android: "Android", H5: "H5", 发行端: "销售端" };
  1345. const salesRows = issueRows.map((r) => ({ ...r, source: srcMap[r.source] }));
  1346. /* ---------- 首页 ---------- */
  1347. // 保守套:现有首页(views/dashboard/index.vue)的内容,文案为代码原文
  1348. // 激进套:工作台。数字是示例值,上线需要后端提供按状态汇总的接口;字段全部来自订单管理
  1349. const home = {
  1350. greeting: "上午好,admin!", weather: "今日天气晴朗,气温在15℃至25℃之间,东南风。",
  1351. tradeDate: "2026-09-25", total: 128,
  1352. status: [["待处理", 6, true], ["执行中", 14], ["待撤", 2, true], ["撤单中", 1], ["确认中", 9, true], ["已完成", 61], ["已结算", 27], ["已撤", 5], ["已拒绝", 3]],
  1353. manualCols: [["主订单编号", 176], ["方向", 60], ["订单状态", 92], ["对冲进度", 92], ["投资者", 88], ["证券代码/名称", 148], ["订单名义本金", 128, "r"], ["下单时间", 150]],
  1354. shortcuts: [["订单管理", "ticket", "共赢票据管理"], ["票据管理", "file", "共赢票据管理"], ["询价管理", "search", "共赢票据管理"], ["共赢票据风险监控", "shield", "风控管理"], ["共赢票据清算", "check", "清算管理"], ["日常报表", "chart", "报表管理"]],
  1355. };
  1356. return { dict, menus, sysName, tradeDays, salesSearch, salesStatuses, salesColumns, salesRows, home, issueSearch, issueActions, issueColumns, issueRows, issueDetail, fill, trsTrade, trsSearch, trsActions, trsColumns, trsRows, salesForm };
  1357. })();
  1358. </script>
  1359. <script>
  1360. /* 完整设计稿补充的内容数据(data.js 之外)。字段、文案取自三端前端代码;表内数值是示例。
  1361. * 来源标注在各段注释里。修改后执行:python3 build.py
  1362. */
  1363. window.SPEC_DATA2 = (function () {
  1364. const D = window.SPEC_DATA;
  1365. // 字典补充(ddl/issue/public/数据字典/seed.sql):票据状态、票据风控状态;变动类型按 DictCode 名称示意
  1366. const dict = {
  1367. note_status: { 待生效: "info", 存续期: "success", 已卖出: "info", 已到期: "warning" },
  1368. note_risk_status: { 正常: "success", 预警: "warning", 危险: "danger", 强平: "danger" },
  1369. sec_market: { A股: "primary" },
  1370. note_change_type: { 发行: "primary", 调整: "warning", 卖出: "danger", 展期: "info", 分红: "success" },
  1371. clearing_step: { 待执行: "info", 执行中: "primary", 执行成功: "success", 跳过: "info", 执行失败: "danger", 停止中: "warning", 已停止: "warning" },
  1372. clearing_batch: { 执行中: "primary", 已完成: "success", 失败: "danger", 已停止: "warning" },
  1373. send_status: { 已发送: "success", 发送失败: "danger", 待发送: "info" },
  1374. gender: { 男: "info", 女: "info" },
  1375. };
  1376. /* ---------- 发行端 · 票据详情抽屉(NoteDetailDrawer + note-detail/*Panel),以 300750 宁德时代的一张存续期票据为例 ---------- */
  1377. const noteDetail = {
  1378. title: "票据详情",
  1379. noteNo: "NT001202608127", subNo: "NT001202608127A",
  1380. tags: [["存续期", "note_status"], ["正常", "note_risk_status"], ["Delta A", "note_type"]],
  1381. facts: [["证券", "300750 宁德时代"], ["投资者", "李婉清"], ["机构", "远航财富"], ["到期日期", "2027-03-29"]],
  1382. buttons: [["修改到期日", ""], ["票据调整", ""], ["添加分红", ""], ["票据卖出", "dng"]],
  1383. tabs: [["main", "主页"], ["changes", "变动记录"]],
  1384. master: {
  1385. title: "主票据信息",
  1386. fields: [["主票据编号", "NT001202608127", "mono"], ["证券市场", "A股", "tag:sec_market"], ["证券代码/名称", "300750 宁德时代"], ["票据方向", "CALL"],
  1387. ["发行日期", "2026-09-29", "mono"], ["到期日期", "2027-03-29", "mono"], ["交易币种", "CNY"], ["结算币种", "HKD"]],
  1388. },
  1389. subA: {
  1390. title: "A类票据信息",
  1391. fields: [["票据编号", "NT001202608127A", "mono"], ["票据类型", "Delta A", "tag:note_type"], ["票据状态", "存续期", "tag:note_status"], ["风险状态", "正常", "tag:note_risk_status"],
  1392. ["投资者编号", "INV001100150", "mono"], ["投资者账户", "HH10023430", "mono"], ["投资者名称", "李婉清"], ["资金账户", "FA001100150", "mono"],
  1393. ["机构名称", "远航财富"], ["报价", "12.50%", "num"], ["A类收益比例", "80.00%", "num"]],
  1394. init: {
  1395. title: "初始信息",
  1396. fields: [["票据名义本金(交易币种)", "2,000,000.00 CNY", "num"], ["票据名义本金(结算币种)", "2,188,800.00 HKD", "num"], ["票据股票数量", "8,100", "num"], ["票据期初价格", "245.26 CNY", "num"],
  1397. ["初始汇率", "1.0944", "num"], ["票据面值", "273,437.50 HKD", "num"], ["发行费用", "2,734.38 HKD", "num"], ["票据发行价", "276,171.88 HKD", "num"]],
  1398. },
  1399. current: {
  1400. title: "当前信息",
  1401. fields: [["票据名义本金", "2,000,000.00 CNY", "num"], ["冻结名义本金", "0.00 CNY", "num"], ["票据面值", "273,437.50 HKD", "num"], ["票据股票数量", "8,100", "num"],
  1402. ["票据期初价格", "245.26 CNY", "num"], ["最新价", "251.80 CNY", "num"], ["账面汇率", "1.0951", "num"], ["增额金额", "0.00 HKD", "num"],
  1403. ["分红金额", "0.00 HKD", "num"], ["预警/危险/强平价格", "220.73 / 208.47 / 196.21 CNY", "num"], ["预警缓冲比/强平比例", "10.00% / 20.00%", "num"],
  1404. ["底层资产浮动盈亏及比例", "+58,002.47 HKD / +2.67%", "num:up"], ["A类票据浮动盈亏及比例", "+46,401.98 HKD / +16.97%", "num:up"]],
  1405. },
  1406. },
  1407. hedge: {
  1408. title: "对冲信息", ratioLabel: "对冲仓位比例", ratio: "100.00%",
  1409. posTitle: "持仓信息",
  1410. posCols: [["对冲系统", 90], ["对冲账号", 110], ["资金号", 100], ["持仓股数", 110, "r"], ["冻结股数", 100, "r"], ["可平仓数量", 110, "r"], ["最新价(交易币种)", 160, "r"],
  1411. ["持仓市值(交易币种)", 180, "r"], ["账面汇率", 100, "r"], ["持仓市值(结算币种)", 180, "r"], ["持仓成本金额(结算币种)", 200, "r"], ["浮动盈亏(结算币种)", 180, "r"],
  1412. ["分红金额(结算币种)", 180, "r"], ["已实现分红金额(结算币种)", 220, "r"], ["未结算日常盈亏(结算币种)", 220, "r"], ["已结算实现盈亏(结算币种)", 220, "r"], ["合计盈亏(结算币种)", 180, "r"]],
  1413. posRows: [
  1414. ["TRS", "hedge01", "880012", "8,100", "0", "8,100", "251.80 CNY", "2,039,580.00 CNY", "1.0951", "2,233,544.06 HKD", "2,188,800.00 HKD", "44,744.06 HKD", "0.00 HKD", "0.00 HKD", "0.00 HKD", "0.00 HKD", "44,744.06 HKD"],
  1415. ["合计", "", "", "8,100", "0", "8,100", "", "2,039,580.00 CNY", "", "2,233,544.06 HKD", "2,188,800.00 HKD", "44,744.06 HKD", "0.00 HKD", "0.00 HKD", "0.00 HKD", "0.00 HKD", "44,744.06 HKD"],
  1416. ],
  1417. tradeTitle: "票据买卖对冲记录信息",
  1418. tradeCols: [["对冲系统", 90], ["对冲账号", 110], ["资金号", 100], ["对冲类型", 100], ["下单类型", 110], ["委托编号", 180], ["状态", 90], ["方向", 80],
  1419. ["委托股数", 110, "r"], ["委托价格(交易币种)", 180, "r"], ["成交股数", 110, "r"], ["成交均价(交易币种)", 180, "r"], ["发生金额(交易币种)", 180, "r"], ["发生金额(结算币种)", 180, "r"], ["交易日", 110]],
  1420. tradeRows: [
  1421. ["TRS", "hedge01", "880012", "直接下单", "算法TWAP", "ALG202609250007", ["已成", "success"], "买入", "5,000", "245.30", "5,000", "245.12", "1,225,600.00 CNY", "1,341,296.64 HKD", "2026-09-25"],
  1422. ["TRS", "hedge01", "880012", "直接下单", "普通单", "T2026092500418", ["已成", "success"], "买入", "2,000", "245.20", "2,000", "245.18", "490,360.00 CNY", "536,649.98 HKD", "2026-09-25"],
  1423. ["自定义", "--", "--", "补录", "普通单", "--", ["已成", "success"], "买入", "--", "--", "1,100", "245.26", "269,786.00 CNY", "295,253.80 HKD", "2026-09-25"],
  1424. ],
  1425. dailyTitle: "日常买卖对冲记录",
  1426. dailyButtons: [["下单", ""], ["补录", ""], ["刷新", "txt"]],
  1427. dailyRows: [
  1428. [[["撤单", "danger"]], "TRS", "hedge01", "880012", "直接下单", "普通单", "T2026100700122", ["已报", "primary"], "卖出", "500", "252.00", "0", "--", "--", "--", "2026-10-07"],
  1429. [[["修改", "primary"], ["删除", "danger"]], "自定义", "--", "--", "补录", "普通单", "--", ["已成", "success"], "买入", "--", "--", "300", "248.90", "74,670.00 CNY", "81,771.12 HKD", "2026-10-06"],
  1430. ],
  1431. },
  1432. changes: {
  1433. sections: [
  1434. { title: "票据变动记录", cols: [["变动类型", 110], ["关联编号", 170], ["变动后A类票据名义本金(交易币种)", 280, "r"], ["变动后A类票据股票数量", 190, "r"], ["变动后A类票据期初价格(交易币种)", 280, "r"], ["变动后持仓股数", 130, "r"], ["系统处理备注", 150], ["创建人", 110], ["创建时间", 165]],
  1435. rows: [[["发行", "note_change_type"], ["ORD001202609250030", "link"], "2,000,000.00 CNY", "8,100", "245.26 CNY", ["8,100", "link"], "--", "admin", "2026-09-25 16:02:11"]] },
  1436. { title: "分红变动记录", cols: [["变动类型", 120], ["变动后A类票据分红金额(结算币种)", 280, "r"], ["变动后对冲分红金额(结算币种)", 240, "r"], ["备注", 150], ["创建人", 110], ["创建时间", 165]], rows: [], empty: "暂无分红变动记录" },
  1437. { title: "增额变动记录", cols: [["变动类型", 120], ["变动后增额金额(结算币种)", 240, "r"], ["备注", 150], ["创建人", 110], ["创建时间", 165]], rows: [], empty: "暂无增额变动记录" },
  1438. { title: "展期记录", cols: [["展期任务编号", 180], ["原到期日", 120], ["新到期日", 120], ["状态", 100], ["操作人", 110], ["创建时间", 165]], rows: [], empty: "暂无展期记录" },
  1439. ],
  1440. },
  1441. };
  1442. /* ---------- 发行端 · 共赢票据清算(views/issue/clearing/index.vue,步骤名与代码 createPendingClearingSteps 逐字一致) ---------- */
  1443. const clearing = {
  1444. market: "A股", tradeDate: "2026-09-25", businessDate: "2026-09-25",
  1445. tabs: [["clearing", "清算"], ["logs", "清算日志"]],
  1446. batchStatus: "执行中", batchNo: "CLR20260925001",
  1447. stepCols: [["步骤序号", 72, "c"], ["步骤名称", 320, "", "wrap"], ["执行状态", 100, "c"], ["开始时间", 165], ["结束时间", 165], ["备注", 180, "", "wrap"], ["操作人", 90]],
  1448. steps: [
  1449. [1, "开启清算", "执行成功", "2026-09-25 17:30:00", "2026-09-25 17:30:02", "", "admin"],
  1450. [2, "备份清算前数据", "执行成功", "2026-09-25 17:30:02", "2026-09-25 17:31:48", "备份 12 张表", "admin"],
  1451. [3, "订单及票据处理", "执行成功", "2026-09-25 17:31:48", "2026-09-25 17:33:10", "处理订单 128 笔、票据 96 张", "admin"],
  1452. [4, "交易交收处理", "执行中", "2026-09-25 17:33:10", "", "", "admin"],
  1453. [5, "备份清算完成的数据、生成营业日日终数据与核心日常报表、更新T+1实时数据", "待执行", "", "", "", ""],
  1454. [6, "确认交易日切换、推进营业日、票据激活、清算完成", "待执行", "", "", "", ""],
  1455. ],
  1456. blockedReason: "",
  1457. errorDetail: "",
  1458. logCols: [["步骤", 64, "c"], ["次数", 64, "c"], ["动作", 80], ["状态", 100], ["操作人", 100], ["开始时间", 165], ["结束时间", 165], ["明细", 260, "", "wrap"]],
  1459. logs: [
  1460. [4, 1, "执行", "执行中", "admin", "2026-09-25 17:33:10", "", "交收 A 类订单 41 笔,已完成 27 笔"],
  1461. [3, 1, "执行", "执行成功", "admin", "2026-09-25 17:31:48", "2026-09-25 17:33:10", "处理订单 128 笔、票据 96 张"],
  1462. [2, 1, "执行", "执行成功", "admin", "2026-09-25 17:30:02", "2026-09-25 17:31:48", "备份 12 张表"],
  1463. [1, 1, "执行", "执行成功", "admin", "2026-09-25 17:30:00", "2026-09-25 17:30:02", ""],
  1464. ],
  1465. historyTitle: "清算记录",
  1466. historyCols: [["清算批次号", 170], ["交易日", 110], ["类型", 90], ["状态", 100], ["操作人", 100], ["开始时间", 165], ["完成时间", 165], ["备注", 220, "", "wrap"]],
  1467. history: [
  1468. ["CLR20260925001", "2026-09-25", "正常清算", "执行中", "admin", "2026-09-25 17:30:00", "", ""],
  1469. ["CLR20260924001", "2026-09-24", "正常清算", "已完成", "admin", "2026-09-24 17:30:00", "2026-09-24 17:41:26", ""],
  1470. ["CLR20260923002", "2026-09-23", "重新清算", "已完成", "admin", "2026-09-23 19:02:14", "2026-09-23 19:13:40", "回拨后重新清算"],
  1471. ["CLR20260923001", "2026-09-23", "正常清算", "失败", "admin", "2026-09-23 17:30:00", "2026-09-23 17:36:51", "第 4 步交收处理失败:对冲账户 880012 资金不足"],
  1472. ],
  1473. };
  1474. /* ---------- 发行端 · 共赢票据风险监控(views/issue/risk-monitor:三个页签;MonitorPane 的搜索项只有占位文字,没有标签) ---------- */
  1475. const risk = {
  1476. tabs: [["monitor", "共赢票据风险监控"], ["status-log", "风险状态变更记录"], ["notification", "追保通知发送记录"]],
  1477. search: [
  1478. { ph: "风险状态", type: "select", multiple: true }, { ph: "票据状态", type: "select", multiple: true, value: ["待生效", "存续期"] }, { ph: "票据编号", type: "input" },
  1479. { ph: "投资者账户", type: "input" }, { ph: "投资者编号/账户/名称", type: "input" }, { ph: "机构名称", type: "select" },
  1480. { ph: "证券市场", type: "select" }, { ph: "证券代码/名称", type: "input" },
  1481. ],
  1482. actions: [["查找", "primary", "search"], ["重置", "", "refresh"], ["数据导出", "", "download"]],
  1483. // 列为示意(RiskMonitorTable 的列由配置生成),字段名取自票据详情
  1484. cols: [["操作", 120], ["票据编号", 170], ["票据状态", 90], ["风险状态", 90], ["投资者账户", 120], ["投资者名称", 110], ["机构名称", 140], ["证券代码/名称", 170],
  1485. ["票据股票数量", 120, "r"], ["最新价", 100, "r"], ["预警/危险/强平价格", 200, "r"], ["底层资产浮动盈亏及比例", 220, "r"], ["到期日期", 110]],
  1486. rows: [
  1487. ["NT001202608127A", "存续期", "正常", "HH10023430", "李婉清", "远航财富", "300750 宁德时代", "8,100", "251.80", "220.73 / 208.47 / 196.21", "+58,002.47 HKD / +2.67%", "2027-03-29"],
  1488. ["NT001202608134A", "存续期", "预警", "HH10023429", "王子豪", "明德资产管理", "000858 五粮液", "12,400", "118.20", "121.46 / 114.71 / 107.96", "-31,886.40 HKD / -2.92%", "2026-12-29"],
  1489. ["NT001202608141A", "存续期", "危险", "HH10023428", "陈思雨", "安衡证券", "601318 中国平安", "28,700", "48.12", "52.18 / 49.28 / 46.38", "-128,776.52 HKD / -7.78%", "2026-12-29"],
  1490. ["NT001202608148A", "存续期", "正常", "HH10023427", "刘建国", "远航财富", "002594 比亚迪", "5,200", "268.40", "225.36 / 212.84 / 200.32", "+92,513.60 HKD / +7.21%", "2027-03-29"],
  1491. ["NT001202608155A", "存续期", "正常", "HH10023426", "赵雅琳", "启原投资", "600036 招商银行", "28,000", "36.95", "32.04 / 30.26 / 28.48", "+41,328.00 HKD / +3.79%", "2027-03-29"],
  1492. ["NT001202608162A", "待生效", "正常", "HH10023425", "孙浩然", "明德资产管理", "601899 紫金矿业", "48,700", "16.42", "-- / -- / --", "--", "2026-10-29"],
  1493. ["NT001202608169A", "存续期", "预警", "HH10023424", "周晓彤", "安衡证券", "600900 长江电力", "9,300", "20.14", "20.86 / 19.70 / 18.55", "-9,356.84 HKD / -4.56%", "2026-12-29"],
  1494. ["NT001202608176A", "存续期", "正常", "HH10023423", "吴俊杰", "远航财富", "688981 中芯国际", "5,600", "90.12", "79.38 / 74.97 / 70.56", "+66,135.52 HKD / +12.60%", "2027-03-29"],
  1495. ],
  1496. statusLogCols: [["票据编号", 170], ["投资者名称", 110], ["机构名称", 140], ["证券代码/名称", 170], ["变更前", 90], ["变更后", 90], ["触发价格", 110, "r"], ["变更时间", 165], ["备注", 220]],
  1497. statusLog: [
  1498. ["NT001202608141A", "陈思雨", "安衡证券", "601318 中国平安", "预警", "危险", "49.10", "2026-09-25 10:12:40", "连续 3 个交易日低于危险价格"],
  1499. ["NT001202608134A", "王子豪", "明德资产管理", "000858 五粮液", "正常", "预警", "121.30", "2026-09-24 14:02:17", ""],
  1500. ["NT001202608169A", "周晓彤", "安衡证券", "600900 长江电力", "正常", "预警", "20.80", "2026-09-23 09:48:05", ""],
  1501. ["NT001202608141A", "陈思雨", "安衡证券", "601318 中国平安", "正常", "预警", "52.02", "2026-09-22 13:31:52", ""],
  1502. ],
  1503. notifyCols: [["票据编号", 170], ["投资者名称", 110], ["机构名称", 140], ["通知渠道", 100], ["发送状态", 100], ["发送时间", 165], ["内容摘要", 360]],
  1504. notify: [
  1505. ["NT001202608141A", "陈思雨", "安衡证券", "邮件", "已发送", "2026-09-25 10:13:02", "票据 NT001202608141A 进入危险状态,请于 2026-09-26 15:00 前追加保证金"],
  1506. ["NT001202608141A", "陈思雨", "安衡证券", "短信", "已发送", "2026-09-25 10:13:02", "【共赢票据】票据进入危险状态,请及时追加保证金"],
  1507. ["NT001202608134A", "王子豪", "明德资产管理", "邮件", "发送失败", "2026-09-24 14:03:10", "票据 NT001202608134A 进入预警状态"],
  1508. ],
  1509. };
  1510. /* ---------- 首页 ----------
  1511. * 现状版(默认):只有一张问候卡,文案取自改版前的 views/dashboard/index.vue(问候语按时段 + 昵称;天气那句是模板写死的)。
  1512. * 工作台版 home:2026-10-08 本稿首版的首页设计,整套保留(变体 work),等首页汇总接口完善后再切回去;数字为示例,字段全部来自各端列表页 */
  1513. const homeNow = { greeting: "下午好,系统管理员!", weather: "今日天气晴朗,气温在15℃至25℃之间,东南风。" };
  1514. const home = {
  1515. issue: {
  1516. noun: "订单", dictKey: "note_order_status", listKey: "共赢票据管理/订单管理",
  1517. status: [["待处理", 6, true], ["执行中", 14], ["待撤", 2, true], ["撤单中", 1], ["确认中", 9, true], ["已完成", 61], ["已结算", 27], ["已撤", 5], ["已拒绝", 3]],
  1518. manualTitle: "待人工处理的订单", manualSub: "处理方式为「人工」或状态为待处理、待撤、确认中",
  1519. manualCols: [["主订单编号", 190, "no", "mono"], ["方向", 70, "direction", "tag:direction"], ["订单状态", 90, "status", "tag:note_order_status"], ["对冲进度", 90, "hedge", "tag:hedge_progress"], ["投资者", 100, "invName"], ["证券代码/名称", 150, "stock"], ["订单名义本金", 150, "notional", "num", "r"], ["下单时间", 165, "orderTime", "mono"]],
  1520. pick: (r) => r.mode === "人工" || ["待处理", "待撤", "确认中"].includes(r.status),
  1521. shortcuts: [["共赢票据管理", "订单管理", "ticket"], ["共赢票据管理", "票据管理", "file"], ["共赢票据管理", "询价管理", "search"], ["风控管理", "共赢票据风险监控", "shield"], ["清算管理", "共赢票据清算", "check"], ["报表管理", "日常报表", "chart"]],
  1522. },
  1523. sales: {
  1524. noun: "订单", dictKey: "note_order_status", listKey: "共赢票据管理/订单管理",
  1525. status: [["待处理", 4, true], ["执行中", 11], ["待撤", 1, true], ["撤单中", 0], ["确认中", 7, true], ["已完成", 48], ["已结算", 22], ["已撤", 4], ["已拒绝", 2]],
  1526. manualTitle: "待处理的订单", manualSub: "状态为待处理、待撤、确认中",
  1527. manualCols: [["主订单编号", 190, "no", "mono"], ["方向", 70, "direction", "tag:direction"], ["订单状态", 90, "status", "tag:note_order_status"], ["投资者", 100, "invName"], ["证券代码/名称", 150, "stock"], ["订单名义本金", 150, "notional", "num", "r"], ["下单方式", 90, "source", "tag:sales_order_source"], ["下单时间", 165, "orderTime", "mono"]],
  1528. pick: (r) => ["待处理", "待撤", "确认中"].includes(r.status),
  1529. shortcuts: [["共赢票据管理", "订单管理", "ticket"], ["共赢票据管理", "票据管理", "file"], ["共赢票据管理", "询价管理", "search"], ["投资者管理", "投资者信息", "user"], ["投资者管理", "开户审核", "check"], ["报表管理", "日结单", "chart"]],
  1530. },
  1531. trs: {
  1532. noun: "委托", dictKey: "trs_order_status", listKey: "用户管理/委托查询",
  1533. status: [["未报", 1, true], ["待报", 0], ["已报", 5], ["部成", 2], ["待撤", 1, true], ["已撤", 3], ["部撤", 1], ["已成", 24], ["废单", 1, true], ["过期", 0], ["部废", 0]],
  1534. manualTitle: "需要关注的委托", manualSub: "状态为未报、待撤、部撤、废单",
  1535. manualCols: [["委托编号", 170, "no", "mono"], ["用户名称", 100, "uname"], ["股票", 150, "stock"], ["方向", 70, "dir", "tag:direction"], ["委托状态", 90, "status", "tag:trs_order_status"], ["委托数量", 100, "qty", "num", "r"], ["委托价格", 100, "price", "num", "r"], ["委托时间", 165, "otime", "mono"]],
  1536. pick: (r) => ["未报", "待撤", "部撤", "废单"].includes(r.status),
  1537. shortcuts: [["用户管理", "委托查询", "search"], ["用户管理", "成交查询", "check"], ["系统设置", "交易下单", "ticket"], ["用户管理", "用户信息", "user"], ["清算管理", "清算", "scale"], ["报表管理", "TRS 报表", "chart"]],
  1538. },
  1539. };
  1540. /* ---------- 个人中心(views/profile/index.vue):账号信息、安全设置、修改密码弹窗 ---------- */
  1541. const profile = {
  1542. nickname: "admin", role: "系统管理员", username: "admin", gender: "男", mobile: "", email: "", dept: "平台运营", createTime: "2026-07-01 10:00:00",
  1543. security: [["账户密码", "定期修改密码有助于保护账户安全", "修改"], ["手机号码", "绑定后可用于接收告警短信", "绑定"], ["邮箱", "绑定后可用于接收通知邮件和重置密码", "绑定"]],
  1544. pwdForm: [{ label: "原密码", type: "password", req: true, ph: "请输入原密码" }, { label: "新密码", type: "password", req: true, ph: "6~20 位,至少两类字符" }, { label: "确认密码", type: "password", req: true, ph: "请再次输入新密码" }],
  1545. };
  1546. /* ---------- 登录页(views/login):文案取自 lang/package/zh-cn.json ---------- */
  1547. const login = {
  1548. copy: { username: "用户名", password: "密码", captcha: "验证码", remember: "记住我", forget: "忘记密码?", login: "登 录", reset: "重置密码", email: "邮箱", enterEmail: "请输入您的邮箱地址", sendReset: "发送重置链接", think: "想起密码?", back: "返回登录", theme: "主题切换", lang: "语言切换" },
  1549. version: "v3.4.2",
  1550. };
  1551. /* ---------- 反馈类:文案尽量取自真实代码(command-result.ts、各页 ElMessageBox / el-empty / el-alert) ---------- */
  1552. const feedback = {
  1553. messages: [["success", "保存成功"], ["error", "导出失败,请稍后重试"], ["info", "正在导出,完成后自动下载"], ["warning", "已受理并进入重试队列,尚未送达 TRS。请勿重复下单;若长时间仍显示「未报」,请联系运维查消息状态。", true]],
  1554. notification: { title: "清算完成", body: "2026-09-25 交易日清算已完成,营业日已推进到 2026-09-28。", time: "17:41", link: "查看清算记录" },
  1555. alerts: [
  1556. ["info", "自开始日期(含)起同步交易日,重复同步不会产生重复数据。"],
  1557. ["warning", "批量设置后,原券池将被清空,新券池为本次上传的内容。"],
  1558. ["danger", "撤单结果未知:上轮撤单未取得明确结果,可人工再次撤单。"],
  1559. ["success", "交易日历已同步到 2026-12-31。"],
  1560. ],
  1561. alertTitled: ["warning", "未启用共赢票据(A类)", "请确认该投资者当前无需A类交易权限。启用后需在「交易品类」里勾选。"],
  1562. confirms: {
  1563. cancel: { title: "撤单", msg: "确定要进行撤单吗?", kind: "danger", ok: "确定撤单", kv: [["委托编号", "ALG202609250007"], ["方向 / 股数", "买入 5,000 股"], ["对冲账号", "hedge01 / 880012"]] },
  1564. remove: { title: "删除对冲记录", msg: "确定删除此对冲记录吗?删除后不能恢复。", kind: "danger", ok: "删除", kv: [["对冲类型", "补录"], ["成交", "1,100 股 × 245.26 CNY"], ["发生金额", "269,786.00 CNY"]] },
  1565. ratio: { title: "确认调整", msg: "建仓比例过低,请确认是否需要调整买入名义本金数值?", kind: "warning", ok: "继续提交", cancel: "返回修改", kv: [["建仓比例", "62.50%"], ["买入名义本金", "2,000,000.00 CNY"]] },
  1566. logout: { title: "退出登录", msg: "确定退出当前账号吗?", kind: "info", ok: "退出", kv: [] },
  1567. },
  1568. empties: [
  1569. ["表格无数据", "暂无数据", "", ""],
  1570. ["查无结果", "没有符合条件的数据", "调整筛选条件后重新查找", "重置筛选"],
  1571. ["需要先选择", "请选择 Worker 查看分片覆盖率", "", ""],
  1572. ["需要先操作", "当前月份暂无日历数据", "请先同步交易日历和交收日历", "同步日历"],
  1573. ["通知为空", "暂无消息", "", ""],
  1574. ],
  1575. results: [
  1576. ["success", "清算完成", "2026-09-25 交易日清算已完成,营业日已推进到 2026-09-28。", [["查看清算记录", "primary"], ["返回", ""]]],
  1577. ["error", "导出失败", "报表生成超时,请缩小日期范围后重试;连续失败请联系运维。", [["重试", "primary"], ["返回", ""]]],
  1578. ],
  1579. };
  1580. /* ---------- 设计稿目录:每个页面的来源、设计要点、待确认 ---------- */
  1581. const pages = [
  1582. { key: "spec", group: "总览", title: "规范摘要", kind: "spec", sub: "选型结果、尺寸、颜色、12 类对应关系、待确认清单" },
  1583. { key: "kit", group: "总览", title: "组件总览", kind: "kit", sub: "两套皮肤(浅色 F、深色 B 石墨灰)下的全部基础组件,顶栏「皮肤」切换",
  1584. src: "分项选型 · 基础视觉 F(晴空蓝 #3C8CFF、文字链接 #1668DC)", points: ["颜色、文字、按钮、输入控件、状态标签、表格、提示、键值、弹窗外观,全部读同一份令牌;换肤只换令牌取值,尺寸、圆角、字体、排布不变。", "深色皮肤 B 石墨灰(10-08 从三版候选里选定):中性灰、扁平、1px 描边、无渐变无投影,主按钮实心蓝,状态标签只描边。对照见「规范摘要 → 换肤」。", "主按钮是左上浅、右下深的渐变,下面有一层淡蓝投影;页面底色是蓝白渐变,右上角一团浅蓝光晕;卡片白底、14px 圆角、淡蓝投影。", "表头很淡的蓝底,行高 40px;状态是无描边的浅色圆角标签;中性色带蓝调;数字用 Inter。"], todo: ["深色默认跟随系统还是手动切换;切换后的选择记在账号还是本机。"] },
  1585. { key: "login", group: "全局", title: "登录页", kind: "login", sub: "分项选型登录页 C:右侧浮卡·全幅底图;左侧只有系统名和版本号", variants: [["login", "登录"], ["reset", "重置密码"]], ends: true,
  1586. src: "分项选型 · 登录页 C(2026-10-07 选定);views/login/index.vue、components/Login.vue、ResetPwd.vue;文案取自 lang/package/zh-cn.json",
  1587. points: ["整页是视觉 F 的蓝白渐变底色,叠一层很淡的网格、几条主色曲线和右上角两个圆(内联 SVG,不用图片文件)。", "左侧一列(垂直居中,和卡片对齐):只有系统名 32px 和版本号。原来的「本系统业务模块」标签和「三端系统」三张入口卡片按 2026-10-08 的意见去掉,三端登录页之间不再互相链接。", "右侧 440px 浮卡,离右边 96px:小标志+系统名、「登录」标题、用户名/密码/验证码标签在上,输入框按代码现状 40px(登录页 size=large),其余页面仍是 32px;记住我、忘记密码;登录按钮通宽。", "宽屏:左列和浮卡成一组,组宽按 1440 基准(左 72、列 440、间距 392、卡 440、右 96),窗口更宽时整组居中,两块间距保持 392px 不变;底图和右上角按钮仍铺满整屏。窗口放不下左列(1100 以下)时只留卡片居中。(2026-10-08 定)", "右上角的主题按钮可以点:在浅色和最近看过的深色皮肤之间来回切(月亮/太阳);语言切换只保留入口;「注册」组件代码里存在但登录页没有入口,本稿不画。", "画面里可以点:忘记密码/返回登录、换一张验证码、显示密码、记住我。"],
  1588. todo: ["验证码图片是示意;登录页的控件要不要也统一成 32px。", "三端登录页只换系统名和标志,其余完全一样——是否需要三端不同的底色做区分(口径是三端完全一致,本稿没有区分)。"] },
  1589. { key: "home", group: "全局", title: "整体框架 · 首页", kind: "shell", active: "首页", ends: true, sub: "框架 A 经典三段 + 首页现状(问候卡);工作台版保留在变体里", variants: [["now", "现状首页"], ["work", "工作台(保留,待接口)"]],
  1590. src: "框架:分项选型框架 A。首页现状版:改版前的 views/dashboard/index.vue(头像、按时段的问候语 + 昵称、天气一句);工作台版:本稿 2026-10-08 首版的首页设计,整套保留",
  1591. points: ["框架 A:浅色侧栏 220px 通高(白底微透、深灰字、选中项淡蓝块,标志是晴空蓝渐变;深色皮肤下是近黑侧栏),收起后是 64px 图标栏,悬停弹出二级菜单。白色顶栏 52px:折叠按钮、面包屑|营业日与两个 A 股交易日、菜单搜索、全屏、通知、用户(界面设置收进用户菜单)。标签页栏 38px,卡片式页签,右键有刷新、关闭其它、关闭所有。内容区四周留 16px。", "首页按 2026-10-08 的现状内容重画(默认看到的就是这版):内容区只有一张问候卡——左边大头像(和个人中心同一个 88px 头像),右边问候语(按时段「上午好/下午好/晚上好」+ 昵称)和天气那句;卡片之下留空,不放数字、表格和入口。", "「工作台」版(今日订单按状态的数字、待人工处理的订单表、快捷入口)整套保留,在上方「工作台(保留,待接口)」里看;等首页汇总接口完善后再切回去。两版的侧栏、顶栏、标签页完全相同,只换内容区。", "三端同一结构:现状版三端只差系统名和菜单;工作台版发行端看订单,销售端看订单,TRS 看委托,数字是示例。", "面包屑和标签页都显示页面名(这是框架 A 的特点);页面内不再放标题。"],
  1592. todo: ["「界面尺寸」切换已去掉(密度已锁定);「界面设置」收进用户菜单——请确认。", "现状首页的天气那句是模板里写死的假内容,本稿按现状保留;要不要去掉,或换成营业日/交易日的一句提示——请确认。", "工作台版的数字口径(今日=营业日当天?)和表格条数(本稿取前 6 条),等接口完善、切回工作台时一并确认。"] },
  1593. { key: "list", group: "全局", title: "列表页 · 搜索区 + 表格", kind: "shell", active: "home", ends: true, sub: "发行端订单管理、销售端订单管理、TRS 委托查询",
  1594. src: "views/issue/order、views/sales/order、views/trs/order;SearchForm 组件(固定 3 列、超过 2 行折叠)",
  1595. points: ["搜索区一张卡片:标签在左、3 列等宽,标签宽度按本页最长标签统一;条件超过两行时折叠,「展开」在按钮组末尾。查找是主按钮,重置、导出是次按钮。", "表格一张卡片:勾选列和操作列固定在左;表头 40px、行高 40px;数字右对齐;状态用字典标签;空值显示「--」。", "勾选后表格顶部出现浅蓝选择条,显示已选条数和「清空」。分页固定在卡片底部,左边是总数。", "点「详情」打开订单详情抽屉(抽屉 E);在抽屉里点「补录」会叠开补录弹窗(弹窗 J)。"],
  1596. todo: ["表格列宽和固定列按代码现状,没有加列设置、密度切换(代码里没有)。"] },
  1597. { key: "drawer", group: "弹层", title: "订单详情抽屉", kind: "shell", end: "issue", active: "home", ovl: ["order"], sub: "抽屉 E 卡片+描述表",
  1598. src: "views/issue/order/components/OrderDetailDrawer 及子组件",
  1599. points: ["宽 1040px;头部是编号、4 个状态标签和一行关键信息(证券、名义本金、投资者、下单时间),右侧刷新、关闭。", "灰底上排卡片:主订单信息、A类订单信息用带边框的描述表(灰底标签格,一行 3 对);对冲执行卡片里是建议、7 个汇总数字、记录表格。", "流程按钮在卡片标题右侧:直接下单、补录是次按钮,完成对冲是主按钮;「转为自动处理」在主订单卡片。", "点「补录」叠开补录弹窗;弹窗关闭后回到抽屉。"],
  1600. todo: ["「直接下单」会打开发行端 HedgeOrderDialog(普通单 + 算法单),本稿没有画,按弹窗 J 的卡片分区实现。"] },
  1601. { key: "dlg-fill", group: "弹层", title: "补录弹窗(短表单)", kind: "shell", end: "issue", active: "home", ovl: ["order", "fill"], sub: "短表单布局:标签在左 + 顶部信息块 + 底部结果块",
  1602. src: "views/issue/order/components/HedgeFillDialog.vue;布局按 2026-10-08 给的下单弹窗",
  1603. points: ["宽 640px;标题下一行写操作的是哪一笔主订单和方向;顶部两个高亮信息块放主订单编号、证券,表单里不再重复。", "标签在左、右对齐、单列;对冲系统是「自定义|TRS」两段切换,点一下下面的字段就变;成交股数、成交均价用带加减的数字框。", "算出来的值放在表单底部的灰色结果块里,写明算法,不可修改;必填项前有红星,校验失败的输入框变红并在下方显示原因。", "底部左侧一句提示,右侧取消、确定。长表单(新建投资者)仍用弹窗 J 的卡片分区。"],
  1604. todo: ["代码现状只有「确定」、校验失败用全局提示报错;本稿加了「取消」和行内校验——请确认。"] },
  1605. { key: "dlg-investor", group: "弹层", title: "新建投资者弹窗(长表单)", kind: "shell", end: "sales", active: "投资者管理/投资者信息", ovl: ["investor"], sub: "弹窗 J 卡片分区",
  1606. src: "views/sales/investor/components/InvestorCreateDialog.vue",
  1607. points: ["宽 920px,四张卡片:基础信息、问卷信息、交易品类、其它信息;卡片标题右侧显示「已填/必填」,有错的组显示红字。", "标签在上、双列;组合项(区号+手机、国别+地址、证件类型+号码)在一格里并排。", "底部左侧实时显示「还有几项必填未填、几项有误」;右侧取消、保存。", "勾选「共赢票据(A类)」后,下面的警告提示条消失(可以点画面试)。"],
  1608. todo: [] },
  1609. { key: "dlg-trs", group: "弹层", title: "TRS 下单弹窗(专用布局)", kind: "shell", end: "trs", active: "系统设置/交易下单", ovl: ["trs"], sub: "交易下单页 + 下单弹窗", variants: [["buy", "买入"], ["sell", "卖出"]],
  1610. src: "views/trs/config/order-place/detail(下单详情页:资金信息、持仓、当日委托;下单弹窗:买卖、价格、数量、五档行情);布局按 2026-10-08 给的下单弹窗",
  1611. points: ["页面:账户选择 + 资金信息描述表 + 「买入」「卖出」两个按钮;下面一张卡片用页内页签切换持仓、当日委托。", "弹窗宽 880px:标题就是方向,标题下一行写账户;顶部两个高亮信息块(可用余额、最大可买/可卖股数);标签在左、单列:交易渠道两段切换、股票代码、委托价格和委托数量用带加减的数字框,下面是快捷档位和份额;底部两个灰色结果块是金额。", "右侧五档行情:名称与代码、实时推送标记、最新价和涨跌、昨收提示;卖五到卖一、中间一行最新价、买一到买五,量用色条表示,点任一档位填入委托价。", "确认按钮跟随方向变色(买入红、卖出绿),文字写明「确认买入 1,000 股」。"],
  1612. todo: ["算法单(TWAP/POV/E-Amount)的参数区本稿没有画,按同一布局在数量下方增加「算法参数」几行实现。"] },
  1613. { key: "drawer-note", group: "弹层", title: "票据详情抽屉(页签 + 描述表)", kind: "shell", end: "issue", active: "共赢票据管理/票据管理", ovl: ["note"], sub: "详情展示 + 页内标签页在抽屉里的样子",
  1614. src: "views/issue/components/NoteDetailDrawer.vue、note-detail/NoteInfoPanel、NoteHedgePanel、NoteChangesPanel",
  1615. points: ["头部:编号、状态标签、关键信息;操作按钮(修改到期日、票据调整、添加分红、票据卖出)放在头部右侧,按票据状态显示。", "头部下方一行页签:主页、变动记录;页签内容是灰底上的卡片。", "描述表:标签长的分组(当前信息)改成一行 2 对,避免标签换行;盈亏用红绿文字。", "对冲信息卡片里三张表:持仓(带合计行)、票据买卖对冲记录、日常买卖对冲记录(标题右侧放下单、补录、刷新)。", "变动记录页签里没有数据的表显示空状态,不显示空表头。"],
  1616. todo: ["票据买卖对冲记录的列按订单对冲记录示意,以代码为准。"] },
  1617. { key: "tabs", group: "页面", title: "页内标签页 · 风险监控", kind: "shell", end: "issue", active: "风控管理/共赢票据风险监控", sub: "页面级页签(三个页签各是一套搜索 + 表格)",
  1618. src: "views/issue/risk-monitor/index.vue(页签)、components/MonitorPane.vue(搜索项只有占位文字)",
  1619. points: ["页面级页签放在内容区最上面一行,不套卡片:40px 高,当前页签蓝字加 2px 下划线;下面才是搜索卡片和表格卡片。", "和浏览器式标签页(框架)、卡片内页签(清算)三者形态不同,一眼能分出层级。", "搜索项按代码现状只有占位文字、没有标签;多选项(票据状态默认选了待生效、存续期)显示成小标签。", "风险状态「危险」的行,操作列出现「强平卖出」;其它行只有「详情」。"],
  1620. todo: ["表格列为示意(代码里的列由配置生成)。", "搜索项要不要补标签(和订单管理页统一)。"] },
  1621. { key: "clearing", group: "页面", title: "清算管理(卡片内页签)", kind: "shell", end: "issue", active: "清算管理/共赢票据清算", sub: "页签在卡片里 + 步骤表 + 日志",
  1622. src: "views/issue/clearing/index.vue(步骤名与代码逐字一致)",
  1623. points: ["一张卡片:标题栏左边是页签(清算、清算日志),右边是工具(刷新、一键清算、重新清算;执行中时日志页签多一个「停止当前任务」)。", "本次清算的交易日、批次号和批次状态放在页签下方的一行信息条里;营业日不重复显示(侧栏已有)。", "步骤表:执行状态用标签;执行中的行高亮;失败时在表格下方出现红色提示条。", "代码里的「A股清算」市场页签只有一项,本稿收成信息条里的「市场 A股」,多市场时再变回页签。"],
  1624. todo: ["市场页签的处理请确认。"] },
  1625. { key: "profile", group: "页面", title: "个人中心", kind: "shell", end: "issue", active: "个人中心", ends: true, sub: "从用户菜单进入", variants: [["page", "页面"], ["pwd", "修改密码弹窗"]],
  1626. src: "views/profile/index.vue",
  1627. points: ["左边一张身份卡:头像、昵称、角色,昵称旁可以改;右边账号信息描述表(用户名、手机、邮箱、部门、创建时间),未绑定的项直接给「绑定」入口。", "安全设置列表:账户密码、手机号码、邮箱,每行右侧一个操作。", "修改密码弹窗按短表单布局:标签在左、单列,宽 480px;密码规则写在新密码下方。"],
  1628. todo: ["头像上传入口代码里有(隐藏的文件选择),本稿放在头像上悬停显示——请确认。"] },
  1629. { key: "err", group: "页面", title: "404 / 401", kind: "shell", end: "issue", active: "404", variants: [["404", "404 页面不存在"], ["401", "401 没有权限"]], sub: "在框架内显示,导航仍可用",
  1630. src: "views/error/404.vue、401.vue",
  1631. points: ["放在框架的内容区里,侧栏和标签页仍可用,不做整页跳转。", "主色描边的大数字,下面是一句说明和两个按钮:返回首页(主按钮)、返回上一页。", "401 写清楚是哪个页面没有权限、找谁开通。"],
  1632. todo: ["代码里 401 页的「如有不满请联系你领导」和外链「随便看看」是模板遗留,本稿改成正式文案——请确认。"] },
  1633. { key: "confirm", group: "反馈", title: "确认框(在页面里)", kind: "shell", end: "issue", active: "home", ovl: ["confirm"], variants: [["cancel", "撤单"], ["remove", "删除记录"], ["ratio", "比例确认"], ["logout", "退出登录"]], sub: "ElMessageBox.confirm 的样子",
  1634. src: "各页 ElMessageBox.confirm 的文案",
  1635. points: ["宽 420px;状态图标和标题同一行(危险红、警告橙、信息蓝),下面的问题和小描述表与图标左对齐,不缩进。", "危险操作的确定按钮是红色实心并写明动作(确定撤单、删除);警告类用主色;按钮靠右,取消在左。", "不放关闭叉号,只能取消或确定。"],
  1636. todo: ["确认框里要不要显示操作对象(代码现状只有一句话)。"] },
  1637. { key: "feedback", group: "反馈", title: "反馈类总览", kind: "feedback", sub: "全局提示、通知、提示条、空状态、加载态、结果提示、校验",
  1638. src: "ElMessage、ElNotification、el-alert、el-empty、v-loading、el-result 的用法;文案尽量取自代码",
  1639. points: ["全局提示(ElMessage):顶部居中,白底硬投影,左侧彩色图标;跨系统命令「已受理未送达」的提示停留 8 秒并可手动关闭。", "通知(ElNotification):右上角卡片,状态图标和标题同一行,正文和链接与图标左对齐。", "提示条:左侧 3px 色条,白底;可带标题;可关闭。", "空状态:一句话说明原因,必要时给一个动作;表格空状态高度固定 240px。", "加载态:表格用遮罩加转圈,按钮用内嵌转圈并禁用;首屏用骨架条。", "结果提示:整块居中,图标、标题、说明、按钮。"],
  1640. todo: [] },
  1641. ];
  1642. return { dict, noteDetail, clearing, risk, homeNow, home, profile, login, feedback, pages };
  1643. })();
  1644. </script>
  1645. <script>
  1646. /* 设计令牌:浅色 F「晴空渐变」(第一批选型最终结果)+ 深色 B「石墨灰」(2026-10-08 三版深色候选里用户选定;未选中的 A 夜空蓝、C 极光玻璃归档在 归档/tokens-深色候选AC.js)。
  1647. * 每套皮肤是同一份令牌表的不同取值;画布根元素 .cv[data-v=皮肤] 上生成为 CSS 变量,所有页面只读变量,不写死颜色。
  1648. * 换肤只改颜色与质感(底色、卡片、侧栏、按钮、表格、标签、投影/描边、装饰),不改尺寸、圆角、字体、排布。
  1649. * 锁定口径:控件 32px、表格行高 40px、正文 14px;1440×900 基准;三端一致;买入红、卖出绿。
  1650. */
  1651. window.TOKENS = (function () {
  1652. const SYS = '-apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif';
  1653. const INTER = '"Inter", ' + SYS;
  1654. const NOSH = "0 0 0 0 transparent"; // 代替 none,这样才能和聚焦光晕拼成合法的 box-shadow
  1655. const SIZE = { "r-ctl": "8px", "r-card": "14px", "r-dlg": "16px", "r-tag": "6px", "r-pop": "10px", "ck-r": "5px", "card-hd-h": "52px", "tag-h": "22px", "tag-fs": "12px", "tag-fw": "500", "th-fw": "500", "th-fs": "13px", "fw-title": "600", "fw-strong": "600", "btn-fw": "500", font: SYS, "font-num": INTER };
  1656. /* ---------------- 浅色 F 晴空渐变(2026-10-08 侧栏改成分项选型框架 E 的浅色侧栏配色:白底微透、深灰字;其余不动) ---------------- */
  1657. const F_GRAD = "linear-gradient(135deg, #5AA9FF 0%, #2F7FF5 100%)";
  1658. const f = {
  1659. key: "f", letter: "F", name: "晴空渐变", label: "浅色", dark: false, title: "基础视觉 F 晴空渐变",
  1660. tagline: "已选定的浅色主题(默认):晴空蓝主色、渐变主按钮、蓝白渐变底色、白卡片淡蓝投影、浅色侧栏。",
  1661. points: ["主色 #3C8CFF,文字链接 #1668DC;主按钮左上浅、右下深的渐变加淡蓝投影。", "页面底色蓝白渐变、右上角一团浅蓝光晕;卡片白底 14px 圆角淡蓝投影。", "侧栏是白底微透(透出底色的蓝白渐变)、深灰字,选中项淡蓝渐变块+蓝字,标志是晴空蓝渐变;表头很淡的蓝底;状态是无描边的浅色圆角标签。"],
  1662. cmp: { 底色: "蓝白渐变+右上浅蓝光晕", 卡片: "白底、淡蓝投影、无描边", 侧栏: "白底微透、深灰字,选中项淡蓝块+蓝字", 主按钮: "蓝色渐变+淡蓝投影", 次按钮与输入框: "白底描边,悬停描边变浅蓝", 表头与行: "淡蓝表头,白行,悬停极淡蓝", 状态标签: "浅色底、彩色字、无描边", 投影与描边: "靠投影分层,几乎不用描边", 装饰: "渐变、光晕" },
  1663. notes: { buttons: "32px;主按钮渐变加淡蓝投影,次按钮白底描边", status: "颜色按字典 tag_type 取;无描边的浅色圆角标签", dialog: "淡蓝投影,圆角 16px;排布见「补录弹窗」", kv: "详情展示统一用带边框的描述表,灰底标签格" },
  1664. tokens: {
  1665. ...SIZE,
  1666. "c-primary": "#3C8CFF", "c-primary-hover": "#63A5FF", "c-primary-active": "#2B76E6", "c-primary-bg": "#E9F3FF", "c-primary-border": "#BBD9FF", "c-link": "#1668DC",
  1667. grad: F_GRAD, "grad-h": "linear-gradient(135deg, #74B8FF 0%, #4A92FA 100%)",
  1668. "pri-bg": "var(--grad)", "pri-bg-h": "var(--grad-h)", "pri-bd": "transparent", "pri-bd-h": "transparent", "pri-t": "#FFFFFF",
  1669. "logo-bg": "var(--grad)", "logo-t": "#FFFFFF",
  1670. buy: "#CB2634", sell: "#008026",
  1671. t1: "#15233B", t2: "#44546C", t3: "#8492A6", t4: "#BAC4D2",
  1672. "bd-ctl": "#D5E0EE", bd: "#E4ECF5", "bd-light": "#EEF3F9",
  1673. "fill-1": "#F5F9FE", "fill-2": "#EDF3FB", "fill-3": "#E0E9F5",
  1674. "bg-page": "#F1F6FD", "bg-card": "#FFFFFF", "frame-outer": "#F7FAFF",
  1675. canvas: "radial-gradient(900px 460px at 88% -8%, rgba(110,180,255,.34), transparent 62%), radial-gradient(760px 420px at 4% 0%, rgba(150,170,255,.20), transparent 60%), linear-gradient(180deg, #EAF3FF 0%, #F3F8FE 38%, #F6F9FD 100%)",
  1676. "pa-bg": "transparent",
  1677. "bg-pop": "#FFFFFF", "bg-ctl": "#FFFFFF", "bg-dlg": "var(--bg-card)", "td-bg": "var(--bg-card)",
  1678. "tip-bg": "rgba(17,19,24,.88)", "tip-t": "#FFFFFF", "hover-veil": "rgba(0,0,0,.04)", veil: "rgba(255,255,255,.72)",
  1679. "card-bd": "0 solid transparent", "card-sh": "0 1px 2px rgba(30,90,180,.05), 0 8px 24px rgba(30,90,180,.07)", "card-hd-bg": "transparent", "card-hd-bd": "#EEF3F9",
  1680. "sh-pop": "0 0 0 1px rgba(30,90,180,.06), 0 10px 28px rgba(30,90,180,.14)", "sh-dlg": "0 24px 64px rgba(20,60,130,.22)", "pop-bd": "0 solid transparent", mask: "rgba(21,45,90,.38)",
  1681. "card-bf": "none", "pop-bf": "none", "dlg-bf": "none", "sd-bf": "none",
  1682. "btn-bg": "#FFFFFF", "btn-bd": "#D5E0EE", "btn-t": "#15233B", "btn-bg-h": "#F5F9FE", "btn-bd-h": "#9CC5FF", "btn-t-h": "#1668DC",
  1683. "btn-sh": "0 1px 2px rgba(30,90,180,.05)", "btn-sh-h": "0 2px 6px rgba(30,90,180,.10)", "btn-pri-sh": "0 4px 12px rgba(47,127,245,.30)",
  1684. "ipt-bg": "#FFFFFF", "ipt-bd": "#D5E0EE", "ipt-sh": NOSH, "ipt-bg-h": "#FFFFFF", "ipt-bd-h": "#9CC5FF",
  1685. focus: "0 0 0 3px rgba(60,140,255,.18)", "focus-bd": "#3C8CFF",
  1686. "ck-bd": "#C3D1E3", "sw-off": "#BAC4D2", "sw-ring": "rgba(0,0,0,.06)", "fx-sh": "6px 0 6px -6px rgba(0,0,0,.14)",
  1687. "th-bg": "#F2F7FE", "th-t": "#5A6B84",
  1688. "row-bd": "#EDF2F8", "row-hover": "#F5F9FF", "row-sel": "#E6F1FF", "row-stripe": "#FFFFFF", "td-t": "#15233B",
  1689. succ: "#17B26A", "succ-t": "#0B8A4D", "succ-bg": "#E3F8EC", "succ-bd": "transparent",
  1690. warn: "#F79009", "warn-t": "#B95E00", "warn-bg": "#FFF3DF", "warn-bd": "transparent",
  1691. dang: "#F04461", "dang-t": "#CB2634", "dang-bg": "#FFE9EC", "dang-bd": "transparent",
  1692. info: "#8492A6", "info-t": "#44546C", "info-bg": "#EDF2F8", "info-bd": "transparent",
  1693. "prim-bg": "#E3F0FF", "prim-t": "#1668DC", "prim-bd": "transparent",
  1694. "pg-bg": "transparent", "pg-bd": "transparent", "pg-on-bg": "var(--grad)", "pg-on-t": "#FFFFFF", "pg-on-bd": "transparent",
  1695. // 侧栏:分项选型框架 E 的浅色侧栏(白底 78% 透出底色渐变、深灰字、选中项淡蓝渐变块+蓝字、右侧 1px 淡蓝线、标志晴空蓝渐变)
  1696. "sd-bg": "rgba(255,255,255,.78)", "sd-sub": "transparent", "sd-t": "#44546C", "sd-t-dim": "#8492A6", "sd-hi": "var(--t1)",
  1697. "sd-act-bg": "linear-gradient(90deg, #DCEBFF, #EDF5FF)", "sd-act-t": "#1668DC", "sd-hover": "rgba(60,140,255,.07)", "sd-bd": "1px solid #E4ECF5", "sd-fly": "var(--bg-pop)", "sd-logo-bg": "var(--grad)", "sd-logo-t": "#FFFFFF",
  1698. },
  1699. };
  1700. /* ---------------- 深色 B 石墨灰:不带色相的暗灰,扁平、靠描边分区、不用投影和渐变;状态标签只描边不铺底 ---------------- */
  1701. const db = {
  1702. key: "db", letter: "B", name: "石墨灰", label: "深色 B", dark: true, title: "深色皮肤 B 石墨灰(2026-10-08 选定)",
  1703. tagline: "已选定的深色主题:中性暗灰,不带蓝调;扁平,卡片靠 1px 描边分区,没有渐变、光晕和投影;主按钮是一块实心蓝,状态标签只描边不铺底,整张表最安静。",
  1704. points: ["底色 #141518、卡片 #1C1E23,中性灰,不偏蓝;没有渐变和光晕,顶栏、卡片靠 1px 描边分区。", "主色 #4A8CF7 实心(主按钮、勾选、分页当前页都不用渐变);文字链接 #8DBBFF。", "侧栏近黑 #111215,选中项是一块深蓝底+亮蓝字。", "状态标签和提示条改成「描边+彩色字」,不铺底色;买入红 #F47781、卖出绿 #4CC38A。"],
  1705. cmp: { 底色: "中性暗灰,纯色", 卡片: "比底色亮一级,1px 灰描边,无投影", 侧栏: "近黑,选中项深蓝块+亮蓝字", 主按钮: "实心蓝,无投影", 次按钮与输入框: "深灰底、灰描边,悬停描边变亮", 表头与行: "灰表头,灰行线,悬停略亮", 状态标签: "只描边+彩色字,不铺底", 投影与描边: "全靠描边,弹层才有投影", 装饰: "无" },
  1706. notes: { buttons: "32px;主按钮实心蓝、无投影,次按钮深灰底描边", status: "颜色按字典 tag_type 取;只描边+彩色字,不铺底色", dialog: "比底色亮一级、1px 描边,弹层才有投影;圆角 16px", kv: "带边框的描述表,标签格比内容格暗一级" },
  1707. tokens: {
  1708. ...SIZE,
  1709. "c-primary": "#4A8CF7", "c-primary-hover": "#6DA3FF", "c-primary-active": "#3B78DD", "c-primary-bg": "#243247", "c-primary-border": "#3A5480", "c-link": "#8DBBFF",
  1710. grad: "#4A8CF7", "grad-h": "#6DA3FF",
  1711. "pri-bg": "#4A8CF7", "pri-bg-h": "#6DA3FF", "pri-bd": "transparent", "pri-bd-h": "transparent", "pri-t": "#FFFFFF",
  1712. "logo-bg": "#4A8CF7", "logo-t": "#FFFFFF",
  1713. buy: "#F47781", sell: "#4CC38A",
  1714. t1: "#E6E8EC", t2: "#B3B8C2", t3: "#848B98", t4: "#5C6370",
  1715. "bd-ctl": "#3B404A", bd: "#2E323A", "bd-light": "#292D34",
  1716. "fill-1": "#22252B", "fill-2": "#292D34", "fill-3": "#343941",
  1717. "bg-page": "#141518", "bg-card": "#1C1E23", "frame-outer": "#111215",
  1718. canvas: "#141518", "pa-bg": "transparent",
  1719. "bg-pop": "#23262C", "bg-ctl": "#141518", "bg-dlg": "#1C1E23", "td-bg": "var(--bg-card)",
  1720. "tip-bg": "#E6E8EC", "tip-t": "#141518", "hover-veil": "rgba(255,255,255,.05)", veil: "rgba(20,21,24,.72)",
  1721. "card-bd": "1px solid #2E323A", "card-sh": NOSH, "card-hd-bg": "transparent", "card-hd-bd": "#2E323A",
  1722. "sh-pop": "0 8px 24px rgba(0,0,0,.50)", "sh-dlg": "0 20px 60px rgba(0,0,0,.60)", "pop-bd": "1px solid #3B404A", mask: "rgba(0,0,0,.60)",
  1723. "card-bf": "none", "pop-bf": "none", "dlg-bf": "none", "sd-bf": "none",
  1724. "btn-bg": "#23262C", "btn-bd": "#3B404A", "btn-t": "#E6E8EC", "btn-bg-h": "#2A2E35", "btn-bd-h": "#555C68", "btn-t-h": "#FFFFFF",
  1725. "btn-sh": NOSH, "btn-sh-h": NOSH, "btn-pri-sh": NOSH,
  1726. "ipt-bg": "#141518", "ipt-bd": "#3B404A", "ipt-sh": NOSH, "ipt-bg-h": "#141518", "ipt-bd-h": "#555C68",
  1727. focus: "0 0 0 2px rgba(74,140,247,.32)", "focus-bd": "#4A8CF7",
  1728. "ck-bd": "#555C68", "sw-off": "#3B404A", "sw-ring": "rgba(255,255,255,.10)", "fx-sh": "6px 0 8px -6px rgba(0,0,0,.6)",
  1729. "th-bg": "#202329", "th-t": "#A0A7B3",
  1730. "row-bd": "#2A2E35", "row-hover": "#22252B", "row-sel": "#243247", "row-stripe": "var(--bg-card)", "td-t": "#DADDE3",
  1731. succ: "#4CC38A", "succ-t": "#6FD6A3", "succ-bg": "transparent", "succ-bd": "rgba(76,195,138,.50)",
  1732. warn: "#E5A84B", "warn-t": "#F0BD6C", "warn-bg": "transparent", "warn-bd": "rgba(229,168,75,.50)",
  1733. dang: "#EC6A73", "dang-t": "#F58B93", "dang-bg": "transparent", "dang-bd": "rgba(236,106,115,.50)",
  1734. info: "#848B98", "info-t": "#B3B8C2", "info-bg": "transparent", "info-bd": "#3B404A",
  1735. "prim-bg": "transparent", "prim-t": "#8DBBFF", "prim-bd": "rgba(74,140,247,.55)",
  1736. "pg-bg": "transparent", "pg-bd": "transparent", "pg-on-bg": "#4A8CF7", "pg-on-t": "#FFFFFF", "pg-on-bd": "transparent",
  1737. "sd-bg": "#111215", "sd-sub": "#0C0D10", "sd-t": "#A7AEBA", "sd-t-dim": "#6B7280", "sd-hi": "#FFFFFF",
  1738. "sd-act-bg": "#243247", "sd-act-t": "#8DBBFF", "sd-hover": "rgba(255,255,255,.05)", "sd-bd": "0 solid transparent", "sd-fly": "var(--sd-bg)", "sd-logo-bg": "#4A8CF7", "sd-logo-t": "#FFFFFF",
  1739. },
  1740. };
  1741. const palette = [
  1742. ["主色与语义", [["主色", "c-primary"], ["主色悬停", "c-primary-hover"], ["主色按下", "c-primary-active"], ["主色浅底", "c-primary-bg"], ["主色描边", "c-primary-border"], ["链接文字", "c-link"], ["买入/上涨", "buy"], ["卖出/下跌", "sell"]]],
  1743. ["文字与线条", [["文字 1 标题正文", "t1"], ["文字 2 次要", "t2"], ["文字 3 辅助", "t3"], ["文字 4 占位禁用", "t4"], ["控件描边", "bd-ctl"], ["分割线", "bd"], ["浅分割线", "bd-light"], ["表格行线", "row-bd"]]],
  1744. ["底色", [["页面底色", "bg-page"], ["卡片", "bg-card"], ["填充 1", "fill-1"], ["填充 2", "fill-2"], ["填充 3", "fill-3"], ["表头底", "th-bg"], ["行悬停", "row-hover"], ["行选中", "row-sel"]]],
  1745. ["状态", [["成功", "succ"], ["成功文字", "succ-t"], ["成功浅底", "succ-bg"], ["警告", "warn"], ["警告文字", "warn-t"], ["警告浅底", "warn-bg"], ["危险", "dang"], ["危险文字", "dang-t"], ["危险浅底", "dang-bg"]]],
  1746. ];
  1747. // 规范摘要「换肤」里每套皮肤的小色条
  1748. const strip = [["底色", "bg-page"], ["卡片", "bg-card"], ["侧栏", "sd-bg"], ["主按钮", "pri-bg"], ["文字", "t1"], ["成功", "succ"], ["买入", "buy"], ["卖出", "sell"]];
  1749. const all = { f, db };
  1750. const order = ["f", "db"];
  1751. // 当前皮肤:对外仍是 window.TOKENS(key / name / tokens…),use(key) 切换时原地改,持有引用的模块不用动
  1752. const cur = { all, order, palette, strip, SYS, INTER, frame: "a", frameName: "经典三段" };
  1753. cur.use = function (key) {
  1754. const t = all[key];
  1755. if (!t || t.key === cur.key) return false;
  1756. Object.assign(cur, { key: t.key, letter: t.letter, name: t.name, label: t.label, dark: t.dark, title: t.title, tagline: t.tagline, points: t.points, cmp: t.cmp, notes: t.notes, tokens: t.tokens });
  1757. return true;
  1758. };
  1759. cur.use("f");
  1760. return cur;
  1761. })();
  1762. </script>
  1763. <script>
  1764. /* 三端 CMS · 完整 UI 设计稿 · 画布核心
  1765. * 画布(.cv)是 1440×900 的真实尺寸页面,用 CSS zoom 缩放显示,画面里的点击都是真实交互。
  1766. * 本文件:图标、令牌注入、框架 A 外壳、列表页、弹窗(新建投资者用 J 卡片分区;补录等短表单用 2026-10-08 给的布局)、抽屉 E(订单详情)、画布挂载与交互。
  1767. * 新增页面(登录、首页、页签页、清算、个人中心、404/401、TRS 下单、票据详情、确认框、反馈类)在 pages.js;设计稿外壳在 book.js。
  1768. */
  1769. window.UI = (function () {
  1770. "use strict";
  1771. const D = window.SPEC_DATA;
  1772. const D2 = window.SPEC_DATA2;
  1773. const T = window.TOKENS;
  1774. const $ = (s, r = document) => r.querySelector(s);
  1775. const $$ = (s, r = document) => Array.from(r.querySelectorAll(s));
  1776. const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
  1777. /* ---------------- 图标 ---------------- */
  1778. const ICONS = {
  1779. home: '<path d="M3.5 10.5 12 3.5l8.5 7"/><path d="M5.5 9v11h13V9"/><path d="M10 20v-5.5h4V20"/>',
  1780. chart: '<path d="M4 4v16h16"/><path d="M8 16v-4"/><path d="M12 16V8"/><path d="M16 16v-6"/>',
  1781. shield: '<path d="M12 3 5 6v5.5c0 4.2 2.9 7.8 7 9.5 4.1-1.7 7-5.3 7-9.5V6Z"/><path d="m9 12 2 2 4-4"/>',
  1782. ticket: '<path d="M4 7.5A1.5 1.5 0 0 1 5.5 6h13A1.5 1.5 0 0 1 20 7.5V10a2 2 0 0 0 0 4v2.5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 16.5V14a2 2 0 0 0 0-4Z"/><path d="M14.5 6v12" stroke-dasharray="2 2.2"/>',
  1783. coin: '<circle cx="12" cy="12" r="8.5"/><path d="m9 7.5 3 3.5 3-3.5M8.5 12h7M8.5 15h7M12 11v6"/>',
  1784. check: '<circle cx="12" cy="12" r="8.5"/><path d="m8.5 12 2.5 2.5 4.5-5"/>',
  1785. gear: '<circle cx="12" cy="12" r="3"/><circle cx="12" cy="12" r="7.6" stroke-dasharray="3.1 2.87"/>',
  1786. cpu: '<rect x="6" y="6" width="12" height="12" rx="2"/><rect x="9.5" y="9.5" width="5" height="5" rx=".5"/><path d="M9.5 3v3M14.5 3v3M9.5 18v3M14.5 18v3M3 9.5h3M3 14.5h3M18 9.5h3M18 14.5h3"/>',
  1787. building: '<path d="M4.5 20V5a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v15"/><path d="M14.5 9.5h4a1 1 0 0 1 1 1V20"/><path d="M3 20h18M8 8h3M8 11.5h3M8 15h3"/>',
  1788. clock: '<circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/>',
  1789. mail: '<rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 7 8 6 8-6"/>',
  1790. user: '<circle cx="12" cy="8.5" r="3.8"/><path d="M4.5 20c.6-3.8 3.7-6.2 7.5-6.2s6.9 2.4 7.5 6.2"/>',
  1791. card: '<rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="M3.5 10h17M7 15h4"/>',
  1792. scale: '<path d="M12 4.5v15M6 19.5h12M5 8h14"/><path d="m5 8-2.5 5.5h5ZM19 8l-2.5 5.5h5Z"/>',
  1793. sliders: '<path d="M4 7h9M17 7h3M4 12h3M11 12h9M4 17h11M19 17h1"/><circle cx="15" cy="7" r="2"/><circle cx="9" cy="12" r="2"/><circle cx="17" cy="17" r="2"/>',
  1794. file: '<path d="M14 3.5H7a1.5 1.5 0 0 0-1.5 1.5v14A1.5 1.5 0 0 0 7 20.5h10a1.5 1.5 0 0 0 1.5-1.5V8Z"/><path d="M14 3.5V8h4.5M9 13h6M9 16.5h6"/>',
  1795. search: '<circle cx="11" cy="11" r="6.5"/><path d="m20 20-4.2-4.2"/>',
  1796. refresh: '<path d="M19.5 11A7.5 7.5 0 0 0 6 6.8L4.5 8.5"/><path d="M4.5 4.5v4h4"/><path d="M4.5 13A7.5 7.5 0 0 0 18 17.2l1.5-1.7"/><path d="M19.5 19.5v-4h-4"/>',
  1797. download: '<path d="M12 4v11"/><path d="m7.5 10.5 4.5 4.5 4.5-4.5"/><path d="M5 19.5h14"/>',
  1798. bell: '<path d="M6.5 16v-5a5.5 5.5 0 1 1 11 0v5l1.5 2H5Z"/><path d="M10 20.5a2 2 0 0 0 4 0"/>',
  1799. fold: '<rect x="3.5" y="4.5" width="17" height="15" rx="2"/><path d="M9.5 4.5v15"/><path d="m16 10-2 2 2 2"/>',
  1800. unfold: '<rect x="3.5" y="4.5" width="17" height="15" rx="2"/><path d="M9.5 4.5v15"/><path d="m14 10 2 2-2 2"/>',
  1801. expand: '<path d="M4.5 9V4.5H9M19.5 9V4.5H15M4.5 15v4.5H9M19.5 15v4.5H15"/>',
  1802. down: '<path d="m6.5 9.5 5.5 5.5 5.5-5.5"/>',
  1803. right: '<path d="m9.5 6.5 5.5 5.5-5.5 5.5"/>',
  1804. left: '<path d="m14.5 6.5-5.5 5.5 5.5 5.5"/>',
  1805. x: '<path d="M7 7l10 10M17 7 7 17"/>',
  1806. calendar: '<rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 10h17M8 3v4M16 3v4"/>',
  1807. logout: '<path d="M14.5 4.5h3a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-3"/><path d="M10 16.5 5.5 12 10 7.5M5.5 12h10"/>',
  1808. info: '<circle cx="12" cy="12" r="8.5"/><path d="M12 11v5.5M12 7.8h.01"/>',
  1809. warn: '<path d="M12 4 2.8 19.5h18.4Z"/><path d="M12 10v4.5M12 17h.01"/>',
  1810. error: '<circle cx="12" cy="12" r="8.5"/><path d="m9 9 6 6M15 9l-6 6"/>',
  1811. success: '<circle cx="12" cy="12" r="8.5"/><path d="m8.5 12 2.5 2.5 4.5-5"/>',
  1812. plus: '<path d="M12 5v14M5 12h14"/>',
  1813. edit: '<path d="M4.5 19.5h4l10-10-4-4-10 10Z"/><path d="m13 7 4 4"/>',
  1814. upload: '<path d="M12 20V9"/><path d="m7.5 13.5 4.5-4.5 4.5 4.5"/><path d="M5 4.5h14"/>',
  1815. layers: '<path d="m12 4 8.5 4.5L12 13 3.5 8.5Z"/><path d="m3.5 13 8.5 4.5 8.5-4.5"/>',
  1816. more: '<circle cx="5.5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="18.5" cy="12" r="1.2"/>',
  1817. lock: '<rect x="5.5" y="10.5" width="13" height="10" rx="2"/><path d="M8.5 10.5V7.5a3.5 3.5 0 0 1 7 0v3"/>',
  1818. eye: '<path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z"/><circle cx="12" cy="12" r="3"/>',
  1819. eyeoff: '<path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z"/><path d="m4 4 16 16"/>',
  1820. globe: '<circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c3 3 3 14 0 17M12 3.5c-3 3-3 14 0 17"/>',
  1821. moon: '<path d="M19.5 14.5A8 8 0 0 1 9.5 4.5a8 8 0 1 0 10 10Z"/>',
  1822. sun: '<circle cx="12" cy="12" r="4"/><path d="M12 3v2.5M12 18.5V21M3 12h2.5M18.5 12H21M5.6 5.6l1.8 1.8M16.6 16.6l1.8 1.8M5.6 18.4l1.8-1.8M16.6 7.4l1.8-1.8"/>',
  1823. key: '<circle cx="8" cy="14" r="3.5"/><path d="m10.5 11.5 8-8M15 6l2.5 2.5M18 3.5 20.5 6"/>',
  1824. phone: '<rect x="7" y="3.5" width="10" height="17" rx="2"/><path d="M11 17.5h2"/>',
  1825. arrow: '<path d="M5 12h14"/><path d="m13 6 6 6-6 6"/>',
  1826. copy: '<rect x="8.5" y="8.5" width="11" height="11" rx="1.5"/><path d="M15.5 8.5v-2a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h2"/>',
  1827. stop: '<circle cx="12" cy="12" r="8.5"/><rect x="9" y="9" width="6" height="6" rx="1"/>',
  1828. play: '<circle cx="12" cy="12" r="8.5"/><path d="m10 8.5 5 3.5-5 3.5Z"/>',
  1829. camera: '<path d="M4.5 8.5h3l1.5-2h6l1.5 2h3v10h-15Z"/><circle cx="12" cy="13" r="3"/>',
  1830. male: '<circle cx="10" cy="14" r="5"/><path d="m13.5 10.5 6-6M14 4.5h5.5V10"/>',
  1831. };
  1832. const ic = (n, cls = "") => `<svg class="ic ${cls}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICONS[n] || ""}</svg>`;
  1833. const CHECK = '<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 6.2 5 8.5 9.5 3.5"/></svg>';
  1834. const EMPTY = '<svg viewBox="0 0 64 48" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" style="color:var(--t4)"><path d="M8 28 16 12h32l8 16v12a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2Z"/><path d="M8 28h14l3 5h14l3-5h14"/></svg>';
  1835. /* ---------------- 设计令牌 → CSS 变量 ---------------- */
  1836. // 每套皮肤各生成一段 .cv[data-v=皮肤]{--令牌:值};画布按当前皮肤的 key 打 data-v,换肤只改属性和重绘
  1837. function injectTokens() {
  1838. const st = document.createElement("style");
  1839. const block = (sel, tokens) => `${sel}{${Object.entries(tokens).map(([n, val]) => `--${n}:${val}`).join(";")}}`;
  1840. st.textContent = T.order.map((k) => block(k === "f" ? `.cv,.cv[data-v="${k}"]` : `.cv[data-v="${k}"]`, T.all[k].tokens)).join("\n");
  1841. document.head.appendChild(st);
  1842. }
  1843. // 切换皮肤:改 window.TOKENS 的当前取值(持有 T 引用的模块都能看到),返回是否真的换了;重绘由调用方负责
  1844. const setTheme = (key) => T.use(key);
  1845. // 深色皮肤之间互切时记住最近一版,登录页的月亮按钮和用户菜单「界面设置」在浅色和这一版之间来回
  1846. const THEME_PICK = { dark: "db" };
  1847. function toggleTheme() {
  1848. if (T.dark) THEME_PICK.dark = T.key;
  1849. return setTheme(T.dark ? "f" : THEME_PICK.dark);
  1850. }
  1851. const dict = Object.assign({}, D.dict, D2.dict);
  1852. const tag = (dk, v) => `<span class="tg tg-${(dict[dk] || {})[v] || "info"}">${esc(v)}</span>`;
  1853. /* ---------------- 三端 ---------------- */
  1854. const listActions = [["查找", "primary", "search"], ["重置", "", "refresh"], ["数据导出", "", "download"]];
  1855. const ENDS = {
  1856. issue: { short: "发行", sys: D.sysName.issue, logo: "发", home: "共赢票据管理/订单管理", extra: "共赢票据管理/票据管理", search: D.issueSearch, actions: D.issueActions, columns: D.issueColumns, rows: D.issueRows.filter((r) => r.sub === 0), sel: true, op: true, src: "issue_frontend/src/views/issue/order" },
  1857. sales: { short: "销售", sys: D.sysName.sales, logo: "销", home: "共赢票据管理/订单管理", extra: "投资者管理/投资者信息", search: D.salesSearch, actions: listActions, columns: D.salesColumns, rows: D.salesRows.filter((r) => r.sub === 0), sel: false, op: true, src: "sales_frontend/src/views/sales/order" },
  1858. trs: { short: "TRS", sys: D.sysName.trs, logo: "T", home: "用户管理/委托查询", extra: "用户管理/成交查询", search: D.trsSearch, actions: D.trsActions, columns: D.trsColumns, rows: D.trsRows.filter((r) => !r.sub), sel: false, op: false, src: "trs_frontend/src/views/trs/order" },
  1859. };
  1860. const leafName = (key) => (key.includes("/") ? key.split("/")[1] : key);
  1861. const groupName = (key) => (key.includes("/") ? key.split("/")[0] : "");
  1862. // 画布状态:end 端,active 当前页键("首页" / "分组/菜单" / "个人中心" / "404" / "401"),ovls 叠开的弹层
  1863. function newCanvasState(end, active) {
  1864. const e = ENDS[end];
  1865. const act = !active || active === "home" ? e.home : active;
  1866. const tags = ["首页", e.home, e.extra];
  1867. if (!tags.includes(act)) tags.push(act);
  1868. return { end, col: false, open: new Set([groupName(act)].filter(Boolean)), active: act, tags, sel: new Set(), sfOpen: false, pop: null, ctx: null, msq: "", ovls: [], tab: {}, variant: null };
  1869. }
  1870. /* ---------------- 全局显示选项(设计稿外壳可切换) ---------------- */
  1871. const OPT = { lblTop: false }; // 短表单弹窗:标签在左(用户 2026-10-08 看过标签在上的效果后定为维持在左);true 时标签在上,单独模式 /lbl=top 可看
  1872. /* ---------------- 弹层状态(一份,和画布无关) ---------------- */
  1873. const OV = {
  1874. order: {},
  1875. fill: { hedgeSys: "自定义", pop: null },
  1876. investor: { checks: [false, false] },
  1877. trs: { channel: "QFII", dir: "买入", price: "11.65", qty: "1,000", algo: false },
  1878. note: { tab: "main" },
  1879. confirm: { kind: "cancel" },
  1880. pwd: {},
  1881. };
  1882. function resetOV() {
  1883. Object.assign(OV.fill, { hedgeSys: "自定义", pop: null });
  1884. OV.investor.checks = [false, false];
  1885. Object.assign(OV.trs, { channel: "QFII", dir: "买入", price: "11.65", qty: "1,000", algo: false });
  1886. OV.note.tab = "main";
  1887. OV.confirm.kind = "cancel";
  1888. }
  1889. /* ---------------- 通用小件 ---------------- */
  1890. function pagerHTML(total) {
  1891. return `<div class="pager"><span class="total">共 <span class="num">${total}</span> 条</span>
  1892. <div class="ipt" style="width:108px"><span class="val">20 条/页</span>${ic("down")}</div>
  1893. <span class="pg-b dis">${ic("left")}</span><span class="pg-b on num">1</span><span class="pg-b dis">${ic("right")}</span>
  1894. <span>前往</span><div class="ipt" style="width:52px;justify-content:center"><span class="num">1</span></div><span>页</span></div>`;
  1895. }
  1896. function ctlHTML(f) {
  1897. if (f.type === "daterange") return `<div class="ipt">${ic("calendar")}<span class="val num">${f.value[0]}</span><span class="sep">${f.sep}</span><span class="val num">${f.value[1]}</span></div>`;
  1898. if (f.type === "select") {
  1899. const vals = Array.isArray(f.value) ? f.value : [];
  1900. return `<div class="ipt">${vals.length ? `<span class="val chips">${vals.map((v) => `<span class="chipv">${esc(v)}</span>`).join("")}</span>` : `<span class="ph">${esc(f.ph)}</span>`}${ic("down")}</div>`;
  1901. }
  1902. return `<div class="ipt"><span class="ph">${esc(f.ph)}</span></div>`;
  1903. }
  1904. function cellHTML(c, r) {
  1905. const v = r[c.key];
  1906. if (v === undefined || v === null || v === "" || v === "--") return '<span class="t4">--</span>';
  1907. if (c.tag) return tag(c.tag, v);
  1908. if (c.link) return `<span class="lnk num">${esc(v)}</span>`;
  1909. if (c.num || c.mono) return `<span class="num">${esc(v)}</span>`;
  1910. return esc(v);
  1911. }
  1912. const NUMLIKE = /^[+\-]?[\d,.]+(%|( \/ [+\-]?[\d,.]+%?)*)?( [A-Z]{3}| 股)?$|^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$|^[A-Z]{1,3}\d{6,}[A-Z]?(-\d)?$/;
  1913. // 通用单元格:字符串、["文字", 字典名] 标签、["文字", "link"] 链接、["文字", "up"|"dn"] 红绿
  1914. function cellAuto(v, col) {
  1915. if (v === undefined || v === null || v === "" || v === "--") return '<span class="t4">--</span>';
  1916. if (Array.isArray(v)) {
  1917. const [t, kind] = v;
  1918. if (kind === "link") return `<span class="lnk num" data-act="noop">${esc(t)}</span>`;
  1919. if (kind === "up" || kind === "dn") return `<span class="num ${kind === "up" ? "buy" : "sell"}">${esc(t)}</span>`;
  1920. if (dict[kind]) return tag(kind, t);
  1921. return `<span class="tg tg-${kind}">${esc(t)}</span>`;
  1922. }
  1923. const s = String(v);
  1924. if ((col && col[2] === "r") || NUMLIKE.test(s)) return `<span class="num">${esc(s)}</span>`;
  1925. return esc(s);
  1926. }
  1927. // 通用表格:cols = [[标题, 宽, 对齐]],rows = 单元格数组;opts.ops(row) 返回操作列内容
  1928. function tableHTML(cols, rows, opts = {}) {
  1929. const head = cols.map((c) => `<th class="${c[2] === "r" ? "r" : c[2] === "c" ? "c" : ""}" style="min-width:${c[1]}px">${esc(c[0])}</th>`).join("");
  1930. const body = rows.length
  1931. ? rows.map((r, ri) => `<tr class="${opts.rowCls ? opts.rowCls(r, ri) : ""}">${cols.map((c, i) => {
  1932. const al = c[2] === "r" ? "r" : c[2] === "c" ? "c" : "";
  1933. const v = opts.cell ? opts.cell(r, i, c) : r[i];
  1934. return `<td class="${al} ${c[3] === "wrap" ? "wrap" : ""}">${v === undefined ? "" : typeof v === "string" && v.startsWith("<") ? v : cellAuto(v, c)}</td>`;
  1935. }).join("")}</tr>`).join("")
  1936. : `<tr><td colspan="${cols.length}" style="padding:0;border-bottom:0"><div class="empty" style="height:${opts.emptyH || 200}px;justify-content:center">${EMPTY}<span>${esc(opts.empty || "暂无数据")}</span>${opts.emptyAct ? `<span class="btn txt" data-act="noop">${esc(opts.emptyAct)}</span>` : ""}</div></td></tr>`;
  1937. return `<div class="tbl-wrap ${opts.cls || ""}" ${opts.keep ? `data-keep="${opts.keep}"` : ""} ${opts.style ? `style="${opts.style}"` : ""}><table class="tbl"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>`;
  1938. }
  1939. const btn = (text, type = "", act = "noop", extra = "") => `<span class="btn ${type}" data-act="${act}" ${extra}>${text}</span>`;
  1940. /* ---------------- 列表页(搜索区 + 表格) ---------------- */
  1941. function listHTML(st) {
  1942. const e = ENDS[st.end];
  1943. const lw = Math.max(...e.search.map((f) => f.label.length)) * 14;
  1944. const collapsible = e.search.length > 5;
  1945. const fields = st.sfOpen || !collapsible ? e.search : e.search.slice(0, 5);
  1946. const rem = 3 - (fields.length % 3);
  1947. const acts = `<div class="sf-act" style="grid-column:span ${rem}">${e.actions.map(([t, type, icn]) => `<span class="btn ${type === "primary" ? "pri" : ""}" data-act="noop">${ic(icn)}${t}</span>`).join("")}${collapsible ? `<span class="btn txt ${st.sfOpen ? "up" : ""}" data-act="sf">${st.sfOpen ? "收起" : "展开"}${ic("down")}</span>` : ""}</div>`;
  1948. const search = `<div class="card"><div class="sf">${fields.map((f) => `<div class="sf-i"><label style="width:${lw}px">${esc(f.label)}</label>${ctlHTML(f)}</div>`).join("")}${acts}</div></div>`;
  1949. const cols = [];
  1950. if (e.sel) cols.push({ sel: true, w: 48, fx: true });
  1951. if (e.op) cols.push({ op: true, title: "操作", w: 72, fx: true });
  1952. e.columns.forEach((c) => cols.push(c));
  1953. let left = 0;
  1954. const fx = cols.filter((c) => c.fx);
  1955. cols.forEach((c) => { if (c.fx) { c._left = left; left += c.w; } });
  1956. const lastFx = fx[fx.length - 1];
  1957. const allOn = e.rows.length && e.rows.every((r) => st.sel.has(r.no));
  1958. const someOn = e.rows.some((r) => st.sel.has(r.no));
  1959. const thCls = (c) => [c.fx ? "fx" : "", c === lastFx ? "fx-end" : "", c.num || c.align === "right" ? "r" : ""].join(" ");
  1960. const pos = (c) => (c.fx ? `left:${c._left}px;` : "");
  1961. const head = cols.map((c) => {
  1962. const style = `${pos(c)}min-width:${c.w}px;width:${c.w}px`;
  1963. if (c.sel) return `<th class="${thCls(c)}" style="${style}"><span class="ck ${allOn ? "on" : someOn ? "half" : ""}" data-act="allsel">${allOn ? CHECK : ""}</span></th>`;
  1964. return `<th class="${thCls(c)}" style="${style}">${esc(c.title)}</th>`;
  1965. }).join("");
  1966. const body = e.rows.map((r) => {
  1967. const on = st.sel.has(r.no);
  1968. return `<tr class="${on ? "is-sel" : ""}">${cols.map((c) => {
  1969. const cls = thCls(c) + (c.key === "org" || c.key === "remark" || c.key === "stock" ? " ellip" : "");
  1970. if (c.sel) return `<td class="${cls}" style="${pos(c)}"><span class="ck ${on ? "on" : ""}" data-act="rowsel" data-no="${esc(r.no)}">${CHECK}</span></td>`;
  1971. if (c.op) return `<td class="${cls}" style="${pos(c)}"><span class="btn txt" data-act="${st.end === "issue" ? "ovopen" : "toast"}" data-o="order" data-msg="销售端订单详情用同一个抽屉,只是没有「对冲执行」卡片">详情</span></td>`;
  1972. return `<td class="${cls}" style="${pos(c)}">${cellHTML(c, r)}</td>`;
  1973. }).join("")}</tr>`;
  1974. }).join("");
  1975. const selbar = st.sel.size ? `<div class="selbar">已选择 <b>${st.sel.size}</b> 项<span class="btn txt" data-act="clearsel">清空</span></div>` : "";
  1976. return `<div class="pg-list">${search}<div class="card tb-card">${selbar}<div class="tbl-wrap" data-keep="tbl"><table class="tbl"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>${pagerHTML(e.rows.length)}</div></div>`;
  1977. }
  1978. function skelHTML(text) {
  1979. const bars = (n) => Array.from({ length: n }, (_, i) => `<div class="bar" style="width:${[92, 86, 95, 78, 90, 84, 97, 81, 88, 93][i % 10]}%"></div>`).join("");
  1980. return `<div class="skel"><div class="card"><div class="rowb">${Array.from({ length: 6 }, () => '<div class="bar"></div>').join("")}</div></div>
  1981. <div class="card" style="flex:1;display:flex;flex-direction:column;gap:18px"><div class="bar" style="height:20px;width:100%"></div><div class="lines">${bars(12)}</div></div>
  1982. <div class="skel-note">${ic("info")}<span>${esc(text)}</span></div></div>`;
  1983. }
  1984. // 页面分发:pages.js 往 pageRenderers 里登记
  1985. const pageRenderers = {};
  1986. function pageHTML(st) {
  1987. const e = ENDS[st.end];
  1988. const key = st.active === e.home ? "__list" : st.active;
  1989. const fn = pageRenderers[key] || (st.active === "首页" ? pageRenderers.__home : null);
  1990. if (fn) return fn(st);
  1991. return skelHTML(`「${leafName(st.active)}」没有单独画样板,按「列表页」规范实现`);
  1992. }
  1993. pageRenderers.__list = listHTML;
  1994. /* ---------------- 外壳部件 ---------------- */
  1995. function brandHTML(st, extra = "") {
  1996. const e = ENDS[st.end];
  1997. return `<div class="brand"><span class="logo">${e.logo}</span><span class="nm txt">${esc(e.sys)}</span>${extra}</div>`;
  1998. }
  1999. function menuHTML(st) {
  2000. const groups = D.menus[st.end];
  2001. const g = ([name, icon, leaves]) => {
  2002. const open = st.open.has(name);
  2003. const hasAct = groupName(st.active) === name;
  2004. return `<div class="mg ${open ? "is-open" : ""} ${hasAct ? "has-act" : ""}"><div class="mg-t" data-act="grp" data-g="${esc(name)}">${ic(icon)}<span class="txt">${esc(name)}</span>${ic("down", "arr")}</div>
  2005. <div class="mg-c"><span class="fly-t">${esc(name)}</span>${leaves.map((l) => `<div class="mi ${st.active === name + "/" + l ? "is-act" : ""}" data-act="leaf" data-k="${esc(name + "/" + l)}">${esc(l)}</div>`).join("")}</div></div>`;
  2006. };
  2007. return `<div class="menu" data-keep="menu"><div class="mi-top ${st.active === "首页" ? "is-act" : ""}" data-act="leaf" data-k="首页">${ic("home")}<span class="txt">首页</span></div>${groups.map(g).join("")}</div>`;
  2008. }
  2009. const tdHTML = () => `<div class="td">${D.tradeDays.map(([k, v]) => `<span>${k}<b>${v}</b></span>`).join("")}<span data-act="tdrf" title="刷新">${ic("refresh", "rf")}</span></div>`;
  2010. const crumbHTML = (st) => {
  2011. if (st.active === "首页") return `<div class="crumb"><b>首页</b></div>`;
  2012. const g = groupName(st.active);
  2013. return `<div class="crumb"><span>首页</span>${g ? `${ic("right")}<span>${esc(g)}</span>` : ""}${ic("right")}<b>${esc(leafName(st.active))}</b></div>`;
  2014. };
  2015. function msResults(st) {
  2016. const q = st.msq.trim();
  2017. const all = [];
  2018. D.menus[st.end].forEach(([g, , leaves]) => leaves.forEach((l) => all.push([g, l])));
  2019. const hits = q ? all.filter(([g, l]) => l.includes(q) || g.includes(q)) : all.slice(0, 6);
  2020. if (!hits.length) return `<div class="ms-empty">没有匹配的菜单</div>`;
  2021. const hl = (s) => (q ? esc(s).split(esc(q)).join(`<mark>${esc(q)}</mark>`) : esc(s));
  2022. return (q ? "" : `<div class="pop-t">全部菜单</div>`) + hits.slice(0, 8).map(([g, l]) => `<div class="pop-i" data-act="leaf" data-k="${esc(g + "/" + l)}"><span>${hl(l)}</span><span class="grp">${esc(g)}</span></div>`).join("");
  2023. }
  2024. const msPopHTML = (st, pos) => `<div class="fl ${pos}"><div class="pop ms-pop"><label class="ipt">${ic("search")}<input data-ms placeholder="输入菜单名称" value="${esc(st.msq)}" autocomplete="off"></label><div class="ms-list">${msResults(st)}</div></div></div>`;
  2025. const NOTICES = [["清算完成", "2026-09-25 交易日清算已完成,营业日已推进到 2026-09-28。", "17:41"], ["追保通知发送失败", "票据 NT001202608134A 的预警邮件发送失败,请在「邮件发送记录」里重发。", "14:03"]];
  2026. function bellHTML(st, pos = "below-r") {
  2027. const pop = `<div class="fl ${pos}"><div class="pop bell-pop"><div class="pop-hd">通知<span class="cnt">${NOTICES.length} 条未读</span><span class="btn txt" data-act="noop">全部已读</span></div>
  2028. ${NOTICES.map(([t, b, tm]) => `<div class="bell-i" data-act="noop"><div class="r"><span>${esc(t)}</span><small>${tm}</small></div><p>${esc(b)}</p></div>`).join("")}
  2029. <div class="bell-ft"><span class="btn txt" data-act="leaf" data-k="通知和告警管理/通知设置">查看更多${ic("right")}</span></div></div></div>`;
  2030. return `<div class="anchor"><span class="icb" data-act="pop" data-pop="bell" title="通知">${ic("bell")}<i class="dot"></i></span>${st.pop === "bell" ? pop : ""}</div>`;
  2031. }
  2032. const userMenu = `<div class="pop"><div class="pop-i" data-act="leaf" data-k="个人中心">${ic("user")}个人中心</div><div class="pop-i" data-act="theme" title="示例:切换浅色/深色皮肤">${ic("moon")}界面设置 · 切换皮肤</div><div class="pop-sep"></div><div class="pop-i" data-act="ovopen" data-o="confirm" data-kind="logout">${ic("logout")}退出登录</div></div>`;
  2033. function userHTML(st, pos = "below-r") {
  2034. return `<div class="anchor"><div class="user" data-act="pop" data-pop="user"><span class="ava">A</span><span class="txt">admin</span>${ic("down")}</div>${st.pop === "user" ? `<div class="fl ${pos}">${userMenu}</div>` : ""}</div>`;
  2035. }
  2036. const msIconHTML = (st) => `<div class="anchor"><span class="icb" data-act="pop" data-pop="ms" title="菜单搜索">${ic("search")}</span>${st.pop === "ms" ? msPopHTML(st, "below-r") : ""}</div>`;
  2037. const fullIcon = `<span class="icb" data-act="toast" data-msg="全屏(浏览器 F11 效果)" title="全屏">${ic("expand")}</span>`;
  2038. function tagsHTML(st) {
  2039. const items = st.tags.map((k, i) => {
  2040. const act = k === st.active;
  2041. return `<div class="tvi ${act ? "is-act" : ""}" data-act="tag" data-k="${esc(k)}" data-i="${i}">${k === "首页" ? ic("home", "hm") : ""}<span>${esc(leafName(k))}</span>${k === "首页" ? "" : `<span data-act="tagx" data-k="${esc(k)}" title="关闭">${ic("x", "x")}</span>`}</div>`;
  2042. }).join("");
  2043. let ctx = "";
  2044. if (st.ctx) {
  2045. const home = st.ctx.k === "首页";
  2046. ctx = `<div class="ctx" style="left:${st.ctx.left}px"><div class="pop"><div class="pop-i" data-act="ctx" data-do="refresh">${ic("refresh")}刷新</div>${home ? "" : `<div class="pop-i" data-act="ctx" data-do="close">${ic("x")}关闭</div>`}<div class="pop-i" data-act="ctx" data-do="others">关闭其它</div><div class="pop-i" data-act="ctx" data-do="all">关闭所有</div></div></div>`;
  2047. }
  2048. return `<div class="tv">${items}${ctx}</div>`;
  2049. }
  2050. /* ---------------- 框架 A:经典三段 ---------------- */
  2051. const overlayRenderers = {};
  2052. const OVL_KIND = { order: "dr", note: "dr", fill: "dg", investor: "dg", trs: "dg", confirm: "dg", pwd: "dg" };
  2053. function overlaysHTML(st) {
  2054. return st.ovls.map((o, i) => {
  2055. const fn = overlayRenderers[o];
  2056. if (!fn) return "";
  2057. return `<div class="ovl ${OVL_KIND[o] === "dr" ? "dr" : ""} ${i === 1 ? "lv2" : i >= 2 ? "lv3" : ""}" data-act="stop" data-ovl="${o}">${fn(OV[o], st)}</div>`;
  2058. }).join("");
  2059. }
  2060. function shellHTML(st) {
  2061. const col = st.col ? "is-col" : "";
  2062. const page = `<main class="pa" data-keep="pa">${pageHTML(st)}</main>`;
  2063. return `<div class="sh fa ${col}"><aside class="sd">${brandHTML(st)}${menuHTML(st)}</aside>
  2064. <div class="mn"><header class="nb"><span class="icb" data-act="col" title="${st.col ? "展开侧栏" : "收起侧栏"}">${ic(st.col ? "unfold" : "fold")}</span>${crumbHTML(st)}<span class="sp"></span>${tdHTML()}${msIconHTML(st)}${fullIcon}${bellHTML(st)}${userHTML(st)}</header>
  2065. ${tagsHTML(st)}${page}</div></div>${overlaysHTML(st)}`;
  2066. }
  2067. /* ================= 表单与弹窗 J ================= */
  2068. const FILL_CTX = { no: "ORD001202609250030", dir: "买入", stock: "300750 宁德时代" };
  2069. const numUnit = (v) => { const m = String(v).match(/^(.*?)\s([A-Z]{3}|股)$/); return m ? `${esc(m[1])}<i>${m[2]}</i>` : esc(v); };
  2070. const lblW = (fields) => Math.max(...fields.map((f) => [...f.label].reduce((n, ch) => n + (ch.charCodeAt(0) > 255 ? 14 : 8), 0))) + 26;
  2071. function fillFields(ov) {
  2072. const common = D.fill.common.map((f) => (f.label === "对冲系统" ? { ...f, value: ov.hedgeSys, act: "hsys" } : f));
  2073. const REQ = new Set(["交易日", "对冲账号", "资金号", "委托编号/算法母单编号", "成交股数", "成交均价", "发生金额(结算币种)"]);
  2074. return [...common, ...(ov.hedgeSys === "自定义" ? D.fill.custom : D.fill.trs)].map((f) => (REQ.has(f.label) ? { ...f, req: true } : f));
  2075. }
  2076. function ctl(f, ov) {
  2077. const err = f.error ? " is-err" : "";
  2078. const unit = f.unit ? `<span class="sfx">${f.unit}</span>` : "";
  2079. const val = (v, ph, num) => (v ? `<span class="val ${num ? "num" : ""}">${esc(v)}</span>` : `<span class="ph">${esc(ph || "")}</span>`);
  2080. switch (f.type) {
  2081. case "select": {
  2082. const pop = f.act === "hsys" && ov.pop === "hsys" ? `<div class="fl"><div class="pop">${["自定义", "TRS"].map((o) => `<div class="pop-i ${o === f.value ? "on" : ""}" data-act="hsysset" data-v="${o}">${o}</div>`).join("")}</div></div>` : "";
  2083. return `<div class="ipt${err}${pop ? " is-focus" : ""}" ${f.act ? `data-act="${f.act}" style="cursor:pointer"` : ""}>${val(f.value, f.ph || "请选择")}${ic("down")}</div>${pop}`;
  2084. }
  2085. case "date": return `<div class="ipt${err}">${val(f.value, f.ph)}${ic("calendar")}</div>`;
  2086. case "amount": return `<div class="ipt${err}">${val(f.value, f.ph, true)}${unit}</div>`;
  2087. case "password": return `<div class="ipt${err}">${ic("lock")}${val(f.value ? "••••••••" : "", f.ph)}${ic("eyeoff")}</div>`;
  2088. case "text": return `<div class="ro"><span class="num">${esc(f.value)}</span>${f.unit ? `<span class="u">${f.unit}</span>` : ""}</div>`;
  2089. case "tag": return `<div class="ro">${tag(f.tag, f.value)}</div>`;
  2090. case "combo": return `<div class="combo">${f.parts.map((p) => `<div class="ipt" style="${p.w ? `flex:none;width:${p.w}px` : "flex:1"}">${val(p.value, p.ph)}${p.select ? ic("down") : ""}</div>`).join("")}</div>`;
  2091. case "checks": return `<div class="checks">${f.options.map(([l], i) => `<span class="chk-l" data-act="chk" data-i="${i}"><span class="ck ${ov.checks[i] ? "on" : ""}">${CHECK}</span>${l}</span>`).join("")}</div>`;
  2092. default: return `<div class="ipt${err}">${val(f.value, f.ph)}</div>`;
  2093. }
  2094. }
  2095. const errHTML = (f) => (f.error ? `<div class="fld-err">${ic("error")}${esc(f.error)}</div>` : "");
  2096. const lbl = (f) => `${f.req ? '<i class="req">*</i>' : ""}${esc(f.label)}`;
  2097. // 一个表单项:pos = l(标签在左右对齐)/ t(在上)
  2098. function item(f, ov, pos, lw, cls = "") {
  2099. const body = `${ctl(f, ov)}${errHTML(f)}`;
  2100. if (pos === "t") return `<div class="fi fi-t ${cls}"><label>${lbl(f)}</label><div class="fc">${body}</div></div>`;
  2101. return `<div class="fi fi-l ${cls}"><label style="width:${lw}px">${lbl(f)}</label><div class="fc">${body}</div></div>`;
  2102. }
  2103. const isFilled = (f, ov) => (f.type === "combo" ? f.parts.every((p) => p.value) : f.type === "checks" ? ov.checks.some(Boolean) : !!f.value);
  2104. function gStat(g, ov) {
  2105. const fs = g.fields || [];
  2106. const req = fs.filter((f) => f.req);
  2107. return { req: req.length, ok: req.filter((f) => isFilled(f, ov) && !f.error).length, err: fs.filter((f) => f.error).length };
  2108. }
  2109. const gst = (s) => (s.err ? `<span class="gst bad">${s.err} 项有误</span>` : s.req ? `<span class="gst ${s.ok === s.req ? "ok" : ""}">${s.ok}/${s.req}</span>` : `<span class="gst">选填</span>`);
  2110. const uploadHTML = (g) => (u) => `<div class="upl"><div class="upl-r"><span class="btn" data-act="noop">${ic("upload")}${g.uploadBtn}</span><span class="upl-tip">${g.uploadTip}</span></div>${u.files.map(([n, s]) => `<div class="upl-f">${ic("file")}<span>${esc(n)}</span><span class="sz">${s}</span>${ic("x", "x")}</div>`).join("")}</div>`;
  2111. function groupBody(g, ov, pos, lw, cols) {
  2112. let h = "";
  2113. if (g.fields) {
  2114. h += `<div class="fgrid ${cols === 2 && g.cols !== 1 ? "c2" : ""}">${g.fields.map((f) => item(f, ov, pos, lw, f.type === "checks" ? "full" : "")).join("")}</div>`;
  2115. if (g.alert && !ov.checks[0]) h += `<div class="fi fi-t" style="margin-top:8px"><div class="fc"><div class="alt alt-warning" style="margin:0">${ic("warn")}<span>${esc(g.alert)}</span></div></div></div>`;
  2116. }
  2117. if (g.uploads) h += `<div class="fgrid">${g.uploads.map((u) => `<div class="fi fi-t"><label>${u.label}</label><div class="fc">${uploadHTML(g)(u)}</div></div>`).join("")}</div>`;
  2118. return h;
  2119. }
  2120. const closeX = `<span class="icb" data-act="ovclose" title="关闭">${ic("x")}</span>`;
  2121. const btnCancel = `<span class="btn" data-act="ovclose">取消</span>`;
  2122. const dgCard = (t, extra, body) => `<section class="dgc"><div class="dgc-hd"><b>${t}</b><span class="sp"></span>${extra}</div><div class="dgc-bd">${body}</div></section>`;
  2123. const dgWrap = (o) => `<div class="dg dg-j ${o.cls || ""}" style="width:${o.w}px" data-act="stop"><div class="dg-hd">${o.hd}</div>${o.mid || ""}<div class="dg-bd" data-keep="${o.keep || "dgbd"}">${o.bd}</div>${o.ft ? `<div class="dg-ft">${o.ft}</div>` : ""}</div>`;
  2124. function kvVal(f) {
  2125. const [, v, kind = ""] = f;
  2126. if (v === "--" || v === "") return '<span class="t4">--</span>';
  2127. if (kind.startsWith("tag:")) return tag(kind.slice(4), v);
  2128. if (kind === "num:up") return `<span class="num buy">${esc(v)}</span>`;
  2129. if (kind === "num:dn") return `<span class="num sell">${esc(v)}</span>`;
  2130. if (kind === "mono" || kind === "num") return `<span class="num">${esc(v)}</span>`;
  2131. return esc(v);
  2132. }
  2133. function kv(fields, cls, perRow = 0) {
  2134. let cells = fields.map((f) => `<div class="${f[2] === "span2" ? "s2" : ""}"><dt>${f[0]}</dt><dd>${kvVal(f)}</dd></div>`);
  2135. if (perRow) { const used = fields.reduce((n, f) => n + (f[2] === "span2" ? 2 : 1), 0); for (let i = used; i % perRow; i++) cells.push("<div><dt></dt><dd></dd></div>"); }
  2136. return `<dl class="kvg ${cls}">${cells.join("")}</dl>`;
  2137. }
  2138. // 加减数字框(短表单里的股数、价格)
  2139. const stepper = (v, unit, err = false) => `<div class="stp ${err ? "is-err" : ""}"><span class="stp-b" data-act="noop">−</span><span class="val num">${esc(v)}</span>${unit ? `<span class="sfx">${unit}</span>` : ""}<span class="stp-b" data-act="noop">+</span></div>`;
  2140. // 短表单弹窗(2026-10-08 用户给的下单弹窗布局):标题下一行写上下文;顶部高亮信息块;标签在左、单列;底部灰色结果块;可带右侧栏
  2141. function sfDialog(o) {
  2142. const tiles = o.tiles && o.tiles.length ? `<div class="sf-tiles">${o.tiles.map(([l, v, u]) => `<div class="sf-tile"><span class="l">${l}</span><b class="${/^[\d,.]/.test(String(v)) ? "num" : ""}">${v}${u ? `<i>${u}</i>` : ""}</b></div>`).join("")}</div>` : "";
  2143. const rows = o.rows.map((r) => `<div class="sf-row"><label>${r.req ? '<i class="req">*</i>' : ""}${esc(r.label)}</label><div class="sf-c">${r.body}${r.extra || ""}</div></div>`).join("");
  2144. const res = o.results && o.results.length ? `<div class="sf-row"><label></label><div class="sf-c"><div class="sf-res">${o.results.map(([l, v, u, note]) => `<div class="sf-rt"><span class="l">${l}</span><b class="num">${v}${u ? `<i>${u}</i>` : ""}</b>${note ? `<small>${note}</small>` : ""}</div>`).join("")}</div></div></div>` : "";
  2145. return `<div class="dg dg-s ${OPT.lblTop ? "lt" : ""} ${o.cls || ""}" style="width:${o.w}px" data-act="stop"><div class="dg-hd"><div class="row"><span class="ttl">${o.title}</span>${o.sub ? `<span class="sub">${o.sub}</span>` : ""}</div>${closeX}</div>
  2146. <div class="dg-bd" data-keep="${o.keep || "dgbd"}"><div class="sf-main" style="--lw:${o.lw || 96}px">${tiles}<div class="sf-rows">${rows}</div>${res}</div>${o.aside || ""}</div><div class="dg-ft">${o.ft}</div></div>`;
  2147. }
  2148. // 补录(短表单)
  2149. overlayRenderers.fill = function (ov) {
  2150. const fields = fillFields(ov);
  2151. const calc = fields.filter((f) => f.type === "text");
  2152. const rows = fields.filter((f) => f.type !== "text").map((f) => {
  2153. if (f.act === "hsys") return { label: f.label, body: `<div class="segc">${["自定义", "TRS"].map((o) => `<span class="${o === ov.hedgeSys ? "on" : ""}" data-act="hsysset" data-v="${o}">${o}</span>`).join("")}</div>` };
  2154. if (f.type === "amount" && f.value) return { label: f.label, req: f.req, body: stepper(f.value, f.unit) };
  2155. return { label: f.label, req: f.req, body: ctl(f, ov), extra: errHTML(f) };
  2156. });
  2157. const results = calc.map((f, i) => [f.label, f.value, f.unit || "", i === 0 ? "= 成交股数 × 成交均价" : ""]);
  2158. return sfDialog({ w: 640, lw: lblW(fields) - 12, title: D.fill.title, sub: `主订单 <span class="num">${FILL_CTX.no}</span> · ${FILL_CTX.dir}`,
  2159. tiles: [["主订单编号", FILL_CTX.no], ["证券代码/名称", FILL_CTX.stock]], rows, results,
  2160. ft: `<span class="hint">${ic("info")}补录后可在对冲记录里修改或删除</span><span class="sp"></span>${btnCancel}<span class="btn pri" data-act="ovtoast" data-msg="示例:这里只看排布,不真正提交">确定</span>` });
  2161. };
  2162. // 新建投资者(长表单)
  2163. overlayRenderers.investor = function (ov) {
  2164. const F = D.salesForm;
  2165. const stats = F.groups.map((g) => gStat(g, ov));
  2166. const lack = stats.reduce((n, s) => n + s.req - s.ok, 0);
  2167. const errs = stats.reduce((n, s) => n + s.err, 0);
  2168. const hint = `<span class="hint ${lack || errs ? "warn" : ""}">${lack || errs ? ic("warn") : ic("success")}${lack ? `还有 <b>${lack}</b> 项必填未填` : "必填项已填完"}${errs ? `,<b>${errs}</b> 项有误` : ""}</span>`;
  2169. return dgWrap({ w: 920, hd: `<div class="row"><span class="ttl">${F.title}</span><span class="sub">${F.mode}</span></div>${closeX}`,
  2170. bd: F.groups.map((g, i) => dgCard(g.title, gst(stats[i]), groupBody(g, ov, "t", 0, 2))).join(""),
  2171. ft: `${hint}<span class="sp"></span>${btnCancel}<span class="btn pri" data-act="ovtoast" data-msg="示例:这里只看排布,不真正提交">保存</span>` });
  2172. };
  2173. /* ================= 抽屉 E:订单详情 ================= */
  2174. function hedgeTable() {
  2175. const h = D.issueDetail.hedge;
  2176. const keys = ["ops", "type", "sys", "acct", "fund", "otype", "no", "status", "dir", "qty", "price", "fqty", "fprice", "amt", "samt"];
  2177. const head = h.columns.map(([t, w, al]) => `<th class="${al === "r" ? "r" : ""}" style="min-width:${w}px">${t}</th>`).join("");
  2178. const body = h.rows.map((r) => `<tr>${keys.map((key, i) => {
  2179. const al = h.columns[i][2] === "r" ? "r" : "";
  2180. const v = r[key];
  2181. let c;
  2182. if (key === "ops") c = v.length ? `<span class="btns" style="gap:12px">${v.map(([t, ty]) => `<span class="btn txt ${ty === "danger" ? "dng" : ""}" data-act="${t === "撤单" ? "ovopen" : t === "删除" ? "ovopen" : "noop"}" data-o="confirm" data-kind="${t === "撤单" ? "cancel" : "remove"}">${t}</span>`).join("")}</span>` : '<span class="t4">--</span>';
  2183. else if (key === "status") c = `<span class="tg tg-${v[1]}">${v[0]}</span>`;
  2184. else if (key === "dir") c = tag("direction", v);
  2185. else if (key === "sys") c = tag("hedge_system", v);
  2186. else if (v === "--") c = '<span class="t4">--</span>';
  2187. else c = al || key === "no" || key === "fund" ? `<span class="num">${esc(v)}</span>` : esc(v);
  2188. return `<td class="${al}">${c}</td>`;
  2189. }).join("")}</tr>`).join("");
  2190. return `<div class="tbl-wrap" data-keep="hedge"><table class="tbl"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>`;
  2191. }
  2192. function tiles(order, bar) {
  2193. const s = Object.fromEntries(D.issueDetail.hedge.summary);
  2194. return `<div class="tiles">${order.map((l) => `<div class="tile"><div class="l">${l}</div><div class="v">${numUnit(s[l])}</div>${bar && l === "对冲比例" ? `<div class="bar"><i style="width:${parseFloat(s[l])}%"></i></div>` : ""}</div>`).join("")}</div>`;
  2195. }
  2196. const drCard = (title, extra, body, cls = "") => `<div class="card ${cls}"><div class="sec-hd"><b>${title}</b><span class="sp"></span>${extra}</div>${body}</div>`;
  2197. overlayRenderers.order = function () {
  2198. const X = D.issueDetail;
  2199. const m = Object.fromEntries(X.master.fields.map((f) => [f[0], f[1]]));
  2200. const a = Object.fromEntries(X.subA.fields.map((f) => [f[0], f[1]]));
  2201. const tags = `<span class="tags">${tag("direction", m["订单方向"])}${tag("note_order_status", m["状态"])}${tag("process_mode", m["处理方式"])}${tag("hedge_progress", m["对冲进度"])}</span>`;
  2202. const rf = `<span class="btn txt" data-act="ovrf" style="color:var(--t2)">${ic("refresh", "rf")}${X.toolbar}</span>`;
  2203. const x = `<span class="icb" data-act="ovclose" title="关闭">${ic("x")}</span>`;
  2204. const bAuto = btn(X.master.buttons[0][0], "", "ovtoast", 'data-msg="示例:不真正提交"');
  2205. const bOrder = btn("直接下单", "", "ovtoast", 'data-msg="发行端 HedgeOrderDialog(普通单+算法单)本稿未画,按弹窗 J 的卡片分区实现"');
  2206. const bFill = btn("补录", "", "ovopen", 'data-o="fill"');
  2207. const bDone = btn("完成对冲", "pri", "ovtoast", 'data-msg="示例:不真正提交"');
  2208. const suggest = `<div class="alt alt-info">${ic("info")}<span>${esc(X.hedge.suggest)}</span></div>`;
  2209. const allTiles = ["对冲比例", "累计成交数量", "累计有效委托数量", "对冲含费成交均价", "累计发生金额(交易币种)", "累计发生金额(结算币种)", "结算汇率"];
  2210. return `<div class="dw dw-e" style="width:1040px" data-act="stop"><div class="dw-hd"><div class="idc"><div class="idr"><span class="no">${m["主订单编号"]}</span>${tags}</div>
  2211. <div class="facts"><span>证券<b>${m["证券代码/名称"]}</b></span><span>名义本金<b>${a["订单名义本金"]}</b></span><span>投资者<b>${a["投资者名称"]}</b></span><span>下单时间<b>${m["下单时间"]}</b></span></div></div><span class="sp"></span>${rf}${x}</div>
  2212. <div class="dw-bd gray" data-keep="dwbd">${drCard(X.master.title, bAuto, kv(X.master.fields, "bd3", 3))}${drCard(X.subA.title, "", kv(X.subA.fields, "bd3", 3))}
  2213. ${drCard(X.hedge.title, bOrder + bFill + bDone, `${suggest}<div class="gap"></div>${tiles(allTiles, true)}<div class="gap"></div>${hedgeTable()}`)}</div></div>`;
  2214. };
  2215. /* ---------------- 画布:挂载、缩放、交互 ---------------- */
  2216. const canvasRenderers = {}; // kind -> fn(spec)
  2217. function renderCanvas(host) {
  2218. const sp = host._spec;
  2219. if (!sp) return;
  2220. const keep = {};
  2221. $$("[data-keep]", host).forEach((el) => (keep[el.dataset.keep] = [el.scrollTop, el.scrollLeft]));
  2222. const hostKeep = [host.scrollTop, host.scrollLeft];
  2223. let inner, cls;
  2224. if (sp.kind === "shell") { inner = shellHTML(sp.st); cls = "shell"; }
  2225. else { const fn = canvasRenderers[sp.kind]; inner = fn ? fn(sp) : ""; cls = sp.kind === "kit" || sp.kind === "feedback" ? "kit" : sp.kind; }
  2226. host.innerHTML = `<div class="cv ${cls}" data-v="${T.key}" ${T.dark ? 'data-dark=""' : ""} data-end="${sp.st ? sp.st.end : sp.end || ""}">${inner}</div>`;
  2227. $$("[data-keep]", host).forEach((el) => { const k = keep[el.dataset.keep]; if (k) { el.scrollTop = k[0]; el.scrollLeft = k[1]; } });
  2228. host.scrollTop = hostKeep[0];
  2229. host.scrollLeft = hostKeep[1];
  2230. fitCanvas(host);
  2231. const ms = $("[data-ms]", host);
  2232. if (ms) { ms.focus(); ms.setSelectionRange(ms.value.length, ms.value.length); }
  2233. }
  2234. function fitCanvas(host) {
  2235. const cv = host.firstElementChild;
  2236. if (!cv) return;
  2237. const z = host._zoom ? host._zoom() : Math.min(1, host.clientWidth / 1440);
  2238. cv.style.zoom = z;
  2239. if (host._fitHeight) host.style.height = cv.classList.contains("kit") ? "" : Math.round(900 * z) + "px";
  2240. }
  2241. function mount(host, spec, opts = {}) {
  2242. const fresh = !host._bound;
  2243. host._spec = spec;
  2244. host._zoom = opts.zoom || null;
  2245. host._fitHeight = !!opts.fitHeight;
  2246. if (fresh && !opts.inert) { bindCanvas(host); host._bound = true; }
  2247. renderCanvas(host);
  2248. }
  2249. function navigate(st, key) {
  2250. if (!st.tags.includes(key)) st.tags.push(key);
  2251. st.active = key;
  2252. if (groupName(key)) st.open.add(groupName(key));
  2253. st.pop = null; st.ctx = null; st.msq = "";
  2254. }
  2255. function closeTag(st, key) {
  2256. const i = st.tags.indexOf(key);
  2257. if (i < 0 || key === "首页") return;
  2258. st.tags.splice(i, 1);
  2259. if (st.active === key) st.active = st.tags[Math.min(i, st.tags.length - 1)] || "首页";
  2260. }
  2261. function toast(host, text) {
  2262. const cv = host.firstElementChild;
  2263. if (!cv) return;
  2264. const t = document.createElement("div");
  2265. t.className = "toast";
  2266. t.style.cssText = "position:absolute;left:50%;top:72px;transform:translateX(-50%);z-index:300";
  2267. t.innerHTML = `${ic("info", "")}<span>${esc(text)}</span>`;
  2268. t.querySelector(".ic").style.color = "var(--c-primary)";
  2269. cv.appendChild(t);
  2270. setTimeout(() => t.remove(), 2200);
  2271. }
  2272. const spinRf = (el) => { const svg = el.querySelector(".rf"); if (svg) { svg.classList.remove("spin"); void svg.getBoundingClientRect(); svg.classList.add("spin"); } };
  2273. // 弹层里的动作;返回 true 表示已处理并需要重绘
  2274. function overlayAction(host, st, el, act) {
  2275. const o = el.closest("[data-ovl]");
  2276. const type = o ? o.dataset.ovl : st.ovls[st.ovls.length - 1];
  2277. const ov = OV[type] || {};
  2278. switch (act) {
  2279. case "ovclose": st.ovls.pop(); OV.fill.pop = null; return true;
  2280. case "ovopen": {
  2281. const t = el.dataset.o;
  2282. if (el.dataset.kind) OV.confirm.kind = el.dataset.kind;
  2283. if (el.dataset.dir) OV.trs.dir = el.dataset.dir;
  2284. if (!st.ovls.includes(t)) st.ovls.push(t);
  2285. return true;
  2286. }
  2287. case "ovset": ov[el.dataset.k] = el.dataset.v; return true;
  2288. case "hsys": OV.fill.pop = OV.fill.pop === "hsys" ? null : "hsys"; return true;
  2289. case "hsysset": OV.fill.hedgeSys = el.dataset.v; OV.fill.pop = null; return true;
  2290. case "chk": OV.investor.checks[+el.dataset.i] = !OV.investor.checks[+el.dataset.i]; return true;
  2291. case "ovrf": spinRf(el); return false;
  2292. case "ovtoast": toast(host, el.dataset.msg); return false;
  2293. default: return null;
  2294. }
  2295. }
  2296. const extraActions = {}; // pages.js 登记:act -> fn(host, st, el) 返回 true 则重绘
  2297. // 画面里的换肤入口(登录页月亮按钮、用户菜单「界面设置」):默认只重绘本画布;设计稿外壳会在 book.js 里覆盖成全局重绘
  2298. extraActions.theme = (host, st) => { if (st && st.pop) st.pop = null; toggleTheme(); return true; };
  2299. function bindCanvas(host) {
  2300. host.addEventListener("click", (ev) => {
  2301. const sp = host._spec;
  2302. if (!sp) return;
  2303. const el = ev.target.closest("[data-act]");
  2304. const act = el && host.contains(el) ? el.dataset.act : null;
  2305. if (sp.kind !== "shell") {
  2306. // 登录页等非外壳画布:只处理登记过的动作
  2307. if (act && extraActions[act]) { if (extraActions[act](host, sp, el)) renderCanvas(host); }
  2308. else if (act === "toast") toast(host, el.dataset.msg);
  2309. return;
  2310. }
  2311. const st = sp.st;
  2312. const inFloat = ev.target.closest(".fl, .ctx");
  2313. if (!act) {
  2314. if ((st.pop || st.ctx) && !inFloat) { st.pop = null; st.ctx = null; renderCanvas(host); }
  2315. return;
  2316. }
  2317. if (act === "stop") { if (st.pop) { st.pop = null; renderCanvas(host); } if (OV.fill.pop) { OV.fill.pop = null; renderCanvas(host); } return; }
  2318. const ovr = overlayAction(host, st, el, act);
  2319. if (ovr !== null) { if (ovr) { st.pop = null; renderCanvas(host); } return; }
  2320. if (extraActions[act]) { if (extraActions[act](host, st, el)) renderCanvas(host); return; }
  2321. switch (act) {
  2322. case "grp": if (!st.col) { st.open.has(el.dataset.g) ? st.open.delete(el.dataset.g) : st.open.add(el.dataset.g); } break;
  2323. case "leaf": navigate(st, el.dataset.k); st.ovls = []; break;
  2324. case "tag": st.active = el.dataset.k; st.ctx = null; st.pop = null; break;
  2325. case "tagx": closeTag(st, el.dataset.k); st.ctx = null; break;
  2326. case "col": st.col = !st.col; st.pop = null; break;
  2327. case "pop": { const p = el.dataset.pop; st.pop = st.pop === p ? null : p; st.ctx = null; if (p === "ms") st.msq = ""; break; }
  2328. case "sf": st.sfOpen = !st.sfOpen; break;
  2329. case "rowsel": { const no = el.dataset.no; st.sel.has(no) ? st.sel.delete(no) : st.sel.add(no); break; }
  2330. case "allsel": { const rows = ENDS[st.end].rows; const all = rows.every((r) => st.sel.has(r.no)); st.sel = all ? new Set() : new Set(rows.map((r) => r.no)); break; }
  2331. case "clearsel": st.sel = new Set(); break;
  2332. case "ptab": st.tab[el.dataset.g] = el.dataset.v; break;
  2333. case "ctx": {
  2334. const k = st.ctx && st.ctx.k;
  2335. const d = el.dataset.do;
  2336. if (d === "close") closeTag(st, k);
  2337. if (d === "others") { st.tags = st.tags.filter((t) => t === "首页" || t === k); st.active = k; }
  2338. if (d === "all") { st.tags = ["首页"]; st.active = "首页"; }
  2339. st.ctx = null;
  2340. break;
  2341. }
  2342. case "tdrf": spinRf(el); return;
  2343. case "toast": toast(host, el.dataset.msg); if (st.pop) { st.pop = null; break; } return;
  2344. case "noop": if (st.pop) { st.pop = null; break; } return;
  2345. default: return;
  2346. }
  2347. if (act !== "pop" && act !== "tag" && act !== "ctx" && act !== "tagx" && act !== "noop") st.pop = st.pop && inFloat && act !== "leaf" ? st.pop : null;
  2348. renderCanvas(host);
  2349. });
  2350. host.addEventListener("contextmenu", (ev) => {
  2351. const st = host._spec && host._spec.st;
  2352. const tab = ev.target.closest(".tvi");
  2353. if (!st || !tab || !host.contains(tab)) return;
  2354. ev.preventDefault();
  2355. st.ctx = { k: tab.dataset.k, left: tab.offsetLeft };
  2356. st.pop = null;
  2357. renderCanvas(host);
  2358. });
  2359. host.addEventListener("input", (ev) => {
  2360. const st = host._spec && host._spec.st;
  2361. if (!st || !ev.target.matches("[data-ms]")) return;
  2362. st.msq = ev.target.value;
  2363. const list = $(".ms-list", host);
  2364. if (list) list.innerHTML = msResults(st);
  2365. });
  2366. host.addEventListener("keydown", (ev) => {
  2367. const st = host._spec && host._spec.st;
  2368. if (!st || !ev.target.matches("[data-ms]")) return;
  2369. if (ev.key === "Enter") { const first = $(".ms-list [data-act=leaf]", host); if (first) { navigate(st, first.dataset.k); renderCanvas(host); } }
  2370. if (ev.key === "Escape") { ev.stopPropagation(); st.pop = null; renderCanvas(host); }
  2371. });
  2372. }
  2373. document.addEventListener("mousedown", (ev) => {
  2374. $$(".bk-host").forEach((host) => {
  2375. const st = host._spec && host._spec.st;
  2376. if (st && (st.pop || st.ctx) && !host.contains(ev.target)) { st.pop = null; st.ctx = null; renderCanvas(host); }
  2377. });
  2378. });
  2379. injectTokens();
  2380. return { D, D2, T, $, $$, esc, ic, CHECK, EMPTY, dict, tag, ENDS, leafName, groupName, newCanvasState, OV, OPT, resetOV,
  2381. pagerHTML, ctlHTML, cellHTML, cellAuto, tableHTML, btn, listHTML, skelHTML, pageRenderers, overlayRenderers, canvasRenderers, extraActions,
  2382. kv, kvVal, item, ctl, lblW, numUnit, dgWrap, dgCard, sfDialog, stepper, closeX, btnCancel, drCard, tiles, errHTML,
  2383. renderCanvas, fitCanvas, mount, navigate, toast, shellHTML, setTheme, toggleTheme, THEME_PICK };
  2384. })();
  2385. </script>
  2386. <script>
  2387. /* 完整设计稿新增的页面与弹层:首页、页内标签页(风险监控)、清算、TRS 交易下单页与下单弹窗、票据详情抽屉、
  2388. * 确认框、修改密码弹窗、个人中心、404/401、登录页、组件总览、反馈类总览。登记到 UI.pageRenderers / overlayRenderers / canvasRenderers。
  2389. */
  2390. (function () {
  2391. "use strict";
  2392. const U = window.UI;
  2393. const { D, D2: P, T, esc, ic, CHECK, EMPTY, tag, ENDS, kv, tableHTML, btn, item, dgWrap, dgCard, sfDialog, stepper, closeX, btnCancel, drCard } = U;
  2394. /* ================= 首页 =================
  2395. * 现状版(默认):按 2026-10-08 的现状内容只画一张问候卡(头像、问候语、天气一句),卡片之下留空。
  2396. * 工作台版:本稿首版的首页设计整套保留(st.variant === "work",设计稿上方的变体按钮切),等首页汇总接口完善后切回去。
  2397. */
  2398. function homeNowHTML() {
  2399. const N = P.homeNow;
  2400. return `<div class="hm-now"><div class="card hm-hello"><span class="ava big">A</span><div class="hm-hello-t"><b>${esc(N.greeting)}</b><p>${esc(N.weather)}</p></div></div></div>`;
  2401. }
  2402. function homeWorkHTML(st) {
  2403. const e = ENDS[st.end];
  2404. const H = P.home[st.end];
  2405. const total = H.status.reduce((n, s) => n + s[1], 0);
  2406. const hot = H.status.filter((s) => s[2]).reduce((n, s) => n + s[1], 0);
  2407. const tiles = H.status.map(([n, c, h]) => `<div class="hm-tile ${h ? "hot" : ""} ${c === 0 ? "zero" : ""}" data-act="leaf" data-k="${H.listKey}" title="打开${H.noun}列表"><span class="l">${n}</span><b class="num">${c}</b></div>`).join("");
  2408. const statusCard = `<div class="card hm-st"><div class="card-hd">上午好,admin<span class="sub">今日${H.noun}共 <b class="num">${total}</b> 笔,其中 <b class="num hot">${hot}</b> 笔需要处理(高亮的状态)</span><span class="sp"></span><span class="btn txt" data-act="leaf" data-k="${H.listKey}">查看全部${ic("right")}</span></div><div class="hm-tiles">${tiles}</div></div>`;
  2409. const sc = H.shortcuts.map(([g, l, icn]) => `<div class="hm-sc" data-act="leaf" data-k="${esc(g + "/" + l)}">${ic(icn)}<b>${esc(l)}</b><small>${esc(g)}</small></div>`).join("");
  2410. const scCard = `<div class="card hm-sc-card"><div class="card-hd">快捷入口<span class="sub">取自本系统菜单</span></div><div class="hm-scs">${sc}</div></div>`;
  2411. const rows = e.rows.filter(H.pick).slice(0, 6);
  2412. const cols = H.manualCols.map((c) => [c[0], c[1], c[4] === "r" ? "r" : ""]).concat([["操作", 72]]);
  2413. const cell = (r, i) => {
  2414. const def = H.manualCols[i];
  2415. if (!def) return `<span class="btn txt" data-act="${st.end === "issue" ? "ovopen" : "leaf"}" data-o="order" data-k="${H.listKey}">详情</span>`;
  2416. const key = def[2];
  2417. const v = key === "stock" && st.end === "trs" ? `${r.code} ${r.sname}` : r[key];
  2418. const kind = def[3] || "";
  2419. if (kind.startsWith("tag:")) return v ? tag(kind.slice(4), v) : "--";
  2420. return v;
  2421. };
  2422. const tblCard = `<div class="card tb-card hm-tb"><div class="tb-bar"><span class="ttl">${H.manualTitle}</span><span class="t3" style="font-size:12px">${H.manualSub};按下单时间倒序,最多 6 条</span><span class="sp"></span><span class="btn txt" data-act="leaf" data-k="${H.listKey}">查看全部${ic("right")}</span></div>${tableHTML(cols, rows, { cell, keep: "hm-tbl", empty: "没有需要处理的" + H.noun })}</div>`;
  2423. return `<div class="hm">${statusCard}${scCard}${tblCard}</div>`;
  2424. }
  2425. U.pageRenderers.__home = (st) => (st.variant === "work" ? homeWorkHTML(st) : homeNowHTML(st));
  2426. /* ================= 页内标签页 ================= */
  2427. // 页面级(ptabs)或卡片内(tabs)的页签条;选中项记在 st.tab[g]
  2428. const ptabs = (st, g, tabs, cls = "ptabs") => {
  2429. const cur = st.tab[g] || tabs[0][0];
  2430. return `<div class="${cls}">${tabs.map(([k, l]) => `<span class="tab ${k === cur ? "on" : ""}" data-act="ptab" data-g="${g}" data-v="${k}">${l}</span>`).join("")}</div>`;
  2431. };
  2432. const searchCard = (fields, actions, withLabel) => {
  2433. const lw = withLabel ? Math.max(...fields.map((f) => f.label.length)) * 14 : 0;
  2434. const rem = 3 - (fields.length % 3);
  2435. const acts = `<div class="sf-act" style="grid-column:span ${rem}">${actions.map(([t, type, icn]) => `<span class="btn ${type === "primary" ? "pri" : ""}" data-act="noop">${ic(icn)}${t}</span>`).join("")}</div>`;
  2436. return `<div class="card"><div class="sf">${fields.map((f) => `<div class="sf-i">${withLabel ? `<label style="width:${lw}px">${esc(f.label)}</label>` : ""}${U.ctlHTML(f)}</div>`).join("")}${acts}</div></div>`;
  2437. };
  2438. // 风险监控:页面级页签,三个页签各是一套搜索 + 表格
  2439. U.pageRenderers["风控管理/共赢票据风险监控"] = function (st) {
  2440. const R = P.risk;
  2441. const cur = st.tab.risk || "monitor";
  2442. let body;
  2443. if (cur === "monitor") {
  2444. const cell = (r, i) => {
  2445. if (i === 0) {
  2446. const danger = r[2] === "危险" || r[2] === "强平";
  2447. return `<span class="btns" style="gap:12px"><span class="btn txt" data-act="ovopen" data-o="note">详情</span>${danger ? `<span class="btn txt dng" data-act="ovopen" data-o="confirm" data-kind="force">强平卖出</span>` : ""}</span>`;
  2448. }
  2449. const v = r[i - 1];
  2450. if (i === 2) return [v, "note_status"];
  2451. if (i === 3) return [v, "note_risk_status"];
  2452. if (i === 11) return v.startsWith("+") ? [v, "up"] : v.startsWith("-") ? [v, "dn"] : v;
  2453. return v;
  2454. };
  2455. body = `${searchCard(R.search, R.actions, false)}<div class="card tb-card">${tableHTML(R.cols, R.rows, { cell, keep: "risk-tbl" })}${U.pagerHTML(R.rows.length)}</div>`;
  2456. } else if (cur === "status-log") {
  2457. const cell = (r, i) => (i === 4 || i === 5 ? [r[i], "note_risk_status"] : r[i]);
  2458. body = `${searchCard(R.search.slice(2, 8), R.actions.slice(0, 2), false)}<div class="card tb-card">${tableHTML(R.statusLogCols, R.statusLog, { cell, keep: "risk-log" })}${U.pagerHTML(R.statusLog.length)}</div>`;
  2459. } else {
  2460. const cell = (r, i) => (i === 4 ? [r[i], "send_status"] : r[i]);
  2461. body = `${searchCard(R.search.slice(2, 5), R.actions.slice(0, 2), false)}<div class="card tb-card">${tableHTML(R.notifyCols, R.notify, { cell, keep: "risk-ntf" })}${U.pagerHTML(R.notify.length)}</div>`;
  2462. }
  2463. return `<div class="pg-list">${ptabs(st, "risk", R.tabs)}${body}</div>`;
  2464. };
  2465. // 销售端同名菜单复用
  2466. U.pageRenderers["风控管理/共赢票据风险监控"].sales = true;
  2467. // 清算管理:页签在卡片标题栏里
  2468. U.pageRenderers["清算管理/共赢票据清算"] = function (st) {
  2469. const C = P.clearing;
  2470. const cur = st.tab.clr || "clearing";
  2471. const running = C.batchStatus === "执行中";
  2472. const tools = cur === "clearing"
  2473. ? `${btn(ic("refresh") + "刷新")}${btn("一键清算", running ? "pri is-dis" : "pri")}${btn("重新清算", "", "ovopen", 'data-o="confirm" data-kind="redo"')}`
  2474. : `${btn(ic("refresh") + "刷新")}${running ? btn(ic("stop") + "停止当前任务", "dng", "ovopen", 'data-o="confirm" data-kind="stop"') : ""}`;
  2475. const info = `<div class="clr-info"><span class="chip">市场<b>${C.market}</b></span><span class="chip">本次清算交易日<b>${C.tradeDate}</b></span><span class="chip">批次<b>${C.batchNo}</b></span><span class="chip">状态${tag("clearing_batch", C.batchStatus)}</span>${running ? `<span class="t3" style="font-size:12px;margin-left:4px">执行中不能再次发起;页面每 5 秒自动刷新</span>` : ""}</div>`;
  2476. let body;
  2477. if (cur === "clearing") {
  2478. const cell = (r, i) => (i === 2 ? [r[i], "clearing_step"] : r[i]);
  2479. const rowCls = (r) => (r[2] === "执行中" ? "is-sel" : "");
  2480. body = `${info}${C.blockedReason ? `<div class="alt alt-info" style="margin-bottom:12px">${ic("info")}<span>${esc(C.blockedReason)}</span></div>` : ""}${tableHTML(C.stepCols, C.steps, { cell, rowCls, keep: "clr-steps", cls: "bd" })}${C.errorDetail ? `<div class="alt alt-danger" style="margin-top:12px">${ic("error")}<span>${esc(C.errorDetail)}</span></div>` : ""}`;
  2481. } else {
  2482. const cell = (r, i) => (i === 3 ? [r[i], "clearing_step"] : r[i]);
  2483. const hcell = (r, i) => (i === 3 ? [r[i], "clearing_batch"] : r[i]);
  2484. body = `${info}${tableHTML(C.logCols, C.logs, { cell, keep: "clr-logs", cls: "bd" })}<div class="sec-hd" style="margin:20px 0 10px"><b>${C.historyTitle}</b><small>${C.history.length} 条</small></div>${tableHTML(C.historyCols, C.history, { cell: hcell, keep: "clr-his", cls: "bd" })}`;
  2485. }
  2486. return `<div class="card clr"><div class="ctabs">${ptabs(st, "clr", C.tabs, "tabs")}<span class="sp"></span>${tools}</div><div class="card-bd" data-keep="clr-bd">${body}</div></div>`;
  2487. };
  2488. /* ================= TRS · 交易下单页 + 下单弹窗 ================= */
  2489. U.pageRenderers["系统设置/交易下单"] = function (st) {
  2490. const H = D.trsTrade.host;
  2491. const cur = st.tab.trs || "pos";
  2492. const acct = `<div class="card"><div class="trs-acct"><div class="sf-i" style="width:340px"><label>账户</label><div class="ipt"><span class="val">${H.account} · ${H.user}</span>${ic("down")}</div></div><span class="sp"></span>${btn(ic("refresh") + "刷新")}${btn("买入", "pri", "ovopen", 'data-o="trs" data-dir="买入"')}${btn("卖出", "", "ovopen", 'data-o="trs" data-dir="卖出"')}</div>
  2493. <div class="card-bd" style="padding-top:0">${kv(H.accountCols.map((c, i) => [c, H.accountRows[0][i], "num"]), "bd3", 3)}</div></div>`;
  2494. const tabs = [["pos", `持仓<span class="cnt">${H.posRows.length}</span>`], ["ord", `当日委托<span class="cnt">${H.orderRows.length}</span>`]];
  2495. const tbl = cur === "pos"
  2496. ? tableHTML(H.posCols, H.posRows, { cell: (r, i) => (i === 2 ? [r[i], "trade_channel"] : i === 10 || i === 11 ? [r[i], r[i].startsWith("+") ? "up" : "dn"] : r[i]), keep: "trs-pos" })
  2497. : tableHTML([["操作", 72]].concat(H.orderCols), H.orderRows, { cell: (r, i) => (i === 0 ? (["已报", "部成"].includes(r[10]) ? `<span class="btn txt dng" data-act="ovopen" data-o="confirm" data-kind="cancel">撤单</span>` : "--") : i === 4 ? [r[i - 1], "trade_channel"] : i === 6 ? [r[i - 1], "direction"] : i === 11 ? [r[i - 1], "trs_order_status"] : r[i - 1]), keep: "trs-ord" });
  2498. return `<div class="pg-list">${acct}<div class="card tb-card"><div class="ctabs">${ptabs(st, "trs", tabs, "tabs")}<span class="sp"></span>${btn(ic("refresh") + "刷新", "txt")}</div>${tbl}</div></div>`;
  2499. };
  2500. U.overlayRenderers.trs = function (ov) {
  2501. const G = D.trsTrade.dialog;
  2502. const H = D.trsTrade.host;
  2503. const buy = ov.dir === "买入";
  2504. const seg = (k, opts) => `<div class="segc">${opts.map((o) => `<span class="${o === ov[k] ? "on" : ""}" data-act="ovset" data-k="${k}" data-v="${o}">${o}</span>`).join("")}</div>`;
  2505. const chips = `<div class="qchips">${G.quick.map(([l, v]) => `<span class="qchip ${v === ov.price ? "on" : ""}" data-act="ovset" data-k="price" data-v="${v}">${l}<b class="num">${v}</b></span>`).join("")}</div>`;
  2506. const QTY = buy ? { "1/4": "25,200", "1/3": "33,600", "1/2": "50,400", "全部可买": "100,900" } : { "1/4": "3,000", "1/3": "4,000", "1/2": "6,000", "全部可卖": "12,000" };
  2507. const qtyChips = `<div class="qchips">${Object.entries(QTY).map(([l, v]) => `<span class="qchip ${v === ov.qty ? "on" : ""}" data-act="ovset" data-k="qty" data-v="${v}">${l}</span>`).join("")}</div>`;
  2508. const amount = parseFloat(ov.price) * parseFloat(ov.qty.replace(/,/g, ""));
  2509. const fmt = (n) => n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  2510. const tiles = buy ? G.strip : [G.strip[0], ["可卖股数", H.posRows[0][4], "股"]];
  2511. const rows = [
  2512. { label: "交易渠道", req: true, body: seg("channel", G.channels) },
  2513. { label: "股票代码", req: true, body: `<div class="ipt"><span class="val num" style="flex:none">${G.stock.code}</span><span class="val" style="flex:none">${G.stock.name}</span><span class="chipv">${G.stock.mkt}</span><span class="sp"></span>${ic("down")}</div>` },
  2514. { label: "委托价格", req: true, body: stepper(ov.price, G.priceCcy), extra: chips },
  2515. { label: "委托数量", req: true, body: stepper(ov.qty, "股"), extra: qtyChips + `<div class="fld-tip">${buy ? G.qtyHelp : "卖出不超过可卖股数"}</div>` },
  2516. ];
  2517. const results = [[`${ov.dir}金额(交易币种)`, fmt(amount), "CNY"], [`${ov.dir}金额(结算币种)`, fmt(amount * 1.0944), "HKD"]];
  2518. const Q = G.quote;
  2519. const prev = parseFloat(Q.prev);
  2520. const maxVol = Math.max(...[...Q.asks, ...Q.bids].map((r) => r[2]));
  2521. const lvl = (rowsQ, cls) => rowsQ.map(([l, pr, v]) => `<div class="ql ${cls} ${pr === ov.price ? "on" : ""}" data-act="ovset" data-k="price" data-v="${pr}"><span>${l}</span><b class="num ${parseFloat(pr) > prev ? "buy" : parseFloat(pr) < prev ? "sell" : ""}">${pr}</b><span class="vol"><em style="width:${Math.round((v / maxVol) * 100)}%"></em><i class="num">${v}</i></span></div>`).join("");
  2522. const aside = `<aside class="qpanel"><div class="qname"><b>${G.stock.name}</b><span class="code num">${G.stock.code}.${G.stock.mkt}</span><span class="live">${Q.source}</span></div>
  2523. <div class="qprice"><b class="num last buy">${Q.last}</b><span class="num chg buy">↑${Q.chg} ${Q.pct}</span></div>
  2524. <div class="qsub">昨收 <b class="num">${Q.prev}</b> · ${Q.hint}</div>
  2525. ${lvl(Q.asks, "ask")}<div class="qmid"><b class="num buy">${Q.last} ↑</b><span class="num buy">${Q.chg} ${Q.pct}</span></div>${lvl(Q.bids, "bid")}</aside>`;
  2526. const okBtn = `<span class="btn ${buy ? "dng-pri" : "ok-pri"}" data-act="ovtoast" data-msg="示例:不真正提交">确认${ov.dir} ${ov.qty} 股</span>`;
  2527. return sfDialog({ w: 880, lw: 72, cls: "dg-trs", keep: "trs-dg", title: ov.dir, sub: `${G.account} · ${G.user}`, tiles, rows, results, aside,
  2528. ft: `<span class="hint">${ic("info")}${G.footHint}</span><span class="sp"></span>${btnCancel}${okBtn}` });
  2529. };
  2530. /* ================= 票据详情抽屉(页签 + 描述表) ================= */
  2531. U.overlayRenderers.note = function (ov) {
  2532. const N = P.noteDetail;
  2533. const tags = `<span class="tags">${N.tags.map(([v, dk]) => tag(dk, v)).join("")}</span>`;
  2534. const rf = `<span class="btn txt" data-act="ovrf" style="color:var(--t2)">${ic("refresh", "rf")}刷新数据</span>`;
  2535. const x = `<span class="icb" data-act="ovclose" title="关闭">${ic("x")}</span>`;
  2536. const hb = N.buttons.map(([t, ty]) => btn(t, ty, "ovtoast", 'data-msg="示例:各操作弹窗按弹窗 J 的卡片分区实现"')).join("");
  2537. const tabs = `<div class="dw-tabs">${N.tabs.map(([k, l]) => `<span class="tab ${ov.tab === k ? "on" : ""}" data-act="ovset" data-k="tab" data-v="${k}">${l}</span>`).join("")}</div>`;
  2538. const subHd = (t, extra = "") => `<div class="sec-hd sub"><b>${t}</b><span class="sp"></span>${extra}</div>`;
  2539. let body;
  2540. if (ov.tab === "main") {
  2541. const sub = N.subA;
  2542. const H = N.hedge;
  2543. const posCell = (r, i) => (i === 0 ? (r[0] === "合计" ? `<b>合计</b>` : [r[0], "hedge_system"]) : r[i]);
  2544. const trCell = (r, i) => (i === 0 ? [r[i], "hedge_system"] : i === 6 ? `<span class="tg tg-${r[i][1]}">${r[i][0]}</span>` : i === 7 ? [r[i], "direction"] : r[i]);
  2545. const dlyCols = [["操作", 100]].concat(H.tradeCols);
  2546. const dlyCell = (r, i) => {
  2547. if (i === 0) return r[0].length ? `<span class="btns" style="gap:12px">${r[0].map(([t, ty]) => `<span class="btn txt ${ty === "danger" ? "dng" : ""}" data-act="ovopen" data-o="confirm" data-kind="${t === "删除" ? "remove" : t === "撤单" ? "cancel" : "cancel"}">${t}</span>`).join("")}</span>` : "--";
  2548. return trCell(r.slice(1), i - 1);
  2549. };
  2550. const dailyBtns = H.dailyButtons.map(([t, ty]) => btn(t === "刷新" ? ic("refresh") + t : t, ty === "txt" ? "txt" : "sm", t === "补录" ? "ovopen" : "ovtoast", t === "补录" ? 'data-o="fill"' : 'data-msg="示例:日常买卖的下单弹窗按「TRS 下单弹窗」的专用布局实现"')).join("");
  2551. body = drCard(N.master.title, "", kv(N.master.fields, "bd3", 3)) +
  2552. drCard(sub.title, "", kv(sub.fields, "bd3", 3) + subHd(sub.init.title) + kv(sub.init.fields, "bd2", 2) + subHd(sub.current.title) + kv(sub.current.fields, "bd2", 2)) +
  2553. drCard(H.title, `<span class="chip">${H.ratioLabel}<b>${H.ratio}</b></span>`,
  2554. subHd(H.posTitle) + tableHTML(H.posCols, H.posRows, { cell: posCell, keep: "nt-pos" }) +
  2555. subHd(H.tradeTitle) + tableHTML(H.tradeCols, H.tradeRows, { cell: trCell, keep: "nt-tr" }) +
  2556. subHd(H.dailyTitle, dailyBtns) + tableHTML(dlyCols, H.dailyRows, { cell: dlyCell, keep: "nt-dly" }));
  2557. } else {
  2558. body = N.changes.sections.map((s) => drCard(s.title, `<small>${s.rows.length} 条</small>`, tableHTML(s.cols, s.rows, { empty: s.empty, emptyH: 120, keep: "nt-" + s.title }))).join("");
  2559. }
  2560. return `<div class="dw dw-e dw-note" style="width:1040px" data-act="stop"><div class="dw-hd"><div class="idc"><div class="idr"><span class="no">${N.noteNo}</span>${tags}</div><div class="facts">${N.facts.map(([l, v]) => `<span>${l}<b>${v}</b></span>`).join("")}</div></div><span class="sp"></span><div class="hbtns col"><div class="r">${rf}${x}</div><div class="r">${hb}</div></div></div>${tabs}<div class="dw-bd gray" data-keep="dwbd-${ov.tab}">${body}</div></div>`;
  2561. };
  2562. /* ================= 确认框、修改密码弹窗 ================= */
  2563. const CONFIRMS = Object.assign({}, P.feedback.confirms, {
  2564. force: { title: "强平卖出", msg: "确定对该票据执行强平卖出吗?卖出后票据状态变为「已卖出」,不能恢复。", kind: "danger", ok: "确定强平", kv: [["票据编号", "NT001202608141A"], ["投资者", "陈思雨 · 安衡证券"], ["证券", "601318 中国平安 · 28,700 股"]] },
  2565. redo: { title: "重新清算", msg: "将回拨到该批次的交易日并重新执行全部步骤,期间不能下单。确定重新清算吗?", kind: "warning", ok: "重新清算", kv: [["交易日", "2026-09-25"], ["批次", "CLR20260925001"]] },
  2566. stop: { title: "停止当前任务", msg: "停止后本批次标记为「已停止」,已完成的步骤不会回滚。确定停止吗?", kind: "danger", ok: "停止", kv: [["批次", "CLR20260925001"], ["当前步骤", "4 · 交易交收处理"]] },
  2567. });
  2568. U.overlayRenderers.confirm = function (ov) {
  2569. const c = CONFIRMS[ov.kind] || CONFIRMS.cancel;
  2570. const icn = c.kind === "danger" ? "error" : c.kind === "warning" ? "warn" : "info";
  2571. const okCls = c.kind === "danger" ? "dng-pri" : "pri";
  2572. return `<div class="cfm" data-act="stop"><div class="cfm-bd"><div class="cfm-hd"><span class="cfm-ic ${c.kind}">${ic(icn)}</span><b>${esc(c.title)}</b></div><p>${esc(c.msg)}</p>${c.kv.length ? `<dl class="cfm-kv">${c.kv.map(([l, v]) => `<div><dt>${l}</dt><dd class="num">${esc(v)}</dd></div>`).join("")}</dl>` : ""}</div><div class="cfm-ft"><span class="btn" data-act="ovclose">${c.cancel || "取消"}</span><span class="btn ${okCls}" data-act="ovclose">${c.ok}</span></div></div>`;
  2573. };
  2574. U.overlayRenderers.pwd = function () {
  2575. const rows = P.profile.pwdForm.map((f, i) => ({ label: f.label, req: f.req, body: U.ctl({ ...f, ph: i === 1 ? "请输入新密码" : f.ph }, {}), extra: i === 1 ? `<div class="fld-tip">6~20 位,至少两类字符</div>` : "" }));
  2576. return sfDialog({ w: 480, lw: 72, title: "修改密码", sub: `${P.profile.username} · ${P.profile.role}`, rows, ft: `<span class="hint">${ic("info")}修改后需要重新登录</span><span class="sp"></span>${btnCancel}<span class="btn pri" data-act="ovtoast" data-msg="示例:不真正提交">确定</span>` });
  2577. };
  2578. /* ================= 个人中心 ================= */
  2579. U.pageRenderers["个人中心"] = function () {
  2580. const p = P.profile;
  2581. const left = `<div class="card prof-id"><div class="prof-ava" data-act="toast" data-msg="上传头像(代码里是隐藏的文件选择)"><span class="ava big">A</span><span class="prof-cam">${ic("camera")}</span></div><div class="prof-nm"><b>${p.nickname}</b><span class="icb sm" data-act="toast" data-msg="修改昵称、性别的弹窗按弹窗 J 实现" title="修改资料">${ic("edit")}</span></div><div class="prof-role">${p.role}</div></div>`;
  2582. const bind = (v, what) => (v ? `<span class="num">${v}</span>` : `<span class="t4">未绑定</span><span class="btn txt" data-act="toast" data-msg="绑定${what}的弹窗按弹窗 J 实现" style="margin-left:12px">绑定</span>`);
  2583. const info = `<div class="card"><div class="card-hd">账号信息</div><div class="card-bd"><dl class="kvg bd1"><div><dt>用户名</dt><dd>${p.username}${ic("male", "gender")}</dd></div><div><dt>手机号码</dt><dd>${bind(p.mobile, "手机")}</dd></div><div><dt>邮箱</dt><dd>${bind(p.email, "邮箱")}</dd></div><div><dt>部门</dt><dd>${p.dept}<span class="t3" style="font-size:12px;margin-left:8px">示例</span></dd></div><div><dt>创建时间</dt><dd class="num">${p.createTime}</dd></div></dl></div></div>`;
  2584. const sec = `<div class="card"><div class="card-hd">安全设置</div><div class="prof-sec">${p.security.map(([t, d, a], i) => `<div class="prof-row"><div><b>${t}</b><small>${d}</small></div><span class="btn txt" data-act="${i === 0 ? "ovopen" : "toast"}" data-o="pwd" data-msg="绑定弹窗按弹窗 J 实现">${a}</span></div>`).join("")}</div></div>`;
  2585. return `<div class="prof">${left}<div class="prof-r">${info}${sec}</div></div>`;
  2586. };
  2587. /* ================= 404 / 401 ================= */
  2588. function errHTML(code, st) {
  2589. const is404 = code === "404";
  2590. const art = `<svg class="err-art" viewBox="0 0 420 160"><text x="210" y="126" text-anchor="middle" font-size="128" font-weight="600" style="font-family:var(--font-num);fill:var(--c-primary-bg);stroke:var(--c-primary);stroke-width:2">${code}</text></svg>`;
  2591. const home = ENDS[st.end].home;
  2592. return `<div class="errp">${art}<div class="err-t"><b>${is404 ? "页面不存在" : "没有访问权限"}</b><p>${is404 ? "请确认输入的网址是否正确,或从左侧菜单重新进入。" : "当前账号没有「共赢票据清算」的访问权限,请联系系统管理员开通后再试。"}</p><div class="btns" style="justify-content:center">${btn("返回首页", "pri", "leaf", 'data-k="首页"')}${btn("返回上一页", "", "leaf", `data-k="${home}"`)}</div></div></div>`;
  2593. }
  2594. U.pageRenderers["404"] = (st) => errHTML("404", st);
  2595. U.pageRenderers["401"] = (st) => errHTML("401", st);
  2596. /* ================= 登录页:分项选型登录页 C「右侧浮卡·全幅底图」(2026-10-07 选定) =================
  2597. * 整页是视觉 F 的渐变底色叠一层淡线条装饰;左侧一列只有系统名和版本号(10-08 去掉了业务模块和三端入口);右侧 440px 浮卡放表单。
  2598. * 画布规格 sp 上记临时状态:cap(验证码种子)、showPw、remember。
  2599. */
  2600. const CAPCH = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  2601. // 验证码图片示意:按种子生成 4 个字符和两条干扰线,点一下换一张
  2602. function capSVG(seed, w = 120, h = 40) {
  2603. let s = (seed * 7919 + 104729) % 233280;
  2604. const rnd = () => (s = (s * 9301 + 49297) % 233280) / 233280;
  2605. const chars = Array.from({ length: 4 }, () => CAPCH[Math.floor(rnd() * CAPCH.length)]);
  2606. const fs = Math.round(h * 0.58);
  2607. const txt = chars.map((ch, i) => { const x = Math.round(w * (0.13 + i * 0.22)), y = Math.round(h * 0.7), r = Math.round((rnd() - 0.5) * 28); return `<text x="${x}" y="${y}" font-size="${fs}" font-weight="600" transform="rotate(${r} ${x} ${y})" style="fill:var(${i % 2 ? "--c-primary" : "--c-link"})">${ch}</text>`; }).join("");
  2608. const wave = (c) => `<path d="M0 ${(h * (0.3 + rnd() * 0.4)).toFixed(1)} C ${(w * 0.25).toFixed(0)} ${(h * rnd()).toFixed(1)}, ${(w * 0.5).toFixed(0)} ${(h * rnd()).toFixed(1)}, ${(w * 0.75).toFixed(0)} ${(h * rnd()).toFixed(1)} S ${w} ${(h * rnd()).toFixed(1)}, ${w} ${(h * (0.3 + rnd() * 0.4)).toFixed(1)}" style="stroke:var(${c});fill:none;stroke-width:1.2"/>`;
  2609. return `<svg viewBox="0 0 ${w} ${h}" aria-label="验证码 ${chars.join("")}"><rect width="${w}" height="${h}" style="fill:var(--fill-2)"/>${wave("--c-primary-border")}${wave("--bd-ctl")}${txt}</svg>`;
  2610. }
  2611. // 底图:淡网格、几条主色曲线、右上角两个圆(内联 SVG,不用图片文件)
  2612. const LG_DECO = `<svg class="lg-deco" viewBox="0 0 1440 900" preserveAspectRatio="none"><defs><pattern id="lgGrid" width="48" height="48" patternUnits="userSpaceOnUse"><path d="M48 0H0v48" fill="none" stroke="currentColor" stroke-width="1"/></pattern></defs><rect width="1440" height="900" fill="url(#lgGrid)" opacity=".05"/>
  2613. <path d="M-40 640C260 560 420 760 760 660S1200 540 1480 620" opacity=".45"/><path d="M-40 700C300 640 480 820 820 700S1260 600 1480 700" opacity=".3"/><path d="M-40 560C200 500 380 640 700 580S1160 440 1480 520" opacity=".2"/><circle cx="1180" cy="150" r="220" opacity=".22"/><circle cx="1180" cy="150" r="300" opacity=".12"/></svg>`;
  2614. U.canvasRenderers.login = function (sp) {
  2615. const end = sp.end || "issue";
  2616. const e = ENDS[end];
  2617. const L = P.login;
  2618. const c = L.copy;
  2619. const v = sp.v || "login";
  2620. // 左侧一列只放系统名和版本号(2026-10-08 去掉了「本系统业务模块」和「三端系统」入口)
  2621. const left = `<div class="lg-c-left"><div><b class="lg-c-sys">${esc(e.sys)}</b><div class="lg-c-ver">${L.version}</div></div></div>`;
  2622. const field = (label, inner) => `<div class="lg-fi"><label>${label}</label>${inner}</div>`;
  2623. const pw = sp.showPw ? "123456" : "••••••";
  2624. let form;
  2625. if (v === "login") {
  2626. form = `<div class="lg-ttl">登录</div>
  2627. ${field(c.username, `<div class="ipt lg-ipt"><span class="val">admin</span></div>`)}
  2628. ${field(c.password, `<div class="ipt lg-ipt"><span class="val num">${pw}</span><span class="lg-eye" data-act="eye" title="${sp.showPw ? "隐藏密码" : "显示密码"}">${ic(sp.showPw ? "eye" : "eyeoff")}</span></div>`)}
  2629. ${field(c.captcha, `<div class="lg-cap-row"><div class="ipt lg-ipt" style="flex:1"><span class="ph">${c.captcha}</span></div><div class="lg-cap" title="点击换一张" data-act="cap">${capSVG((sp.cap || 0) + 3)}</div></div>`)}
  2630. <div class="lg-row"><span class="chk-l" data-act="rem"><span class="ck ${sp.remember === false ? "" : "on"}">${CHECK}</span>${c.remember}</span><span class="lnk" data-act="lv" data-v="reset">${c.forget}</span></div>
  2631. <span class="btn pri lg-btn" data-act="toast" data-msg="示例:不真正登录">${c.login}</span>`;
  2632. } else {
  2633. form = `<div class="lg-ttl">${c.reset}</div><p class="lg-p">输入账号绑定的邮箱,重置链接会发到这个邮箱。</p>
  2634. ${field(c.email, `<div class="ipt lg-ipt"><span class="ph">${c.enterEmail}</span></div>`)}
  2635. <span class="btn pri lg-btn" data-act="toast" data-msg="示例:验证码已发送到您的邮箱">${c.sendReset}</span>
  2636. <div class="lg-row center"><span class="t3">${c.think}</span><span class="lnk" data-act="lv" data-v="login">${c.back}</span></div>`;
  2637. }
  2638. return `<div class="lg">${LG_DECO}<div class="lg-actions"><span class="icb" data-act="theme" title="${c.theme}:在浅色和最近看过的深色皮肤之间切换">${ic(T.dark ? "sun" : "moon")}</span><span class="icb" data-act="toast" data-msg="示例:${c.lang}" title="${c.lang}">${ic("globe")}</span></div>
  2639. <div class="lg-stage">${left}<div class="lg-card"><div class="lg-brand sm"><span class="lg-logo sm">${e.logo}</span><b>${esc(e.sys)}</b></div>${form}</div></div></div>`;
  2640. };
  2641. U.extraActions.cap = (host, sp) => { sp.cap = (sp.cap || 0) + 1; return true; };
  2642. U.extraActions.eye = (host, sp) => { sp.showPw = !sp.showPw; return true; };
  2643. U.extraActions.rem = (host, sp) => { sp.remember = sp.remember === false; return true; };
  2644. U.extraActions.lv = (host, sp, el) => { sp.v = el.dataset.v; return true; };
  2645. /* ================= 组件总览 ================= */
  2646. U.canvasRenderers.kit = function () {
  2647. const Tk = T.tokens;
  2648. const dict = U.dict;
  2649. const sw = (label, key) => { const val = Tk[key]; const none = val === "transparent"; return `<div class="swc"><i style="background:${none ? "repeating-linear-gradient(45deg,var(--bg-card) 0 4px,var(--fill-3) 4px 8px)" : val}"></i><b>${label}</b><span>${none ? "无" : esc(val)}</span></div>`; };
  2650. const group = (items) => `<div class="sw-group">${items.map(([l, key]) => sw(l, key)).join("")}</div>`;
  2651. const colors = `<div class="card"><div class="card-hd">颜色<span class="sub">主色 ${Tk["c-primary"]} 只做填充,文字链接 ${Tk["c-link"]};买入红、卖出绿语义不变</span></div><div class="card-bd">${T.palette.map(([, items]) => group(items)).join("")}</div></div>`;
  2652. const ty = (spec, html) => `<div class="ty-row"><span class="spec">${spec}</span><div>${html}</div></div>`;
  2653. const type = `<div class="card s6"><div class="card-hd">文字<span class="sub">中文系统字体,英文和数字 Inter</span></div><div class="card-bd" style="padding-top:8px;padding-bottom:8px">
  2654. ${ty("20 / 28 · 编号标题", `<span class="num" style="font-size:20px;line-height:28px;font-weight:var(--fw-title);color:var(--t1)">ORD001202609250031</span>`)}
  2655. ${ty("16 / 24 · 弹窗标题", `<span style="font-size:16px;line-height:24px;font-weight:var(--fw-title);color:var(--t1)">撤销订单</span>`)}
  2656. ${ty("15 / 22 · 区块标题", `<span style="font-size:15px;font-weight:var(--fw-title);color:var(--t1)">订单信息</span>`)}
  2657. ${ty("14 / 22 · 正文", `<span style="color:var(--t1)">订单已进入对冲流程,预计交收日 <span class="num">2026-09-29</span>。</span>`)}
  2658. ${ty("14 / 22 · 次要", `<span style="color:var(--t2)">投资者名称、机构名称等次要信息</span>`)}
  2659. ${ty("12 / 18 · 辅助", `<span style="font-size:12px;line-height:18px;color:var(--t3)">表头、键名、输入框下方的提示</span>`)}
  2660. ${ty("数字", `<span class="num" style="color:var(--t1);font-size:16px">3,000,000.00 1,688.00 0.0003 2026-09-25 09:31:07</span>`)}
  2661. </div></div>`;
  2662. const buttons = `<div class="card s6"><div class="card-hd">按钮<span class="sub">${T.notes.buttons}</span></div><div class="card-bd">
  2663. <div class="kit-row"><span class="kit-lbl">常规</span><span class="btn pri">${ic("search")}查找</span><span class="btn">${ic("refresh")}重置</span><span class="btn">${ic("download")}数据导出</span><span class="btn dng">撤单</span><span class="btn is-dis">禁用</span></div>
  2664. <div class="kit-row"><span class="kit-lbl">悬停</span><span class="btn pri is-hover">${ic("plus")}新增</span><span class="btn is-hover">${ic("refresh")}重置</span><span class="btn icon">${ic("refresh")}</span><span class="btn is-loading"><i class="spin"></i>查找中</span></div>
  2665. <div class="kit-row"><span class="kit-lbl">表格内</span><span class="btn txt">详情</span><span class="btn txt">编辑</span><span class="btn txt dng">撤单</span><span class="btn sm pri">确认</span><span class="btn sm">补录</span></div>
  2666. <div class="kit-row"><span class="kit-lbl">方向</span><span class="btn dng-pri">确认买入 1,000 股</span><span class="btn ok-pri">确认卖出 500 股</span><span class="btn">取消</span></div>
  2667. </div></div>`;
  2668. const f = (label, html, extra = "") => `<div><div class="f-l">${label}</div>${html}${extra}</div>`;
  2669. const forms = `<div class="card"><div class="card-hd">输入控件<span class="sub">默认 32px;登录页 40px</span></div><div class="card-bd"><div class="kit-fields">
  2670. ${f("默认", `<div class="ipt"><span class="ph">主订单编号</span></div>`)}
  2671. ${f("已填写", `<div class="ipt"><span class="val num">ORD001202609250031</span></div>`)}
  2672. ${f("聚焦", `<div class="ipt is-focus"><span class="val">贵州茅台</span><span style="width:1px;height:16px;background:var(--t1);margin-left:-5px"></span></div>`)}
  2673. ${f("下拉选择", `<div class="ipt is-focus"><span class="val">待处理</span>${ic("down")}</div>`, `<div class="pop" style="margin-top:4px"><div class="pop-i">全部</div><div class="pop-i on">待处理<span class="sp" style="flex:1"></span>${ic("check")}</div><div class="pop-i is-hover">执行中</div><div class="pop-i">已完成</div></div>`)}
  2674. ${f("日期区间", `<div class="ipt">${ic("calendar")}<span class="val num">2026-09-25</span><span class="sep">~</span><span class="val num">2026-09-25</span></div>`, `<div class="f-l" style="margin-top:16px">带单位</div><div class="ipt"><span class="val num">1,000</span><span class="sfx">股</span></div><div class="f-l" style="margin-top:16px">多选</div><div class="ipt"><span class="val chips"><span class="chipv">待生效</span><span class="chipv">存续期</span></span>${ic("down")}</div>`)}
  2675. ${f("校验失败", `<div class="ipt is-err"><span class="ph">请输入委托数量</span></div>`, `<div class="fld-err">${ic("error")}委托数量必须是 100 的整数倍</div><div class="f-l" style="margin-top:16px">禁用</div><div class="ipt is-dis"><span class="val">CNY</span></div><div class="f-l" style="margin-top:16px">两段切换</div><div class="segc"><span class="on">自定义</span><span>TRS</span></div>`)}
  2676. ${f("勾选与开关", `<div class="kit-row" style="gap:20px"><span class="chk-l"><span class="ck on">${CHECK}</span>已勾选</span><span class="chk-l"><span class="ck">${CHECK}</span>未勾选</span><span class="chk-l"><span class="ck half"></span>部分</span></div><div class="kit-row" style="gap:20px"><span class="chk-l"><span class="rd on"></span>限价</span><span class="chk-l"><span class="rd"></span>市价</span><span class="chk-l"><span class="sw on"></span>开启</span><span class="chk-l"><span class="sw"></span>关闭</span></div>`)}
  2677. </div></div></div>`;
  2678. const tags = (dk, list) => (list || Object.keys(dict[dk])).map((t) => tag(dk, t)).join("");
  2679. const status = `<div class="card"><div class="card-hd">状态与字典<span class="sub">${T.notes.status}</span></div><div class="card-bd">
  2680. <div class="kit-row"><span class="kit-lbl">订单状态</span>${tags("note_order_status")}</div>
  2681. <div class="kit-row"><span class="kit-lbl">委托状态</span>${tags("trs_order_status", ["已报", "部成", "已成", "已撤", "废单", "过期"])}</div>
  2682. <div class="kit-row"><span class="kit-lbl">票据</span>${tags("note_status")}${tags("note_risk_status")}</div>
  2683. <div class="kit-row"><span class="kit-lbl">方向</span>${tags("direction")}<span class="t3" style="margin-left:12px">文字形式:</span><span class="buy" style="font-weight:var(--fw-strong)">买入</span><span class="sell" style="font-weight:var(--fw-strong)">卖出</span><span class="t3" style="margin-left:12px">盈亏:</span><span class="num buy">+58,002.47</span><span class="num sell">-31,886.40</span></div>
  2684. <div class="kit-row"><span class="kit-lbl">操作类型</span>${tags("op_type")}${tags("process_mode")}${tags("hedge_progress")}</div>
  2685. </div></div>`;
  2686. const kvs = `<div class="card s6"><div class="card-hd">描述表<span class="sub">${T.notes.kv}</span></div><div class="card-bd">
  2687. <dl class="kvg bd2 n"><div><dt>主订单编号</dt><dd class="num">ORD001202609250031</dd></div><div><dt>订单状态</dt><dd>${tag("note_order_status", "待处理")}</dd></div><div><dt>订单方向</dt><dd>${tag("direction", "买入")}</dd></div><div><dt>投资者</dt><dd>张明远 · INV001100151</dd></div><div><dt>证券</dt><dd>600519 贵州茅台</dd></div><div><dt>名义本金</dt><dd class="num">3,000,000.00 CNY</dd></div><div><dt>下单时间</dt><dd class="num">2026-09-25 09:31:07</dd></div><div><dt>备注</dt><dd class="t4">--</dd></div></dl>
  2688. <div class="f-l" style="margin:16px 0 8px">数字卡片(汇总)</div><div class="tiles" style="grid-template-columns:repeat(3,1fr)"><div class="tile"><div class="l">对冲比例</div><div class="v">77.78%</div><div class="bar"><i style="width:77.78%"></i></div></div><div class="tile"><div class="l">累计成交数量</div><div class="v">6,300</div></div><div class="tile"><div class="l">对冲含费成交均价</div><div class="v">245.2112<i>CNY</i></div></div></div>
  2689. </div></div>`;
  2690. const rows = D.issueRows.filter((r) => r.sub === 0).slice(0, 5);
  2691. const table = `<div class="card"><div class="ctabs"><div class="tabs"><span class="tab on">全部<span class="cnt">12</span></span><span class="tab">待处理<span class="cnt">1</span></span><span class="tab">执行中<span class="cnt">3</span></span><span class="tab">已完成<span class="cnt">5</span></span></div><span class="sp"></span><span class="btn txt">${ic("refresh")}刷新</span></div>
  2692. <div class="tbl-wrap"><table class="tbl"><thead><tr><th style="width:48px"><span class="ck half"></span></th><th>主订单编号</th><th>订单方向</th><th>订单状态</th><th>投资者名称</th><th>证券代码/名称</th><th class="r">订单名义本金(交易币种)</th><th>下单时间</th><th>操作</th></tr></thead><tbody>
  2693. ${rows.map((r, i) => `<tr class="${i === 1 ? "is-sel" : ""} ${i === 3 ? "is-hover" : ""}"><td><span class="ck ${i === 1 ? "on" : ""}">${CHECK}</span></td><td><span class="lnk num">${r.no}</span></td><td>${tag("direction", r.direction)}</td><td>${tag("note_order_status", r.status)}</td><td>${r.invName}</td><td>${r.stock}</td><td class="r num">${r.notional === "--" ? '<span class="t4">--</span>' : r.notional}</td><td class="num">${r.orderTime}</td><td><span class="btns" style="gap:12px"><span class="btn txt">详情</span><span class="btn txt dng">撤单</span></span></td></tr>`).join("")}
  2694. </tbody></table></div>
  2695. ${U.pagerHTML(12)}
  2696. <div style="padding:0 16px 12px;font-size:12px;color:var(--t3)">第 2 行为选中态,第 4 行为悬停态;卡片内页签见「清算管理」。</div></div>`;
  2697. const feedback = `<div class="card s6"><div class="card-hd">提示与反馈<span class="sub">完整排布见「反馈类总览」</span></div><div class="card-bd">
  2698. <div style="display:grid;grid-template-columns:1fr;gap:10px">
  2699. <div class="alt alt-info">${ic("info")}<span>自开始日期(含)起同步交易日,重复同步不会产生重复数据。</span></div>
  2700. <div class="alt alt-danger">${ic("error")}<span>撤单结果未知:上轮撤单未取得明确结果,可人工再次撤单。</span></div>
  2701. </div>
  2702. <div class="kit-row" style="margin-top:16px;gap:16px"><span class="toast">${ic("success", "ok")}<span>保存成功</span></span><span class="toast">${ic("error", "no")}<span>导出失败,请稍后重试</span></span><span class="tip">点击复制编号</span></div>
  2703. </div></div>`;
  2704. const dialog = `<div class="card s6"><div class="card-hd">弹窗与弹层<span class="sub">${T.notes.dialog}</span></div><div class="card-bd"><div class="kit-mask">
  2705. <div class="dlg"><div class="dlg-hd">撤销订单<span class="x">${ic("x")}</span></div><div class="dlg-bd" style="display:flex;flex-direction:column;gap:14px">
  2706. <div class="alt alt-warning">${ic("warn")}<span>撤销后订单进入「撤单中」,不能恢复。</span></div>
  2707. <dl class="kv" style="grid-template-columns:1fr;gap:8px"><div><dt>主订单编号</dt><dd class="num">ORD001202609250031</dd></div><div><dt>证券</dt><dd>600519 贵州茅台</dd></div></dl>
  2708. </div><div class="dlg-ft"><span class="btn">取消</span><span class="btn pri">确认撤销</span></div></div>
  2709. </div></div></div>`;
  2710. return `<div class="kit-top"><h1>组件总览 · ${T.title}</h1><span>主色 ${Tk["c-primary"]}|正文 14px · 控件 32px · 表格行高 40px · 圆角 ${Tk["r-ctl"]}/${Tk["r-card"]}|尺寸和圆角四套皮肤相同,换肤只改颜色与质感</span></div>
  2711. <div class="kit-grid">${colors}${type}${buttons}${forms}${status}${table}${kvs}${feedback}${dialog}</div>`;
  2712. };
  2713. /* ================= 反馈类总览 ================= */
  2714. U.canvasRenderers.feedback = function () {
  2715. const F = P.feedback;
  2716. const card = (title, sub, body, cls = "") => `<div class="card ${cls}"><div class="card-hd">${title}${sub ? `<span class="sub">${sub}</span>` : ""}</div><div class="card-bd">${body}</div></div>`;
  2717. const icOf = (t) => ic(t === "success" ? "success" : t === "error" || t === "danger" ? "error" : t === "warning" ? "warn" : "info", t);
  2718. const msgs = `<div class="fb-msgs">${F.messages.map(([t, m, close]) => `<div class="toast msg-${t}">${icOf(t)}<span>${esc(m)}</span>${close ? `<span class="x">${ic("x")}</span>` : ""}</div>`).join("")}</div><div class="fb-note">顶部居中,距顶 72px;多条时向下堆叠;默认 3 秒消失,带关闭按钮的停留 8 秒。</div>`;
  2719. const ntf = `<div class="ntf"><div class="ntf-hd">${ic("success", "ok")}<b>${F.notification.title}</b><small>${F.notification.time}</small><span class="x">${ic("x")}</span></div><p>${F.notification.body}</p><span class="lnk">${F.notification.link}${ic("right")}</span></div><div class="fb-note">右上角,距顶 16px、距右 16px;用于后台任务完成这类不打断操作的消息。</div>`;
  2720. const alerts = `<div class="fb-alts">${F.alerts.map(([t, m]) => `<div class="alt alt-${t}">${icOf(t)}<span>${esc(m)}</span></div>`).join("")}<div class="alt alt-${F.alertTitled[0]} titled">${icOf("warning")}<div><b>${F.alertTitled[1]}</b><p>${F.alertTitled[2]}</p></div><span class="x">${ic("x")}</span></div></div>`;
  2721. const cfm = U.overlayRenderers.confirm({ kind: "cancel" });
  2722. const confirm = `<div class="kit-mask" style="min-height:260px">${cfm}</div><div class="fb-note">在页面里的样子见「确认框」;危险操作红色实心按钮并写明动作。</div>`;
  2723. const empties = `<div class="fb-empties">${F.empties.map(([n, t, d, a]) => `<div class="fb-cell"><div class="fb-lbl">${n}</div><div class="empty box">${EMPTY}<span>${esc(t)}</span>${d ? `<small>${esc(d)}</small>` : ""}${a ? `<span class="btn sm">${esc(a)}</span>` : ""}</div></div>`).join("")}</div>`;
  2724. const rows = D.issueRows.filter((r) => r.sub === 0).slice(0, 4);
  2725. const loadTbl = `<div class="fb-load"><div class="tbl-wrap bd" style="position:relative"><table class="tbl"><thead><tr><th>主订单编号</th><th>订单方向</th><th>订单状态</th><th>投资者名称</th><th class="r">订单名义本金</th></tr></thead><tbody>${rows.map((r) => `<tr><td class="num">${r.no}</td><td>${tag("direction", r.direction)}</td><td>${tag("note_order_status", r.status)}</td><td>${r.invName}</td><td class="r num">${r.notional}</td></tr>`).join("")}</tbody></table><div class="loading"><i class="spin big"></i><span>加载中</span></div></div>
  2726. <div class="fb-load-r"><div class="f-l">按钮</div><div class="btns"><span class="btn pri is-loading"><i class="spin"></i>查找中</span><span class="btn is-loading"><i class="spin"></i>导出中</span></div><div class="f-l" style="margin-top:16px">首屏骨架</div><div class="skel" style="height:auto;gap:8px"><div class="bar" style="width:40%"></div><div class="bar" style="width:92%"></div><div class="bar" style="width:84%"></div><div class="bar" style="width:70%"></div></div></div></div>`;
  2727. const results = `<div class="fb-results">${F.results.map(([t, ttl, d, bs]) => `<div class="result"><span class="res-ic ${t}">${icOf(t)}</span><b>${ttl}</b><p>${d}</p><div class="btns">${bs.map(([b, ty]) => `<span class="btn ${ty === "primary" ? "pri" : ""}">${b}</span>`).join("")}</div></div>`).join("")}</div>`;
  2728. const validate = `<div class="fgrid c2" style="max-width:720px"><div class="fi fi-t"><label><i class="req">*</i>E-mail</label><div class="fc"><div class="ipt is-err"><span class="val">wanqing.li@</span></div><div class="fld-err">${ic("error")}邮箱格式不正确</div></div></div><div class="fi fi-t"><label><i class="req">*</i>发生金额(结算币种)</label><div class="fc"><div class="ipt is-err"><span class="ph">请输入</span><span class="sfx">HKD</span></div><div class="fld-err">${ic("error")}请输入发生金额(HKD)</div></div></div></div>
  2729. <div class="fb-note" style="margin-top:12px">错误在失焦时校验、提交时全部校验;长表单的弹窗底部汇总「还有几项必填未填、几项有误」,第一处错误滚动到可见。</div>`;
  2730. return `<div class="kit-top"><h1>反馈类总览</h1><span>全局提示、通知、提示条、确认框、空状态、加载态、结果提示、表单校验</span></div>
  2731. <div class="kit-grid">${card("全局提示", "ElMessage", msgs, "s6")}${card("通知", "ElNotification", ntf, "s6")}${card("提示条", "el-alert:按状态取色;可带标题、可关闭", alerts, "s6")}${card("确认框", "ElMessageBox.confirm", confirm, "s6")}${card("空状态", "el-empty:说明原因,必要时给一个动作", empties)}${card("加载态", "v-loading、按钮 loading、骨架", loadTbl, "s6")}${card("结果提示", "el-result:整块居中", results, "s6")}${card("表单校验", "输入框变红 + 下方原因", validate)}</div>`;
  2732. };
  2733. })();
  2734. </script>
  2735. <script>
  2736. /* 设计稿外壳:左侧页面目录、舞台(1440×900 画布缩放显示)、说明面板、规范摘要、全屏、单独模式。 */
  2737. (function () {
  2738. "use strict";
  2739. const U = window.UI;
  2740. const { $, $$, esc, D2, T, ENDS, ic } = U;
  2741. const PAGES = D2.pages;
  2742. const byKey = Object.fromEntries(PAGES.map((p) => [p.key, p]));
  2743. const GROUPS = ["总览", "全局", "弹层", "页面", "反馈"];
  2744. const ALL_ENDS = ["issue", "sales", "trs"];
  2745. const S = { page: "spec", end: "issue", variant: {}, theme: "f" }; // theme:当前皮肤(f 浅色;da/db/dc 三版深色候选)
  2746. const CS = {}; // 画布状态缓存:页面:端:变体
  2747. const SP = {}; // 画布规格缓存
  2748. const endsOf = (p) => (p.ends ? ALL_ENDS : [p.end || "issue"]);
  2749. const curEnd = (p) => (endsOf(p).includes(S.end) ? S.end : endsOf(p)[0]);
  2750. const curVariant = (p) => S.variant[p.key] || (p.variants ? p.variants[0][0] : "");
  2751. function stateFor(p, end, v) {
  2752. const key = [p.key, end, v].join(":");
  2753. if (!CS[key]) {
  2754. const st = U.newCanvasState(end, p.key === "err" ? v : p.active);
  2755. st.ovls = [...(p.ovl || [])];
  2756. if (p.key === "profile" && v === "pwd") st.ovls = ["pwd"];
  2757. if (p.key === "home") st.variant = v; // 首页:now 现状问候卡(默认)/work 工作台(保留,待接口)
  2758. CS[key] = st;
  2759. }
  2760. return CS[key];
  2761. }
  2762. function specFor(p) {
  2763. const end = curEnd(p);
  2764. const v = curVariant(p);
  2765. const key = [p.key, end, v].join(":");
  2766. if (p.kind === "shell") {
  2767. if (p.key === "confirm") U.OV.confirm.kind = v;
  2768. if (p.key === "dlg-trs") Object.assign(U.OV.trs, { dir: v === "sell" ? "卖出" : "买入", qty: v === "sell" ? "500" : "1,000" });
  2769. return { kind: "shell", st: stateFor(p, end, v) };
  2770. }
  2771. if (!SP[key]) SP[key] = p.kind === "login" ? { kind: "login", end, v } : { kind: p.kind };
  2772. return SP[key];
  2773. }
  2774. // 登录页里切换「忘记密码/返回登录」时同步变体按钮
  2775. U.extraActions.lv = (host, sp, el) => { sp.v = el.dataset.v; S.variant.login = sp.v; renderBar(); return true; };
  2776. // 画面里的换肤入口(登录页月亮/太阳按钮、用户菜单「界面设置」):浅色和最近看过的深色之间切换;外壳模式下所有画布、顶栏、规范摘要一起重绘
  2777. U.extraActions.theme = (host, st) => {
  2778. if (st && st.pop) st.pop = null;
  2779. U.toggleTheme();
  2780. S.theme = T.key;
  2781. if (!$("#bkBar")) return true; // 单独模式:只重绘这张画布
  2782. writeHash(); renderAll();
  2783. return false;
  2784. };
  2785. /* ---------------- 规范摘要 ---------------- */
  2786. function specHTML() {
  2787. const picks = [
  2788. ["基础视觉", "F", "晴空渐变", "主色晴空蓝 #3C8CFF,文字链接 #1668DC;主按钮渐变加淡蓝投影;页面底色蓝白渐变、右上角浅蓝光晕;卡片白底 14px 圆角淡蓝投影;表头淡蓝底;状态是无描边的浅色圆角标签;数字用 Inter。", "kit"],
  2789. ["整体框架", "A", "经典三段", "浅色侧栏 220px 通高(白底微透、深灰字,深色皮肤下近黑);白色顶栏 52px 放折叠按钮、面包屑、营业日与两个 A 股交易日、菜单搜索、全屏、通知、用户;下面 38px 卡片式标签页;内容区四周留 16px。", "home"],
  2790. ["弹窗", "J", "卡片分区", "长表单 920px:内容区灰底,每一组一张白卡片,标签在上、双列,卡片标题右侧显示已填/必填。短表单改按 10-08 给的下单弹窗布局:标签在左、单列,顶部高亮信息块,底部灰色结果块(补录 640px、修改密码 480px、TRS 下单 880px 加右侧行情栏)。", "dlg-fill"],
  2791. ["抽屉", "E", "卡片+描述表", "宽 1040px;头部是编号、状态标签和一行关键信息;灰底上排卡片,键值用带边框的描述表(灰底标签格,一行 3 对);汇总做成数字卡片。", "drawer"],
  2792. ["登录页", "C", "右侧浮卡·全幅底图", "整页蓝白渐变底色叠淡线条装饰;表单卡片 440px 靠右浮着,输入框按代码现状 40px;左侧只放系统名和版本号(10-08 去掉了本系统业务模块和三端入口)。(10-07 追加的分项选型类别,选 C。)", "login"],
  2793. ["深色皮肤", "B", "石墨灰", "中性暗灰不偏蓝;扁平,卡片靠 1px 描边分区,没有渐变、光晕和投影;主按钮实心蓝;状态标签只描边不铺底。尺寸、圆角、字体、排布和浅色完全相同,只换令牌取值。(10-08 三版深色候选里选 B;A 夜空蓝、C 极光玻璃未选。)", "kit"],
  2794. ];
  2795. const dims = [
  2796. ["设计基准", "1440 × 900;三端完全一致,不做识别色"], ["宽屏(大于 1440)", "登录页:左列和浮卡成一组按 1440 基准居中,两块间距保持 392px,底图铺满整屏(2026-10-08 定)"], ["控件高度", "32px(登录页按代码现状 40px)"], ["表格", "表头 40px、行高 40px;表头 13px、很淡的蓝底;数字右对齐用 Inter"],
  2797. ["文字", "正文 14px / 22px,辅助 12px / 18px,区块标题 15px,弹窗标题 16px,编号标题 20px;中文系统字体,英文和数字 Inter"],
  2798. ["圆角", "控件 8px、卡片 14px、标签 6px、弹窗 16px"], ["投影", "卡片和主按钮用淡蓝投影;弹层 0 10px 28px;弹窗与抽屉 0 24px 64px"],
  2799. ["侧栏与顶栏", "侧栏 220px 浅色(深色皮肤下近黑),收起后 64px;顶栏 52px;标签页栏 38px;内容区四周留 16px,卡片间距 12px"], ["页签", "框架标签页 28px 卡片式,当前页带主色圆点;页面级页签 40px 无底色;卡片内页签 48px 在卡片标题栏里;抽屉内页签 42px"],
  2800. ["弹窗宽度", "短表单 640px、长表单 920px、确认框 420px、修改密码 480px、TRS 下单 880px"], ["抽屉宽度", "1040px(订单详情、票据详情)"],
  2801. ["搜索区", "一张卡片,3 列等宽,标签在左、按本页最长标签统一宽度;超过 2 行折叠"], ["描述表", "标签格 116px 灰底,一行 3 对;标签长的分组一行 2 对(标签格 190px);单列 132px"],
  2802. ["不放的东西", "页面标题(面包屑和标签页已有页面名)、界面尺寸切换;首页现状版按现状保留了天气那句(待确认),工作台版不放"],
  2803. ];
  2804. const swatches = T.palette.map(([g, items]) => `<div class="sp-grp"><h4>${g}</h4><div class="sp-sw">${items.map(([l, k]) => `<div class="sp-swc"><i style="background:${T.tokens[k]}"></i><b>${l}</b><span>${T.tokens[k]}</span></div>`).join("")}</div></div>`).join("");
  2805. const map = [
  2806. ["基础视觉", "F 晴空渐变", ["kit"]], ["整体框架", "A 经典三段", ["home", "list"]], ["登录页", "C 右侧浮卡·全幅底图;左侧只有系统名和版本号", ["login"]],
  2807. ["搜索区", "3 列标签在左,可折叠;无标签时只放占位", ["list", "tabs"]], ["表格", "固定列、选择条、分页、空状态、加载态", ["list", "clearing", "drawer-note", "feedback"]],
  2808. ["弹窗", "长表单 J 卡片分区;短表单标签在左+信息块+结果块(10-08 布局);TRS 下单同一布局加行情栏", ["dlg-fill", "dlg-investor", "dlg-trs", "profile"]], ["抽屉", "E 卡片+描述表", ["drawer", "drawer-note"]],
  2809. ["详情展示", "带边框描述表 + 数字卡片", ["drawer", "drawer-note", "profile", "dlg-trs"]], ["页内标签页", "页面级/卡片内/抽屉内三种位置", ["tabs", "clearing", "drawer-note", "dlg-trs"]],
  2810. ["反馈类", "确认框、提示、通知、空状态、加载、结果、校验", ["confirm", "feedback"]], ["首页", "现状:一张问候卡(头像、问候语、天气一句);工作台版(状态数字、待处理表、快捷入口)整套保留在变体里,等首页接口完善后切回", ["home"]], ["其他页", "个人中心、404 / 401", ["profile", "err"]],
  2811. ];
  2812. const go = (k) => `<a class="sp-go" href="#p/${k}">${esc(byKey[k].title)}</a>`;
  2813. const themes = T.order.map((k) => T.all[k]);
  2814. const tv = (t, key) => { const v = t.tokens[key]; const m = /^var\(--([\w-]+)\)$/.exec(v || ""); return m ? tv(t, m[1]) : v; }; // 色条在外壳里,var() 不生效,解析成实际值
  2815. const stripHTML = (t) => `<div class="sp-strip" style="background:${tv(t, "bg-page")}">${T.strip.map(([l, key]) => `<i title="${l}" style="background:${tv(t, key)}"></i>`).join("")}</div>`;
  2816. const themeCards = themes.map((t) => `<div class="sp-theme ${t.key === T.key ? "on" : ""}" data-theme="${t.key}"><div class="sp-pick-hd"><span class="rv-letter ${t.dark ? "dk" : ""}">${t.letter}</span><b>${t.label} · ${t.name}</b>${t.key === T.key ? '<span class="sp-cur">正在看</span>' : ""}</div>${stripHTML(t)}<p>${esc(t.tagline)}</p><ul>${t.points.map((x) => `<li>${esc(x)}</li>`).join("")}</ul><button type="button" class="bk-btn sm" data-theme-go="${t.key}">用这套皮肤看首页</button></div>`).join("");
  2817. const cmpRows = Object.keys(themes[0].cmp).map((r) => `<tr><th>${r}</th>${themes.map((t) => `<td>${esc(t.cmp[r])}</td>`).join("")}</tr>`).join("");
  2818. const todos = PAGES.filter((p) => p.todo && p.todo.length).map((p) => `<li><b>${go(p.key)}</b><ul>${p.todo.map((t) => `<li>${esc(t)}</li>`).join("")}</ul></li>`).join("");
  2819. return `<article class="spec">
  2820. <section><h2>这份稿子是什么</h2>
  2821. <p>2026-10-08 第一批分项选型的评审结果拼成的<b>完整 UI 设计</b>:基础视觉 F 晴空渐变、整体框架 A 经典三段、长表单弹窗 J 卡片分区、抽屉 E 卡片+描述表;短表单弹窗按 10-08 给的下单弹窗布局;登录页按 10-07 追加的分项选型类别选定的 C 右侧浮卡·全幅底图。(基础视觉原选 L,改过 B,最终定 F;框架原选 E,最终定 A。)原计划第二、三批(搜索区、表格、详情展示、页内标签页、反馈类、首页、其他页)不再分批出方案,直接按这些选择画成一套,每一类在本稿里都有对应的页面(见下表)。</p>
  2822. <p>每个页面都是 1440×900 的真实尺寸画布,可以直接点:菜单、标签页(含右键)、收起侧栏、菜单搜索、通知、用户菜单、搜索展开、勾选表格行、打开详情抽屉、在抽屉里叠开补录弹窗、切换页签、切换方向和价格。菜单、字段、列名取自三端真实代码;表里的数值是示例。</p>
  2823. <p>换肤:两套皮肤——浅色 F 晴空渐变(默认)和深色 B 石墨灰(10-08 从三版深色候选里选定;A 夜空蓝、C 极光玻璃未选)。顶栏右侧的「皮肤」可以切着看每一个页面,画面里登录页右上角的月亮/太阳按钮、用户菜单的「界面设置」也能来回切。</p></section>
  2824. <section><h2>选型结果</h2><div class="sp-picks">${picks.map(([c, k, n, d, key]) => `<div class="sp-pick"><div class="sp-pick-hd"><span class="rv-letter">${k}</span><b>${c} · ${n}</b></div><p>${d}</p>${go(key)}</div>`).join("")}</div></section>
  2825. <section><h2>尺寸与规则</h2><table class="sp-tbl"><tbody>${dims.map(([k, v]) => `<tr><th>${k}</th><td>${v}</td></tr>`).join("")}</tbody></table></section>
  2826. <section><h2>颜色<small class="sp-h2-sub">当前皮肤:${T.label} ${T.name}(顶栏切换后这里跟着变)</small></h2>${swatches}</section>
  2827. <section><h2>换肤<small class="sp-h2-sub">两套皮肤尺寸、圆角、字体、排布相同,只改颜色与质感;深色 B 石墨灰为 10-08 选定</small></h2><div class="sp-themes">${themeCards}</div>
  2828. <table class="sp-tbl cmp"><thead><tr><th></th>${themes.map((t) => `<th>${t.label} · ${t.name}</th>`).join("")}</tr></thead><tbody>${cmpRows}</tbody></table></section>
  2829. <section><h2>12 类在本稿里的位置</h2><table class="sp-tbl map"><thead><tr><th>分类</th><th>怎么处理</th><th>看哪个页面</th></tr></thead><tbody>${map.map(([c, h, keys]) => `<tr><th>${c}</th><td>${h}</td><td>${keys.map(go).join("")}</td></tr>`).join("")}</tbody></table></section>
  2830. <section><h2>待确认</h2><ul class="sp-todo">${todos}<li><b>全局</b><ul><li>换肤:深色已定 B 石墨灰。默认是浅色还是跟随系统;用户切换后的选择记在账号上还是本机浏览器——请确认。</li><li>第二、三批不再分批出方案,改为直接按本稿实现——请确认这个做法。</li></ul></li></ul></section>
  2831. <section><h2>文件</h2><p>本文件由 <code>src/build.py</code> 生成,单文件、离线可用;源码在 <code>src/</code>(<code>tokens.js</code> 令牌、<code>data.js</code>/<code>data2.js</code> 内容、<code>app.js</code>/<code>pages.js</code> 画布、<code>*.css</code> 样式)。<code>node test.mjs</code> 在生成的文件里实际点击每一类交互并断言。</p></section>
  2832. </article>`;
  2833. }
  2834. /* ---------------- 外壳渲染 ---------------- */
  2835. const page = () => byKey[S.page] || byKey.spec;
  2836. function renderNav() {
  2837. const p = page();
  2838. $("#bkNav").innerHTML = GROUPS.map((g) => `<div class="bk-grp"><div class="bk-grp-t">${g}</div>${PAGES.filter((x) => x.group === g).map((x) => `<a class="bk-item ${x.key === p.key ? "on" : ""}" href="#p/${x.key}"><b>${esc(x.title)}</b><small>${esc(x.sub || "")}</small></a>`).join("")}</div>`).join("");
  2839. }
  2840. const seg = (name, items, cur) => `<div class="bk-seg" data-seg="${name}">${items.map(([v, label]) => `<button type="button" class="${v === cur ? "on" : ""}" data-v="${v}">${label}</button>`).join("")}</div>`;
  2841. function renderBar() {
  2842. const p = page();
  2843. let left = `<b class="bk-ttl">${esc(p.title)}</b><span class="bk-sub">${esc(p.sub || "")}</span>`;
  2844. let right = "";
  2845. if (p.ends) right += `<span class="bk-lbl">看哪一端</span>` + seg("end", ALL_ENDS.map((k) => [k, ENDS[k].short]), curEnd(p));
  2846. if (p.variants) right += seg("variant", p.variants, curVariant(p));
  2847. if (p.kind !== "spec") right += `<button type="button" class="bk-btn" data-full>全屏查看</button>`;
  2848. $("#bkBar").innerHTML = `<div class="bk-bar-l">${left}</div><div class="bk-bar-r">${right}</div>`;
  2849. }
  2850. function renderStage() {
  2851. const p = page();
  2852. const host = $("#bkStage");
  2853. const spec = $("#bkSpec");
  2854. if (p.kind === "spec") {
  2855. host.hidden = true; spec.hidden = false; spec.innerHTML = specHTML();
  2856. host._spec = null; host.innerHTML = "";
  2857. return;
  2858. }
  2859. spec.hidden = true; host.hidden = false;
  2860. host.classList.toggle("is-kit", p.kind === "kit" || p.kind === "feedback");
  2861. U.mount(host, specFor(p), { fitHeight: true });
  2862. }
  2863. function renderNotes() {
  2864. const p = page();
  2865. const box = $("#bkNotes");
  2866. if (p.kind === "spec") { box.hidden = true; return; }
  2867. box.hidden = false;
  2868. box.innerHTML = `<div class="bk-note"><h4>来源</h4><p>${esc(p.src || "")}</p></div>
  2869. <div class="bk-note"><h4>设计要点</h4><ul>${(p.points || []).map((t) => `<li>${esc(t)}</li>`).join("")}</ul></div>
  2870. ${p.todo && p.todo.length ? `<div class="bk-note todo"><h4>待确认</h4><ul>${p.todo.map((t) => `<li>${esc(t)}</li>`).join("")}</ul></div>` : ""}`;
  2871. }
  2872. const themeSeg = () => seg("theme", T.order.map((k) => [k, `${T.all[k].label}${k === "f" ? " F" : ""}`]), T.key);
  2873. function renderTheme() {
  2874. const el = $("#bkTheme");
  2875. if (el) el.innerHTML = `<span class="bk-lbl">皮肤</span>${themeSeg()}<span class="bk-theme-nm" title="${esc(T.tagline)}">${esc(T.name)}</span>`;
  2876. }
  2877. function setTheme(k) {
  2878. if (!U.setTheme(k)) return;
  2879. if (T.dark) U.THEME_PICK.dark = k;
  2880. S.theme = k;
  2881. writeHash(); renderAll();
  2882. }
  2883. function renderAll() { renderTheme(); renderNav(); renderBar(); renderStage(); renderNotes(); if (!$("#bkFull").hidden) renderFull(); }
  2884. /* ---------------- 全屏 ---------------- */
  2885. function renderFull() {
  2886. const p = page();
  2887. let bar = `<b class="bk-ttl">${esc(p.title)}</b>`;
  2888. if (p.ends) bar += seg("end", ALL_ENDS.map((k) => [k, ENDS[k].short]), curEnd(p));
  2889. if (p.variants) bar += seg("variant", p.variants, curVariant(p));
  2890. bar += `<span class="bk-lbl">皮肤</span>${themeSeg()}`;
  2891. bar += `<span class="sp"></span><span class="bk-k">←/→ 切换页面,Esc 退出</span><button type="button" class="bk-x" data-close title="退出全屏">✕</button>`;
  2892. $("#bkFullBar").innerHTML = bar;
  2893. const host = $("#bkFullStage");
  2894. const kit = p.kind === "kit" || p.kind === "feedback";
  2895. host.classList.toggle("is-kit", kit);
  2896. U.mount(host, specFor(p), { zoom: () => { const w = window.innerWidth - 32, h = window.innerHeight - 64; return kit ? Math.min(1, w / 1440) : Math.min(1, w / 1440, h / 900); } });
  2897. host.style.width = Math.round(1440 * host.firstElementChild.style.zoom) + "px";
  2898. host.style.height = kit ? window.innerHeight - 64 + "px" : Math.round(900 * host.firstElementChild.style.zoom) + "px";
  2899. }
  2900. function openFull() { $("#bkFull").hidden = false; document.body.classList.add("bk-lock"); renderFull(); }
  2901. function closeFull() { $("#bkFull").hidden = true; document.body.classList.remove("bk-lock"); $("#bkFullStage")._spec = null; $("#bkFullStage").innerHTML = ""; renderStage(); }
  2902. /* ---------------- 路由与事件 ---------------- */
  2903. function readHash() {
  2904. const m = location.hash.match(/^#p\/([\w-]+)(?:\/(issue|sales|trs))?(?:\/([\w-]+))?(?:\/t=([a-z]+))?$/);
  2905. if (!m) return false;
  2906. if (!byKey[m[1]]) return false;
  2907. S.page = m[1];
  2908. if (m[2]) S.end = m[2];
  2909. if (m[3]) S.variant[S.page] = m[3];
  2910. if (m[4] && T.all[m[4]]) { U.setTheme(m[4]); if (T.dark) U.THEME_PICK.dark = m[4]; S.theme = m[4]; } // 地址里没带皮肤就沿用当前的
  2911. return true;
  2912. }
  2913. function writeHash() {
  2914. const p = page();
  2915. let h = `#p/${p.key}`;
  2916. if (p.ends) h += `/${curEnd(p)}`;
  2917. if (p.variants) h += (p.ends ? "" : `/${curEnd(p)}`) + `/${curVariant(p)}`;
  2918. if (T.key !== "f") h += `/t=${T.key}`;
  2919. if (location.hash !== h) history.replaceState(null, "", h);
  2920. }
  2921. function go(key) { S.page = key; writeHash(); renderAll(); window.scrollTo(0, 0); }
  2922. function bind() {
  2923. document.addEventListener("click", (e) => {
  2924. const t = e.target;
  2925. if (t.closest(".bk-host")) return;
  2926. const a = t.closest("a[href^='#p/']");
  2927. if (a) { e.preventDefault(); location.hash = a.getAttribute("href"); readHash(); writeHash(); renderAll(); window.scrollTo(0, 0); return; }
  2928. if (t.closest("[data-full]")) { openFull(); return; }
  2929. const tg = t.closest("[data-theme-go]");
  2930. if (tg) { if (U.setTheme(tg.dataset.themeGo)) { if (T.dark) U.THEME_PICK.dark = T.key; S.theme = T.key; } go("home"); return; }
  2931. if (t.closest("[data-close]")) { closeFull(); return; }
  2932. const sb = t.closest(".bk-seg button");
  2933. if (sb) {
  2934. const name = sb.closest(".bk-seg").dataset.seg;
  2935. const v = sb.dataset.v;
  2936. const p = page();
  2937. if (name === "theme") { setTheme(v); return; }
  2938. if (name === "end") S.end = v;
  2939. if (name === "variant") { S.variant[p.key] = v; if (p.key === "login") { const sp = SP[[p.key, curEnd(p), v].join(":")]; if (sp) sp.v = v; } }
  2940. writeHash();
  2941. renderBar(); renderStage();
  2942. if (!$("#bkFull").hidden) renderFull();
  2943. }
  2944. });
  2945. document.addEventListener("keydown", (e) => {
  2946. if (e.key === "Escape" && !$("#bkFull").hidden) { closeFull(); return; }
  2947. if (e.target.closest("input, textarea")) return;
  2948. if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
  2949. const i = PAGES.findIndex((p) => p.key === S.page);
  2950. go(PAGES[(i + (e.key === "ArrowRight" ? 1 : PAGES.length - 1)) % PAGES.length].key);
  2951. e.preventDefault();
  2952. }
  2953. });
  2954. window.addEventListener("hashchange", () => { if (readHash()) { renderAll(); } });
  2955. let rt = 0;
  2956. window.addEventListener("resize", () => { cancelAnimationFrame(rt); rt = requestAnimationFrame(() => { $$(".bk-host").forEach((h) => h._spec && U.fitCanvas(h)); if (!$("#bkFull").hidden) renderFull(); }); });
  2957. }
  2958. /* ---------------- 单独模式:#solo/<页面>[/<端>][/<变体>],原尺寸渲染一张画布,供截图和自动检查 ---------------- */
  2959. const solo = location.hash.match(/^#solo\/([\w-]+)(?:\/(issue|sales|trs))?(?:\/([\w-]+))?(?:\/lbl=(top|left))?(?:\/t=([a-z]+))?$/);
  2960. if (solo && byKey[solo[1]] && byKey[solo[1]].kind !== "spec") {
  2961. if (solo[4]) U.OPT.lblTop = solo[4] === "top";
  2962. if (solo[5] && T.all[solo[5]]) { U.setTheme(solo[5]); if (T.dark) U.THEME_PICK.dark = solo[5]; S.theme = solo[5]; }
  2963. S.page = solo[1];
  2964. if (solo[2]) S.end = solo[2];
  2965. if (solo[3]) S.variant[S.page] = solo[3];
  2966. document.body.className = "bk-solo";
  2967. document.body.innerHTML = '<div class="bk-host" id="solo"></div>';
  2968. U.mount($("#solo"), specFor(page()), { zoom: () => 1 });
  2969. window.__solo = $("#solo");
  2970. window.__book = { S, PAGES, specFor, page };
  2971. return;
  2972. }
  2973. readHash();
  2974. bind();
  2975. renderAll();
  2976. window.__book = { S, PAGES, specFor, page, go, CS, setTheme };
  2977. })();
  2978. </script>
  2979. </body>
  2980. </html>