xto-fronted 0.4.86 → 0.4.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/dist/assets/404-C9Uh6Uu-.css +1 -0
  2. package/dist/assets/404-zjGLLssH.js +1 -0
  3. package/dist/assets/index-B5xc4gQB.css +1 -0
  4. package/dist/assets/index-BDgOY6Rp.js +1 -0
  5. package/dist/assets/index-BIoRANs0.js +1 -0
  6. package/dist/assets/index-Bz0BgZQ1.js +1 -0
  7. package/dist/assets/index-CAdztNsv.css +1 -0
  8. package/dist/assets/index-CCXrcISf.css +1 -0
  9. package/dist/assets/{index-eQ-JQMk3.css → index-CfpZmcpk.css} +1 -1
  10. package/dist/assets/index-CwJSA85U.js +1 -0
  11. package/dist/assets/index-CwRA10ac.js +1 -0
  12. package/dist/assets/index-D8NDxq9d.js +1 -0
  13. package/dist/assets/index-DEB6-Iv_.js +2 -0
  14. package/dist/assets/index-DM4Ezclc.css +1 -0
  15. package/dist/assets/index-DYv7nImj.css +1 -0
  16. package/dist/assets/index-t-2Y0KhA.css +1 -0
  17. package/dist/assets/vendor-CUVPinTg.js +13 -0
  18. package/dist/assets/{vue-vendor-05sbU7Th.js → vue-vendor-DeJXJVbN.js} +7 -7
  19. package/dist/assets/{xto-base-BSTP-Yn2.js → xto-base-PwLGsxxb.js} +1 -1
  20. package/dist/assets/{xto-core-cL8BAdce.js → xto-core-CtL4zKiV.js} +1 -1
  21. package/dist/assets/{xto-data-Cw0dv5K5.js → xto-data-bCXQa7fT.js} +1 -1
  22. package/dist/assets/{xto-feedback-CTk0sMCW.js → xto-feedback-CPydp0kn.js} +1 -1
  23. package/dist/assets/{xto-form-C5i2lk3C.js → xto-form-bywohdAf.js} +1 -1
  24. package/dist/assets/{xto-navigation-DHsTg0WG.js → xto-navigation-Bbdpine9.js} +1 -1
  25. package/dist/index.html +9 -9
  26. package/package.json +7 -3
  27. package/src/App.vue +48 -19
  28. package/src/assets/styles/_root.scss +183 -139
  29. package/src/assets/styles/_variables.scss +70 -45
  30. package/src/assets/styles/index.scss +267 -42
  31. package/src/components/Layout/Sidebar.vue +43 -2
  32. package/src/components/Layout/TopMenu.vue +37 -12
  33. package/src/components/Layout/index.vue +1 -0
  34. package/src/composables/useI18n.ts +44 -0
  35. package/src/index.ts +15 -1
  36. package/src/stores/index.ts +2 -1
  37. package/src/stores/locale.ts +67 -0
  38. package/src/types/json-bigint.d.ts +18 -0
  39. package/src/types/xto.d.ts +24 -0
  40. package/src/utils/request.ts +18 -1
  41. package/src/views/dashboard/index.vue +545 -283
  42. package/src/views/error/403.vue +215 -20
  43. package/src/views/error/404.vue +217 -20
  44. package/src/views/login/index.vue +483 -91
  45. package/src/views/system/menu/index.vue +690 -381
  46. package/src/views/system/role/index.vue +583 -304
  47. package/src/views/system/user/index.vue +655 -326
  48. package/dist/App.vue.d.ts +0 -2
  49. package/dist/api/auth.d.ts +0 -8
  50. package/dist/api/system.d.ts +0 -16
  51. package/dist/api/user.d.ts +0 -13
  52. package/dist/assets/404-BNfk6aVM.js +0 -1
  53. package/dist/assets/404-ByYjFnhI.js +0 -1
  54. package/dist/assets/404-CH5_LdtS.css +0 -1
  55. package/dist/assets/404-sqW80Hc-.css +0 -1
  56. package/dist/assets/index-BBhC8PX4.js +0 -1
  57. package/dist/assets/index-BOEFG4lP.css +0 -1
  58. package/dist/assets/index-BSa4SMHI.js +0 -1
  59. package/dist/assets/index-BihVoviB.js +0 -1
  60. package/dist/assets/index-Bmux4tGG.js +0 -1
  61. package/dist/assets/index-BrvR0Fn_.css +0 -1
  62. package/dist/assets/index-C4ZCZoPp.js +0 -1
  63. package/dist/assets/index-CMgRqiaT.js +0 -1
  64. package/dist/assets/index-CUh_s55Z.css +0 -1
  65. package/dist/assets/index-CV768Wu1.js +0 -1
  66. package/dist/assets/index-D0OnMilp.js +0 -1
  67. package/dist/assets/index-D9wlAuR_.js +0 -1
  68. package/dist/assets/index-DPkO-STg.js +0 -1
  69. package/dist/assets/index-DawJb02s.css +0 -1
  70. package/dist/assets/index-DcFOybjo.js +0 -1
  71. package/dist/assets/index-DiHSZ6SJ.js +0 -2
  72. package/dist/assets/index-GDP-IkXE.css +0 -1
  73. package/dist/assets/index-Gx75raue.js +0 -2
  74. package/dist/assets/index-JsZ6rkQj.js +0 -1
  75. package/dist/assets/index-PfV8pzQz.css +0 -1
  76. package/dist/assets/index-Swfu6yvD.css +0 -1
  77. package/dist/assets/vendor-42ANG6Sg.js +0 -6
  78. package/dist/assets/vite-Dw-pgLOX.js +0 -1
  79. package/dist/assets/vue-vendor-Br-l7wbK.js +0 -29
  80. package/dist/assets/xto-base-C-IBqjVs.js +0 -1
  81. package/dist/assets/xto-base-C6eqMPdO.css +0 -1
  82. package/dist/assets/xto-core-DZK7Cyg0.js +0 -1
  83. package/dist/assets/xto-data-BFpiDgJi.js +0 -1
  84. package/dist/assets/xto-data-CnAQAQH2.css +0 -1
  85. package/dist/assets/xto-feedback-B2M02fn3.js +0 -1
  86. package/dist/assets/xto-feedback-ByaS-C7_.css +0 -1
  87. package/dist/assets/xto-form-CrsyAjyr.css +0 -1
  88. package/dist/assets/xto-form-NRjKKNcY.js +0 -1
  89. package/dist/assets/xto-layout-D1stVnJI.css +0 -1
  90. package/dist/assets/xto-navigation-BuRQVoD8.css +0 -1
  91. package/dist/assets/xto-navigation-qLRTxo68.js +0 -1
  92. package/dist/components/Layout/Footer.vue.d.ts +0 -2
  93. package/dist/components/Layout/Header.vue.d.ts +0 -5
  94. package/dist/components/Layout/MixTopMenu.vue.d.ts +0 -5
  95. package/dist/components/Layout/Sidebar.vue.d.ts +0 -11
  96. package/dist/components/Layout/SidebarMenuItem.vue.d.ts +0 -5
  97. package/dist/components/Layout/Tabs.vue.d.ts +0 -2
  98. package/dist/components/Layout/TopMenu.vue.d.ts +0 -5
  99. package/dist/components/Layout/index.vue.d.ts +0 -2
  100. package/dist/composables/useApp.d.ts +0 -29
  101. package/dist/composables/useAuth.d.ts +0 -6
  102. package/dist/composables/useForm.d.ts +0 -20
  103. package/dist/composables/useTable.d.ts +0 -29
  104. package/dist/directives/permission.d.ts +0 -4
  105. package/dist/enums/index.d.ts +0 -32
  106. package/dist/index-0h_oG71z.js +0 -345
  107. package/dist/index-1EEezXR3.js +0 -142
  108. package/dist/index-1EkaJDoD.js +0 -142
  109. package/dist/index-1VN-SSaj.js +0 -2873
  110. package/dist/index-23nX2t9j.js +0 -142
  111. package/dist/index-2RwL0s53.js +0 -142
  112. package/dist/index-2tvOdd6w.js +0 -372
  113. package/dist/index-3zeoNaqT.js +0 -345
  114. package/dist/index-8oCLh51t.js +0 -475
  115. package/dist/index-AF5JN8Up.js +0 -142
  116. package/dist/index-B02xiP9g.js +0 -372
  117. package/dist/index-B05xso4D.js +0 -345
  118. package/dist/index-B1VS4tRU.js +0 -475
  119. package/dist/index-B3_VQZ-d.js +0 -372
  120. package/dist/index-B3kL6i_g.js +0 -345
  121. package/dist/index-B4Wny0vt.js +0 -345
  122. package/dist/index-B5RdBQ-f.js +0 -475
  123. package/dist/index-B5rbuoWS.js +0 -2835
  124. package/dist/index-BAFpPlnI.js +0 -475
  125. package/dist/index-BAJakzRz.js +0 -142
  126. package/dist/index-BAOHlfVd.js +0 -142
  127. package/dist/index-BAzJWzWG.js +0 -2852
  128. package/dist/index-BEIxh_R3.js +0 -2982
  129. package/dist/index-BEWrRNps.js +0 -475
  130. package/dist/index-BFtKVmzo.js +0 -142
  131. package/dist/index-BHPrW1wN.js +0 -142
  132. package/dist/index-BHQIqN1E.js +0 -372
  133. package/dist/index-BHZ_y8lp.js +0 -2067
  134. package/dist/index-BHvnTk5Z.js +0 -475
  135. package/dist/index-BIf7CYCW.js +0 -475
  136. package/dist/index-BK1Y1z9_.js +0 -142
  137. package/dist/index-BK8qDl14.js +0 -372
  138. package/dist/index-BKnta6nv.js +0 -372
  139. package/dist/index-BLIem13z.js +0 -345
  140. package/dist/index-BNBv_FCu.js +0 -142
  141. package/dist/index-BNwtCm83.js +0 -345
  142. package/dist/index-BOEMA7WH.js +0 -345
  143. package/dist/index-BOwDukt-.js +0 -2346
  144. package/dist/index-BR9WVjrV.js +0 -2851
  145. package/dist/index-BRMkw154.js +0 -475
  146. package/dist/index-BRQBannD.js +0 -345
  147. package/dist/index-BShKtLTm.js +0 -475
  148. package/dist/index-BVpqTdd2.js +0 -3132
  149. package/dist/index-BWGQANAN.js +0 -2851
  150. package/dist/index-BZKw7U60.js +0 -475
  151. package/dist/index-BadGWG4q.js +0 -475
  152. package/dist/index-BbeWpCd3.js +0 -345
  153. package/dist/index-BeUYNx5P.js +0 -475
  154. package/dist/index-BgiGtxRr.js +0 -142
  155. package/dist/index-BhKJLk9M.js +0 -2824
  156. package/dist/index-BijsKQ3r.js +0 -475
  157. package/dist/index-BjEfspqP.js +0 -475
  158. package/dist/index-BjEnT8WQ.js +0 -372
  159. package/dist/index-BpOcz1m4.js +0 -142
  160. package/dist/index-BrRdLbLB.js +0 -142
  161. package/dist/index-BsHOXfCq.js +0 -372
  162. package/dist/index-Bt5ocakb.js +0 -475
  163. package/dist/index-BtE24Mt4.js +0 -142
  164. package/dist/index-BvcBQjw6.js +0 -2836
  165. package/dist/index-C0krZRWf.js +0 -2859
  166. package/dist/index-C1cKUqmA.js +0 -372
  167. package/dist/index-C3ANmIDM.js +0 -475
  168. package/dist/index-C5JrEkwD.js +0 -345
  169. package/dist/index-C5m4qNMQ.js +0 -2851
  170. package/dist/index-C69vu_ot.js +0 -3147
  171. package/dist/index-C6Um6eFy.js +0 -475
  172. package/dist/index-C75ePQYC.js +0 -372
  173. package/dist/index-C7wKLGhC.js +0 -142
  174. package/dist/index-C8DBDnxx.js +0 -2836
  175. package/dist/index-CAnqlIjX.js +0 -475
  176. package/dist/index-CB36fztK.js +0 -345
  177. package/dist/index-CBKo6Enz.js +0 -345
  178. package/dist/index-CBcuyFIa.js +0 -2326
  179. package/dist/index-CCxIu4ex.js +0 -372
  180. package/dist/index-CD1ZEMzL.js +0 -2836
  181. package/dist/index-CDx-6tvY.js +0 -345
  182. package/dist/index-CGrvOpdb.js +0 -372
  183. package/dist/index-CHJWNf3f.js +0 -475
  184. package/dist/index-CIb06BxE.js +0 -372
  185. package/dist/index-CLiJcB9v.js +0 -142
  186. package/dist/index-CNS6Ohfw.js +0 -2853
  187. package/dist/index-COkjB5Jm.js +0 -142
  188. package/dist/index-CS5h1O5W.js +0 -345
  189. package/dist/index-CT0deRSL.js +0 -372
  190. package/dist/index-CTQQBaf4.js +0 -345
  191. package/dist/index-CUAC0ZFB.js +0 -475
  192. package/dist/index-CVMV9Tfg.js +0 -142
  193. package/dist/index-CWOx59Vc.js +0 -142
  194. package/dist/index-CWdSR99B.js +0 -372
  195. package/dist/index-CWvOcS8Y.js +0 -345
  196. package/dist/index-C_8yJlf1.js +0 -475
  197. package/dist/index-C_MzXmh2.js +0 -2830
  198. package/dist/index-Cb7HXiBR.js +0 -372
  199. package/dist/index-CbA8oUvZ.js +0 -142
  200. package/dist/index-CbEIzXG9.js +0 -475
  201. package/dist/index-Cb_m0zz1.js +0 -475
  202. package/dist/index-Cc6HgGzc.js +0 -142
  203. package/dist/index-CcWdW9Mj.js +0 -372
  204. package/dist/index-CdAqkP8L.js +0 -345
  205. package/dist/index-CeVUHSt9.js +0 -2847
  206. package/dist/index-CejRpy0a.js +0 -475
  207. package/dist/index-Cg5wO_jJ.js +0 -372
  208. package/dist/index-CgZM1vHq.js +0 -372
  209. package/dist/index-CgofFg1D.js +0 -372
  210. package/dist/index-ChheXGDK.js +0 -372
  211. package/dist/index-ChuFHoI0.js +0 -372
  212. package/dist/index-CiOJZ6_b.js +0 -372
  213. package/dist/index-ClCWmz_7.js +0 -2844
  214. package/dist/index-CmajGrKA.js +0 -142
  215. package/dist/index-Cn_4o3fi.js +0 -345
  216. package/dist/index-CoJBI1vE.js +0 -345
  217. package/dist/index-CqLLaQ-P.js +0 -372
  218. package/dist/index-CsRhLwmR.js +0 -475
  219. package/dist/index-CttJvDTG.js +0 -475
  220. package/dist/index-Cu3m9UeX.js +0 -345
  221. package/dist/index-CyQbOMHa.js +0 -2859
  222. package/dist/index-D-NcbLKL.js +0 -345
  223. package/dist/index-D0gstkiI.js +0 -2836
  224. package/dist/index-D0krzDtx.js +0 -475
  225. package/dist/index-D1moFd4V.js +0 -372
  226. package/dist/index-D2Hs4bZj.js +0 -345
  227. package/dist/index-D4QhrTZs.js +0 -475
  228. package/dist/index-D6CC02F-.js +0 -372
  229. package/dist/index-D6s7Za5s.js +0 -142
  230. package/dist/index-D7i5bck2.js +0 -3132
  231. package/dist/index-D8YBeNfn.js +0 -345
  232. package/dist/index-D93bv6lU.js +0 -372
  233. package/dist/index-DAX_bFj5.js +0 -142
  234. package/dist/index-DBii9cjr.js +0 -475
  235. package/dist/index-DBzpSkNi.js +0 -345
  236. package/dist/index-DCuKNBck.js +0 -3153
  237. package/dist/index-DE3uKf0K.js +0 -142
  238. package/dist/index-DGAqp0QX.js +0 -372
  239. package/dist/index-DGh_w_v1.js +0 -475
  240. package/dist/index-DItKvJyI.js +0 -475
  241. package/dist/index-DKAyB0fv.js +0 -345
  242. package/dist/index-DKN4xsXi.js +0 -2851
  243. package/dist/index-DN_iIn8V.js +0 -345
  244. package/dist/index-DONIBpuJ.js +0 -345
  245. package/dist/index-DPDK8Ht4.js +0 -2851
  246. package/dist/index-DPr3ldST.js +0 -2354
  247. package/dist/index-DS1wfdxM.js +0 -142
  248. package/dist/index-DUW5cCh8.js +0 -372
  249. package/dist/index-DUv28uVd.js +0 -142
  250. package/dist/index-DVS-5SZn.js +0 -475
  251. package/dist/index-DWI51JAp.js +0 -372
  252. package/dist/index-DWVmEexu.js +0 -475
  253. package/dist/index-DXf9wUqQ.js +0 -142
  254. package/dist/index-DXmB-sjP.js +0 -345
  255. package/dist/index-DY_e_Dxv.js +0 -372
  256. package/dist/index-DZar-us7.js +0 -142
  257. package/dist/index-D_nwqVrw.js +0 -2847
  258. package/dist/index-DbEeir7h.js +0 -2834
  259. package/dist/index-Dbf5ypbh.js +0 -142
  260. package/dist/index-DcSK-2pt.js +0 -345
  261. package/dist/index-DclK-Mc7.js +0 -2864
  262. package/dist/index-DcnzaxVi.js +0 -142
  263. package/dist/index-Dd8PlF0M.js +0 -2857
  264. package/dist/index-DdkVT39s.js +0 -345
  265. package/dist/index-DeLbU1t-.js +0 -345
  266. package/dist/index-DfV_J8Te.js +0 -345
  267. package/dist/index-DgPnNd8U.js +0 -475
  268. package/dist/index-Dg_JLa7s.js +0 -345
  269. package/dist/index-Di2VPQjn.js +0 -345
  270. package/dist/index-DibHUbqc.js +0 -372
  271. package/dist/index-DjCVSrpw.js +0 -475
  272. package/dist/index-Djdb936p.js +0 -142
  273. package/dist/index-DmFL_GSt.js +0 -142
  274. package/dist/index-DmP4X2k0.js +0 -372
  275. package/dist/index-DmtmRd1r.js +0 -372
  276. package/dist/index-Dn8MA3kh.js +0 -475
  277. package/dist/index-Do7uXDSi.js +0 -2071
  278. package/dist/index-Dpv1M9Hu.js +0 -372
  279. package/dist/index-DqUuZC8f.js +0 -372
  280. package/dist/index-DtEXQIjn.js +0 -372
  281. package/dist/index-Dtw-qZ0V.js +0 -372
  282. package/dist/index-Dv3hIz8H.js +0 -345
  283. package/dist/index-Dv9jnaWp.js +0 -345
  284. package/dist/index-Dw8KZwGJ.js +0 -2354
  285. package/dist/index-DwJILh-Q.js +0 -3149
  286. package/dist/index-DwzGV3B9.js +0 -142
  287. package/dist/index-DyybaRkK.js +0 -345
  288. package/dist/index-DzerABZq.js +0 -372
  289. package/dist/index-F53NVkhc.js +0 -142
  290. package/dist/index-Fl0_k4XF.js +0 -475
  291. package/dist/index-HMYRCPrs.js +0 -2326
  292. package/dist/index-I49wpmT-.js +0 -372
  293. package/dist/index-IPv1O8Zq.js +0 -345
  294. package/dist/index-JLqnaery.js +0 -372
  295. package/dist/index-JRCQ1CYj.js +0 -142
  296. package/dist/index-L86-iO8O.js +0 -345
  297. package/dist/index-Q56EoFor.js +0 -142
  298. package/dist/index-QxKMr6p0.js +0 -2829
  299. package/dist/index-THRvW9Xm.js +0 -2856
  300. package/dist/index-WIUmTlfL.js +0 -345
  301. package/dist/index-WRrsK0pR.js +0 -475
  302. package/dist/index-Wq652QGf.js +0 -2836
  303. package/dist/index-YnSLCdjP.js +0 -475
  304. package/dist/index-Ynf0lvBE.js +0 -142
  305. package/dist/index-_OtTmb9i.js +0 -142
  306. package/dist/index-_oqoy_3D.js +0 -142
  307. package/dist/index-cl4Y-BWw.js +0 -475
  308. package/dist/index-d_eHJkDW.js +0 -475
  309. package/dist/index-dnmEyUWB.js +0 -372
  310. package/dist/index-dz6CwD6A.js +0 -142
  311. package/dist/index-g4rkaRry.js +0 -475
  312. package/dist/index-g9bRMdAX.js +0 -142
  313. package/dist/index-h4z4-a2p.js +0 -2852
  314. package/dist/index-h7BhcQYM.js +0 -475
  315. package/dist/index-hI3qofTF.js +0 -345
  316. package/dist/index-iyQQumAA.js +0 -372
  317. package/dist/index-js_OhKON.js +0 -345
  318. package/dist/index-kgb4MdHr.js +0 -475
  319. package/dist/index-logKl0VM.js +0 -3144
  320. package/dist/index-lrFMrOlR.js +0 -3132
  321. package/dist/index-mEHxtQWj.js +0 -372
  322. package/dist/index-mNm1BV3n.js +0 -2857
  323. package/dist/index-oOzAFXfr.js +0 -142
  324. package/dist/index-s3vGq0ro.js +0 -345
  325. package/dist/index-wt7AbUqc.js +0 -2342
  326. package/dist/index.d.ts +0 -54
  327. package/dist/index.es.js +0 -91
  328. package/dist/index.umd.js +0 -1
  329. package/dist/main.d.ts +0 -0
  330. package/dist/router/dynamicRoutes.d.ts +0 -30
  331. package/dist/router/guards.d.ts +0 -17
  332. package/dist/router/index.d.ts +0 -6
  333. package/dist/router/layoutRoute.d.ts +0 -18
  334. package/dist/router/staticRoutes.d.ts +0 -2
  335. package/dist/stores/app.d.ts +0 -93
  336. package/dist/stores/auth.d.ts +0 -41
  337. package/dist/stores/index.d.ts +0 -9
  338. package/dist/stores/menu.d.ts +0 -77
  339. package/dist/stores/user.d.ts +0 -92
  340. package/dist/style.css +0 -1
  341. package/dist/utils/auth.d.ts +0 -27
  342. package/dist/utils/config.d.ts +0 -30
  343. package/dist/utils/permission.d.ts +0 -18
  344. package/dist/utils/request.d.ts +0 -24
  345. package/dist/utils/storage.d.ts +0 -24
  346. package/dist/views/dashboard/index.vue.d.ts +0 -2
  347. package/dist/views/error/403.vue.d.ts +0 -2
  348. package/dist/views/error/404.vue.d.ts +0 -2
  349. package/dist/views/login/index.vue.d.ts +0 -4
  350. package/dist/views/system/menu/index.vue.d.ts +0 -4
  351. package/dist/views/system/role/index.vue.d.ts +0 -4
  352. package/dist/views/system/user/index.vue.d.ts +0 -4
@@ -1,381 +1,690 @@
1
- <script setup lang="ts">
2
- import { ref, reactive, computed, onMounted } from 'vue'
3
- import { Form, FormItem, Input, Select, Switch, InputNumber } from '@xto/form'
4
- import { Card, Tag } from '@xto/data'
5
- import { Modal, Message } from '@xto/feedback'
6
- import { Space, Button } from '@xto/base'
7
- import { MenuType, MenuTypeOptions, Status } from '@/enums'
8
-
9
- // 菜单数据类型
10
- interface Menu {
11
- id: number
12
- parentId: number | null
13
- name: string
14
- path: string
15
- component: string
16
- redirect: string
17
- icon: string
18
- title: string
19
- type: MenuType
20
- sort: number
21
- status: Status
22
- hidden: boolean
23
- keepAlive: boolean
24
- children?: Menu[]
25
- }
26
-
27
- // Mock 数据
28
- const mockMenus: Menu[] = [
29
- {
30
- id: 1,
31
- parentId: null,
32
- name: 'Dashboard',
33
- path: '/dashboard',
34
- component: 'dashboard/index',
35
- redirect: '',
36
- icon: 'dashboard',
37
- title: '仪表盘',
38
- type: MenuType.MENU,
39
- sort: 1,
40
- status: Status.ENABLED,
41
- hidden: false,
42
- keepAlive: true
43
- },
44
- {
45
- id: 2,
46
- parentId: null,
47
- name: 'System',
48
- path: '/system',
49
- component: '',
50
- redirect: '/system/user',
51
- icon: 'setting',
52
- title: '系统管理',
53
- type: MenuType.DIRECTORY,
54
- sort: 2,
55
- status: Status.ENABLED,
56
- hidden: false,
57
- keepAlive: false,
58
- children: [
59
- {
60
- id: 21,
61
- parentId: 2,
62
- name: 'SystemUser',
63
- path: '/system/user',
64
- component: 'system/user/index',
65
- redirect: '',
66
- icon: 'user',
67
- title: '用户管理',
68
- type: MenuType.MENU,
69
- sort: 1,
70
- status: Status.ENABLED,
71
- hidden: false,
72
- keepAlive: true
73
- },
74
- {
75
- id: 22,
76
- parentId: 2,
77
- name: 'SystemRole',
78
- path: '/system/role',
79
- component: 'system/role/index',
80
- redirect: '',
81
- icon: 'role',
82
- title: '角色管理',
83
- type: MenuType.MENU,
84
- sort: 2,
85
- status: Status.ENABLED,
86
- hidden: false,
87
- keepAlive: true
88
- },
89
- {
90
- id: 23,
91
- parentId: 2,
92
- name: 'SystemMenu',
93
- path: '/system/menu',
94
- component: 'system/menu/index',
95
- redirect: '',
96
- icon: 'menu',
97
- title: '菜单管理',
98
- type: MenuType.MENU,
99
- sort: 3,
100
- status: Status.ENABLED,
101
- hidden: false,
102
- keepAlive: true
103
- }
104
- ]
105
- }
106
- ]
107
-
108
- const loading = ref(false)
109
- const menuList = ref<Menu[]>([])
110
-
111
- // 弹窗
112
- const modalVisible = ref(false)
113
- const modalTitle = computed(() => formData.id ? '编辑菜单' : '新增菜单')
114
- const formData = reactive({
115
- id: 0,
116
- parentId: null as number | null,
117
- name: '',
118
- path: '',
119
- component: '',
120
- redirect: '',
121
- icon: '',
122
- title: '',
123
- type: MenuType.MENU,
124
- sort: 0,
125
- status: Status.ENABLED,
126
- hidden: false,
127
- keepAlive: true
128
- })
129
-
130
- const rules: Record<string, any[]> = {
131
- name: [
132
- { required: true, message: '请输入菜单名称', trigger: 'blur' }
133
- ],
134
- path: [
135
- { required: true, message: '请输入路由路径', trigger: 'blur' }
136
- ],
137
- title: [
138
- { required: true, message: '请输入菜单标题', trigger: 'blur' }
139
- ]
140
- }
141
-
142
- const formRef = ref()
143
-
144
- // 获取菜单列表
145
- const getMenuList = () => {
146
- loading.value = true
147
- setTimeout(() => {
148
- menuList.value = mockMenus
149
- loading.value = false
150
- }, 300)
151
- }
152
-
153
- // 新增
154
- const handleAdd = (parentId: number | null = null) => {
155
- Object.assign(formData, {
156
- id: 0,
157
- parentId,
158
- name: '',
159
- path: '',
160
- component: '',
161
- redirect: '',
162
- icon: '',
163
- title: '',
164
- type: MenuType.MENU,
165
- sort: 0,
166
- status: Status.ENABLED,
167
- hidden: false,
168
- keepAlive: true
169
- })
170
- modalVisible.value = true
171
- }
172
-
173
- // 编辑
174
- const handleEdit = (node: Menu) => {
175
- Object.assign(formData, node)
176
- modalVisible.value = true
177
- }
178
-
179
- // 提交
180
- const handleSubmit = async () => {
181
- try {
182
- await formRef.value?.validate()
183
- Message.success(formData.id ? '编辑成功' : '新增成功')
184
- modalVisible.value = false
185
- getMenuList()
186
- } catch (error) {
187
- console.error(error)
188
- }
189
- }
190
-
191
- // 菜单图标
192
- const getMenuIcon = (icon?: string) => {
193
- const iconMap: Record<string, string> = {
194
- dashboard: '📊',
195
- system: '⚙️',
196
- user: '👤',
197
- role: '👥',
198
- menu: '📋',
199
- setting: '🔧'
200
- }
201
- return iconMap[icon || ''] || '📄'
202
- }
203
-
204
- onMounted(() => {
205
- getMenuList()
206
- })
207
- </script>
208
-
209
- <template>
210
- <div class="menu-page">
211
- <Card class="menu-card">
212
- <!-- 工具栏 -->
213
- <div class="toolbar">
214
- <Button type="primary" @click="handleAdd()">新增菜单</Button>
215
- </div>
216
-
217
- <!-- 菜单树 -->
218
- <div class="menu-tree">
219
- <table class="tree-table">
220
- <thead>
221
- <tr>
222
- <th>菜单名称</th>
223
- <th>图标</th>
224
- <th>路由路径</th>
225
- <th>类型</th>
226
- <th>排序</th>
227
- <th>状态</th>
228
- <th>操作</th>
229
- </tr>
230
- </thead>
231
- <tbody>
232
- <template v-for="menu in menuList" :key="menu.id">
233
- <tr class="tree-row tree-row--level-0">
234
- <td>
235
- <span class="menu-name" @click="handleEdit(menu)">{{ menu.title }}</span>
236
- </td>
237
- <td>{{ getMenuIcon(menu.icon) }}</td>
238
- <td>{{ menu.path }}</td>
239
- <td>
240
- <Tag :type="menu.type === MenuType.DIRECTORY ? 'primary' : menu.type === MenuType.MENU ? 'success' : 'warning'" size="small">
241
- {{ menu.type === MenuType.DIRECTORY ? '目录' : menu.type === MenuType.MENU ? '菜单' : '按钮' }}
242
- </Tag>
243
- </td>
244
- <td>{{ menu.sort }}</td>
245
- <td>
246
- <Tag :type="menu.status === Status.ENABLED ? 'success' : 'danger'" size="small">
247
- {{ menu.status === Status.ENABLED ? '启用' : '禁用' }}
248
- </Tag>
249
- </td>
250
- <td>
251
- <Space>
252
- <Button type="primary" link size="small" @click="handleAdd(menu.id)">新增</Button>
253
- <Button type="primary" link size="small" @click="handleEdit(menu)">编辑</Button>
254
- </Space>
255
- </td>
256
- </tr>
257
- <template v-if="menu.children" v-for="child in menu.children" :key="child.id">
258
- <tr class="tree-row tree-row--level-1">
259
- <td>
260
- <span class="tree-indent"></span>
261
- <span class="menu-name" @click="handleEdit(child)">{{ child.title }}</span>
262
- </td>
263
- <td>{{ getMenuIcon(child.icon) }}</td>
264
- <td>{{ child.path }}</td>
265
- <td>
266
- <Tag :type="child.type === MenuType.DIRECTORY ? 'primary' : child.type === MenuType.MENU ? 'success' : 'warning'" size="small">
267
- {{ child.type === MenuType.DIRECTORY ? '目录' : child.type === MenuType.MENU ? '菜单' : '按钮' }}
268
- </Tag>
269
- </td>
270
- <td>{{ child.sort }}</td>
271
- <td>
272
- <Tag :type="child.status === Status.ENABLED ? 'success' : 'danger'" size="small">
273
- {{ child.status === Status.ENABLED ? '启用' : '禁用' }}
274
- </Tag>
275
- </td>
276
- <td>
277
- <Space>
278
- <Button type="primary" link size="small" @click="handleEdit(child)">编辑</Button>
279
- </Space>
280
- </td>
281
- </tr>
282
- </template>
283
- </template>
284
- </tbody>
285
- </table>
286
- </div>
287
- </Card>
288
-
289
- <!-- 编辑弹窗 -->
290
- <Modal v-model="modalVisible" :title="modalTitle" width="600px">
291
- <Form ref="formRef" :model="formData" :rules="rules" label-width="80px">
292
- <FormItem label="上级菜单">
293
- <Input v-model="formData.parentId" placeholder="上级菜单ID" disabled />
294
- </FormItem>
295
- <FormItem label="菜单类型">
296
- <Select v-model="formData.type" :options="MenuTypeOptions" />
297
- </FormItem>
298
- <FormItem label="菜单名称" prop="name">
299
- <Input v-model="formData.name" placeholder="请输入菜单名称(路由name)" />
300
- </FormItem>
301
- <FormItem label="菜单标题" prop="title">
302
- <Input v-model="formData.title" placeholder="请输入菜单标题" />
303
- </FormItem>
304
- <FormItem label="路由路径" prop="path">
305
- <Input v-model="formData.path" placeholder="请输入路由路径" />
306
- </FormItem>
307
- <FormItem label="组件路径">
308
- <Input v-model="formData.component" placeholder="请输入组件路径" />
309
- </FormItem>
310
- <FormItem label="图标">
311
- <Input v-model="formData.icon" placeholder="请输入图标名称" />
312
- </FormItem>
313
- <FormItem label="排序">
314
- <InputNumber v-model="formData.sort" :min="0" />
315
- </FormItem>
316
- <FormItem label="状态">
317
- <Switch v-model="formData.status" :active-value="Status.ENABLED" :inactive-value="Status.DISABLED" />
318
- </FormItem>
319
- <FormItem label="隐藏">
320
- <Switch v-model="formData.hidden" />
321
- </FormItem>
322
- <FormItem label="缓存">
323
- <Switch v-model="formData.keepAlive" />
324
- </FormItem>
325
- </Form>
326
- <template #footer>
327
- <Space>
328
- <Button @click="modalVisible = false">取消</Button>
329
- <Button type="primary" @click="handleSubmit">确定</Button>
330
- </Space>
331
- </template>
332
- </Modal>
333
- </div>
334
- </template>
335
-
336
- <style lang="scss" scoped>
337
- .menu-page {
338
- padding: 20px;
339
-
340
- .toolbar {
341
- margin-bottom: 15px;
342
- }
343
- }
344
-
345
- .tree-table {
346
- width: 100%;
347
- border-collapse: collapse;
348
-
349
- th, td {
350
- padding: 12px;
351
- text-align: left;
352
- border-bottom: 1px solid var(--color-border-lighter);
353
- }
354
-
355
- th {
356
- font-weight: 500;
357
- color: var(--color-text-regular);
358
- background-color: var(--color-fill-light);
359
- }
360
-
361
- .tree-row--level-1 {
362
- td:first-child {
363
- padding-left: 30px;
364
- }
365
- }
366
- }
367
-
368
- .tree-indent {
369
- display: inline-block;
370
- width: 20px;
371
- }
372
-
373
- .menu-name {
374
- cursor: pointer;
375
- color: var(--color-primary);
376
-
377
- &:hover {
378
- text-decoration: underline;
379
- }
380
- }
381
- </style>
1
+ <script setup lang="ts">
2
+ import { ref, reactive, computed, onMounted } from 'vue'
3
+ import { Form, FormItem, Input, Select, Switch, InputNumber } from '@xto/form'
4
+ import { Tag } from '@xto/data'
5
+ import { Modal, Message } from '@xto/feedback'
6
+ import { Space, Button } from '@xto/base'
7
+ import { MenuType, MenuTypeOptions, Status } from '@/enums'
8
+
9
+ // 菜单数据类型
10
+ interface Menu {
11
+ id: number
12
+ parentId: number | null
13
+ name: string
14
+ path: string
15
+ component: string
16
+ redirect: string
17
+ icon: string
18
+ title: string
19
+ type: MenuType
20
+ sort: number
21
+ status: Status
22
+ hidden: boolean
23
+ keepAlive: boolean
24
+ children?: Menu[]
25
+ }
26
+
27
+ // Mock 数据
28
+ const mockMenus: Menu[] = [
29
+ {
30
+ id: 1,
31
+ parentId: null,
32
+ name: 'Dashboard',
33
+ path: '/dashboard',
34
+ component: 'dashboard/index',
35
+ redirect: '',
36
+ icon: 'dashboard',
37
+ title: '仪表盘',
38
+ type: MenuType.MENU,
39
+ sort: 1,
40
+ status: Status.ENABLED,
41
+ hidden: false,
42
+ keepAlive: true
43
+ },
44
+ {
45
+ id: 2,
46
+ parentId: null,
47
+ name: 'System',
48
+ path: '/system',
49
+ component: '',
50
+ redirect: '/system/user',
51
+ icon: 'setting',
52
+ title: '系统管理',
53
+ type: MenuType.DIRECTORY,
54
+ sort: 2,
55
+ status: Status.ENABLED,
56
+ hidden: false,
57
+ keepAlive: false,
58
+ children: [
59
+ {
60
+ id: 21,
61
+ parentId: 2,
62
+ name: 'SystemUser',
63
+ path: '/system/user',
64
+ component: 'system/user/index',
65
+ redirect: '',
66
+ icon: 'user',
67
+ title: '用户管理',
68
+ type: MenuType.MENU,
69
+ sort: 1,
70
+ status: Status.ENABLED,
71
+ hidden: false,
72
+ keepAlive: true
73
+ },
74
+ {
75
+ id: 22,
76
+ parentId: 2,
77
+ name: 'SystemRole',
78
+ path: '/system/role',
79
+ component: 'system/role/index',
80
+ redirect: '',
81
+ icon: 'role',
82
+ title: '角色管理',
83
+ type: MenuType.MENU,
84
+ sort: 2,
85
+ status: Status.ENABLED,
86
+ hidden: false,
87
+ keepAlive: true
88
+ },
89
+ {
90
+ id: 23,
91
+ parentId: 2,
92
+ name: 'SystemMenu',
93
+ path: '/system/menu',
94
+ component: 'system/menu/index',
95
+ redirect: '',
96
+ icon: 'menu',
97
+ title: '菜单管理',
98
+ type: MenuType.MENU,
99
+ sort: 3,
100
+ status: Status.ENABLED,
101
+ hidden: false,
102
+ keepAlive: true
103
+ }
104
+ ]
105
+ }
106
+ ]
107
+
108
+ const loading = ref(false)
109
+ const menuList = ref<Menu[]>([])
110
+
111
+ // 弹窗
112
+ const modalVisible = ref(false)
113
+ const modalTitle = computed(() => formData.id ? '编辑菜单' : '新增菜单')
114
+ const formData = reactive({
115
+ id: 0,
116
+ parentId: null as number | null,
117
+ name: '',
118
+ path: '',
119
+ component: '',
120
+ redirect: '',
121
+ icon: '',
122
+ title: '',
123
+ type: MenuType.MENU,
124
+ sort: 0,
125
+ status: Status.ENABLED,
126
+ hidden: false,
127
+ keepAlive: true
128
+ })
129
+
130
+ const rules: Record<string, any[]> = {
131
+ name: [
132
+ { required: true, message: '请输入菜单名称', trigger: 'blur' }
133
+ ],
134
+ path: [
135
+ { required: true, message: '请输入路由路径', trigger: 'blur' }
136
+ ],
137
+ title: [
138
+ { required: true, message: '请输入菜单标题', trigger: 'blur' }
139
+ ]
140
+ }
141
+
142
+ const formRef = ref()
143
+
144
+ // 获取菜单列表
145
+ const getMenuList = () => {
146
+ loading.value = true
147
+ setTimeout(() => {
148
+ menuList.value = mockMenus
149
+ loading.value = false
150
+ }, 300)
151
+ }
152
+
153
+ // 新增
154
+ const handleAdd = (parentId: number | null = null) => {
155
+ Object.assign(formData, {
156
+ id: 0,
157
+ parentId,
158
+ name: '',
159
+ path: '',
160
+ component: '',
161
+ redirect: '',
162
+ icon: '',
163
+ title: '',
164
+ type: MenuType.MENU,
165
+ sort: 0,
166
+ status: Status.ENABLED,
167
+ hidden: false,
168
+ keepAlive: true
169
+ })
170
+ modalVisible.value = true
171
+ }
172
+
173
+ // 编辑
174
+ const handleEdit = (node: Menu) => {
175
+ Object.assign(formData, node)
176
+ modalVisible.value = true
177
+ }
178
+
179
+ // 提交
180
+ const handleSubmit = async () => {
181
+ try {
182
+ await formRef.value?.validate()
183
+ Message.success(formData.id ? '编辑成功' : '新增成功')
184
+ modalVisible.value = false
185
+ getMenuList()
186
+ } catch (error) {
187
+ console.error(error)
188
+ }
189
+ }
190
+
191
+ // 菜单图标
192
+ const getMenuIcon = (icon?: string) => {
193
+ const iconMap: Record<string, string> = {
194
+ dashboard: '📊',
195
+ system: '⚙️',
196
+ user: '👤',
197
+ role: '👥',
198
+ menu: '📋',
199
+ setting: '🔧'
200
+ }
201
+ return iconMap[icon || ''] || '📄'
202
+ }
203
+
204
+ // 获取类型标签类型
205
+ const getTypeTagType = (type: MenuType) => {
206
+ switch (type) {
207
+ case MenuType.DIRECTORY: return 'primary'
208
+ case MenuType.MENU: return 'success'
209
+ default: return 'warning'
210
+ }
211
+ }
212
+
213
+ // 获取类型标签文本
214
+ const getTypeTagText = (type: MenuType) => {
215
+ switch (type) {
216
+ case MenuType.DIRECTORY: return '目录'
217
+ case MenuType.MENU: return '菜单'
218
+ default: return '按钮'
219
+ }
220
+ }
221
+
222
+ onMounted(() => {
223
+ getMenuList()
224
+ })
225
+ </script>
226
+
227
+ <template>
228
+ <div class="menu-page">
229
+ <!-- 表格 -->
230
+ <div class="table-section">
231
+ <!-- 工具栏 -->
232
+ <div class="table-toolbar">
233
+ <div class="toolbar-left">
234
+ <Button type="primary" @click="handleAdd()">
235
+ <template #icon>
236
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
237
+ <line x1="12" y1="5" x2="12" y2="19"/>
238
+ <line x1="5" y1="12" x2="19" y2="12"/>
239
+ </svg>
240
+ </template>
241
+ 新增菜单
242
+ </Button>
243
+ </div>
244
+ <div class="toolbar-right">
245
+ <span class="table-count">共 {{ menuList.length }} 个菜单</span>
246
+ </div>
247
+ </div>
248
+
249
+ <!-- 菜单树表格 -->
250
+ <div class="table-wrapper">
251
+ <table class="data-table">
252
+ <thead>
253
+ <tr>
254
+ <th class="col-name">菜单名称</th>
255
+ <th class="col-icon">图标</th>
256
+ <th class="col-path">路由路径</th>
257
+ <th class="col-type">类型</th>
258
+ <th class="col-sort">排序</th>
259
+ <th class="col-status">状态</th>
260
+ <th class="col-actions">操作</th>
261
+ </tr>
262
+ </thead>
263
+ <tbody>
264
+ <tr v-if="loading">
265
+ <td colspan="7" class="loading-cell">
266
+ <div class="loading-content">
267
+ <div class="loading-spinner"></div>
268
+ <span>加载中...</span>
269
+ </div>
270
+ </td>
271
+ </tr>
272
+ <tr v-else-if="menuList.length === 0">
273
+ <td colspan="7" class="empty-cell">
274
+ <div class="empty-content">
275
+ <svg viewBox="0 0 64 41" fill="none">
276
+ <g transform="translate(0 1)">
277
+ <ellipse fill="#f5f5f5" cx="32" cy="33" rx="32" ry="7"/>
278
+ <g stroke="var(--color-text-placeholder)" stroke-width="2">
279
+ <path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"/>
280
+ <path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35H11.95C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="var(--color-fill)"/>
281
+ </g>
282
+ </g>
283
+ </svg>
284
+ <span>暂无数据</span>
285
+ </div>
286
+ </td>
287
+ </tr>
288
+ <template v-else v-for="menu in menuList" :key="menu.id">
289
+ <tr class="data-row tree-row--level-0">
290
+ <td class="col-name">
291
+ <div class="menu-info">
292
+ <span class="menu-icon-wrapper">{{ getMenuIcon(menu.icon) }}</span>
293
+ <span class="menu-title" @click="handleEdit(menu)">{{ menu.title }}</span>
294
+ </div>
295
+ </td>
296
+ <td class="col-icon">
297
+ <span class="icon-text">{{ getMenuIcon(menu.icon) }}</span>
298
+ </td>
299
+ <td class="col-path">
300
+ <code class="path-code">{{ menu.path }}</code>
301
+ </td>
302
+ <td class="col-type">
303
+ <Tag :type="getTypeTagType(menu.type)" size="small">
304
+ {{ getTypeTagText(menu.type) }}
305
+ </Tag>
306
+ </td>
307
+ <td class="col-sort">
308
+ <span class="sort-badge">{{ menu.sort }}</span>
309
+ </td>
310
+ <td class="col-status">
311
+ <Tag :type="menu.status === Status.ENABLED ? 'success' : 'danger'" size="small">
312
+ {{ menu.status === Status.ENABLED ? '启用' : '禁用' }}
313
+ </Tag>
314
+ </td>
315
+ <td class="col-actions">
316
+ <Space class="action-buttons">
317
+ <Button type="primary" link size="small" @click="handleAdd(menu.id)">
318
+ <template #icon>
319
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
320
+ <line x1="12" y1="5" x2="12" y2="19"/>
321
+ <line x1="5" y1="12" x2="19" y2="12"/>
322
+ </svg>
323
+ </template>
324
+ 新增
325
+ </Button>
326
+ <Button type="primary" link size="small" @click="handleEdit(menu)">
327
+ <template #icon>
328
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
329
+ <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
330
+ <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
331
+ </svg>
332
+ </template>
333
+ 编辑
334
+ </Button>
335
+ </Space>
336
+ </td>
337
+ </tr>
338
+ <template v-if="menu.children" v-for="child in menu.children" :key="child.id">
339
+ <tr class="data-row tree-row--level-1">
340
+ <td class="col-name">
341
+ <div class="menu-info">
342
+ <span class="tree-indent"></span>
343
+ <span class="tree-line"></span>
344
+ <span class="menu-icon-wrapper">{{ getMenuIcon(child.icon) }}</span>
345
+ <span class="menu-title" @click="handleEdit(child)">{{ child.title }}</span>
346
+ </div>
347
+ </td>
348
+ <td class="col-icon">
349
+ <span class="icon-text">{{ getMenuIcon(child.icon) }}</span>
350
+ </td>
351
+ <td class="col-path">
352
+ <code class="path-code">{{ child.path }}</code>
353
+ </td>
354
+ <td class="col-type">
355
+ <Tag :type="getTypeTagType(child.type)" size="small">
356
+ {{ getTypeTagText(child.type) }}
357
+ </Tag>
358
+ </td>
359
+ <td class="col-sort">
360
+ <span class="sort-badge">{{ child.sort }}</span>
361
+ </td>
362
+ <td class="col-status">
363
+ <Tag :type="child.status === Status.ENABLED ? 'success' : 'danger'" size="small">
364
+ {{ child.status === Status.ENABLED ? '启用' : '禁用' }}
365
+ </Tag>
366
+ </td>
367
+ <td class="col-actions">
368
+ <Space class="action-buttons">
369
+ <Button type="primary" link size="small" @click="handleEdit(child)">
370
+ <template #icon>
371
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
372
+ <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
373
+ <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
374
+ </svg>
375
+ </template>
376
+ 编辑
377
+ </Button>
378
+ </Space>
379
+ </td>
380
+ </tr>
381
+ </template>
382
+ </template>
383
+ </tbody>
384
+ </table>
385
+ </div>
386
+ </div>
387
+
388
+ <!-- 编辑弹窗 -->
389
+ <Modal v-model="modalVisible" :title="modalTitle" width="600px" class="menu-modal">
390
+ <Form ref="formRef" :model="formData" :rules="rules" label-width="80px" class="menu-form">
391
+ <div class="form-grid">
392
+ <FormItem label="上级菜单" class="form-item-full">
393
+ <Input v-model="formData.parentId" placeholder="上级菜单ID" disabled />
394
+ </FormItem>
395
+ <FormItem label="菜单类型">
396
+ <Select v-model="formData.type" :options="MenuTypeOptions" />
397
+ </FormItem>
398
+ <FormItem label="排序">
399
+ <InputNumber v-model="formData.sort" :min="0" />
400
+ </FormItem>
401
+ <FormItem label="菜单名称" prop="name">
402
+ <Input v-model="formData.name" placeholder="请输入菜单名称(路由name)" />
403
+ </FormItem>
404
+ <FormItem label="菜单标题" prop="title">
405
+ <Input v-model="formData.title" placeholder="请输入菜单标题" />
406
+ </FormItem>
407
+ <FormItem label="路由路径" prop="path" class="form-item-full">
408
+ <Input v-model="formData.path" placeholder="请输入路由路径" />
409
+ </FormItem>
410
+ <FormItem label="组件路径" class="form-item-full">
411
+ <Input v-model="formData.component" placeholder="请输入组件路径" />
412
+ </FormItem>
413
+ <FormItem label="图标">
414
+ <Input v-model="formData.icon" placeholder="请输入图标名称" />
415
+ </FormItem>
416
+ <FormItem label="状态">
417
+ <div class="status-field">
418
+ <Switch v-model="formData.status" :active-value="Status.ENABLED" :inactive-value="Status.DISABLED" />
419
+ <span class="status-label">{{ formData.status === Status.ENABLED ? '启用' : '禁用' }}</span>
420
+ </div>
421
+ </FormItem>
422
+ <FormItem label="隐藏">
423
+ <div class="status-field">
424
+ <Switch v-model="formData.hidden" />
425
+ <span class="status-label">{{ formData.hidden ? '隐藏' : '显示' }}</span>
426
+ </div>
427
+ </FormItem>
428
+ <FormItem label="缓存">
429
+ <div class="status-field">
430
+ <Switch v-model="formData.keepAlive" />
431
+ <span class="status-label">{{ formData.keepAlive ? '开启' : '关闭' }}</span>
432
+ </div>
433
+ </FormItem>
434
+ </div>
435
+ </Form>
436
+ <template #footer>
437
+ <Space>
438
+ <Button @click="modalVisible = false">取消</Button>
439
+ <Button type="primary" @click="handleSubmit">确定</Button>
440
+ </Space>
441
+ </template>
442
+ </Modal>
443
+ </div>
444
+ </template>
445
+
446
+ <style lang="scss" scoped>
447
+ .menu-page {
448
+ padding: 24px;
449
+ background: var(--bg-color-page);
450
+ min-height: 100%;
451
+ }
452
+
453
+ // 表格区域
454
+ .table-section {
455
+ background: var(--bg-color);
456
+ border-radius: var(--border-radius-large);
457
+ box-shadow: var(--box-shadow-card);
458
+ overflow: hidden;
459
+ }
460
+
461
+ .table-toolbar {
462
+ display: flex;
463
+ justify-content: space-between;
464
+ align-items: center;
465
+ padding: 16px 24px;
466
+ border-bottom: 1px solid var(--color-border-lighter);
467
+
468
+ .toolbar-left {
469
+ display: flex;
470
+ gap: 12px;
471
+ }
472
+
473
+ .table-count {
474
+ font-size: 14px;
475
+ color: var(--color-text-secondary);
476
+ }
477
+ }
478
+
479
+ .table-wrapper {
480
+ overflow-x: auto;
481
+ }
482
+
483
+ // 表格样式
484
+ .data-table {
485
+ width: 100%;
486
+ border-collapse: collapse;
487
+
488
+ th, td {
489
+ padding: 14px 16px;
490
+ text-align: left;
491
+ border-bottom: 1px solid var(--color-border-lighter);
492
+ }
493
+
494
+ th {
495
+ font-size: 14px;
496
+ font-weight: 500;
497
+ color: var(--color-text-secondary);
498
+ background: var(--color-fill-light);
499
+ white-space: nowrap;
500
+ }
501
+
502
+ .data-row {
503
+ transition: background-color 0.2s;
504
+
505
+ &:hover {
506
+ background: var(--color-primary-light-6);
507
+ }
508
+ }
509
+
510
+ td {
511
+ vertical-align: middle;
512
+ }
513
+
514
+ .loading-cell,
515
+ .empty-cell {
516
+ padding: 60px 20px;
517
+ }
518
+
519
+ .loading-content,
520
+ .empty-content {
521
+ display: flex;
522
+ flex-direction: column;
523
+ align-items: center;
524
+ gap: 16px;
525
+ color: var(--color-text-placeholder);
526
+
527
+ svg {
528
+ width: 64px;
529
+ height: 41px;
530
+ }
531
+ }
532
+
533
+ .loading-spinner {
534
+ width: 32px;
535
+ height: 32px;
536
+ border: 3px solid var(--color-border-lighter);
537
+ border-top-color: var(--color-primary);
538
+ border-radius: 50%;
539
+ animation: spin 0.8s linear infinite;
540
+ }
541
+ }
542
+
543
+ @keyframes spin {
544
+ to { transform: rotate(360deg); }
545
+ }
546
+
547
+ // 树形结构
548
+ .tree-row--level-1 {
549
+ .col-name {
550
+ padding-left: 40px;
551
+ }
552
+ }
553
+
554
+ .tree-indent {
555
+ display: inline-block;
556
+ width: 24px;
557
+ }
558
+
559
+ .tree-line {
560
+ position: relative;
561
+ display: inline-block;
562
+ width: 16px;
563
+ height: 1px;
564
+ background: var(--color-border-light);
565
+ margin-right: 8px;
566
+ vertical-align: middle;
567
+ }
568
+
569
+ // 列样式
570
+ .col-name {
571
+ min-width: 200px;
572
+ }
573
+
574
+ .menu-info {
575
+ display: flex;
576
+ align-items: center;
577
+ gap: 8px;
578
+ }
579
+
580
+ .menu-icon-wrapper {
581
+ width: 28px;
582
+ height: 28px;
583
+ display: flex;
584
+ align-items: center;
585
+ justify-content: center;
586
+ background: var(--color-fill-light);
587
+ border-radius: 6px;
588
+ font-size: 14px;
589
+ }
590
+
591
+ .menu-title {
592
+ font-size: 14px;
593
+ font-weight: 500;
594
+ color: var(--color-text-primary);
595
+ cursor: pointer;
596
+ transition: color 0.2s;
597
+
598
+ &:hover {
599
+ color: var(--color-primary);
600
+ }
601
+ }
602
+
603
+ .col-icon {
604
+ width: 80px;
605
+ }
606
+
607
+ .icon-text {
608
+ font-size: 16px;
609
+ }
610
+
611
+ .col-path {
612
+ min-width: 140px;
613
+ }
614
+
615
+ .path-code {
616
+ font-family: 'SF Mono', Monaco, Consolas, monospace;
617
+ font-size: 12px;
618
+ padding: 4px 8px;
619
+ background: var(--color-fill-light);
620
+ border-radius: 4px;
621
+ color: var(--color-text-secondary);
622
+ }
623
+
624
+ .col-type {
625
+ width: 80px;
626
+ }
627
+
628
+ .col-sort {
629
+ width: 60px;
630
+ }
631
+
632
+ .sort-badge {
633
+ display: inline-flex;
634
+ align-items: center;
635
+ justify-content: center;
636
+ min-width: 24px;
637
+ height: 24px;
638
+ padding: 0 8px;
639
+ background: var(--color-fill);
640
+ border-radius: 12px;
641
+ font-size: 12px;
642
+ color: var(--color-text-secondary);
643
+ }
644
+
645
+ .col-status {
646
+ width: 80px;
647
+ }
648
+
649
+ .col-actions {
650
+ min-width: 140px;
651
+ }
652
+
653
+ .action-buttons {
654
+ :deep(.x-button) {
655
+ padding: 4px 8px;
656
+
657
+ svg {
658
+ width: 14px;
659
+ height: 14px;
660
+ margin-right: 4px;
661
+ }
662
+ }
663
+ }
664
+
665
+ // 弹窗表单
666
+ .menu-form {
667
+ padding: 16px 0;
668
+
669
+ .form-grid {
670
+ display: grid;
671
+ grid-template-columns: repeat(2, 1fr);
672
+ gap: 0 24px;
673
+ }
674
+
675
+ .form-item-full {
676
+ grid-column: span 2;
677
+ }
678
+
679
+ .status-field {
680
+ display: flex;
681
+ align-items: center;
682
+ gap: 12px;
683
+ }
684
+
685
+ .status-label {
686
+ font-size: 14px;
687
+ color: var(--color-text-secondary);
688
+ }
689
+ }
690
+ </style>