fresh-mint-green.html 133 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>叁强鲜配 · 配送调度</title>
  7. <link rel="preconnect" href="https://fonts.googleapis.com" />
  8. <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
  9. <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
  10. <style>
  11. :root {
  12. --primary: #10b981;
  13. --primary-dark: #059669;
  14. --primary-bg: #ecfdf5;
  15. --text: #1e293b;
  16. --text-muted: #64748b;
  17. --border: #d9e8e2;
  18. /* 工作区:极淡绿底;卡片/面板仍为白。强调色仍只用于导航选中、主按钮、主状态标签 */
  19. --bg-page: #fefefe;
  20. --surface: #ffffff;
  21. --table-head: #f8fafc;
  22. --row-alt: #fafafa;
  23. --row-hover: #f1f5f9;
  24. --shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  25. --radius: 10px;
  26. /* 主内容区:全宽,仅四边留白 */
  27. --content-padding: 24px;
  28. }
  29. * { box-sizing: border-box; }
  30. .sr-only {
  31. position: absolute;
  32. width: 1px;
  33. height: 1px;
  34. padding: 0;
  35. margin: -1px;
  36. overflow: hidden;
  37. clip: rect(0, 0, 0, 0);
  38. white-space: nowrap;
  39. border: 0;
  40. }
  41. body {
  42. margin: 0;
  43. min-height: 100vh;
  44. font-family: Inter, -apple-system, sans-serif;
  45. font-size: 14px;
  46. line-height: 1.5;
  47. color: var(--text);
  48. background: var(--bg-page);
  49. }
  50. .layout { display: flex; min-height: 100vh; }
  51. .sidebar {
  52. width: 220px;
  53. flex-shrink: 0;
  54. background: var(--surface);
  55. border-right: 1px solid var(--border);
  56. padding: 24px 16px;
  57. }
  58. .logo {
  59. font-size: 16px;
  60. font-weight: 700;
  61. color: var(--text);
  62. padding: 0 12px 20px;
  63. margin-bottom: 8px;
  64. border-bottom: 1px solid var(--border);
  65. }
  66. .logo span { color: var(--primary); }
  67. .nav-details {
  68. border: none;
  69. margin: 0 0 4px;
  70. padding: 0;
  71. }
  72. .nav-details > summary {
  73. list-style: none;
  74. cursor: pointer;
  75. display: flex;
  76. align-items: center;
  77. justify-content: space-between;
  78. padding: 8px 12px;
  79. margin: 0;
  80. border-radius: var(--radius);
  81. font-size: 12px;
  82. font-weight: 700;
  83. color: var(--text-muted);
  84. letter-spacing: 0.02em;
  85. user-select: none;
  86. }
  87. .nav-details > summary::-webkit-details-marker { display: none; }
  88. .nav-details > summary::after {
  89. content: "";
  90. width: 6px;
  91. height: 6px;
  92. border-right: 2px solid var(--text-muted);
  93. border-bottom: 2px solid var(--text-muted);
  94. transform: rotate(-45deg);
  95. flex-shrink: 0;
  96. margin-left: 8px;
  97. opacity: 0.7;
  98. transition: transform 0.2s ease;
  99. }
  100. .nav-details[open] > summary::after {
  101. transform: rotate(45deg);
  102. margin-top: -2px;
  103. }
  104. .nav-details > summary:hover {
  105. background: var(--row-alt);
  106. color: var(--text);
  107. }
  108. .nav-sub {
  109. padding: 2px 0 6px 4px;
  110. margin-left: 4px;
  111. border-left: 2px solid var(--border);
  112. }
  113. .nav a {
  114. display: block;
  115. padding: 8px 12px 8px 14px;
  116. margin: 2px 0;
  117. border-radius: var(--radius);
  118. color: var(--text-muted);
  119. text-decoration: none;
  120. font-weight: 500;
  121. font-size: 13px;
  122. }
  123. .nav a:hover { background: var(--row-alt); color: var(--text); }
  124. .nav a.active { background: var(--primary-bg); color: var(--primary-dark); font-weight: 600; }
  125. .nav-foot { margin-top: 24px; padding: 16px 12px 0; font-size: 13px; }
  126. .nav-foot a { color: var(--text-muted); text-decoration: none; }
  127. .nav-foot a:hover { color: var(--primary-dark); }
  128. .main {
  129. flex: 1;
  130. min-width: 0;
  131. display: flex;
  132. flex-direction: column;
  133. width: 100%;
  134. }
  135. .header {
  136. height: 56px;
  137. padding: 0 var(--content-padding);
  138. display: flex;
  139. align-items: center;
  140. justify-content: space-between;
  141. background: var(--surface);
  142. border-bottom: 1px solid var(--border);
  143. }
  144. .crumb { font-size: 13px; color: var(--text-muted); }
  145. .crumb b { color: var(--text); font-weight: 600; }
  146. .content {
  147. width: 100%;
  148. max-width: none;
  149. flex: 1;
  150. padding: var(--content-padding);
  151. box-sizing: border-box;
  152. display: flex;
  153. flex-direction: column;
  154. min-height: 0;
  155. }
  156. .top-progress-bar {
  157. position: fixed;
  158. top: 0;
  159. left: 0;
  160. right: 0;
  161. height: 3px;
  162. z-index: 9999;
  163. pointer-events: none;
  164. overflow: hidden;
  165. opacity: 0;
  166. transition: opacity 0.15s ease;
  167. }
  168. .top-progress-bar.is-active {
  169. opacity: 1;
  170. }
  171. .top-progress-bar::after {
  172. content: "";
  173. position: absolute;
  174. top: 0;
  175. left: 0;
  176. height: 100%;
  177. width: 40%;
  178. background: linear-gradient(90deg, var(--primary), #34d399);
  179. animation: top-progress-slide 0.85s ease-in-out infinite;
  180. }
  181. @keyframes top-progress-slide {
  182. 0% { transform: translateX(-100%); }
  183. 100% { transform: translateX(350%); }
  184. }
  185. .tabs-strip {
  186. display: flex;
  187. flex-wrap: wrap;
  188. align-items: flex-end;
  189. gap: 4px;
  190. margin: 0 0 16px;
  191. padding: 0 2px 10px;
  192. border-bottom: 1px solid var(--border);
  193. }
  194. .tab-chip {
  195. appearance: none;
  196. -webkit-appearance: none;
  197. border: 1px solid var(--border);
  198. background: var(--surface);
  199. color: var(--text-muted);
  200. font-size: 13px;
  201. font-weight: 600;
  202. font-family: inherit;
  203. padding: 8px 14px;
  204. border-radius: 8px 8px 0 0;
  205. cursor: pointer;
  206. margin-bottom: -11px;
  207. position: relative;
  208. z-index: 1;
  209. max-width: 220px;
  210. overflow: hidden;
  211. text-overflow: ellipsis;
  212. white-space: nowrap;
  213. }
  214. .tab-chip:hover {
  215. color: var(--text);
  216. background: var(--row-alt);
  217. }
  218. .tab-chip.is-active {
  219. color: var(--primary-dark);
  220. background: var(--surface);
  221. border-bottom-color: var(--surface);
  222. box-shadow: 0 -2px 0 var(--primary) inset;
  223. z-index: 2;
  224. }
  225. .tab-panel-mount {
  226. flex: 1;
  227. min-height: 0;
  228. position: relative;
  229. }
  230. .tab-panel {
  231. display: none;
  232. animation: tab-fade-in 0.2s ease;
  233. }
  234. .tab-panel.is-active {
  235. display: block;
  236. }
  237. @keyframes tab-fade-in {
  238. from { opacity: 0.85; }
  239. to { opacity: 1; }
  240. }
  241. .tab-panel.is-refreshing {
  242. position: relative;
  243. pointer-events: none;
  244. min-height: 160px;
  245. }
  246. .tab-panel.is-refreshing::before {
  247. content: "";
  248. position: absolute;
  249. inset: 0;
  250. background: rgba(255, 255, 255, 0.55);
  251. z-index: 20;
  252. border-radius: var(--radius);
  253. }
  254. .tab-panel.is-refreshing::after {
  255. content: "";
  256. position: absolute;
  257. left: 50%;
  258. top: 48px;
  259. width: 28px;
  260. height: 28px;
  261. margin-left: -14px;
  262. border: 3px solid var(--border);
  263. border-top-color: var(--primary);
  264. border-radius: 50%;
  265. animation: tab-spin 0.7s linear infinite;
  266. z-index: 21;
  267. }
  268. @keyframes tab-spin {
  269. to { transform: rotate(360deg); }
  270. }
  271. .btn.is-loading,
  272. .btn-primary.is-loading,
  273. .btn-op.is-loading,
  274. .pager-btn.is-loading,
  275. .btn-icon.is-loading,
  276. .upload-zone-btn.is-loading,
  277. .btn-block.is-loading {
  278. position: relative;
  279. pointer-events: none;
  280. cursor: not-allowed;
  281. color: #94a3b8 !important;
  282. background: #f1f5f9 !important;
  283. border-color: #cbd5e1 !important;
  284. box-shadow: none !important;
  285. opacity: 1;
  286. padding-left: 30px;
  287. }
  288. .btn-op.is-loading {
  289. padding-left: 24px;
  290. }
  291. .btn-icon.is-loading {
  292. padding-left: 22px;
  293. }
  294. /* 加载时把原有左侧图标隐藏,视觉上替换为 loading 图标 */
  295. .is-loading .ic,
  296. .is-loading .ic-sm {
  297. opacity: 0;
  298. }
  299. .btn.is-loading::before,
  300. .btn-primary.is-loading::before,
  301. .btn-op.is-loading::before,
  302. .pager-btn.is-loading::before,
  303. .btn-icon.is-loading::before,
  304. .upload-zone-btn.is-loading::before,
  305. .btn-block.is-loading::before {
  306. content: "";
  307. position: absolute;
  308. left: 10px;
  309. top: 50%;
  310. width: 14px;
  311. height: 14px;
  312. margin-top: -7px;
  313. border: 2px solid #cbd5e1;
  314. border-top-color: #64748b;
  315. border-radius: 50%;
  316. animation: tab-spin 0.65s linear infinite;
  317. }
  318. .btn-op.is-loading::before,
  319. .btn-icon.is-loading::before {
  320. left: 8px;
  321. width: 12px;
  322. height: 12px;
  323. margin-top: -6px;
  324. }
  325. .page-head {
  326. margin-bottom: 16px;
  327. }
  328. .page-head h1 {
  329. margin: 0 0 4px;
  330. font-size: 20px;
  331. font-weight: 700;
  332. letter-spacing: -0.02em;
  333. }
  334. .desc {
  335. margin: 0;
  336. color: var(--text-muted);
  337. font-size: 13px;
  338. line-height: 1.45;
  339. max-width: 560px;
  340. }
  341. .stats {
  342. display: grid;
  343. grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  344. gap: 12px;
  345. margin-bottom: 16px;
  346. }
  347. .stat {
  348. background: var(--surface);
  349. border: 1px solid var(--border);
  350. border-radius: var(--radius);
  351. padding: 12px 14px;
  352. box-shadow: var(--shadow);
  353. display: flex;
  354. flex-direction: column;
  355. gap: 8px;
  356. min-height: 112px;
  357. }
  358. .stat-hd {
  359. display: flex;
  360. align-items: center;
  361. justify-content: flex-start;
  362. margin-bottom: 0;
  363. }
  364. .stat-ic {
  365. width: 44px;
  366. height: 44px;
  367. border-radius: 10px;
  368. display: flex;
  369. align-items: center;
  370. justify-content: center;
  371. flex-shrink: 0;
  372. background: var(--primary-bg);
  373. color: var(--primary-dark);
  374. border: 1px solid rgba(16, 185, 129, 0.12);
  375. }
  376. .stat-ic svg {
  377. width: 24px;
  378. height: 24px;
  379. display: block;
  380. }
  381. .stat-main {
  382. display: flex;
  383. align-items: flex-end;
  384. justify-content: space-between;
  385. gap: 10px;
  386. min-width: 0;
  387. flex: 1;
  388. }
  389. .stat .k {
  390. font-size: 14px;
  391. color: var(--text);
  392. margin: 0;
  393. line-height: 1.35;
  394. font-weight: 600;
  395. max-width: 52%;
  396. word-break: break-word;
  397. }
  398. .stat-side {
  399. min-width: 0;
  400. text-align: right;
  401. margin-left: auto;
  402. }
  403. .stat .v {
  404. font-size: 22px;
  405. font-weight: 700;
  406. letter-spacing: -0.02em;
  407. font-variant-numeric: tabular-nums;
  408. line-height: 1.1;
  409. }
  410. .stat .hint {
  411. font-size: 11px;
  412. color: var(--text-muted);
  413. margin-top: 4px;
  414. line-height: 1.3;
  415. white-space: nowrap;
  416. }
  417. .stat.warn .stat-ic {
  418. background: #fffbeb;
  419. border-color: rgba(217, 119, 6, 0.2);
  420. color: #d97706;
  421. }
  422. .stat.warn .v,
  423. .stat.warn .hint {
  424. color: #d97706;
  425. }
  426. /* 通用图标尺寸(按钮内、行内操作等) */
  427. .ic {
  428. width: 16px;
  429. height: 16px;
  430. flex-shrink: 0;
  431. display: block;
  432. }
  433. .ic-sm { width: 14px; height: 14px; }
  434. /* 带清除的输入框 */
  435. .input-clear-wrap {
  436. position: relative;
  437. width: 100%;
  438. display: block;
  439. }
  440. .input-clear-wrap .input {
  441. width: 100%;
  442. min-width: 0;
  443. padding-right: 34px;
  444. }
  445. .input-clear {
  446. position: absolute;
  447. right: 4px;
  448. top: 50%;
  449. transform: translateY(-50%);
  450. width: 28px;
  451. height: 28px;
  452. padding: 0;
  453. margin: 0;
  454. border: none;
  455. border-radius: 6px;
  456. background: transparent;
  457. color: var(--text-muted);
  458. cursor: pointer;
  459. display: flex;
  460. align-items: center;
  461. justify-content: center;
  462. }
  463. .input-clear:hover {
  464. background: var(--row-alt);
  465. color: var(--text);
  466. }
  467. .input-clear:focus-visible {
  468. outline: 2px solid var(--primary);
  469. outline-offset: 1px;
  470. }
  471. .input-clear[hidden] {
  472. display: none !important;
  473. }
  474. .panel {
  475. background: var(--surface);
  476. border: 1px solid var(--border);
  477. border-radius: var(--radius);
  478. box-shadow: var(--shadow);
  479. overflow: hidden;
  480. }
  481. .panel-top {
  482. padding: 10px 16px;
  483. border-bottom: 1px solid var(--border);
  484. background: var(--surface);
  485. display: flex;
  486. flex-wrap: wrap;
  487. align-items: center;
  488. justify-content: space-between;
  489. gap: 8px;
  490. }
  491. .panel-top h2 {
  492. margin: 0;
  493. font-size: 14px;
  494. font-weight: 600;
  495. display: flex;
  496. align-items: center;
  497. gap: 8px;
  498. }
  499. .panel-top h2 .panel-ic {
  500. width: 18px;
  501. height: 18px;
  502. color: var(--primary-dark);
  503. flex-shrink: 0;
  504. }
  505. .tools { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
  506. /* 列表上方:多条件检索 */
  507. .filter-block {
  508. padding: 12px 16px 10px;
  509. border-bottom: 1px solid var(--border);
  510. background: var(--surface);
  511. }
  512. .filter-form { margin: 0; }
  513. .filter-grid {
  514. display: grid;
  515. grid-template-columns: repeat(4, 1fr);
  516. gap: 10px 14px;
  517. margin-bottom: 10px;
  518. }
  519. @media (max-width: 1100px) {
  520. .filter-grid { grid-template-columns: repeat(2, 1fr); }
  521. }
  522. @media (max-width: 560px) {
  523. .filter-grid { grid-template-columns: 1fr; }
  524. }
  525. .filter-field label {
  526. display: block;
  527. font-size: 11px;
  528. font-weight: 500;
  529. color: var(--text-muted);
  530. margin-bottom: 4px;
  531. }
  532. .filter-field .input,
  533. .filter-field .select {
  534. width: 100%;
  535. min-width: 0;
  536. }
  537. .select {
  538. height: 36px;
  539. padding: 0 12px;
  540. border: 1px solid var(--border);
  541. border-radius: 8px;
  542. font-size: 14px;
  543. font-family: inherit;
  544. color: var(--text);
  545. background: var(--surface);
  546. cursor: pointer;
  547. appearance: none;
  548. background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  549. background-repeat: no-repeat;
  550. background-position: right 12px center;
  551. padding-right: 32px;
  552. }
  553. .select:focus {
  554. outline: none;
  555. border-color: var(--primary);
  556. box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
  557. }
  558. .filter-extra {
  559. padding-top: 10px;
  560. margin-top: 2px;
  561. border-top: 1px dashed var(--border);
  562. }
  563. .filter-extra[hidden] {
  564. display: none !important;
  565. }
  566. .filter-hint {
  567. margin: 0 0 8px;
  568. font-size: 11px;
  569. color: var(--text-muted);
  570. }
  571. .filter-actions {
  572. display: flex;
  573. flex-wrap: wrap;
  574. align-items: center;
  575. justify-content: space-between;
  576. gap: 10px;
  577. border-top: 1px dashed var(--border);
  578. padding-top: 10px;
  579. }
  580. .filter-actions-end {
  581. display: flex;
  582. flex-wrap: wrap;
  583. gap: 8px;
  584. align-items: center;
  585. }
  586. .btn-link {
  587. border: none;
  588. background: transparent;
  589. color: var(--primary-dark);
  590. font-weight: 600;
  591. padding: 0 8px;
  592. height: 36px;
  593. }
  594. .btn-link:hover {
  595. background: var(--primary-bg);
  596. border-radius: 8px;
  597. }
  598. .input {
  599. height: 36px;
  600. padding: 0 12px;
  601. border: 1px solid var(--border);
  602. border-radius: 8px;
  603. font-size: 14px;
  604. font-family: inherit;
  605. color: var(--text);
  606. min-width: 200px;
  607. background: var(--surface);
  608. }
  609. .input:focus {
  610. outline: none;
  611. border-color: var(--primary);
  612. background: var(--surface);
  613. box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
  614. }
  615. .btn {
  616. height: 36px;
  617. padding: 0 14px;
  618. border-radius: 8px;
  619. font-size: 14px;
  620. font-weight: 600;
  621. font-family: inherit;
  622. cursor: pointer;
  623. border: 1px solid var(--border);
  624. background: var(--surface);
  625. color: var(--text);
  626. display: inline-flex;
  627. align-items: center;
  628. justify-content: center;
  629. gap: 6px;
  630. }
  631. .btn:hover { background: var(--row-alt); }
  632. .btn-primary {
  633. background: var(--primary);
  634. border-color: var(--primary);
  635. color: #fff;
  636. }
  637. .btn-primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
  638. table {
  639. width: 100%;
  640. border-collapse: collapse;
  641. font-size: 14px;
  642. }
  643. th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid var(--border); }
  644. th {
  645. font-size: 12px;
  646. font-weight: 600;
  647. color: var(--text-muted);
  648. background: var(--table-head);
  649. }
  650. tbody tr:nth-child(even) td { background: var(--row-alt); }
  651. tbody tr:hover td { background: var(--row-hover); }
  652. tbody tr:last-child td { border-bottom: none; }
  653. .data-table tbody tr:last-child td { border-bottom: 1px solid var(--border); }
  654. /* 列表区:上方工具栏(单行)+ 表格(末列操作 sticky固定在右侧) */
  655. .list-body {
  656. border-top: 1px solid var(--border);
  657. }
  658. .list-toolbar {
  659. padding: 0;
  660. background: var(--table-head);
  661. border-bottom: 1px solid var(--border);
  662. }
  663. .list-toolbar-row {
  664. display: flex;
  665. flex-wrap: wrap;
  666. align-items: center;
  667. justify-content: flex-start;
  668. gap: 8px;
  669. padding: 8px 16px;
  670. }
  671. .list-toolbar-selected {
  672. font-size: 12px;
  673. font-weight: 600;
  674. color: var(--text-muted);
  675. white-space: nowrap;
  676. padding: 0 10px 0 0;
  677. margin-right: 4px;
  678. border-right: 1px solid var(--border);
  679. line-height: 36px;
  680. min-height: 36px;
  681. display: inline-flex;
  682. align-items: center;
  683. }
  684. .list-toolbar-selected .sel-num {
  685. color: var(--primary-dark);
  686. font-weight: 700;
  687. margin: 0 2px;
  688. }
  689. .list-pager {
  690. display: flex;
  691. flex-wrap: wrap;
  692. align-items: center;
  693. justify-content: space-between;
  694. gap: 10px 12px;
  695. padding: 8px 16px;
  696. background: var(--surface);
  697. border-top: 1px solid var(--border);
  698. }
  699. .pager-meta {
  700. font-size: 13px;
  701. color: var(--text-muted);
  702. }
  703. .pager-controls {
  704. display: flex;
  705. flex-wrap: wrap;
  706. align-items: center;
  707. gap: 8px;
  708. }
  709. .pager-size-label {
  710. font-size: 13px;
  711. color: var(--text-muted);
  712. margin: 0;
  713. }
  714. .pager-select {
  715. width: auto;
  716. min-width: 72px;
  717. height: 36px;
  718. }
  719. .pager-size-suffix {
  720. font-size: 13px;
  721. color: var(--text-muted);
  722. margin-right: 4px;
  723. }
  724. .pager-btn:disabled {
  725. opacity: 0.45;
  726. cursor: not-allowed;
  727. }
  728. .pager-nums {
  729. display: inline-flex;
  730. flex-wrap: wrap;
  731. align-items: center;
  732. gap: 4px;
  733. }
  734. .pager-num {
  735. min-width: 36px;
  736. padding: 0 10px;
  737. height: 32px;
  738. font-size: 13px;
  739. }
  740. .pager-num.is-current {
  741. background: var(--primary);
  742. border-color: var(--primary);
  743. color: #fff;
  744. cursor: default;
  745. }
  746. .pager-num.is-current:hover {
  747. background: var(--primary-dark);
  748. border-color: var(--primary-dark);
  749. color: #fff;
  750. }
  751. .pager-gap {
  752. padding: 0 4px;
  753. color: var(--text-muted);
  754. font-size: 13px;
  755. user-select: none;
  756. }
  757. .data-table tbody tr.is-hidden {
  758. display: none !important;
  759. }
  760. .btn-block {
  761. width: 100%;
  762. height: auto;
  763. min-height: 34px;
  764. padding: 8px 8px;
  765. font-size: 12px;
  766. line-height: 1.3;
  767. justify-content: center;
  768. }
  769. .table-scroll {
  770. overflow-x: auto;
  771. overflow-y: auto;
  772. max-height: min(520px, calc(100vh - 300px));
  773. -webkit-overflow-scrolling: touch;
  774. }
  775. .data-table { min-width: 1320px; }
  776. /* 图文列:左对齐,文案单行省略;可叠副标题 */
  777. .data-table .td-cell-media {
  778. text-align: left;
  779. max-width: 240px;
  780. vertical-align: middle;
  781. }
  782. .cell-media {
  783. display: flex;
  784. align-items: center;
  785. gap: 10px;
  786. min-width: 0;
  787. }
  788. .cell-media-img {
  789. width: 40px;
  790. height: 40px;
  791. border-radius: 8px;
  792. object-fit: cover;
  793. flex-shrink: 0;
  794. border: 1px solid var(--border);
  795. background: var(--row-alt);
  796. }
  797. .cell-media-stack {
  798. min-width: 0;
  799. flex: 1;
  800. display: flex;
  801. flex-direction: column;
  802. gap: 2px;
  803. }
  804. .cell-media-text {
  805. font-size: 13px;
  806. font-weight: 500;
  807. color: var(--text);
  808. overflow: hidden;
  809. text-overflow: ellipsis;
  810. white-space: nowrap;
  811. }
  812. .cell-media-sub {
  813. font-size: 12px;
  814. color: var(--text-muted);
  815. overflow: hidden;
  816. text-overflow: ellipsis;
  817. white-space: nowrap;
  818. }
  819. /* 表头居中;表体默认居中,长文本列用 .td-left */
  820. .data-table tbody td {
  821. text-align: center;
  822. vertical-align: middle;
  823. }
  824. .data-table .td-left {
  825. text-align: left;
  826. }
  827. .data-table .col-qty,
  828. .data-table .col-amt {
  829. font-variant-numeric: tabular-nums;
  830. white-space: nowrap;
  831. }
  832. /* 列表金额列:统一颜色,便于阅读 */
  833. .data-table tbody td.col-amt,
  834. .data-table tfoot .total-row td.col-amt {
  835. color: var(--primary-dark);
  836. font-weight: 600;
  837. }
  838. .view-subtable td.money-val {
  839. font-variant-numeric: tabular-nums;
  840. }
  841. .data-table tfoot .total-row td {
  842. text-align: center;
  843. font-weight: 600;
  844. color: var(--text);
  845. background: var(--primary-bg);
  846. border-top: 2px solid var(--primary);
  847. border-bottom: none;
  848. padding-top: 14px;
  849. padding-bottom: 14px;
  850. }
  851. .data-table tfoot .total-row .td-total-label {
  852. text-align: center;
  853. letter-spacing: 0.12em;
  854. }
  855. .data-table tfoot .total-row .td-check,
  856. .data-table tfoot .total-row .td-op {
  857. background: var(--primary-bg);
  858. }
  859. /* 表头:居中 + 纵向滚动时吸顶 */
  860. .data-table thead th {
  861. text-align: center;
  862. position: sticky;
  863. top: 0;
  864. z-index: 3;
  865. background: var(--table-head);
  866. box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
  867. }
  868. .data-table thead th.th-check {
  869. z-index: 6;
  870. box-shadow: 6px 0 10px -6px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(0, 0, 0, 0.06);
  871. }
  872. .data-table thead th.th-op {
  873. z-index: 5;
  874. box-shadow: -6px 0 10px -6px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(0, 0, 0, 0.06);
  875. }
  876. /* 首列多选:固定在左侧,横向滚动时仍可选 */
  877. th.th-check,
  878. td.td-check {
  879. position: sticky;
  880. left: 0;
  881. z-index: 2;
  882. width: 52px;
  883. min-width: 52px;
  884. max-width: 52px;
  885. padding: 12px 10px;
  886. text-align: center;
  887. vertical-align: middle;
  888. box-shadow: 6px 0 10px -6px rgba(0, 0, 0, 0.1);
  889. background: var(--surface);
  890. }
  891. tbody tr:nth-child(even) td.td-check { background: var(--row-alt); }
  892. tbody tr:hover td.td-check { background: var(--row-hover); }
  893. .th-check-inner {
  894. display: flex;
  895. align-items: center;
  896. justify-content: center;
  897. }
  898. .row-check {
  899. appearance: none;
  900. -webkit-appearance: none;
  901. width: 16px;
  902. height: 16px;
  903. margin: 0;
  904. cursor: pointer;
  905. flex-shrink: 0;
  906. border: 1px solid var(--border);
  907. border-radius: 4px;
  908. background: var(--surface);
  909. vertical-align: middle;
  910. position: relative;
  911. box-sizing: border-box;
  912. }
  913. .row-check:hover {
  914. border-color: var(--primary);
  915. }
  916. .row-check:focus-visible {
  917. outline: 2px solid var(--primary);
  918. outline-offset: 2px;
  919. }
  920. .row-check:checked {
  921. background: var(--primary);
  922. border-color: var(--primary);
  923. }
  924. .row-check:checked::after {
  925. content: "";
  926. position: absolute;
  927. left: 5px;
  928. top: 2px;
  929. width: 3px;
  930. height: 6px;
  931. border: solid #fff;
  932. border-width: 0 2px 2px 0;
  933. transform: rotate(45deg);
  934. box-sizing: border-box;
  935. }
  936. .row-check:indeterminate {
  937. background: var(--primary);
  938. border-color: var(--primary);
  939. }
  940. .row-check:indeterminate::after {
  941. content: "";
  942. position: absolute;
  943. left: 3px;
  944. top: 6px;
  945. width: 10px;
  946. height: 2px;
  947. background: #fff;
  948. border-radius: 1px;
  949. transform: none;
  950. border: none;
  951. }
  952. th.th-op,
  953. td.td-op {
  954. position: sticky;
  955. right: 0;
  956. left: auto;
  957. z-index: 2;
  958. width: auto;
  959. min-width: 196px;
  960. max-width: 220px;
  961. box-shadow: -6px 0 10px -6px rgba(0, 0, 0, 0.1);
  962. background: var(--surface);
  963. vertical-align: middle;
  964. text-align: center;
  965. }
  966. tbody tr:nth-child(even) td.td-op { background: var(--row-alt); }
  967. tbody tr:hover td.td-op { background: var(--row-hover); }
  968. .cell-ops {
  969. display: flex;
  970. flex-direction: row;
  971. flex-wrap: nowrap;
  972. align-items: center;
  973. gap: 6px;
  974. justify-content: center;
  975. }
  976. .btn-op {
  977. height: 28px;
  978. padding: 0 8px;
  979. flex-shrink: 0;
  980. white-space: nowrap;
  981. font-size: 12px;
  982. font-weight: 600;
  983. border-radius: 6px;
  984. border: 1px solid var(--border);
  985. background: var(--surface);
  986. cursor: pointer;
  987. font-family: inherit;
  988. color: var(--text);
  989. display: inline-flex;
  990. align-items: center;
  991. gap: 4px;
  992. }
  993. .btn-op:hover { background: var(--primary-bg); border-color: var(--primary); color: var(--primary-dark); }
  994. .btn-op.primary { background: var(--primary); border-color: var(--primary); color: #fff; }
  995. .btn-op.primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); color: #fff; }
  996. /* 主状态用品牌绿;次要状态用中性灰,避免一屏多种「彩」 */
  997. .tag {
  998. display: inline-block;
  999. padding: 2px 8px;
  1000. border-radius: 6px;
  1001. font-size: 12px;
  1002. font-weight: 600;
  1003. background: var(--primary-bg);
  1004. color: var(--primary-dark);
  1005. }
  1006. .tag.muted {
  1007. background: var(--table-head);
  1008. color: var(--text-muted);
  1009. font-weight: 500;
  1010. }
  1011. /* 树形列表:父表与子表列定义完全分离,子表为内嵌独立表格 */
  1012. .tree-panel-note {
  1013. margin: 0 0 12px;
  1014. padding: 10px 14px;
  1015. font-size: 13px;
  1016. color: var(--text-muted);
  1017. background: var(--primary-bg);
  1018. border-radius: 8px;
  1019. border: 1px solid rgba(16, 185, 129, 0.2);
  1020. line-height: 1.5;
  1021. }
  1022. .tree-parent-grid {
  1023. min-width: 920px;
  1024. table-layout: fixed;
  1025. }
  1026. table.data-table.tree-parent-grid {
  1027. min-width: 920px;
  1028. }
  1029. .tree-child-table .th-op,
  1030. .tree-child-table td.td-op {
  1031. position: static;
  1032. left: auto;
  1033. right: auto;
  1034. box-shadow: none;
  1035. min-width: 0;
  1036. max-width: none;
  1037. }
  1038. .tree-parent-grid thead th {
  1039. text-align: center;
  1040. font-size: 12px;
  1041. color: var(--text-muted);
  1042. }
  1043. .tree-parent-grid thead th.th-tree-parent-left {
  1044. text-align: left;
  1045. }
  1046. .tree-parent-grid tbody tr.tree-row-parent td {
  1047. vertical-align: middle;
  1048. background: var(--surface);
  1049. font-weight: 500;
  1050. }
  1051. .tree-parent-grid tbody tr.tree-row-parent:hover td {
  1052. background: var(--primary-bg);
  1053. }
  1054. .tree-row-children-wrap td.td-tree-nested {
  1055. padding: 0 !important;
  1056. border-bottom: 1px solid var(--border);
  1057. vertical-align: top;
  1058. background: #f8fafc;
  1059. }
  1060. .tree-nested-inner {
  1061. padding: 12px 12px 14px 16px;
  1062. border-left: 3px solid var(--primary);
  1063. margin: 0;
  1064. }
  1065. .tree-nested-label {
  1066. font-size: 12px;
  1067. font-weight: 700;
  1068. color: var(--text-muted);
  1069. margin: 0 0 8px;
  1070. letter-spacing: 0.02em;
  1071. }
  1072. .tree-child-table {
  1073. width: 100%;
  1074. border-collapse: collapse;
  1075. font-size: 13px;
  1076. background: var(--surface);
  1077. border: 1px solid var(--border);
  1078. border-radius: 8px;
  1079. overflow: hidden;
  1080. }
  1081. .tree-child-table thead th {
  1082. padding: 8px 10px;
  1083. text-align: center;
  1084. font-size: 11px;
  1085. font-weight: 600;
  1086. color: var(--text-muted);
  1087. background: #e2e8f0;
  1088. border-bottom: 1px solid var(--border);
  1089. }
  1090. .tree-child-table thead th.th-child-left {
  1091. text-align: left;
  1092. }
  1093. .tree-child-table tbody td {
  1094. padding: 8px 10px;
  1095. text-align: center;
  1096. border-bottom: 1px solid var(--border);
  1097. vertical-align: middle;
  1098. background: var(--surface);
  1099. }
  1100. .tree-child-table tbody tr:last-child td {
  1101. border-bottom: none;
  1102. }
  1103. .tree-child-table tbody tr:hover td {
  1104. background: #f1f5f9;
  1105. }
  1106. .tree-child-table tbody td.td-child-left {
  1107. text-align: left;
  1108. }
  1109. .tree-child-table .col-amt {
  1110. font-variant-numeric: tabular-nums;
  1111. color: var(--primary-dark);
  1112. font-weight: 600;
  1113. }
  1114. .tree-row-children-wrap.is-hidden {
  1115. display: none !important;
  1116. }
  1117. /* 孙列表:再嵌套一层独立表格,列与父表、子表均不同 */
  1118. .tree-nested-deep {
  1119. margin-top: 4px;
  1120. padding-left: 14px !important;
  1121. border-left: 3px solid #f59e0b;
  1122. background: linear-gradient(90deg, rgba(254, 243, 199, 0.35) 0%, transparent 12px);
  1123. }
  1124. .tree-nested-deep .tree-nested-label {
  1125. color: #b45309;
  1126. }
  1127. .td-tree-nested-l2 {
  1128. padding: 0 !important;
  1129. border-bottom: none !important;
  1130. background: #fffbeb !important;
  1131. vertical-align: top;
  1132. }
  1133. .tree-l2-wrap.is-hidden {
  1134. display: none !important;
  1135. }
  1136. .tree-grandchild-table {
  1137. width: 100%;
  1138. border-collapse: collapse;
  1139. font-size: 12px;
  1140. background: var(--surface);
  1141. border: 1px solid #fcd34d;
  1142. border-radius: 8px;
  1143. overflow: hidden;
  1144. }
  1145. .tree-grandchild-table thead th {
  1146. padding: 6px 8px;
  1147. text-align: center;
  1148. font-size: 10px;
  1149. font-weight: 600;
  1150. color: #92400e;
  1151. background: #fef3c7;
  1152. border-bottom: 1px solid #fcd34d;
  1153. }
  1154. .tree-grandchild-table thead th.th-gc-left {
  1155. text-align: left;
  1156. }
  1157. .tree-grandchild-table tbody td {
  1158. padding: 6px 8px;
  1159. text-align: center;
  1160. border-bottom: 1px solid #fef3c7;
  1161. vertical-align: middle;
  1162. background: #fff;
  1163. }
  1164. .tree-grandchild-table tbody tr:last-child td {
  1165. border-bottom: none;
  1166. }
  1167. .tree-grandchild-table tbody tr:hover td {
  1168. background: #fffbeb;
  1169. }
  1170. .tree-grandchild-table .td-gc-left {
  1171. text-align: left;
  1172. }
  1173. .tree-grandchild-table .col-amt {
  1174. font-variant-numeric: tabular-nums;
  1175. color: #b45309;
  1176. font-weight: 600;
  1177. }
  1178. .tree-grandchild-table .th-op,
  1179. .tree-grandchild-table td.td-op {
  1180. position: static;
  1181. box-shadow: none;
  1182. min-width: 0;
  1183. }
  1184. .tree-grandchild-table .td-tree-lead {
  1185. min-width: 80px;
  1186. width: auto;
  1187. padding: 6px 8px !important;
  1188. text-align: left !important;
  1189. }
  1190. .tree-grandchild-table .td-tree-lead .td-tree-lead-inner {
  1191. justify-content: flex-start;
  1192. }
  1193. .tree-expand-nested {
  1194. width: 24px;
  1195. height: 24px;
  1196. }
  1197. /* 无下级时占位,与展开按钮同宽,保证勾选对齐 */
  1198. .tree-expand-spacer {
  1199. display: inline-block;
  1200. width: 24px;
  1201. height: 24px;
  1202. flex-shrink: 0;
  1203. }
  1204. .tree-child-table .td-tree-lead {
  1205. min-width: 108px;
  1206. width: auto;
  1207. }
  1208. .tree-cb-grandchild {
  1209. appearance: none;
  1210. -webkit-appearance: none;
  1211. width: 14px;
  1212. height: 14px;
  1213. margin: 0;
  1214. cursor: pointer;
  1215. flex-shrink: 0;
  1216. border: 1px dashed #d97706;
  1217. border-radius: 3px;
  1218. background: #fffbeb;
  1219. vertical-align: middle;
  1220. position: relative;
  1221. box-sizing: border-box;
  1222. }
  1223. .tree-cb-grandchild:hover {
  1224. border-style: solid;
  1225. border-color: #f59e0b;
  1226. }
  1227. .tree-cb-grandchild:checked {
  1228. background: #f59e0b;
  1229. border-color: #f59e0b;
  1230. border-style: solid;
  1231. }
  1232. .tree-cb-grandchild:checked::after {
  1233. content: "";
  1234. position: absolute;
  1235. left: 3px;
  1236. top: 0px;
  1237. width: 3px;
  1238. height: 6px;
  1239. border: solid #fff;
  1240. border-width: 0 2px 2px 0;
  1241. transform: rotate(45deg);
  1242. }
  1243. .td-tree-lead {
  1244. width: 120px;
  1245. min-width: 120px;
  1246. padding: 10px 12px !important;
  1247. text-align: left !important;
  1248. }
  1249. .td-tree-lead-inner {
  1250. display: flex;
  1251. align-items: center;
  1252. gap: 8px;
  1253. }
  1254. .tree-expand {
  1255. flex-shrink: 0;
  1256. width: 28px;
  1257. height: 28px;
  1258. padding: 0;
  1259. border: 1px solid var(--border);
  1260. border-radius: 6px;
  1261. background: var(--surface);
  1262. cursor: pointer;
  1263. display: inline-flex;
  1264. align-items: center;
  1265. justify-content: center;
  1266. color: var(--text-muted);
  1267. transition: transform 0.2s ease, background 0.15s;
  1268. }
  1269. .tree-expand:hover {
  1270. border-color: var(--primary);
  1271. color: var(--primary-dark);
  1272. background: var(--primary-bg);
  1273. }
  1274. .tree-expand[aria-expanded="false"] .tree-expand-ic {
  1275. transform: rotate(-90deg);
  1276. }
  1277. .tree-expand-ic {
  1278. display: block;
  1279. transition: transform 0.2s ease;
  1280. }
  1281. /* 父行专用勾选:尺寸与主色描边,与平面列表的 .row-check 区分 */
  1282. .tree-cb-parent {
  1283. appearance: none;
  1284. -webkit-appearance: none;
  1285. width: 18px;
  1286. height: 18px;
  1287. margin: 0;
  1288. cursor: pointer;
  1289. flex-shrink: 0;
  1290. border: 2px solid var(--primary);
  1291. border-radius: 5px;
  1292. background: var(--surface);
  1293. vertical-align: middle;
  1294. position: relative;
  1295. box-sizing: border-box;
  1296. }
  1297. .tree-cb-parent:checked {
  1298. background: var(--primary);
  1299. border-color: var(--primary);
  1300. }
  1301. .tree-cb-parent:checked::after {
  1302. content: "";
  1303. position: absolute;
  1304. left: 5px;
  1305. top: 2px;
  1306. width: 4px;
  1307. height: 8px;
  1308. border: solid #fff;
  1309. border-width: 0 2px 2px 0;
  1310. transform: rotate(45deg);
  1311. }
  1312. .tree-cb-child {
  1313. appearance: none;
  1314. -webkit-appearance: none;
  1315. width: 15px;
  1316. height: 15px;
  1317. margin: 0;
  1318. cursor: pointer;
  1319. flex-shrink: 0;
  1320. border: 1px solid #94a3b8;
  1321. border-radius: 4px;
  1322. background: #fff;
  1323. vertical-align: middle;
  1324. position: relative;
  1325. box-sizing: border-box;
  1326. }
  1327. .tree-cb-child:hover {
  1328. border-color: var(--primary);
  1329. }
  1330. .tree-cb-child:checked {
  1331. background: #64748b;
  1332. border-color: #64748b;
  1333. }
  1334. .tree-cb-child:checked::after {
  1335. content: "";
  1336. position: absolute;
  1337. left: 4px;
  1338. top: 1px;
  1339. width: 3px;
  1340. height: 6px;
  1341. border: solid #fff;
  1342. border-width: 0 2px 2px 0;
  1343. transform: rotate(45deg);
  1344. }
  1345. .tree-badge {
  1346. display: inline-block;
  1347. margin-left: 8px;
  1348. padding: 1px 6px;
  1349. font-size: 11px;
  1350. font-weight: 700;
  1351. border-radius: 4px;
  1352. vertical-align: middle;
  1353. }
  1354. .tree-badge.parent {
  1355. background: var(--primary-bg);
  1356. color: var(--primary-dark);
  1357. }
  1358. .tree-badge.child {
  1359. background: #e2e8f0;
  1360. color: #475569;
  1361. }
  1362. .backdrop {
  1363. position: fixed;
  1364. inset: 0;
  1365. background: rgba(15, 23, 42, 0.35);
  1366. display: none;
  1367. align-items: center;
  1368. justify-content: center;
  1369. z-index: 50;
  1370. padding: 16px;
  1371. }
  1372. .backdrop.open { display: flex; align-items: flex-start; justify-content: center; padding-top: 32px; padding-bottom: 32px; }
  1373. .dialog {
  1374. width: 100%;
  1375. max-width: 400px;
  1376. background: var(--surface);
  1377. border-radius: var(--radius);
  1378. border: 1px solid var(--border);
  1379. box-shadow: 0 16px 48px rgba(0, 0, 0, 0.12);
  1380. padding: 20px;
  1381. }
  1382. .dialog.dialog-lg {
  1383. max-width: 720px;
  1384. max-height: calc(100vh - 64px);
  1385. overflow: hidden;
  1386. display: flex;
  1387. flex-direction: column;
  1388. padding: 0;
  1389. }
  1390. .dialog.dialog-view-xl {
  1391. max-width: 920px;
  1392. width: 100%;
  1393. max-height: calc(100vh - 48px);
  1394. overflow: hidden;
  1395. display: flex;
  1396. flex-direction: column;
  1397. padding: 0;
  1398. }
  1399. .dialog-view-xl .dialog-scroll {
  1400. overflow-y: auto;
  1401. padding: 0;
  1402. flex: 1;
  1403. }
  1404. .view-hero {
  1405. display: flex;
  1406. flex-wrap: wrap;
  1407. align-items: flex-start;
  1408. gap: 16px;
  1409. padding: 20px 22px;
  1410. background: linear-gradient(135deg, var(--primary-bg) 0%, var(--surface) 55%);
  1411. border-bottom: 1px solid var(--border);
  1412. }
  1413. .view-hero-img {
  1414. width: 72px;
  1415. height: 72px;
  1416. border-radius: 12px;
  1417. object-fit: cover;
  1418. border: 1px solid var(--border);
  1419. flex-shrink: 0;
  1420. }
  1421. .view-hero-main { flex: 1; min-width: 200px; }
  1422. .view-hero-main h2 {
  1423. margin: 0 0 8px;
  1424. font-size: 18px;
  1425. font-weight: 700;
  1426. letter-spacing: -0.02em;
  1427. }
  1428. .view-hero-tags {
  1429. display: flex;
  1430. flex-wrap: wrap;
  1431. gap: 8px;
  1432. align-items: center;
  1433. }
  1434. .view-hero-meta {
  1435. margin: 10px 0 0;
  1436. font-size: 13px;
  1437. color: var(--text-muted);
  1438. line-height: 1.5;
  1439. }
  1440. .view-section {
  1441. padding: 18px 22px;
  1442. border-bottom: 1px solid var(--border);
  1443. }
  1444. .view-section:last-child { border-bottom: none; }
  1445. .view-section h3 {
  1446. margin: 0 0 14px;
  1447. font-size: 13px;
  1448. font-weight: 700;
  1449. color: var(--text-muted);
  1450. letter-spacing: 0.06em;
  1451. }
  1452. .view-kv-grid {
  1453. display: grid;
  1454. grid-template-columns: repeat(2, 1fr);
  1455. gap: 12px 28px;
  1456. }
  1457. @media (max-width: 640px) {
  1458. .view-kv-grid { grid-template-columns: 1fr; }
  1459. }
  1460. .view-kv {
  1461. display: grid;
  1462. grid-template-columns: 96px 1fr;
  1463. gap: 8px 12px;
  1464. font-size: 13px;
  1465. align-items: start;
  1466. }
  1467. .view-kv dt {
  1468. margin: 0;
  1469. color: var(--text-muted);
  1470. font-weight: 500;
  1471. }
  1472. .view-kv dd {
  1473. margin: 0;
  1474. color: var(--text);
  1475. word-break: break-word;
  1476. }
  1477. .view-bento {
  1478. display: grid;
  1479. grid-template-columns: 1fr 1fr;
  1480. gap: 12px;
  1481. }
  1482. @media (max-width: 640px) {
  1483. .view-bento { grid-template-columns: 1fr; }
  1484. }
  1485. .view-card {
  1486. border: 1px solid var(--border);
  1487. border-radius: var(--radius);
  1488. padding: 14px 16px;
  1489. background: var(--surface);
  1490. }
  1491. .view-card h4 {
  1492. margin: 0 0 8px;
  1493. font-size: 12px;
  1494. font-weight: 700;
  1495. color: var(--text-muted);
  1496. }
  1497. .view-card p {
  1498. margin: 0;
  1499. font-size: 13px;
  1500. color: var(--text);
  1501. line-height: 1.55;
  1502. }
  1503. .view-timeline {
  1504. position: relative;
  1505. padding-left: 18px;
  1506. border-left: 2px solid var(--border);
  1507. margin-left: 8px;
  1508. }
  1509. .view-tl-item {
  1510. position: relative;
  1511. padding-bottom: 18px;
  1512. font-size: 13px;
  1513. }
  1514. .view-tl-item:last-child { padding-bottom: 0; }
  1515. .view-tl-item::before {
  1516. content: "";
  1517. position: absolute;
  1518. left: -25px;
  1519. top: 4px;
  1520. width: 10px;
  1521. height: 10px;
  1522. border-radius: 50%;
  1523. background: var(--primary);
  1524. border: 2px solid var(--surface);
  1525. box-shadow: 0 0 0 1px var(--primary);
  1526. }
  1527. .view-tl-item.muted::before {
  1528. background: var(--border);
  1529. box-shadow: none;
  1530. }
  1531. .view-tl-time { font-size: 12px; color: var(--text-muted); margin-bottom: 4px; }
  1532. .view-tl-title { font-weight: 600; color: var(--text); }
  1533. .view-tl-desc { color: var(--text-muted); margin-top: 4px; line-height: 1.45; }
  1534. .view-gallery {
  1535. display: grid;
  1536. grid-template-columns: repeat(4, 1fr);
  1537. gap: 10px;
  1538. }
  1539. @media (max-width: 640px) {
  1540. .view-gallery { grid-template-columns: repeat(2, 1fr); }
  1541. }
  1542. .view-gallery a {
  1543. display: block;
  1544. border-radius: 8px;
  1545. overflow: hidden;
  1546. border: 1px solid var(--border);
  1547. aspect-ratio: 4/3;
  1548. }
  1549. .view-gallery img {
  1550. width: 100%;
  1551. height: 100%;
  1552. object-fit: cover;
  1553. display: block;
  1554. }
  1555. .view-route-bar {
  1556. height: 120px;
  1557. border-radius: var(--radius);
  1558. background: linear-gradient(90deg, #ecfdf5 0%, #d1fae5 40%, #a7f3d0 100%);
  1559. border: 1px solid var(--border);
  1560. position: relative;
  1561. display: flex;
  1562. align-items: center;
  1563. justify-content: space-between;
  1564. padding: 0 24px;
  1565. margin-bottom: 12px;
  1566. }
  1567. .view-route-pin {
  1568. text-align: center;
  1569. font-size: 12px;
  1570. color: var(--text);
  1571. max-width: 120px;
  1572. }
  1573. .view-route-pin strong { display: block; font-size: 13px; margin-bottom: 4px; }
  1574. .view-route-line {
  1575. flex: 1;
  1576. height: 3px;
  1577. background: var(--primary);
  1578. opacity: 0.45;
  1579. margin: 0 12px;
  1580. border-radius: 2px;
  1581. }
  1582. .view-subtable-wrap {
  1583. border: 1px solid var(--border);
  1584. border-radius: 8px;
  1585. overflow: hidden;
  1586. }
  1587. .view-subtable {
  1588. width: 100%;
  1589. border-collapse: collapse;
  1590. font-size: 12px;
  1591. }
  1592. .view-subtable th,
  1593. .view-subtable td {
  1594. padding: 8px 12px;
  1595. text-align: center;
  1596. border-bottom: 1px solid var(--border);
  1597. }
  1598. .view-subtable th {
  1599. background: var(--table-head);
  1600. color: var(--text-muted);
  1601. font-weight: 600;
  1602. }
  1603. .view-subtable tr:last-child td { border-bottom: none; }
  1604. .view-subtable .ta-left { text-align: left; }
  1605. .dialog-lg .dialog-scroll {
  1606. overflow-y: auto;
  1607. padding: 20px 22px;
  1608. flex: 1;
  1609. }
  1610. .dialog-lg .dialog-head {
  1611. padding: 16px 22px;
  1612. border-bottom: 1px solid var(--border);
  1613. flex-shrink: 0;
  1614. }
  1615. .dialog-lg .dialog-foot,
  1616. .dialog-view-xl .dialog-foot {
  1617. padding: 12px 22px;
  1618. border-top: 1px solid var(--border);
  1619. display: flex;
  1620. justify-content: flex-end;
  1621. gap: 8px;
  1622. flex-shrink: 0;
  1623. background: var(--table-head);
  1624. }
  1625. .dialog h3 { margin: 0 0 6px; font-size: 17px; font-weight: 600; }
  1626. .dialog .note { margin: 0 0 16px; font-size: 13px; color: var(--text-muted); }
  1627. .field { margin-bottom: 12px; }
  1628. .field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; }
  1629. .field .input { width: 100%; min-width: 0; }
  1630. .dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
  1631. .form-section {
  1632. margin-bottom: 20px;
  1633. padding-bottom: 16px;
  1634. border-bottom: 1px dashed var(--border);
  1635. }
  1636. .form-section:last-of-type { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
  1637. .form-section h4 {
  1638. margin: 0 0 12px;
  1639. font-size: 13px;
  1640. font-weight: 700;
  1641. color: var(--text-muted);
  1642. letter-spacing: 0.04em;
  1643. }
  1644. .form-grid-2 {
  1645. display: grid;
  1646. grid-template-columns: 1fr 1fr;
  1647. gap: 12px 16px;
  1648. }
  1649. @media (max-width: 560px) {
  1650. .form-grid-2 { grid-template-columns: 1fr; }
  1651. }
  1652. .textarea {
  1653. width: 100%;
  1654. min-height: 72px;
  1655. padding: 10px 12px;
  1656. border: 1px solid var(--border);
  1657. border-radius: 8px;
  1658. font-size: 14px;
  1659. font-family: inherit;
  1660. resize: vertical;
  1661. }
  1662. .textarea:focus {
  1663. outline: none;
  1664. border-color: var(--primary);
  1665. box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
  1666. }
  1667. .upload-zone {
  1668. border: 1px dashed var(--border);
  1669. border-radius: 8px;
  1670. padding: 16px;
  1671. text-align: center;
  1672. background: var(--row-alt);
  1673. cursor: pointer;
  1674. transition: border-color 0.15s, background 0.15s;
  1675. }
  1676. .upload-zone:hover { border-color: var(--primary); background: var(--primary-bg); }
  1677. .upload-zone input[type="file"] { position: absolute; width: 0; height: 0; opacity: 0; pointer-events: none; }
  1678. .upload-zone-text { font-size: 13px; color: var(--text-muted); margin: 0; }
  1679. .upload-zone-btn { margin-top: 8px; }
  1680. .upload-preview {
  1681. display: flex;
  1682. flex-wrap: wrap;
  1683. gap: 10px;
  1684. margin-top: 12px;
  1685. }
  1686. .upload-item {
  1687. position: relative;
  1688. width: 72px;
  1689. height: 72px;
  1690. border-radius: 8px;
  1691. overflow: hidden;
  1692. border: 1px solid var(--border);
  1693. background: var(--surface);
  1694. }
  1695. .upload-item img {
  1696. width: 100%;
  1697. height: 100%;
  1698. object-fit: cover;
  1699. display: block;
  1700. }
  1701. .upload-item .rm {
  1702. position: absolute;
  1703. top: 2px;
  1704. right: 2px;
  1705. width: 20px;
  1706. height: 20px;
  1707. border: none;
  1708. border-radius: 4px;
  1709. background: rgba(0, 0, 0, 0.55);
  1710. color: #fff;
  1711. font-size: 14px;
  1712. line-height: 1;
  1713. cursor: pointer;
  1714. padding: 0;
  1715. }
  1716. .mini-table-wrap {
  1717. border: 1px solid var(--border);
  1718. border-radius: 8px;
  1719. overflow: hidden;
  1720. }
  1721. .mini-table {
  1722. width: 100%;
  1723. border-collapse: collapse;
  1724. font-size: 13px;
  1725. }
  1726. .mini-table th {
  1727. background: var(--table-head);
  1728. padding: 8px 10px;
  1729. text-align: left;
  1730. font-size: 11px;
  1731. font-weight: 600;
  1732. color: var(--text-muted);
  1733. border-bottom: 1px solid var(--border);
  1734. }
  1735. .mini-table td {
  1736. padding: 6px 8px;
  1737. border-bottom: 1px solid var(--border);
  1738. vertical-align: middle;
  1739. }
  1740. .mini-table tr:last-child td { border-bottom: none; }
  1741. .mini-table .input {
  1742. min-width: 0;
  1743. width: 100%;
  1744. height: 32px;
  1745. font-size: 13px;
  1746. }
  1747. .mini-table .input-clear-wrap .input {
  1748. padding-right: 30px;
  1749. height: 32px;
  1750. }
  1751. .mini-table .input-clear {
  1752. right: 2px;
  1753. width: 26px;
  1754. height: 26px;
  1755. }
  1756. .mini-table .btn-icon {
  1757. height: 28px;
  1758. padding: 0 8px;
  1759. font-size: 12px;
  1760. }
  1761. </style>
  1762. </head>
  1763. <body>
  1764. <div class="top-progress-bar" id="topProgressBar" aria-hidden="true"></div>
  1765. <div class="layout">
  1766. <aside class="sidebar">
  1767. <div class="logo">叁强<span>鲜配</span></div>
  1768. <nav class="nav" aria-label="主导航">
  1769. <details class="nav-details" open>
  1770. <summary class="nav-summary">工作台</summary>
  1771. <div class="nav-sub">
  1772. <a href="#" data-tab-id="overview">运营概览</a>
  1773. </div>
  1774. </details>
  1775. <details class="nav-details" open>
  1776. <summary class="nav-summary">业务管理</summary>
  1777. <div class="nav-sub">
  1778. <a href="#" data-tab-id="order">订单管理</a>
  1779. <a class="active" href="#" data-tab-id="delivery">配送调度</a>
  1780. <a href="#" data-tab-id="product">商品中心</a>
  1781. <a href="#" data-tab-id="warehouse">仓储与库存</a>
  1782. </div>
  1783. </details>
  1784. <details class="nav-details" open>
  1785. <summary class="nav-summary">数据中心</summary>
  1786. <div class="nav-sub">
  1787. <a href="#" data-tab-id="report">报表中心</a>
  1788. <a href="#" data-tab-id="analysis">经营分析</a>
  1789. </div>
  1790. </details>
  1791. <details class="nav-details" open>
  1792. <summary class="nav-summary">系统设置</summary>
  1793. <div class="nav-sub">
  1794. <a href="#" data-tab-id="user">用户与权限</a>
  1795. <a href="#" data-tab-id="dict">参数与字典</a>
  1796. <a href="#" data-tab-id="log">操作日志</a>
  1797. </div>
  1798. </details>
  1799. </nav>
  1800. <div class="nav-foot"><a href="./index.html">返回配色入口</a></div>
  1801. </aside>
  1802. <div class="main">
  1803. <header class="header">
  1804. <div class="crumb">工作台 / <b id="crumbCurrent">配送调度</b></div>
  1805. </header>
  1806. <main class="content">
  1807. <div class="tabs-strip" id="tabsStrip" role="tablist" aria-label="已打开页面"></div>
  1808. <div class="tab-panel-mount" id="tabPanelMount">
  1809. <div class="tab-panel is-active" data-tab-id="delivery" id="tabPanelDelivery">
  1810. <header class="page-head">
  1811. <h1>配送调度</h1>
  1812. <p class="desc">点击左侧菜单会在上方打开对应 Tab,并重新加载该页内容(演示)。按钮操作带有加载动效。</p>
  1813. <p class="page-refresh-meta" style="margin:8px 0 0;font-size:12px;color:var(--text-muted)">
  1814. 最近加载:<time id="deliveryRefreshedAt">—</time>
  1815. </p>
  1816. </header>
  1817. <section class="stats" aria-label="概览">
  1818. <div class="stat">
  1819. <div class="stat-hd">
  1820. <span class="stat-ic" aria-hidden="true">
  1821. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18V6a2 2 0 00-2-2H4a2 2 0 00-2 2v11a1 1 0 001 1h2"/><path d="M15 18h2"/><path d="M19 18h2v-3.34a1 1 0 00-.14-.51l-2.86-4.65a1 1 0 00-.86-.5H14"/><circle cx="7" cy="18" r="2"/><circle cx="17" cy="18" r="2"/></svg>
  1822. </span>
  1823. </div>
  1824. <div class="stat-main">
  1825. <div class="k">在途车辆</div>
  1826. <div class="stat-side">
  1827. <div class="v">42</div>
  1828. <div class="hint">较昨日 +3</div>
  1829. </div>
  1830. </div>
  1831. </div>
  1832. <div class="stat">
  1833. <div class="stat-hd">
  1834. <span class="stat-ic" aria-hidden="true">
  1835. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/></svg>
  1836. </span>
  1837. </div>
  1838. <div class="stat-main">
  1839. <div class="k">待分配</div>
  1840. <div class="stat-side">
  1841. <div class="v">18</div>
  1842. <div class="hint">30 分钟内处理</div>
  1843. </div>
  1844. </div>
  1845. </div>
  1846. <div class="stat">
  1847. <div class="stat-hd">
  1848. <span class="stat-ic" aria-hidden="true">
  1849. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
  1850. </span>
  1851. </div>
  1852. <div class="stat-main">
  1853. <div class="k">准时送达</div>
  1854. <div class="stat-side">
  1855. <div class="v">97%</div>
  1856. <div class="hint">目标 ≥ 96%</div>
  1857. </div>
  1858. </div>
  1859. </div>
  1860. <div class="stat warn">
  1861. <div class="stat-hd">
  1862. <span class="stat-ic" aria-hidden="true">
  1863. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
  1864. </span>
  1865. </div>
  1866. <div class="stat-main">
  1867. <div class="k">异常告警</div>
  1868. <div class="stat-side">
  1869. <div class="v">4</div>
  1870. <div class="hint">待处理</div>
  1871. </div>
  1872. </div>
  1873. </div>
  1874. <div class="stat">
  1875. <div class="stat-hd">
  1876. <span class="stat-ic" aria-hidden="true">
  1877. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 17l6-6 4 4 8-8"/><path d="M21 10V4h-6"/></svg>
  1878. </span>
  1879. </div>
  1880. <div class="stat-main">
  1881. <div class="k">平均履约时长</div>
  1882. <div class="stat-side">
  1883. <div class="v">42m</div>
  1884. <div class="hint">环比 -5.1%</div>
  1885. </div>
  1886. </div>
  1887. </div>
  1888. <div class="stat">
  1889. <div class="stat-hd">
  1890. <span class="stat-ic" aria-hidden="true">
  1891. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M16 3v4M8 3v4"/><path d="M3 12h18"/></svg>
  1892. </span>
  1893. </div>
  1894. <div class="stat-main">
  1895. <div class="k">今日交易额</div>
  1896. <div class="stat-side">
  1897. <div class="v">¥128k</div>
  1898. <div class="hint">较昨日 +6.2%</div>
  1899. </div>
  1900. </div>
  1901. </div>
  1902. </section>
  1903. <section class="panel">
  1904. <div class="panel-top">
  1905. <h2 style="margin:0">
  1906. <svg class="panel-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg>
  1907. 任务列表
  1908. </h2>
  1909. </div>
  1910. <div class="filter-block">
  1911. <form class="filter-form" action="#" method="get" onsubmit="return false;">
  1912. <p class="filter-hint">默认展示常用条件;特殊场景可展开全部。</p>
  1913. <div class="filter-grid">
  1914. <div class="filter-field">
  1915. <label for="f-no">任务号</label>
  1916. <div class="input-clear-wrap">
  1917. <input id="f-no" type="text" class="input" placeholder="精确或模糊" autocomplete="off" />
  1918. <button type="button" class="input-clear" aria-label="清除任务号" title="清除">
  1919. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>
  1920. </button>
  1921. </div>
  1922. </div>
  1923. <div class="filter-field">
  1924. <label for="f-route">线路 / 站点</label>
  1925. <div class="input-clear-wrap">
  1926. <input id="f-route" type="text" class="input" placeholder="关键词" autocomplete="off" />
  1927. <button type="button" class="input-clear" aria-label="清除线路关键词" title="清除">
  1928. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>
  1929. </button>
  1930. </div>
  1931. </div>
  1932. <div class="filter-field">
  1933. <label for="f-status">任务状态</label>
  1934. <select id="f-status" class="select" aria-label="任务状态">
  1935. <option value="">全部</option>
  1936. <option value="1">待分配</option>
  1937. <option value="2">执行中</option>
  1938. <option value="3">待装车</option>
  1939. <option value="4">已完成</option>
  1940. <option value="5">已取消</option>
  1941. </select>
  1942. </div>
  1943. <div class="filter-field">
  1944. <label for="f-driver">司机</label>
  1945. <select id="f-driver" class="select" aria-label="司机">
  1946. <option value="">全部</option>
  1947. <option value="wq">王强</option>
  1948. <option value="lm">李敏</option>
  1949. <option value="zs">张三</option>
  1950. </select>
  1951. </div>
  1952. </div>
  1953. <div id="filter-extra" class="filter-extra" hidden>
  1954. <div class="filter-grid">
  1955. <div class="filter-field">
  1956. <label for="f-region">所属区域</label>
  1957. <select id="f-region" class="select" aria-label="所属区域">
  1958. <option value="">全部</option>
  1959. <option value="hd">华东</option>
  1960. <option value="hn">华南</option>
  1961. <option value="hb">华北</option>
  1962. </select>
  1963. </div>
  1964. <div class="filter-field">
  1965. <label for="f-priority">优先级</label>
  1966. <select id="f-priority" class="select" aria-label="优先级">
  1967. <option value="">全部</option>
  1968. <option value="high">高</option>
  1969. <option value="mid">中</option>
  1970. <option value="low">低</option>
  1971. </select>
  1972. </div>
  1973. <div class="filter-field">
  1974. <label for="f-date-from">计划日期从</label>
  1975. <input id="f-date-from" type="date" class="input" />
  1976. </div>
  1977. <div class="filter-field">
  1978. <label for="f-date-to">计划日期至</label>
  1979. <input id="f-date-to" type="date" class="input" />
  1980. </div>
  1981. </div>
  1982. </div>
  1983. <div class="filter-actions">
  1984. <button
  1985. type="button"
  1986. class="btn btn-link"
  1987. id="toggleFilter"
  1988. aria-expanded="false"
  1989. aria-controls="filter-extra"
  1990. >
  1991. 展开更多条件
  1992. </button>
  1993. <div class="filter-actions-end">
  1994. <button type="reset" class="btn">
  1995. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
  1996. 重置条件
  1997. </button>
  1998. <button type="submit" class="btn btn-primary">
  1999. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
  2000. 查询
  2001. </button>
  2002. </div>
  2003. </div>
  2004. </form>
  2005. </div>
  2006. <div class="list-body">
  2007. <div class="list-toolbar">
  2008. <div class="list-toolbar-row" role="toolbar" aria-label="列表操作">
  2009. <span class="list-toolbar-selected" id="selectedCountBadge" aria-live="polite">已选<span class="sel-num" id="selectedCountNum">0</span>条</span>
  2010. <button type="button" class="btn btn-primary" id="btnNew">
  2011. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
  2012. 新增任务
  2013. </button>
  2014. <button type="button" class="btn" id="btnBatchDelete">
  2015. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
  2016. 批量删除
  2017. </button>
  2018. <button type="button" class="btn" id="btnImport">
  2019. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
  2020. 导入
  2021. </button>
  2022. <input type="file" id="importFileInput" accept=".xlsx,.xls,.csv" style="display:none" aria-hidden="true" />
  2023. <button type="button" class="btn" id="btnExportList">
  2024. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
  2025. 导出
  2026. </button>
  2027. <button type="button" class="btn">
  2028. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg>
  2029. 分配司机
  2030. </button>
  2031. <button type="button" class="btn">
  2032. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
  2033. 标记完成
  2034. </button>
  2035. <button type="button" class="btn">
  2036. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
  2037. 导出所选
  2038. </button>
  2039. </div>
  2040. </div>
  2041. <div class="table-scroll">
  2042. <table class="data-table">
  2043. <thead>
  2044. <tr>
  2045. <th class="th-check" scope="col">
  2046. <div class="th-check-inner">
  2047. <input type="checkbox" class="row-check" id="tableCheckAll" aria-label="全选当前页" title="全选" />
  2048. </div>
  2049. </th>
  2050. <th>任务号</th>
  2051. <th>线路</th>
  2052. <th>站点 / 货品</th>
  2053. <th>状态</th>
  2054. <th>司机</th>
  2055. <th>计划时间</th>
  2056. <th>备注</th>
  2057. <th>创建时间</th>
  2058. <th>数量</th>
  2059. <th>金额(元)</th>
  2060. <th class="th-op">操作</th>
  2061. </tr>
  2062. </thead>
  2063. <tbody>
  2064. <tr>
  2065. <td class="td-check">
  2066. <div class="th-check-inner">
  2067. <input type="checkbox" class="row-check" name="taskRow" value="TK2026041501" aria-label="选择 TK2026041501" />
  2068. </div>
  2069. </td>
  2070. <td>TK2026041501</td>
  2071. <td class="td-left">华东仓 → 城配点 A</td>
  2072. <td class="td-cell-media td-left">
  2073. <div class="cell-media">
  2074. <img class="cell-media-img" src="https://picsum.photos/seed/sqxp-a/80/80" width="40" height="40" alt="" />
  2075. <div class="cell-media-stack">
  2076. <div class="cell-media-text">虹桥集散 · 厢式 3 号</div>
  2077. <div class="cell-media-sub">精品蔬菜箱 · 温控启用 · 需轻放易损品超长说明用于列表省略号演示……</div>
  2078. </div>
  2079. </div>
  2080. </td>
  2081. <td><span class="tag">执行中</span></td>
  2082. <td>王强</td>
  2083. <td style="color:var(--text-muted)">14:00 – 16:00</td>
  2084. <td class="td-left" style="color:var(--text-muted)">—</td>
  2085. <td style="color:var(--text-muted)">2026-04-15 09:12</td>
  2086. <td class="col-qty" data-qty="128">128</td>
  2087. <td class="col-amt" data-amt="15880">¥15,880.00</td>
  2088. <td class="td-op">
  2089. <div class="cell-ops">
  2090. <button type="button" class="btn-op primary js-btn-view">
  2091. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
  2092. 查看
  2093. </button>
  2094. <button type="button" class="btn-op">
  2095. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
  2096. 编辑
  2097. </button>
  2098. </div>
  2099. </td>
  2100. </tr>
  2101. <tr>
  2102. <td class="td-check">
  2103. <div class="th-check-inner">
  2104. <input type="checkbox" class="row-check" name="taskRow" value="TK2026041502" aria-label="选择 TK2026041502" />
  2105. </div>
  2106. </td>
  2107. <td>TK2026041502</td>
  2108. <td class="td-left">冷链 → 门店 B</td>
  2109. <td class="td-cell-media td-left">
  2110. <div class="cell-media">
  2111. <img class="cell-media-img" src="https://picsum.photos/seed/sqxp-b/80/80" width="40" height="40" alt="" />
  2112. <div class="cell-media-stack">
  2113. <div class="cell-media-text">浦东冷库 · 冷藏车 2 号</div>
  2114. <div class="cell-media-sub">冷冻水产 · 温控 -18℃ · 到点需扫码交接超长字段演示……</div>
  2115. </div>
  2116. </div>
  2117. </td>
  2118. <td><span class="tag muted">待装车</span></td>
  2119. <td>李敏</td>
  2120. <td style="color:var(--text-muted)">16:30 前</td>
  2121. <td class="td-left" style="color:var(--text-muted)">需保温</td>
  2122. <td style="color:var(--text-muted)">2026-04-15 08:40</td>
  2123. <td class="col-qty" data-qty="86">86</td>
  2124. <td class="col-amt" data-amt="9320.5">¥9,320.50</td>
  2125. <td class="td-op">
  2126. <div class="cell-ops">
  2127. <button type="button" class="btn-op primary js-btn-view">
  2128. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
  2129. 查看
  2130. </button>
  2131. <button type="button" class="btn-op">
  2132. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
  2133. 编辑
  2134. </button>
  2135. </div>
  2136. </td>
  2137. </tr>
  2138. </tbody>
  2139. <tfoot>
  2140. <tr class="total-row">
  2141. <td class="td-check" aria-hidden="true"></td>
  2142. <td class="td-total-label" colspan="8">本页合计</td>
  2143. <td class="col-qty" id="totalQty">0</td>
  2144. <td class="col-amt" id="totalAmt">¥0.00</td>
  2145. <td class="td-op" aria-hidden="true"></td>
  2146. </tr>
  2147. </tfoot>
  2148. </table>
  2149. </div>
  2150. <nav class="list-pager" aria-label="列表分页">
  2151. <div class="pager-meta">
  2152. <span id="pagerRange">第 0–0 条,共 0 条</span>
  2153. </div>
  2154. <div class="pager-controls">
  2155. <label class="pager-size-label" for="pageSizeSel">每页</label>
  2156. <select id="pageSizeSel" class="select pager-select" aria-label="每页条数">
  2157. <option value="10" selected>10</option>
  2158. <option value="20">20</option>
  2159. <option value="50">50</option>
  2160. </select>
  2161. <span class="pager-size-suffix">条</span>
  2162. <button type="button" class="btn pager-btn" id="pagerPrev">上一页</button>
  2163. <div class="pager-nums" id="pagerNums" aria-label="页码"></div>
  2164. <button type="button" class="btn pager-btn" id="pagerNext">下一页</button>
  2165. </div>
  2166. </nav>
  2167. </div>
  2168. </section>
  2169. <section class="panel" aria-labelledby="tree-panel-title">
  2170. <div class="panel-top">
  2171. <h2 id="tree-panel-title" style="margin:0">
  2172. <svg class="panel-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v18"/><path d="M3 12h18"/><path d="M7 7h.01"/><path d="M17 7h.01"/><path d="M7 17h.01"/><path d="M17 17h.01"/></svg>
  2173. 树形列表示例
  2174. </h2>
  2175. </div>
  2176. <p class="tree-panel-note">
  2177. 共三层:<strong>父列表</strong>(汇总单维度)→ <strong>子列表</strong>(配送任务维度)→ <strong>孙列表</strong>(拣货批次维度,可选展开)。<strong>每一层都是独立表格、列字段互不共用</strong>;<strong>各级展开/收起均在首列</strong>(与勾选同一列,无下级列)。勾选分别为 <code>tree-cb-parent</code> / <code>tree-cb-child</code> / <code>tree-cb-grandchild</code>。
  2178. </p>
  2179. <div class="table-scroll">
  2180. <table class="data-table tree-parent-grid">
  2181. <thead>
  2182. <tr>
  2183. <th class="td-tree-lead" scope="col">展开 / 选择</th>
  2184. <th class="th-tree-parent-left" scope="col" style="min-width:160px">汇总单号</th>
  2185. <th class="th-tree-parent-left" scope="col" style="min-width:200px">计划线路 / 区域</th>
  2186. <th scope="col" style="width:88px">含子单数</th>
  2187. <th scope="col" style="min-width:120px">汇总金额</th>
  2188. <th scope="col" style="min-width:100px">对账状态</th>
  2189. <th class="th-op" scope="col" style="min-width:120px">父级操作</th>
  2190. </tr>
  2191. </thead>
  2192. <tbody>
  2193. <tr class="tree-row-parent" data-tree-id="g1">
  2194. <td class="td-tree-lead">
  2195. <div class="td-tree-lead-inner">
  2196. <button type="button" class="tree-expand tree-expand-root" aria-expanded="true" title="展开或收起子列表">
  2197. <svg class="tree-expand-ic" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
  2198. </button>
  2199. <input type="checkbox" class="tree-cb-parent" name="treeParent" value="g1" aria-label="选择汇总单 H2026041501" />
  2200. </div>
  2201. </td>
  2202. <td class="td-left">H2026041501 <span class="tree-badge parent">父</span></td>
  2203. <td class="td-left">华东仓 → 城配早班(多站)</td>
  2204. <td>3</td>
  2205. <td class="col-amt">¥42,360.00</td>
  2206. <td><span class="tag muted">待对账</span></td>
  2207. <td class="td-op">
  2208. <button type="button" class="btn-op primary">拆单复核</button>
  2209. </td>
  2210. </tr>
  2211. <tr class="tree-row-children-wrap" data-tree-parent="g1">
  2212. <td class="td-tree-nested" colspan="7">
  2213. <div class="tree-nested-inner">
  2214. <div class="tree-nested-label">子列表 · 配送任务(列与父表独立;可再展开孙)</div>
  2215. <table class="tree-child-table">
  2216. <thead>
  2217. <tr>
  2218. <th class="td-tree-lead" scope="col">展开 / 选择</th>
  2219. <th class="th-child-left" scope="col" style="min-width:128px">配送任务号</th>
  2220. <th class="th-child-left" scope="col" style="min-width:120px">目的站点</th>
  2221. <th scope="col" style="width:64px">温区</th>
  2222. <th scope="col" style="width:56px">件数</th>
  2223. <th scope="col" style="min-width:92px">任务金额</th>
  2224. <th class="th-child-left" scope="col" style="min-width:64px">司机</th>
  2225. <th class="th-op" scope="col" style="min-width:88px">任务操作</th>
  2226. </tr>
  2227. </thead>
  2228. <tbody>
  2229. <tr class="tree-l1-row">
  2230. <td class="td-tree-lead">
  2231. <div class="td-tree-lead-inner">
  2232. <button type="button" class="tree-expand tree-expand-nested" aria-expanded="false" data-expand-target="g1-l2-t1" title="展开或收起孙列表(拣货批次)">
  2233. <svg class="tree-expand-ic" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
  2234. </button>
  2235. <input type="checkbox" class="tree-cb-child" name="treeChild" value="g1-c1" aria-label="选择任务 TK2026041501" />
  2236. </div>
  2237. </td>
  2238. <td class="td-child-left">TK2026041501</td>
  2239. <td class="td-child-left">虹桥门店</td>
  2240. <td>冷藏</td>
  2241. <td>62</td>
  2242. <td class="col-amt">¥14,200.00</td>
  2243. <td class="td-child-left">王强</td>
  2244. <td class="td-op">
  2245. <button type="button" class="btn-op">改派</button>
  2246. </td>
  2247. </tr>
  2248. <tr class="tree-l2-wrap is-hidden" data-expand-for="g1-l2-t1">
  2249. <td class="td-tree-nested-l2" colspan="8">
  2250. <div class="tree-nested-inner tree-nested-deep">
  2251. <div class="tree-nested-label">孙列表 · 拣货批次明细(列与父表、子表均不同)</div>
  2252. <table class="tree-grandchild-table">
  2253. <thead>
  2254. <tr>
  2255. <th class="td-tree-lead" scope="col" style="width:56px">展开 / 选择</th>
  2256. <th class="th-gc-left" scope="col" style="min-width:120px">拣货批次号</th>
  2257. <th class="th-gc-left" scope="col" style="min-width:100px">SKU</th>
  2258. <th class="th-gc-left" scope="col" style="min-width:120px">品名</th>
  2259. <th scope="col" style="width:72px">规格</th>
  2260. <th scope="col" style="width:52px">件数</th>
  2261. <th scope="col" style="width:52px">重量(kg)</th>
  2262. <th scope="col" style="min-width:88px">批次小计</th>
  2263. <th class="th-op" scope="col" style="min-width:80px">批次操作</th>
  2264. </tr>
  2265. </thead>
  2266. <tbody>
  2267. <tr>
  2268. <td class="td-tree-lead">
  2269. <div class="td-tree-lead-inner">
  2270. <span class="tree-expand-spacer" aria-hidden="true"></span>
  2271. <input type="checkbox" class="tree-cb-grandchild" name="treeGc" value="g1-gc1" aria-label="选择批次 B2026041501" />
  2272. </div>
  2273. </td>
  2274. <td class="td-gc-left">B2026041501-A</td>
  2275. <td class="td-gc-left">VG-10086</td>
  2276. <td class="td-gc-left">精品黄瓜</td>
  2277. <td>5kg/箱</td>
  2278. <td>20</td>
  2279. <td>100</td>
  2280. <td class="col-amt">¥360.00</td>
  2281. <td class="td-op">
  2282. <button type="button" class="btn-op">复核</button>
  2283. </td>
  2284. </tr>
  2285. <tr>
  2286. <td class="td-tree-lead">
  2287. <div class="td-tree-lead-inner">
  2288. <span class="tree-expand-spacer" aria-hidden="true"></span>
  2289. <input type="checkbox" class="tree-cb-grandchild" name="treeGc" value="g1-gc2" aria-label="选择批次 B2026041501" />
  2290. </div>
  2291. </td>
  2292. <td class="td-gc-left">B2026041501-B</td>
  2293. <td class="td-gc-left">TM-22031</td>
  2294. <td class="td-gc-left">番茄礼盒</td>
  2295. <td>3kg/箱</td>
  2296. <td>12</td>
  2297. <td>36</td>
  2298. <td class="col-amt">¥390.00</td>
  2299. <td class="td-op">
  2300. <button type="button" class="btn-op">复核</button>
  2301. </td>
  2302. </tr>
  2303. </tbody>
  2304. </table>
  2305. </div>
  2306. </td>
  2307. </tr>
  2308. <tr class="tree-l1-row">
  2309. <td class="td-tree-lead">
  2310. <div class="td-tree-lead-inner">
  2311. <span class="tree-expand-spacer" aria-hidden="true"></span>
  2312. <input type="checkbox" class="tree-cb-child" name="treeChild" value="g1-c2" aria-label="选择任务 TK2026041502" />
  2313. </div>
  2314. </td>
  2315. <td class="td-child-left">TK2026041502</td>
  2316. <td class="td-child-left">浦东门店</td>
  2317. <td>冷冻</td>
  2318. <td>78</td>
  2319. <td class="col-amt">¥18,960.00</td>
  2320. <td class="td-child-left">李敏</td>
  2321. <td class="td-op">
  2322. <button type="button" class="btn-op">改派</button>
  2323. </td>
  2324. </tr>
  2325. <tr class="tree-l1-row">
  2326. <td class="td-tree-lead">
  2327. <div class="td-tree-lead-inner">
  2328. <span class="tree-expand-spacer" aria-hidden="true"></span>
  2329. <input type="checkbox" class="tree-cb-child" name="treeChild" value="g1-c3" aria-label="选择任务 TK2026041503" />
  2330. </div>
  2331. </td>
  2332. <td class="td-child-left">TK2026041503</td>
  2333. <td class="td-child-left">嘉定门店</td>
  2334. <td>常温</td>
  2335. <td>46</td>
  2336. <td class="col-amt">¥9,200.00</td>
  2337. <td class="td-child-left">张三</td>
  2338. <td class="td-op">
  2339. <button type="button" class="btn-op">改派</button>
  2340. </td>
  2341. </tr>
  2342. </tbody>
  2343. </table>
  2344. </div>
  2345. </td>
  2346. </tr>
  2347. <tr class="tree-row-parent" data-tree-id="g2">
  2348. <td class="td-tree-lead">
  2349. <div class="td-tree-lead-inner">
  2350. <button type="button" class="tree-expand tree-expand-root" aria-expanded="false" title="展开或收起子列表">
  2351. <svg class="tree-expand-ic" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
  2352. </button>
  2353. <input type="checkbox" class="tree-cb-parent" name="treeParent" value="g2" aria-label="选择汇总单 H2026041502" />
  2354. </div>
  2355. </td>
  2356. <td class="td-left">H2026041502 <span class="tree-badge parent">父</span></td>
  2357. <td class="td-left">冷链仓 → 午后波次</td>
  2358. <td>2</td>
  2359. <td class="col-amt">¥28,100.00</td>
  2360. <td><span class="tag">对账中</span></td>
  2361. <td class="td-op">
  2362. <button type="button" class="btn-op primary">拆单复核</button>
  2363. </td>
  2364. </tr>
  2365. <tr class="tree-row-children-wrap is-hidden" data-tree-parent="g2">
  2366. <td class="td-tree-nested" colspan="7">
  2367. <div class="tree-nested-inner">
  2368. <div class="tree-nested-label">子列表 · 配送任务(列与父表独立;可再展开孙)</div>
  2369. <table class="tree-child-table">
  2370. <thead>
  2371. <tr>
  2372. <th class="td-tree-lead" scope="col">展开 / 选择</th>
  2373. <th class="th-child-left" scope="col" style="min-width:128px">配送任务号</th>
  2374. <th class="th-child-left" scope="col" style="min-width:120px">目的站点</th>
  2375. <th scope="col" style="width:64px">温区</th>
  2376. <th scope="col" style="width:56px">件数</th>
  2377. <th scope="col" style="min-width:92px">任务金额</th>
  2378. <th class="th-child-left" scope="col" style="min-width:64px">司机</th>
  2379. <th class="th-op" scope="col" style="min-width:88px">任务操作</th>
  2380. </tr>
  2381. </thead>
  2382. <tbody>
  2383. <tr class="tree-l1-row">
  2384. <td class="td-tree-lead">
  2385. <div class="td-tree-lead-inner">
  2386. <button type="button" class="tree-expand tree-expand-nested" aria-expanded="false" data-expand-target="g2-l2-t1" title="展开或收起孙列表(拣货批次)">
  2387. <svg class="tree-expand-ic" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
  2388. </button>
  2389. <input type="checkbox" class="tree-cb-child" name="treeChild" value="g2-c1" aria-label="选择任务 TK2026041504" />
  2390. </div>
  2391. </td>
  2392. <td class="td-child-left">TK2026041504</td>
  2393. <td class="td-child-left">宝山前置仓</td>
  2394. <td>冷冻</td>
  2395. <td>40</td>
  2396. <td class="col-amt">¥11,200.00</td>
  2397. <td class="td-child-left">赵磊</td>
  2398. <td class="td-op">
  2399. <button type="button" class="btn-op">改派</button>
  2400. </td>
  2401. </tr>
  2402. <tr class="tree-l2-wrap is-hidden" data-expand-for="g2-l2-t1">
  2403. <td class="td-tree-nested-l2" colspan="8">
  2404. <div class="tree-nested-inner tree-nested-deep">
  2405. <div class="tree-nested-label">孙列表 · 拣货批次明细(列与父表、子表均不同)</div>
  2406. <table class="tree-grandchild-table">
  2407. <thead>
  2408. <tr>
  2409. <th class="td-tree-lead" scope="col" style="width:56px">展开 / 选择</th>
  2410. <th class="th-gc-left" scope="col" style="min-width:120px">拣货批次号</th>
  2411. <th class="th-gc-left" scope="col" style="min-width:100px">SKU</th>
  2412. <th class="th-gc-left" scope="col" style="min-width:120px">品名</th>
  2413. <th scope="col" style="width:72px">规格</th>
  2414. <th scope="col" style="width:52px">件数</th>
  2415. <th scope="col" style="width:52px">重量(kg)</th>
  2416. <th scope="col" style="min-width:88px">批次小计</th>
  2417. <th class="th-op" scope="col" style="min-width:80px">批次操作</th>
  2418. </tr>
  2419. </thead>
  2420. <tbody>
  2421. <tr>
  2422. <td class="td-tree-lead">
  2423. <div class="td-tree-lead-inner">
  2424. <span class="tree-expand-spacer" aria-hidden="true"></span>
  2425. <input type="checkbox" class="tree-cb-grandchild" name="treeGc" value="g2-gc1" aria-label="选择批次 B2026041504" />
  2426. </div>
  2427. </td>
  2428. <td class="td-gc-left">B2026041504-X</td>
  2429. <td class="td-gc-left">FS-88302</td>
  2430. <td class="td-gc-left">冷冻水产拼箱</td>
  2431. <td>混箱</td>
  2432. <td>18</td>
  2433. <td>42</td>
  2434. <td class="col-amt">¥5,600.00</td>
  2435. <td class="td-op">
  2436. <button type="button" class="btn-op">复核</button>
  2437. </td>
  2438. </tr>
  2439. </tbody>
  2440. </table>
  2441. </div>
  2442. </td>
  2443. </tr>
  2444. <tr class="tree-l1-row">
  2445. <td class="td-tree-lead">
  2446. <div class="td-tree-lead-inner">
  2447. <span class="tree-expand-spacer" aria-hidden="true"></span>
  2448. <input type="checkbox" class="tree-cb-child" name="treeChild" value="g2-c2" aria-label="选择任务 TK2026041505" />
  2449. </div>
  2450. </td>
  2451. <td class="td-child-left">TK2026041505</td>
  2452. <td class="td-child-left">闵行门店</td>
  2453. <td>冷藏</td>
  2454. <td>54</td>
  2455. <td class="col-amt">¥16,900.00</td>
  2456. <td class="td-child-left">孙洁</td>
  2457. <td class="td-op">
  2458. <button type="button" class="btn-op">改派</button>
  2459. </td>
  2460. </tr>
  2461. </tbody>
  2462. </table>
  2463. </div>
  2464. </td>
  2465. </tr>
  2466. </tbody>
  2467. </table>
  2468. </div>
  2469. </section>
  2470. </div>
  2471. </div>
  2472. </main>
  2473. </div>
  2474. </div>
  2475. <div class="backdrop" id="backdrop" role="dialog" aria-modal="true" aria-labelledby="dlgTitle">
  2476. <div class="dialog dialog-lg" id="dlgNewTask">
  2477. <div class="dialog-head">
  2478. <h3 id="dlgTitle" style="margin:0">新建配送任务</h3>
  2479. <p class="note" style="margin:6px 0 0">复杂表单示意:基础信息、图片凭证、装车明细与备注。</p>
  2480. </div>
  2481. <div class="dialog-scroll">
  2482. <div class="form-section">
  2483. <h4>基础信息</h4>
  2484. <div class="form-grid-2">
  2485. <div class="field" style="margin:0">
  2486. <label for="dlgTaskName">任务名称</label>
  2487. <div class="input-clear-wrap">
  2488. <input id="dlgTaskName" type="text" class="input" placeholder="如:城配 A 线早班" autocomplete="off" />
  2489. <button type="button" class="input-clear" aria-label="清除任务名称" title="清除">
  2490. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>
  2491. </button>
  2492. </div>
  2493. </div>
  2494. <div class="field" style="margin:0">
  2495. <label for="dlgCarrier">承运商</label>
  2496. <select id="dlgCarrier" class="select" style="width:100%;min-width:0">
  2497. <option value="">请选择</option>
  2498. <option value="1">叁强物流</option>
  2499. <option value="2">合作车队 A</option>
  2500. </select>
  2501. </div>
  2502. <div class="field" style="margin:0">
  2503. <label for="dlgPhone">调度电话</label>
  2504. <div class="input-clear-wrap">
  2505. <input id="dlgPhone" type="tel" class="input" placeholder="11 位手机号" autocomplete="off" />
  2506. <button type="button" class="input-clear" aria-label="清除电话" title="清除">
  2507. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>
  2508. </button>
  2509. </div>
  2510. </div>
  2511. <div class="field" style="margin:0">
  2512. <label for="dlgEta">要求到达</label>
  2513. <div class="input-clear-wrap">
  2514. <input id="dlgEta" type="datetime-local" class="input" />
  2515. <button type="button" class="input-clear" aria-label="清除到达时间" title="清除">
  2516. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>
  2517. </button>
  2518. </div>
  2519. </div>
  2520. </div>
  2521. </div>
  2522. <div class="form-section">
  2523. <h4>现场凭证(图片上传)</h4>
  2524. <div class="upload-zone" id="uploadZone" role="button" tabindex="0">
  2525. <p class="upload-zone-text">支持多张,示意:装车单、封签、车况等</p>
  2526. <button type="button" class="btn btn-block upload-zone-btn" id="btnPickFiles">
  2527. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
  2528. 选择图片
  2529. </button>
  2530. <input id="dlgUpload" type="file" accept="image/*" multiple style="display:none" />
  2531. </div>
  2532. <div class="upload-preview" id="uploadPreview" aria-live="polite"></div>
  2533. </div>
  2534. <div class="form-section">
  2535. <h4>装车明细</h4>
  2536. <div class="mini-table-wrap">
  2537. <table class="mini-table">
  2538. <thead>
  2539. <tr>
  2540. <th style="width:48px">#</th>
  2541. <th>商品名称</th>
  2542. <th style="width:100px">数量</th>
  2543. <th style="width:80px">单位</th>
  2544. <th style="width:72px">操作</th>
  2545. </tr>
  2546. </thead>
  2547. <tbody id="dlgLinesBody">
  2548. <tr>
  2549. <td>1</td>
  2550. <td>
  2551. <div class="input-clear-wrap">
  2552. <input type="text" class="input" name="lineName[]" placeholder="如:精品黄瓜" autocomplete="off" />
  2553. <button type="button" class="input-clear" aria-label="清除商品名称" title="清除">
  2554. <svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>
  2555. </button>
  2556. </div>
  2557. </td>
  2558. <td><input type="number" class="input" name="lineQty[]" placeholder="0" min="0" step="1" /></td>
  2559. <td>
  2560. <select class="select" name="lineUnit[]" style="width:100%;min-width:0">
  2561. <option>件</option>
  2562. <option>箱</option>
  2563. <option>kg</option>
  2564. </select>
  2565. </td>
  2566. <td><button type="button" class="btn btn-icon js-remove-line">删除</button></td>
  2567. </tr>
  2568. </tbody>
  2569. </table>
  2570. </div>
  2571. <button type="button" class="btn" id="btnAddLine" style="margin-top:10px">
  2572. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
  2573. 添加一行
  2574. </button>
  2575. </div>
  2576. <div class="form-section">
  2577. <h4>备注说明</h4>
  2578. <label for="dlgRemark" class="sr-only">备注</label>
  2579. <textarea id="dlgRemark" class="textarea" placeholder="特殊要求、限行说明、客户交接注意事项等"></textarea>
  2580. </div>
  2581. </div>
  2582. <div class="dialog-foot">
  2583. <button type="button" class="btn" id="btnCancel">
  2584. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>
  2585. 取消
  2586. </button>
  2587. <button type="button" class="btn btn-primary" id="btnSaveDemo">
  2588. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
  2589. 保存
  2590. </button>
  2591. </div>
  2592. </div>
  2593. </div>
  2594. <div class="backdrop" id="backdropView" role="dialog" aria-modal="true" aria-labelledby="viewTitleTaskNo">
  2595. <div class="dialog dialog-view-xl" id="dlgView">
  2596. <div class="dialog-head" style="padding:0;border:none">
  2597. <div class="view-hero">
  2598. <img id="viewHeroImg" class="view-hero-img" src="https://picsum.photos/seed/view-hero/144/144" width="72" height="72" alt="" />
  2599. <div class="view-hero-main">
  2600. <h2 id="viewTitleTaskNo">任务详情</h2>
  2601. <div class="view-hero-tags">
  2602. <span class="tag" id="viewStatusTag">执行中</span>
  2603. <span class="tag muted" id="viewPriorityTag">优先级 · 中</span>
  2604. <span class="tag muted">区域 · 华东</span>
  2605. </div>
  2606. <p class="view-hero-meta" id="viewHeroMeta">
  2607. 线路:<span id="viewRouteLine">—</span> · 司机 <span id="viewDriverName">—</span> · 计划 <span id="viewPlanBrief">—</span>
  2608. </p>
  2609. </div>
  2610. </div>
  2611. </div>
  2612. <div class="dialog-scroll">
  2613. <div class="view-section">
  2614. <h3>基础信息</h3>
  2615. <dl class="view-kv-grid">
  2616. <div class="view-kv"><dt>任务号</dt><dd id="viewTaskNo">—</dd></div>
  2617. <div class="view-kv"><dt>创建时间</dt><dd id="viewCreatedAt">—</dd></div>
  2618. <div class="view-kv"><dt>线路</dt><dd id="viewRouteFull">—</dd></div>
  2619. <div class="view-kv"><dt>计划时间</dt><dd id="viewPlanTime">—</dd></div>
  2620. <div class="view-kv"><dt>站点 / 货品</dt><dd id="viewStationBrief">—</dd></div>
  2621. <div class="view-kv"><dt>备注</dt><dd id="viewRemarkFull">—</dd></div>
  2622. </dl>
  2623. </div>
  2624. <div class="view-section">
  2625. <h3>费用与件量</h3>
  2626. <div class="view-bento">
  2627. <div class="view-card">
  2628. <h4>本单数量(件)</h4>
  2629. <p id="viewQtyBig" style="font-size:22px;font-weight:700;color:var(--primary-dark)">0</p>
  2630. </div>
  2631. <div class="view-card">
  2632. <h4>本单金额(元)</h4>
  2633. <p id="viewAmtBig" style="font-size:22px;font-weight:700;color:var(--primary-dark)">¥0.00</p>
  2634. </div>
  2635. <div class="view-card" style="grid-column:1/-1">
  2636. <h4>费用说明</h4>
  2637. <p>含装卸、路桥及温控附加费;实际结算以回单签收后财务对账为准。以下为系统根据当前线路与货品类型估算的分摊明细。</p>
  2638. </div>
  2639. </div>
  2640. </div>
  2641. <div class="view-section">
  2642. <h3>路由示意</h3>
  2643. <div class="view-route-bar">
  2644. <div class="view-route-pin"><strong>始发仓</strong>华东中心仓</div>
  2645. <div class="view-route-line" aria-hidden="true"></div>
  2646. <div class="view-route-pin"><strong>经停</strong><span id="viewRouteMid">城配集散</span></div>
  2647. <div class="view-route-line" aria-hidden="true"></div>
  2648. <div class="view-route-pin"><strong>目的地</strong><span id="viewRouteEnd">门店/站点</span></div>
  2649. </div>
  2650. <p style="margin:0;font-size:12px;color:var(--text-muted)">示意图仅供调度参考,导航以司机端实时路径为准。</p>
  2651. </div>
  2652. <div class="view-section">
  2653. <h3>执行轨迹</h3>
  2654. <div class="view-timeline">
  2655. <div class="view-tl-item">
  2656. <div class="view-tl-time">2026-04-15 08:20</div>
  2657. <div class="view-tl-title">任务创建 · 调度已排班</div>
  2658. <div class="view-tl-desc">系统自动生成运单,已推送至承运商接口。</div>
  2659. </div>
  2660. <div class="view-tl-item">
  2661. <div class="view-tl-time" id="viewTlTime1">2026-04-15 09:00</div>
  2662. <div class="view-tl-title">司机接单 · 前往提货点</div>
  2663. <div class="view-tl-desc" id="viewTlDesc1">车辆已出库,预计途中耗时以实时路况为准。</div>
  2664. </div>
  2665. <div class="view-tl-item muted">
  2666. <div class="view-tl-time">待发生</div>
  2667. <div class="view-tl-title">到站卸货 · 签收确认</div>
  2668. <div class="view-tl-desc">完成后将同步回单照片与电子签名。</div>
  2669. </div>
  2670. </div>
  2671. </div>
  2672. <div class="view-section">
  2673. <h3>现场影像</h3>
  2674. <div class="view-gallery">
  2675. <a href="https://picsum.photos/seed/v1/800/600" target="_blank" rel="noopener noreferrer"><img src="https://picsum.photos/seed/v1/400/300" width="400" height="300" alt="装车" loading="lazy" /></a>
  2676. <a href="https://picsum.photos/seed/v2/800/600" target="_blank" rel="noopener noreferrer"><img src="https://picsum.photos/seed/v2/400/300" width="400" height="300" alt="封签" loading="lazy" /></a>
  2677. <a href="https://picsum.photos/seed/v3/800/600" target="_blank" rel="noopener noreferrer"><img src="https://picsum.photos/seed/v3/400/300" width="400" height="300" alt="车况" loading="lazy" /></a>
  2678. <a href="https://picsum.photos/seed/v4/800/600" target="_blank" rel="noopener noreferrer"><img src="https://picsum.photos/seed/v4/400/300" width="400" height="300" alt="回单" loading="lazy" /></a>
  2679. </div>
  2680. </div>
  2681. <div class="view-section">
  2682. <h3>装车明细(示意)</h3>
  2683. <div class="view-subtable-wrap">
  2684. <table class="view-subtable">
  2685. <thead>
  2686. <tr>
  2687. <th class="ta-left">品名</th>
  2688. <th>规格</th>
  2689. <th>数量</th>
  2690. <th>单价</th>
  2691. <th>小计</th>
  2692. </tr>
  2693. </thead>
  2694. <tbody>
  2695. <tr>
  2696. <td class="ta-left">精品黄瓜</td>
  2697. <td>5kg/箱</td>
  2698. <td>20</td>
  2699. <td class="money-val">¥18.00</td>
  2700. <td class="money-val">¥360.00</td>
  2701. </tr>
  2702. <tr>
  2703. <td class="ta-left">番茄(礼盒)</td>
  2704. <td>3kg/箱</td>
  2705. <td>12</td>
  2706. <td class="money-val">¥32.50</td>
  2707. <td class="money-val">¥390.00</td>
  2708. </tr>
  2709. <tr>
  2710. <td class="ta-left">叶菜组合</td>
  2711. <td>2kg/袋</td>
  2712. <td>30</td>
  2713. <td class="money-val">¥9.80</td>
  2714. <td class="money-val">¥294.00</td>
  2715. </tr>
  2716. </tbody>
  2717. </table>
  2718. </div>
  2719. </div>
  2720. </div>
  2721. <div class="dialog-foot">
  2722. <button type="button" class="btn" id="btnViewClose">
  2723. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>
  2724. 关闭
  2725. </button>
  2726. <button type="button" class="btn btn-primary" id="btnViewPrintDemo">
  2727. <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
  2728. 打印预览(演示)
  2729. </button>
  2730. </div>
  2731. </div>
  2732. </div>
  2733. <script>
  2734. (function () {
  2735. var el = document.getElementById("backdrop");
  2736. var dlgNew = document.getElementById("dlgNewTask");
  2737. var backdropView = document.getElementById("backdropView");
  2738. var dlgView = document.getElementById("dlgView");
  2739. var uploadInput = document.getElementById("dlgUpload");
  2740. var uploadPreview = document.getElementById("uploadPreview");
  2741. var uploadZone = document.getElementById("uploadZone");
  2742. var btnPickFiles = document.getElementById("btnPickFiles");
  2743. function syncInputClearButtons(container) {
  2744. (container || document).querySelectorAll(".input-clear-wrap").forEach(function (wrap) {
  2745. var input = wrap.querySelector("input");
  2746. var btn = wrap.querySelector(".input-clear");
  2747. if (input && btn) btn.hidden = !input.value;
  2748. });
  2749. }
  2750. document.addEventListener("input", function (e) {
  2751. var wrap = e.target.closest(".input-clear-wrap");
  2752. if (!wrap || !e.target.matches("input")) return;
  2753. var btn = wrap.querySelector(".input-clear");
  2754. if (btn) btn.hidden = !e.target.value;
  2755. });
  2756. document.addEventListener("click", function (e) {
  2757. var btn = e.target.closest(".input-clear");
  2758. if (!btn) return;
  2759. var wrap = btn.closest(".input-clear-wrap");
  2760. if (!wrap) return;
  2761. var input = wrap.querySelector("input");
  2762. if (!input) return;
  2763. e.preventDefault();
  2764. input.value = "";
  2765. input.dispatchEvent(new Event("input", { bubbles: true }));
  2766. input.focus();
  2767. btn.hidden = true;
  2768. });
  2769. syncInputClearButtons();
  2770. var taskTableTbody = document.querySelector(".data-table tbody");
  2771. function getVisibleRowChecks() {
  2772. var out = [];
  2773. if (!taskTableTbody) return out;
  2774. [].forEach.call(taskTableTbody.querySelectorAll("tr"), function (tr) {
  2775. if (tr.classList.contains("is-hidden")) return;
  2776. var cb = tr.querySelector(".row-check");
  2777. if (cb) out.push(cb);
  2778. });
  2779. return out;
  2780. }
  2781. function syncSelectedCountBadge() {
  2782. var numEl = document.getElementById("selectedCountNum");
  2783. if (!numEl || !taskTableTbody) return;
  2784. var n = 0;
  2785. taskTableTbody.querySelectorAll(".row-check").forEach(function (cb) {
  2786. if (cb.checked) n++;
  2787. });
  2788. numEl.textContent = String(n);
  2789. }
  2790. function syncTableCheckAllUI() {
  2791. var all = document.getElementById("tableCheckAll");
  2792. if (!all) return;
  2793. var list = getVisibleRowChecks();
  2794. var n = 0;
  2795. list.forEach(function (cb) {
  2796. if (cb.checked) n++;
  2797. });
  2798. all.checked = n === list.length && list.length > 0;
  2799. all.indeterminate = n > 0 && n < list.length;
  2800. syncSelectedCountBadge();
  2801. }
  2802. (function initTaskTableSelectAll() {
  2803. var all = document.getElementById("tableCheckAll");
  2804. if (!all || !taskTableTbody) return;
  2805. all.addEventListener("change", function () {
  2806. getVisibleRowChecks().forEach(function (cb) {
  2807. cb.checked = all.checked;
  2808. });
  2809. all.indeterminate = false;
  2810. syncSelectedCountBadge();
  2811. });
  2812. taskTableTbody.addEventListener("change", function (e) {
  2813. if (!e.target.classList.contains("row-check")) return;
  2814. syncTableCheckAllUI();
  2815. });
  2816. })();
  2817. (function seedDemoTableRows() {
  2818. var tb = document.querySelector(".data-table tbody");
  2819. if (!tb || !tb.rows.length) return;
  2820. var templateRow = tb.rows[0];
  2821. var drivers = ["王强", "李敏", "张三", "赵磊", "孙洁", "周洋"];
  2822. for (var i = 0; i < 16; i++) {
  2823. var n = 3 + i;
  2824. var tr = templateRow.cloneNode(true);
  2825. var id = "TK20260415" + (n < 10 ? "0" : "") + n;
  2826. if (tr.cells[1]) tr.cells[1].textContent = id;
  2827. var cb = tr.querySelector(".row-check");
  2828. if (cb) {
  2829. cb.value = id;
  2830. cb.setAttribute("aria-label", "选择 " + id);
  2831. cb.checked = false;
  2832. }
  2833. if (tr.cells[2]) {
  2834. var code = String.fromCharCode(65 + (i % 26));
  2835. tr.cells[2].textContent =
  2836. "区域 " + code + " → 城配站 #" + (i + 1) + "(列多时横向滚动)";
  2837. tr.cells[2].classList.add("td-left");
  2838. }
  2839. if (tr.cells[3]) {
  2840. var wrap = tr.cells[3].querySelector(".cell-media");
  2841. if (wrap) {
  2842. var img = wrap.querySelector(".cell-media-img");
  2843. var t1 = wrap.querySelector(".cell-media-text");
  2844. var t2 = wrap.querySelector(".cell-media-sub");
  2845. if (img) img.src = "https://picsum.photos/seed/" + encodeURIComponent(id) + "/80/80";
  2846. if (t1) t1.textContent = "城配集散 · 厢式 " + ((i % 9) + 1) + " 号";
  2847. if (t2) {
  2848. t2.textContent =
  2849. "混装配载 · 批次 " +
  2850. (i + 1) +
  2851. " · 超长说明用于测试列表格内省略号与左对齐图文混排……";
  2852. }
  2853. }
  2854. tr.cells[3].classList.add("td-cell-media", "td-left");
  2855. }
  2856. if (tr.cells[5]) tr.cells[5].textContent = drivers[i % drivers.length];
  2857. if (tr.cells[6]) {
  2858. tr.cells[6].textContent = 9 + (i % 6) + ":00 – " + (11 + (i % 6)) + ":00";
  2859. }
  2860. if (tr.cells[7]) {
  2861. tr.cells[7].textContent =
  2862. i % 4 === 0 ? "优先卸货" : i % 4 === 1 ? "" : "—";
  2863. tr.cells[7].classList.add("td-left");
  2864. }
  2865. if (tr.cells[8]) {
  2866. var hh = 8 + (i % 9);
  2867. var mm = (i * 7) % 60;
  2868. tr.cells[8].textContent =
  2869. "2026-04-15 " + (hh < 10 ? "0" : "") + hh + ":" + (mm < 10 ? "0" : "") + mm;
  2870. }
  2871. if (tr.cells[9]) {
  2872. var q = 5 + ((i * 3) % 42);
  2873. tr.cells[9].textContent = String(q);
  2874. tr.cells[9].setAttribute("data-qty", String(q));
  2875. tr.cells[9].className = "col-qty";
  2876. }
  2877. if (tr.cells[10]) {
  2878. var amt = Math.round((320 + i * 217.35) * 100) / 100;
  2879. tr.cells[10].setAttribute("data-amt", String(amt));
  2880. tr.cells[10].textContent =
  2881. "¥" +
  2882. amt.toLocaleString("zh-CN", {
  2883. minimumFractionDigits: 2,
  2884. maximumFractionDigits: 2
  2885. });
  2886. tr.cells[10].className = "col-amt";
  2887. }
  2888. tb.appendChild(tr);
  2889. }
  2890. })();
  2891. function refreshTaskTableTotals() {
  2892. var tb = taskTableTbody;
  2893. var qtyEl = document.getElementById("totalQty");
  2894. var amtEl = document.getElementById("totalAmt");
  2895. if (!tb || !qtyEl || !amtEl) return;
  2896. var qtySum = 0;
  2897. var amtSum = 0;
  2898. tb.querySelectorAll("tr").forEach(function (tr) {
  2899. if (tr.classList.contains("is-hidden")) return;
  2900. var qCell = tr.querySelector(".col-qty");
  2901. var aCell = tr.querySelector(".col-amt");
  2902. if (qCell) {
  2903. var qRaw = qCell.getAttribute("data-qty") || qCell.textContent.replace(/,/g, "");
  2904. var qv = parseFloat(qRaw, 10);
  2905. if (!isNaN(qv)) qtySum += qv;
  2906. }
  2907. if (aCell) {
  2908. var aRaw =
  2909. aCell.getAttribute("data-amt") ||
  2910. aCell.textContent.replace(/[¥¥,\s]/g, "").trim();
  2911. var av = parseFloat(aRaw, 10);
  2912. if (!isNaN(av)) amtSum += av;
  2913. }
  2914. });
  2915. qtyEl.textContent = String(Math.round(qtySum));
  2916. var amtRounded = Math.round(amtSum * 100) / 100;
  2917. amtEl.textContent =
  2918. "¥" +
  2919. amtRounded.toLocaleString("zh-CN", {
  2920. minimumFractionDigits: 2,
  2921. maximumFractionDigits: 2
  2922. });
  2923. amtEl.setAttribute("data-amt", String(amtRounded));
  2924. }
  2925. (function initTaskTablePager() {
  2926. var tb = taskTableTbody;
  2927. var prev = document.getElementById("pagerPrev");
  2928. var next = document.getElementById("pagerNext");
  2929. var nums = document.getElementById("pagerNums");
  2930. var rangeEl = document.getElementById("pagerRange");
  2931. var sizeSel = document.getElementById("pageSizeSel");
  2932. if (!tb || !prev || !next || !nums || !rangeEl || !sizeSel) return;
  2933. var state = { page: 1 };
  2934. function rowList() {
  2935. return [].slice.call(tb.querySelectorAll("tr"));
  2936. }
  2937. function pageCount() {
  2938. var total = rowList().length;
  2939. var size = parseInt(sizeSel.value, 10) || 10;
  2940. return Math.max(1, Math.ceil(total / size) || 1);
  2941. }
  2942. function render() {
  2943. var rows = rowList();
  2944. var total = rows.length;
  2945. var size = parseInt(sizeSel.value, 10) || 10;
  2946. var pages = Math.max(1, Math.ceil(total / size) || 1);
  2947. if (state.page > pages) state.page = pages;
  2948. if (state.page < 1) state.page = 1;
  2949. var start = (state.page - 1) * size;
  2950. var end = Math.min(start + size, total);
  2951. rows.forEach(function (tr, i) {
  2952. if (i >= start && i < end) tr.classList.remove("is-hidden");
  2953. else tr.classList.add("is-hidden");
  2954. });
  2955. if (total === 0) {
  2956. rangeEl.textContent = "共 0 条";
  2957. } else {
  2958. rangeEl.textContent =
  2959. "第 " + (start + 1) + "–" + end + " 条,共 " + total + " 条";
  2960. }
  2961. prev.disabled = state.page <= 1;
  2962. next.disabled = state.page >= pages;
  2963. nums.innerHTML = "";
  2964. var cur = state.page;
  2965. function addNumBtn(p, isCur) {
  2966. var b = document.createElement("button");
  2967. b.type = "button";
  2968. b.className = "btn pager-num" + (isCur ? " is-current" : "");
  2969. b.textContent = String(p);
  2970. b.setAttribute("aria-label", "第 " + p + " 页");
  2971. if (isCur) {
  2972. b.setAttribute("aria-current", "page");
  2973. b.disabled = true;
  2974. } else {
  2975. b.addEventListener("click", function () {
  2976. state.page = p;
  2977. render();
  2978. });
  2979. }
  2980. nums.appendChild(b);
  2981. }
  2982. if (pages <= 7) {
  2983. for (var p = 1; p <= pages; p++) addNumBtn(p, p === cur);
  2984. } else {
  2985. var marks = {};
  2986. [1, pages, cur - 1, cur, cur + 1].forEach(function (x) {
  2987. if (x >= 1 && x <= pages) marks[x] = true;
  2988. });
  2989. var order = Object.keys(marks)
  2990. .map(function (k) {
  2991. return parseInt(k, 10);
  2992. })
  2993. .sort(function (a, b) {
  2994. return a - b;
  2995. });
  2996. var last = 0;
  2997. order.forEach(function (p) {
  2998. if (p - last > 1) {
  2999. var gap = document.createElement("span");
  3000. gap.className = "pager-gap";
  3001. gap.textContent = "…";
  3002. nums.appendChild(gap);
  3003. }
  3004. addNumBtn(p, p === cur);
  3005. last = p;
  3006. });
  3007. }
  3008. refreshTaskTableTotals();
  3009. syncTableCheckAllUI();
  3010. }
  3011. prev.addEventListener("click", function () {
  3012. state.page--;
  3013. render();
  3014. });
  3015. next.addEventListener("click", function () {
  3016. state.page++;
  3017. render();
  3018. });
  3019. sizeSel.addEventListener("change", function () {
  3020. state.page = 1;
  3021. render();
  3022. });
  3023. render();
  3024. })();
  3025. function openTaskView(tr) {
  3026. if (!tr || !backdropView) return;
  3027. function cell(i) {
  3028. return tr.cells[i] || null;
  3029. }
  3030. var taskNo = cell(1) ? cell(1).textContent.trim() : "—";
  3031. var elTask = document.getElementById("viewTaskNo");
  3032. var elTitle = document.getElementById("viewTitleTaskNo");
  3033. if (elTask) elTask.textContent = taskNo;
  3034. if (elTitle) elTitle.textContent = "任务 · " + taskNo;
  3035. var route = cell(2) ? cell(2).textContent.trim() : "—";
  3036. if (document.getElementById("viewRouteFull")) {
  3037. document.getElementById("viewRouteFull").textContent = route;
  3038. }
  3039. if (document.getElementById("viewRouteLine")) {
  3040. document.getElementById("viewRouteLine").textContent = route;
  3041. }
  3042. var mText = "";
  3043. var mSub = "";
  3044. var imgSrc = "https://picsum.photos/seed/view-hero/144/144";
  3045. var c3 = cell(3);
  3046. if (c3) {
  3047. var t1 = c3.querySelector(".cell-media-text");
  3048. var t2 = c3.querySelector(".cell-media-sub");
  3049. var img = c3.querySelector(".cell-media-img");
  3050. if (t1) mText = t1.textContent.trim();
  3051. if (t2) mSub = t2.textContent.trim();
  3052. if (img) {
  3053. var src = img.getAttribute("src") || "";
  3054. if (src) imgSrc = src.replace(/\/\d+\/\d+(\?.*)?$/, "/144/144");
  3055. }
  3056. var elSB = document.getElementById("viewStationBrief");
  3057. if (elSB) elSB.textContent = mText + (mSub ? " · " + mSub : "");
  3058. }
  3059. var heroImg = document.getElementById("viewHeroImg");
  3060. if (heroImg) heroImg.src = imgSrc;
  3061. var st = cell(4);
  3062. var tagEl = document.getElementById("viewStatusTag");
  3063. if (st && tagEl) {
  3064. var span = st.querySelector(".tag");
  3065. tagEl.textContent = span ? span.textContent.trim() : st.textContent.trim();
  3066. tagEl.className = span ? span.className : "tag";
  3067. }
  3068. var driver = cell(5) ? cell(5).textContent.trim() : "—";
  3069. if (document.getElementById("viewDriverName")) {
  3070. document.getElementById("viewDriverName").textContent = driver;
  3071. }
  3072. var plan = cell(6) ? cell(6).textContent.trim() : "—";
  3073. if (document.getElementById("viewPlanTime")) {
  3074. document.getElementById("viewPlanTime").textContent = plan;
  3075. }
  3076. if (document.getElementById("viewPlanBrief")) {
  3077. document.getElementById("viewPlanBrief").textContent = plan;
  3078. }
  3079. var remark = cell(7) ? cell(7).textContent.trim() : "";
  3080. if (document.getElementById("viewRemarkFull")) {
  3081. document.getElementById("viewRemarkFull").textContent = remark || "—";
  3082. }
  3083. var created = cell(8) ? cell(8).textContent.trim() : "—";
  3084. if (document.getElementById("viewCreatedAt")) {
  3085. document.getElementById("viewCreatedAt").textContent = created;
  3086. }
  3087. var qty = cell(9) ? cell(9).textContent.trim() : "0";
  3088. var amt = cell(10) ? cell(10).textContent.trim() : "¥0.00";
  3089. if (document.getElementById("viewQtyBig")) {
  3090. document.getElementById("viewQtyBig").textContent = qty;
  3091. }
  3092. if (document.getElementById("viewAmtBig")) {
  3093. document.getElementById("viewAmtBig").textContent = amt;
  3094. }
  3095. var parts = route.split("→");
  3096. if (document.getElementById("viewRouteMid")) {
  3097. document.getElementById("viewRouteMid").textContent =
  3098. parts.length > 1 ? parts[0].trim().slice(0, 14) : "经停点";
  3099. }
  3100. if (document.getElementById("viewRouteEnd")) {
  3101. document.getElementById("viewRouteEnd").textContent =
  3102. parts.length > 1 ? parts[parts.length - 1].trim() : route.slice(0, 18);
  3103. }
  3104. if (document.getElementById("viewTlTime1")) {
  3105. document.getElementById("viewTlTime1").textContent = created || "—";
  3106. }
  3107. if (document.getElementById("viewTlDesc1")) {
  3108. document.getElementById("viewTlDesc1").textContent =
  3109. "司机「" + driver + "」执行本单,线路:" + route + "。";
  3110. }
  3111. backdropView.classList.add("open");
  3112. var sc = dlgView && dlgView.querySelector(".dialog-scroll");
  3113. if (sc) sc.scrollTop = 0;
  3114. }
  3115. function closeTaskView() {
  3116. if (backdropView) backdropView.classList.remove("open");
  3117. }
  3118. if (taskTableTbody) {
  3119. taskTableTbody.addEventListener("click", function (e) {
  3120. if (!e.target.closest(".js-btn-view")) return;
  3121. var tr = e.target.closest("tr");
  3122. if (tr) openTaskView(tr);
  3123. });
  3124. }
  3125. var btnViewClose = document.getElementById("btnViewClose");
  3126. var btnViewPrintDemo = document.getElementById("btnViewPrintDemo");
  3127. if (btnViewClose) btnViewClose.onclick = closeTaskView;
  3128. if (btnViewPrintDemo) {
  3129. btnViewPrintDemo.onclick = function () {
  3130. alert("演示:可在此调用 window.print() 或对接导出 PDF。");
  3131. };
  3132. }
  3133. if (backdropView) {
  3134. backdropView.onclick = function (e) {
  3135. if (e.target === backdropView) closeTaskView();
  3136. };
  3137. }
  3138. if (dlgView) {
  3139. dlgView.addEventListener("click", function (e) {
  3140. e.stopPropagation();
  3141. });
  3142. }
  3143. function clearUploadPreview() {
  3144. if (!uploadPreview) return;
  3145. uploadPreview.querySelectorAll(".upload-item").forEach(function (item) {
  3146. var u = item.dataset.url;
  3147. if (u) URL.revokeObjectURL(u);
  3148. item.remove();
  3149. });
  3150. }
  3151. function openDialog() {
  3152. el.classList.add("open");
  3153. document.getElementById("dlgTaskName").focus();
  3154. }
  3155. function closeDialog() {
  3156. el.classList.remove("open");
  3157. clearUploadPreview();
  3158. }
  3159. document.getElementById("btnNew").onclick = openDialog;
  3160. var importFileInput = document.getElementById("importFileInput");
  3161. var btnBatchDelete = document.getElementById("btnBatchDelete");
  3162. var btnImport = document.getElementById("btnImport");
  3163. var btnExportList = document.getElementById("btnExportList");
  3164. if (btnBatchDelete) {
  3165. btnBatchDelete.onclick = function () {
  3166. alert("演示:将删除当前勾选的记录,接入接口后在此提交批量删除。");
  3167. };
  3168. }
  3169. if (btnImport && importFileInput) {
  3170. btnImport.onclick = function () {
  3171. importFileInput.click();
  3172. };
  3173. importFileInput.addEventListener("change", function () {
  3174. if (importFileInput.files && importFileInput.files.length) {
  3175. alert("演示:已选择文件「" + importFileInput.files[0].name + "」,可在此解析并上传。");
  3176. }
  3177. importFileInput.value = "";
  3178. });
  3179. }
  3180. if (btnExportList) {
  3181. btnExportList.onclick = function () {
  3182. alert("演示:导出当前列表(可按筛选条件导出结果集)。");
  3183. };
  3184. }
  3185. document.getElementById("btnCancel").onclick = closeDialog;
  3186. el.onclick = function (e) {
  3187. if (e.target === el) closeDialog();
  3188. };
  3189. document.addEventListener("keydown", function (e) {
  3190. if (e.key !== "Escape") return;
  3191. if (backdropView && backdropView.classList.contains("open")) {
  3192. closeTaskView();
  3193. return;
  3194. }
  3195. if (el.classList.contains("open")) closeDialog();
  3196. });
  3197. if (dlgNew) {
  3198. dlgNew.addEventListener("click", function (e) {
  3199. e.stopPropagation();
  3200. });
  3201. }
  3202. function addPreviewFile(file) {
  3203. if (!file.type.startsWith("image/")) return;
  3204. var url = URL.createObjectURL(file);
  3205. var wrap = document.createElement("div");
  3206. wrap.className = "upload-item";
  3207. wrap.dataset.url = url;
  3208. var img = document.createElement("img");
  3209. img.src = url;
  3210. img.alt = "预览";
  3211. var rm = document.createElement("button");
  3212. rm.type = "button";
  3213. rm.className = "rm";
  3214. rm.setAttribute("aria-label", "移除");
  3215. rm.textContent = "×";
  3216. rm.onclick = function () {
  3217. URL.revokeObjectURL(url);
  3218. wrap.remove();
  3219. };
  3220. wrap.appendChild(img);
  3221. wrap.appendChild(rm);
  3222. uploadPreview.appendChild(wrap);
  3223. }
  3224. function onFilesSelected(files) {
  3225. for (var i = 0; i < files.length; i++) {
  3226. addPreviewFile(files[i]);
  3227. }
  3228. uploadInput.value = "";
  3229. }
  3230. if (uploadInput && uploadPreview) {
  3231. uploadInput.addEventListener("change", function () {
  3232. if (uploadInput.files && uploadInput.files.length) {
  3233. onFilesSelected(uploadInput.files);
  3234. }
  3235. });
  3236. }
  3237. if (btnPickFiles && uploadInput) {
  3238. btnPickFiles.addEventListener("click", function (e) {
  3239. e.preventDefault();
  3240. e.stopPropagation();
  3241. uploadInput.click();
  3242. });
  3243. }
  3244. if (uploadZone && uploadInput) {
  3245. uploadZone.addEventListener("click", function (e) {
  3246. if (e.target === uploadZone || (e.target.classList && e.target.classList.contains("upload-zone-text"))) {
  3247. uploadInput.click();
  3248. }
  3249. });
  3250. uploadZone.addEventListener("keydown", function (e) {
  3251. if (e.key === "Enter" || e.key === " ") {
  3252. e.preventDefault();
  3253. uploadInput.click();
  3254. }
  3255. });
  3256. }
  3257. var dlgLinesBody = document.getElementById("dlgLinesBody");
  3258. function renumberLines() {
  3259. if (!dlgLinesBody) return;
  3260. var rows = dlgLinesBody.querySelectorAll("tr");
  3261. for (var i = 0; i < rows.length; i++) {
  3262. rows[i].querySelector("td:first-child").textContent = String(i + 1);
  3263. }
  3264. }
  3265. function bindRemove(btn) {
  3266. btn.addEventListener("click", function () {
  3267. var tr = btn.closest("tr");
  3268. if (tr && dlgLinesBody.querySelectorAll("tr").length > 1) {
  3269. tr.remove();
  3270. renumberLines();
  3271. }
  3272. });
  3273. }
  3274. if (dlgLinesBody) {
  3275. dlgLinesBody.querySelectorAll(".js-remove-line").forEach(function (b) {
  3276. bindRemove(b);
  3277. });
  3278. }
  3279. var btnAddLine = document.getElementById("btnAddLine");
  3280. if (btnAddLine && dlgLinesBody) {
  3281. btnAddLine.addEventListener("click", function () {
  3282. var tr = document.createElement("tr");
  3283. tr.innerHTML =
  3284. '<td></td>' +
  3285. '<td><div class="input-clear-wrap">' +
  3286. '<input type="text" class="input" name="lineName[]" placeholder="如:精品黄瓜" autocomplete="off" />' +
  3287. '<button type="button" class="input-clear" aria-label="清除商品名称" title="清除">' +
  3288. '<svg class="ic-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>' +
  3289. '</button></div></td>' +
  3290. '<td><input type="number" class="input" name="lineQty[]" placeholder="0" min="0" step="1" /></td>' +
  3291. '<td><select class="select" name="lineUnit[]" style="width:100%;min-width:0"><option>件</option><option>箱</option><option>kg</option></select></td>' +
  3292. '<td><button type="button" class="btn btn-icon js-remove-line">删除</button></td>';
  3293. bindRemove(tr.querySelector(".js-remove-line"));
  3294. dlgLinesBody.appendChild(tr);
  3295. renumberLines();
  3296. });
  3297. }
  3298. var btnSaveDemo = document.getElementById("btnSaveDemo");
  3299. if (btnSaveDemo) {
  3300. btnSaveDemo.addEventListener("click", function () {
  3301. alert("演示页:此处可提交表单或调接口。");
  3302. });
  3303. }
  3304. var toggleFilter = document.getElementById("toggleFilter");
  3305. var filterExtra = document.getElementById("filter-extra");
  3306. if (toggleFilter && filterExtra) {
  3307. toggleFilter.addEventListener("click", function () {
  3308. if (filterExtra.hasAttribute("hidden")) {
  3309. filterExtra.removeAttribute("hidden");
  3310. toggleFilter.setAttribute("aria-expanded", "true");
  3311. toggleFilter.textContent = "收起更多条件";
  3312. } else {
  3313. filterExtra.setAttribute("hidden", "");
  3314. toggleFilter.setAttribute("aria-expanded", "false");
  3315. toggleFilter.textContent = "展开更多条件";
  3316. }
  3317. });
  3318. var filterForm = document.querySelector(".filter-form");
  3319. if (filterForm) {
  3320. filterForm.addEventListener("reset", function () {
  3321. filterExtra.setAttribute("hidden", "");
  3322. toggleFilter.setAttribute("aria-expanded", "false");
  3323. toggleFilter.textContent = "展开更多条件";
  3324. setTimeout(function () {
  3325. syncInputClearButtons(filterForm);
  3326. }, 0);
  3327. });
  3328. }
  3329. }
  3330. (function initTreeTableExpand() {
  3331. document.querySelectorAll(".tree-expand-root").forEach(function (btn) {
  3332. btn.addEventListener("click", function () {
  3333. var parentTr = btn.closest("tr.tree-row-parent");
  3334. if (!parentTr) return;
  3335. var pid = parentTr.getAttribute("data-tree-id");
  3336. if (!pid) return;
  3337. var expanded = btn.getAttribute("aria-expanded") === "true";
  3338. var wrap = document.querySelector('tr.tree-row-children-wrap[data-tree-parent="' + pid + '"]');
  3339. if (wrap) wrap.classList.toggle("is-hidden", expanded);
  3340. btn.setAttribute("aria-expanded", expanded ? "false" : "true");
  3341. });
  3342. });
  3343. document.querySelectorAll(".tree-expand-nested").forEach(function (btn) {
  3344. btn.addEventListener("click", function () {
  3345. var tid = btn.getAttribute("data-expand-target");
  3346. if (!tid) return;
  3347. var expanded = btn.getAttribute("aria-expanded") === "true";
  3348. var wrap = document.querySelector('tr.tree-l2-wrap[data-expand-for="' + tid + '"]');
  3349. if (wrap) wrap.classList.toggle("is-hidden", expanded);
  3350. btn.setAttribute("aria-expanded", expanded ? "false" : "true");
  3351. });
  3352. });
  3353. })();
  3354. function setTopProgress(on) {
  3355. var bar = document.getElementById("topProgressBar");
  3356. if (!bar) return;
  3357. bar.classList.toggle("is-active", !!on);
  3358. }
  3359. function demoDelay(ms) {
  3360. return new Promise(function (resolve) {
  3361. setTimeout(resolve, ms);
  3362. });
  3363. }
  3364. function buildPlaceholderPanel(title) {
  3365. return (
  3366. '<header class="page-head">' +
  3367. "<h1>" +
  3368. title +
  3369. "</h1>" +
  3370. '<p class="desc">演示占位页:真实项目中为对应路由页面。从左侧菜单进入时会先显示加载,再刷新本页数据。</p>' +
  3371. '<p class="page-refresh-meta" style="margin:8px 0 0;font-size:12px;color:var(--text-muted)">最近加载:<time class="placeholder-refreshed-at">—</time></p>' +
  3372. "</header>" +
  3373. '<section class="panel" style="padding:24px">' +
  3374. '<p style="margin:0;color:var(--text-muted);line-height:1.6">「' +
  3375. title +
  3376. "」模块内容区。可对接 Vue Router、KeepAlive 或 qiankun 子应用。</p>" +
  3377. "</section>"
  3378. );
  3379. }
  3380. function ensurePlaceholderTabPanel(tabId, title) {
  3381. var mount = document.getElementById("tabPanelMount");
  3382. if (!mount) return null;
  3383. var panel = mount.querySelector('.tab-panel[data-tab-id="' + tabId + '"]');
  3384. if (!panel) {
  3385. panel = document.createElement("div");
  3386. panel.className = "tab-panel";
  3387. panel.setAttribute("data-tab-id", tabId);
  3388. panel.id = "tabPanel_" + tabId;
  3389. panel.innerHTML = buildPlaceholderPanel(title);
  3390. mount.appendChild(panel);
  3391. }
  3392. return panel;
  3393. }
  3394. function activateTabStrip(tabId) {
  3395. var strip = document.getElementById("tabsStrip");
  3396. if (strip) {
  3397. strip.querySelectorAll(".tab-chip").forEach(function (chip) {
  3398. var on = chip.getAttribute("data-tab-id") === tabId;
  3399. chip.classList.toggle("is-active", on);
  3400. chip.setAttribute("aria-selected", on ? "true" : "false");
  3401. });
  3402. }
  3403. var mount = document.getElementById("tabPanelMount");
  3404. if (mount) {
  3405. mount.querySelectorAll(".tab-panel").forEach(function (p) {
  3406. p.classList.toggle("is-active", p.getAttribute("data-tab-id") === tabId);
  3407. });
  3408. }
  3409. }
  3410. function ensureTabChip(tabId, title) {
  3411. var strip = document.getElementById("tabsStrip");
  3412. if (!strip) return null;
  3413. var chip = strip.querySelector('.tab-chip[data-tab-id="' + tabId + '"]');
  3414. if (!chip) {
  3415. chip = document.createElement("button");
  3416. chip.type = "button";
  3417. chip.className = "tab-chip";
  3418. chip.setAttribute("data-tab-id", tabId);
  3419. chip.setAttribute("role", "tab");
  3420. chip.setAttribute("aria-selected", "false");
  3421. chip.textContent = title;
  3422. chip.addEventListener("click", function () {
  3423. activateTabStrip(tabId);
  3424. var crumb = document.getElementById("crumbCurrent");
  3425. if (crumb) crumb.textContent = chip.textContent;
  3426. document.querySelectorAll(".nav a[data-tab-id]").forEach(function (a) {
  3427. a.classList.toggle("active", a.getAttribute("data-tab-id") === tabId);
  3428. });
  3429. });
  3430. strip.appendChild(chip);
  3431. } else {
  3432. chip.textContent = title;
  3433. }
  3434. return chip;
  3435. }
  3436. function refreshTabContent(tabId, title) {
  3437. var panel =
  3438. tabId === "delivery"
  3439. ? document.getElementById("tabPanelDelivery")
  3440. : ensurePlaceholderTabPanel(tabId, title);
  3441. if (!panel) return Promise.resolve();
  3442. panel.classList.add("is-refreshing");
  3443. setTopProgress(true);
  3444. return demoDelay(380 + Math.random() * 220).then(function () {
  3445. panel.classList.remove("is-refreshing");
  3446. setTopProgress(false);
  3447. if (tabId === "delivery") {
  3448. var t = document.getElementById("deliveryRefreshedAt");
  3449. if (t) {
  3450. t.textContent = new Date().toLocaleString("zh-CN", { hour12: false });
  3451. }
  3452. refreshTaskTableTotals();
  3453. } else {
  3454. var pt = panel.querySelector(".placeholder-refreshed-at");
  3455. if (pt) {
  3456. pt.textContent = new Date().toLocaleString("zh-CN", { hour12: false });
  3457. }
  3458. }
  3459. });
  3460. }
  3461. function openTabFromMenu(tabId, title) {
  3462. if (tabId !== "delivery") {
  3463. ensurePlaceholderTabPanel(tabId, title);
  3464. }
  3465. ensureTabChip(tabId, title);
  3466. activateTabStrip(tabId);
  3467. var crumb = document.getElementById("crumbCurrent");
  3468. if (crumb) crumb.textContent = title;
  3469. document.querySelectorAll(".nav a[data-tab-id]").forEach(function (a) {
  3470. a.classList.toggle("active", a.getAttribute("data-tab-id") === tabId);
  3471. });
  3472. return refreshTabContent(tabId, title);
  3473. }
  3474. (function initMainTabs() {
  3475. var nav = document.querySelector(".nav");
  3476. if (nav) {
  3477. nav.querySelectorAll("a[data-tab-id]").forEach(function (a) {
  3478. a.addEventListener("click", function (e) {
  3479. e.preventDefault();
  3480. var id = a.getAttribute("data-tab-id");
  3481. var title = a.textContent.trim();
  3482. if (id) openTabFromMenu(id, title);
  3483. });
  3484. });
  3485. }
  3486. ensureTabChip("delivery", "配送调度");
  3487. activateTabStrip("delivery");
  3488. var t0 = document.getElementById("deliveryRefreshedAt");
  3489. if (t0) {
  3490. t0.textContent = new Date().toLocaleString("zh-CN", { hour12: false });
  3491. }
  3492. })();
  3493. document.addEventListener(
  3494. "click",
  3495. function (e) {
  3496. var btn = e.target.closest("button");
  3497. if (!btn || btn.disabled) return;
  3498. if (btn.classList.contains("input-clear")) return;
  3499. if (btn.classList.contains("tree-expand")) return;
  3500. if (btn.classList.contains("tab-chip")) return;
  3501. if (btn.classList.contains("btn-link")) return;
  3502. if (btn.classList.contains("pager-num") && btn.classList.contains("is-current")) return;
  3503. if (btn.closest(".th-check-inner") && btn.type === "button") return;
  3504. if (
  3505. btn.matches(
  3506. ".btn, .btn-primary, .btn-op, .pager-btn, .btn-icon, .upload-zone-btn, .btn-block"
  3507. )
  3508. ) {
  3509. var wasDisabled = btn.disabled;
  3510. btn.classList.add("is-loading");
  3511. if (!wasDisabled) btn.disabled = true;
  3512. window.clearTimeout(btn._demoLoadT);
  3513. btn._demoLoadT = window.setTimeout(function () {
  3514. btn.classList.remove("is-loading");
  3515. if (!wasDisabled) btn.disabled = false;
  3516. }, 550);
  3517. }
  3518. },
  3519. true
  3520. );
  3521. })();
  3522. </script>
  3523. </body>
  3524. </html>