@robot-admin/naive-ui-components 0.9.2 → 0.10.0

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 (369) hide show
  1. package/CHANGELOG.md +82 -76
  2. package/LICENSE +21 -0
  3. package/README.md +314 -294
  4. package/dist/C_ActionBar-BAcfOfRw.css.map +1 -0
  5. package/dist/C_ActionBar-DG2HW7t5.css.map +1 -0
  6. package/dist/C_ActionBar.css +47 -47
  7. package/dist/C_ActionBar.js.map +1 -1
  8. package/dist/C_ActionBar2.js +1 -1
  9. package/dist/C_ActionBar2.js.map +1 -1
  10. package/dist/{C_AntV-BY8jqqmi.css.map → C_AntV-D_jTK8oZ.css.map} +1 -1
  11. package/dist/{C_AntV-BbkHc-_a.css.map → C_AntV-DanTAq22.css.map} +1 -1
  12. package/dist/C_AntV.css +79 -79
  13. package/dist/C_AntV.js.map +1 -1
  14. package/dist/C_AntV2.js +3 -3
  15. package/dist/C_AntV2.js.map +1 -1
  16. package/dist/{C_AudioPlayer-DKRIGhi7.css.map → C_AudioPlayer-BnYIJXPD.css.map} +1 -1
  17. package/dist/{C_AudioPlayer-DlB6eNsa.css.map → C_AudioPlayer-ldTy9Y34.css.map} +1 -1
  18. package/dist/C_AudioPlayer.css +47 -47
  19. package/dist/C_AudioPlayer.js.map +1 -1
  20. package/dist/C_AudioPlayer2.js +1 -1
  21. package/dist/C_AudioPlayer2.js.map +1 -1
  22. package/dist/{C_AvatarGroup-D02zci-i.css.map → C_AvatarGroup-B9d-GJ9c.css.map} +1 -1
  23. package/dist/{C_AvatarGroup-DatHaUlY.css.map → C_AvatarGroup-SIzxuuZr.css.map} +1 -1
  24. package/dist/C_AvatarGroup.cjs +1 -1
  25. package/dist/C_AvatarGroup.css +21 -21
  26. package/dist/C_AvatarGroup.d.cts +1 -1
  27. package/dist/C_AvatarGroup.d.ts +1 -1
  28. package/dist/C_AvatarGroup.js +1 -1
  29. package/dist/C_AvatarGroup.js.map +1 -1
  30. package/dist/C_AvatarGroup2.js +1 -1
  31. package/dist/C_AvatarGroup2.js.map +1 -1
  32. package/dist/{C_Barcode-DYDRDe2O.css.map → C_Barcode-CAU2mFcx.css.map} +1 -1
  33. package/dist/{C_Barcode-muJdc_O4.css.map → C_Barcode-D2oJaEg3.css.map} +1 -1
  34. package/dist/C_Barcode.css +7 -7
  35. package/dist/C_Barcode.js.map +1 -1
  36. package/dist/C_Barcode2.js +1 -1
  37. package/dist/C_Barcode2.js.map +1 -1
  38. package/dist/C_Breadcrumb.js.map +1 -1
  39. package/dist/C_Breadcrumb2.js +1 -1
  40. package/dist/C_Breadcrumb2.js.map +1 -1
  41. package/dist/{C_Captcha-9_hk6VmY.css.map → C_Captcha-B3yrtG7t.css.map} +1 -1
  42. package/dist/{C_Captcha-MtASlJ0f.css.map → C_Captcha-D4hDdWDu.css.map} +1 -1
  43. package/dist/C_Captcha.css +19 -19
  44. package/dist/C_Captcha.js.map +1 -1
  45. package/dist/C_Captcha2.js +1 -1
  46. package/dist/C_Captcha2.js.map +1 -1
  47. package/dist/C_Cascade-CmCrLVft.css.map +1 -0
  48. package/dist/C_Cascade-U6A5AJJb.css.map +1 -0
  49. package/dist/C_Cascade.css +3 -3
  50. package/dist/C_Cascade.d.cts +2 -2
  51. package/dist/C_Cascade.d.ts +2 -2
  52. package/dist/C_Cascade.js.map +1 -1
  53. package/dist/C_Cascade2.js +1 -1
  54. package/dist/C_Cascade2.js.map +1 -1
  55. package/dist/{C_Chat-Csjz_BFw.css.map → C_Chat-CF1SU7mc.css.map} +1 -1
  56. package/dist/{C_Chat-DO1ZBA_0.css.map → C_Chat-DeUv6uuI.css.map} +1 -1
  57. package/dist/C_Chat.css +65 -65
  58. package/dist/C_Chat.js.map +1 -1
  59. package/dist/C_Chat2.js +1 -1
  60. package/dist/C_Chat2.js.map +1 -1
  61. package/dist/{C_City-Bam08Qv3.css.map → C_City-DdkX6u64.css.map} +1 -1
  62. package/dist/{C_City-Bb3W-KaN.css.map → C_City-iP-dIvui.css.map} +1 -1
  63. package/dist/C_City.css +29 -29
  64. package/dist/C_City.js.map +1 -1
  65. package/dist/C_City2.js +1 -1
  66. package/dist/C_City2.js.map +1 -1
  67. package/dist/C_Code-BWmJMNS7.css.map +1 -0
  68. package/dist/C_Code-k14vjgVl.css.map +1 -0
  69. package/dist/C_Code.css +3 -3
  70. package/dist/C_Code.js.map +1 -1
  71. package/dist/C_Code2.js +1 -1
  72. package/dist/C_Code2.js.map +1 -1
  73. package/dist/{C_CollapsePanel-CWkFCqNQ.css.map → C_CollapsePanel-BSKcWSmY.css.map} +1 -1
  74. package/dist/{C_CollapsePanel-pcVJA_4z.css.map → C_CollapsePanel-DRvRq8tB.css.map} +1 -1
  75. package/dist/C_CollapsePanel.css +35 -35
  76. package/dist/C_CollapsePanel.js.map +1 -1
  77. package/dist/C_CollapsePanel2.js +1 -1
  78. package/dist/C_CollapsePanel2.js.map +1 -1
  79. package/dist/{C_ContextMenu-B3Cgz_wJ.css.map → C_ContextMenu-Bn_uhJSo.css.map} +1 -1
  80. package/dist/{C_ContextMenu-u1LixF3_.css.map → C_ContextMenu-C9iuxG2n.css.map} +1 -1
  81. package/dist/C_ContextMenu.css +17 -17
  82. package/dist/C_ContextMenu.js.map +1 -1
  83. package/dist/C_ContextMenu2.js +1 -1
  84. package/dist/C_ContextMenu2.js.map +1 -1
  85. package/dist/{C_Cron-Civir5-I.css.map → C_Cron-BIQ2By9v.css.map} +1 -1
  86. package/dist/{C_Cron-iw3qpMsy.css.map → C_Cron-BNSAGD6A.css.map} +1 -1
  87. package/dist/C_Cron.css +57 -57
  88. package/dist/C_Cron.js.map +1 -1
  89. package/dist/C_Cron2.js +1 -1
  90. package/dist/C_Cron2.js.map +1 -1
  91. package/dist/C_Date.js.map +1 -1
  92. package/dist/C_Date2.js +1 -1
  93. package/dist/C_Date2.js.map +1 -1
  94. package/dist/{C_Draggable-BCjIvIwT.css.map → C_Draggable-BDAbk4s5.css.map} +1 -1
  95. package/dist/{C_Draggable-DwkSlCOS.css.map → C_Draggable-UhfDLXop.css.map} +1 -1
  96. package/dist/C_Draggable.css +46 -46
  97. package/dist/C_Draggable.js.map +1 -1
  98. package/dist/C_Draggable2.js +1 -1
  99. package/dist/C_Draggable2.js.map +1 -1
  100. package/dist/{C_Editor-BfjjYQ9B.css.map → C_Editor-CZOaChru.css.map} +1 -1
  101. package/dist/{C_Editor-Do2StvoJ.css.map → C_Editor-kJAQq5z6.css.map} +1 -1
  102. package/dist/C_Editor.css +14 -14
  103. package/dist/C_Editor.js.map +1 -1
  104. package/dist/C_Editor2.js +1 -1
  105. package/dist/C_Editor2.js.map +1 -1
  106. package/dist/{C_FilePreview-BhcbiXsV.css.map → C_FilePreview-Bs_omgJS.css.map} +1 -1
  107. package/dist/{C_FilePreview-DJudcIbW.css.map → C_FilePreview-DbwoyF2j.css.map} +1 -1
  108. package/dist/C_FilePreview.css +91 -91
  109. package/dist/C_FilePreview.js.map +1 -1
  110. package/dist/C_FilePreview2.js +1 -1
  111. package/dist/C_FilePreview2.js.map +1 -1
  112. package/dist/{C_Form-BtcAPIRi.css.map → C_Form-B_2ql62V.css.map} +1 -1
  113. package/dist/{C_Form-D_gwQ7hC.css.map → C_Form-CX-7xsz3.css.map} +1 -1
  114. package/dist/C_Form.css +223 -223
  115. package/dist/C_Form.d.cts +2 -2
  116. package/dist/C_Form.d.ts +2 -2
  117. package/dist/C_Form.js.map +1 -1
  118. package/dist/C_Form2.js +1 -1
  119. package/dist/C_Form2.js.map +1 -1
  120. package/dist/{C_FormSearch-Bi4XYjjk.css.map → C_FormSearch-C3F8peAY.css.map} +1 -1
  121. package/dist/{C_FormSearch-DUGOoX0u.css.map → C_FormSearch-qW56bJPo.css.map} +1 -1
  122. package/dist/C_FormSearch.css +45 -45
  123. package/dist/C_FormSearch.js.map +1 -1
  124. package/dist/C_FormSearch2.js +1 -1
  125. package/dist/C_FormSearch2.js.map +1 -1
  126. package/dist/{C_FormulaEditor-CJk3SSUJ.css.map → C_FormulaEditor-ChOvDbsZ.css.map} +1 -1
  127. package/dist/{C_FormulaEditor-Cy7mjfVO.css.map → C_FormulaEditor-mfiIUDY4.css.map} +1 -1
  128. package/dist/C_FormulaEditor.css +78 -78
  129. package/dist/C_FormulaEditor.js.map +1 -1
  130. package/dist/C_FormulaEditor2.js +1 -1
  131. package/dist/C_FormulaEditor2.js.map +1 -1
  132. package/dist/C_FullCalendar-C2nsqrgS.css.map +1 -0
  133. package/dist/C_FullCalendar-DdFtXVhy.css.map +1 -0
  134. package/dist/C_FullCalendar.css +2 -2
  135. package/dist/C_FullCalendar.js.map +1 -1
  136. package/dist/C_FullCalendar2.js +1 -1
  137. package/dist/C_FullCalendar2.js.map +1 -1
  138. package/dist/{C_GlobalSearch-B6wK3m3k.css.map → C_GlobalSearch-Dq4Wo01K.css.map} +1 -1
  139. package/dist/{C_GlobalSearch-DoPjldFm.css.map → C_GlobalSearch-RB7J43gu.css.map} +1 -1
  140. package/dist/C_GlobalSearch.css +112 -112
  141. package/dist/C_GlobalSearch.js.map +1 -1
  142. package/dist/C_GlobalSearch2.js +1 -1
  143. package/dist/C_GlobalSearch2.js.map +1 -1
  144. package/dist/C_Guide.js.map +1 -1
  145. package/dist/C_Guide2.js +1 -1
  146. package/dist/C_Guide2.js.map +1 -1
  147. package/dist/C_Icon.d.cts +1 -1
  148. package/dist/C_Icon.d.ts +1 -1
  149. package/dist/C_Icon.js.map +1 -1
  150. package/dist/C_Icon2.js.map +1 -1
  151. package/dist/{C_ImageCropper-BqZpkZfU.css.map → C_ImageCropper-CdTX2nxU.css.map} +1 -1
  152. package/dist/{C_ImageCropper-CylwGuzS.css.map → C_ImageCropper-ilOqPklJ.css.map} +1 -1
  153. package/dist/C_ImageCropper.css +23 -23
  154. package/dist/C_ImageCropper.js.map +1 -1
  155. package/dist/C_ImageCropper2.js +1 -1
  156. package/dist/C_ImageCropper2.js.map +1 -1
  157. package/dist/C_Language.d.cts +1 -1
  158. package/dist/C_Language.d.ts +1 -1
  159. package/dist/C_Language.js.map +1 -1
  160. package/dist/C_Language2.js +1 -1
  161. package/dist/C_Language2.js.map +1 -1
  162. package/dist/{C_Login-BkdnkBKX.css.map → C_Login-B3qYkLrN.css.map} +1 -1
  163. package/dist/{C_Login-VkMM6OJ2.css.map → C_Login-xMI1_ojE.css.map} +1 -1
  164. package/dist/C_Login.css +72 -72
  165. package/dist/C_Login.js.map +1 -1
  166. package/dist/C_Login2.js +1 -1
  167. package/dist/C_Login2.js.map +1 -1
  168. package/dist/{C_Map-DNnBcY70.css.map → C_Map-CWMMN58_.css.map} +1 -1
  169. package/dist/{C_Map-LEXIx6zM.css.map → C_Map-xSR2H-9o.css.map} +1 -1
  170. package/dist/C_Map.css +4 -4
  171. package/dist/C_Map.js.map +1 -1
  172. package/dist/C_Map2.js +1 -1
  173. package/dist/C_Map2.js.map +1 -1
  174. package/dist/{C_Markdown-DwmI0_0Q.css.map → C_Markdown-C7lTVJ1S.css.map} +1 -1
  175. package/dist/{C_Markdown-k0794RW0.css.map → C_Markdown-CZDm_baQ.css.map} +1 -1
  176. package/dist/C_Markdown.css +33 -33
  177. package/dist/C_Markdown.js.map +1 -1
  178. package/dist/C_Markdown2.js +1 -1
  179. package/dist/C_Markdown2.js.map +1 -1
  180. package/dist/C_Menu.js.map +1 -1
  181. package/dist/C_Menu2.js +1 -1
  182. package/dist/C_Menu2.js.map +1 -1
  183. package/dist/{C_NotificationCenter-DT6fQ79O.css.map → C_NotificationCenter-D7GGGWg7.css.map} +1 -1
  184. package/dist/{C_NotificationCenter-DbgBiyqB.css.map → C_NotificationCenter-SSmeHA45.css.map} +1 -1
  185. package/dist/C_NotificationCenter.css +89 -89
  186. package/dist/C_NotificationCenter.js.map +1 -1
  187. package/dist/C_NotificationCenter2.js +1 -1
  188. package/dist/C_NotificationCenter2.js.map +1 -1
  189. package/dist/C_OrgChart.js.map +1 -1
  190. package/dist/C_OrgChart2.js.map +1 -1
  191. package/dist/C_Progress.js.map +1 -1
  192. package/dist/C_Progress2.js +1 -1
  193. package/dist/C_Progress2.js.map +1 -1
  194. package/dist/{C_QRCode-CFNTda_1.css.map → C_QRCode-B_1lHUdx.css.map} +1 -1
  195. package/dist/{C_QRCode-CmWr9lK-.css.map → C_QRCode-CuQRYu0s.css.map} +1 -1
  196. package/dist/C_QRCode.css +9 -9
  197. package/dist/C_QRCode.js.map +1 -1
  198. package/dist/C_QRCode2.js +1 -1
  199. package/dist/C_QRCode2.js.map +1 -1
  200. package/dist/{C_Signature-BDx4ctsE.css.map → C_Signature-Bp-RBpAl.css.map} +1 -1
  201. package/dist/{C_Signature-Bz_uNbwv.css.map → C_Signature-Cb2VW1ok.css.map} +1 -1
  202. package/dist/C_Signature.css +15 -15
  203. package/dist/C_Signature.js.map +1 -1
  204. package/dist/C_Signature2.js +1 -1
  205. package/dist/C_Signature2.js.map +1 -1
  206. package/dist/C_Skeleton.js.map +1 -1
  207. package/dist/C_Skeleton2.js.map +1 -1
  208. package/dist/{C_SplitPane-BOwr1C7K.css.map → C_SplitPane-CgGWYsRE.css.map} +1 -1
  209. package/dist/{C_SplitPane-N-a_D-7F.css.map → C_SplitPane-DGf6WEeR.css.map} +1 -1
  210. package/dist/C_SplitPane.css +31 -31
  211. package/dist/C_SplitPane.js.map +1 -1
  212. package/dist/C_SplitPane2.js +1 -1
  213. package/dist/C_SplitPane2.js.map +1 -1
  214. package/dist/C_Steps-27Exh7PM.css.map +1 -0
  215. package/dist/C_Steps-DU9ZsFrs.css.map +1 -0
  216. package/dist/C_Steps.css +38 -38
  217. package/dist/C_Steps.d.cts +1 -1
  218. package/dist/C_Steps.d.ts +1 -1
  219. package/dist/C_Steps.js.map +1 -1
  220. package/dist/C_Steps2.js +1 -1
  221. package/dist/C_Steps2.js.map +1 -1
  222. package/dist/{C_Table-BcM5LqAM.css.map → C_Table--kbQaaBv.css.map} +1 -1
  223. package/dist/{C_Table-HATVoq1F.css.map → C_Table-D631kdXC.css.map} +1 -1
  224. package/dist/C_Table.css +104 -104
  225. package/dist/C_Table.js.map +1 -1
  226. package/dist/C_Table2.js +3 -3
  227. package/dist/C_Table2.js.map +1 -1
  228. package/dist/{C_TagsView-DX3LecK1.css.map → C_TagsView-BN8vMRsY.css.map} +1 -1
  229. package/dist/{C_TagsView-DqOTa8bL.css.map → C_TagsView-s1dnSJAV.css.map} +1 -1
  230. package/dist/C_TagsView.css +36 -36
  231. package/dist/C_TagsView.d.cts +1 -1
  232. package/dist/C_TagsView.d.ts +1 -1
  233. package/dist/C_TagsView.js.map +1 -1
  234. package/dist/C_TagsView2.js +1 -1
  235. package/dist/C_TagsView2.js.map +1 -1
  236. package/dist/C_Theme.d.cts +1 -1
  237. package/dist/C_Theme.d.ts +1 -1
  238. package/dist/C_Theme.js.map +1 -1
  239. package/dist/C_Theme2.js +1 -1
  240. package/dist/C_Theme2.js.map +1 -1
  241. package/dist/C_Time-BQ9PGy8B.css.map +1 -0
  242. package/dist/C_Time-CO73krC6.css.map +1 -0
  243. package/dist/C_Time.css +5 -5
  244. package/dist/C_Time.js.map +1 -1
  245. package/dist/C_Time2.js +1 -1
  246. package/dist/C_Time2.js.map +1 -1
  247. package/dist/{C_Timeline-BKkimrZx.css.map → C_Timeline-4rk0Dzgn.css.map} +1 -1
  248. package/dist/{C_Timeline-DTuSCX6p.css.map → C_Timeline-DdESmqwL.css.map} +1 -1
  249. package/dist/C_Timeline.cjs +1 -1
  250. package/dist/C_Timeline.css +45 -45
  251. package/dist/C_Timeline.d.cts +1 -1
  252. package/dist/C_Timeline.d.ts +1 -1
  253. package/dist/C_Timeline.js +1 -1
  254. package/dist/C_Timeline.js.map +1 -1
  255. package/dist/C_Timeline2.js +1 -1
  256. package/dist/C_Timeline2.js.map +1 -1
  257. package/dist/{C_Transfer-BOB6OBqS.css.map → C_Transfer-B-vjvhjI.css.map} +1 -1
  258. package/dist/{C_Transfer-Dpq7ezWC.css.map → C_Transfer-EzdHzZNv.css.map} +1 -1
  259. package/dist/C_Transfer.css +31 -31
  260. package/dist/C_Transfer.js.map +1 -1
  261. package/dist/C_Transfer2.js +1 -1
  262. package/dist/C_Transfer2.js.map +1 -1
  263. package/dist/{C_Tree-D98nX9qT.css.map → C_Tree-CsXgGfhn.css.map} +1 -1
  264. package/dist/{C_Tree-ulfm7b-P.css.map → C_Tree-P7eRpnOa.css.map} +1 -1
  265. package/dist/C_Tree.css +33 -33
  266. package/dist/C_Tree.js.map +1 -1
  267. package/dist/C_Tree2.js +1 -1
  268. package/dist/C_Tree2.js.map +1 -1
  269. package/dist/{C_Upload-BR9fg5r4.css.map → C_Upload-CHjiVVh7.css.map} +1 -1
  270. package/dist/{C_Upload-CPqf74Ea.css.map → C_Upload-DE3OLsZD.css.map} +1 -1
  271. package/dist/C_Upload.css +50 -50
  272. package/dist/C_Upload.js.map +1 -1
  273. package/dist/C_Upload2.js +2 -2
  274. package/dist/C_Upload2.js.map +1 -1
  275. package/dist/{C_VideoPlayer-4J96tKfK.css.map → C_VideoPlayer-CnaNOFtW.css.map} +1 -1
  276. package/dist/{C_VideoPlayer-D35seHkm.css.map → C_VideoPlayer-cnP88zNV.css.map} +1 -1
  277. package/dist/C_VideoPlayer.css +68 -68
  278. package/dist/C_VideoPlayer.js.map +1 -1
  279. package/dist/C_VideoPlayer2.js +3 -3
  280. package/dist/C_VideoPlayer2.js.map +1 -1
  281. package/dist/{C_VtableGantt-C2lxFmWg.css.map → C_VtableGantt-CZlJgvpC.css.map} +1 -1
  282. package/dist/{C_VtableGantt-Dwnkw_WL.css.map → C_VtableGantt-DKfQkql0.css.map} +1 -1
  283. package/dist/C_VtableGantt.css +15 -15
  284. package/dist/C_VtableGantt.js.map +1 -1
  285. package/dist/C_VtableGantt2.js +1 -1
  286. package/dist/C_VtableGantt2.js.map +1 -1
  287. package/dist/{C_WaterFall-C4HDqf-a.css.map → C_WaterFall-BJ_39ext.css.map} +1 -1
  288. package/dist/{C_WaterFall-HWB-gPON.css.map → C_WaterFall-cYc1vntE.css.map} +1 -1
  289. package/dist/C_WaterFall.css +16 -16
  290. package/dist/C_WaterFall.js.map +1 -1
  291. package/dist/C_WaterFall2.js +1 -1
  292. package/dist/C_WaterFall2.js.map +1 -1
  293. package/dist/{C_WorkFlow-2hJfvvTK.css.map → C_WorkFlow-BFzRGGhJ.css.map} +1 -1
  294. package/dist/{C_WorkFlow-DHoNjl8-.css.map → C_WorkFlow-DSyCOvRp.css.map} +1 -1
  295. package/dist/C_WorkFlow.css +193 -193
  296. package/dist/C_WorkFlow.js.map +1 -1
  297. package/dist/C_WorkFlow2.js +1 -1
  298. package/dist/C_WorkFlow2.js.map +1 -1
  299. package/dist/constants.d.ts +1 -1
  300. package/dist/constants2.d.ts +1 -1
  301. package/dist/constants3.d.ts +1 -1
  302. package/dist/constants4.d.ts +2 -2
  303. package/dist/constants5.d.ts +2 -2
  304. package/dist/data.d.ts +2 -2
  305. package/dist/index.cjs +1 -1
  306. package/dist/index.d.cts +9 -9
  307. package/dist/index.d.ts +9 -9
  308. package/dist/index.js +1 -1
  309. package/dist/index.js.map +1 -1
  310. package/dist/index.vue.d.ts +1 -1
  311. package/dist/index10.vue.d.ts +2 -2
  312. package/dist/index11.vue.d.ts +2 -2
  313. package/dist/index12.vue.d.ts +1 -1
  314. package/dist/index13.vue.d.ts +2 -2
  315. package/dist/index14.vue.d.ts +2 -2
  316. package/dist/index15.vue.d.ts +29 -9
  317. package/dist/index16.vue.d.ts +11 -4
  318. package/dist/index17.vue.d.ts +1 -1
  319. package/dist/index18.vue.d.ts +3 -3
  320. package/dist/index2.vue.d.ts +6 -6
  321. package/dist/index20.vue.d.ts +1 -1
  322. package/dist/index21.vue.d.ts +3 -3
  323. package/dist/index22.vue.d.ts +3 -3
  324. package/dist/index23.vue.d.ts +15 -4
  325. package/dist/index24.vue.d.ts +13 -4
  326. package/dist/index25.vue.d.ts +4 -2
  327. package/dist/index26.vue.d.ts +2 -2
  328. package/dist/index27.vue.d.ts +3 -3
  329. package/dist/index28.vue.d.ts +4 -4
  330. package/dist/index3.vue.d.ts +1 -1
  331. package/dist/index4.vue.d.ts +4 -4
  332. package/dist/index6.vue.d.ts +3 -3
  333. package/dist/index7.vue.d.ts +7 -5
  334. package/dist/resolver.cjs +1 -1
  335. package/dist/resolver.cjs.map +1 -1
  336. package/dist/resolver.d.cts +2 -21
  337. package/dist/resolver.d.ts +2 -21
  338. package/dist/resolver.js +1 -1
  339. package/dist/resolver.js.map +1 -1
  340. package/dist/style.css +1991 -1991
  341. package/dist/useCalendarEvents.d.ts +8 -5
  342. package/dist/useCollapsePanel.d.ts +2 -2
  343. package/dist/useCropperCore.d.ts +6 -6
  344. package/dist/useDraggableLayout.d.ts +3 -3
  345. package/dist/useDynamicFormState.d.ts +31 -3726
  346. package/dist/useEdgeInteraction.d.ts +2 -2
  347. package/dist/useFullscreen.d.ts +2 -2
  348. package/dist/useInfiniteScroll.d.ts +2 -2
  349. package/dist/useModalEdit.d.ts +6 -6
  350. package/dist/useQRCode.d.ts +5 -5
  351. package/dist/useSignatureHistory.d.ts +2 -2
  352. package/dist/useSplitResize.d.ts +6 -6
  353. package/dist/useTimeSelection.d.ts +3 -3
  354. package/dist/useTreeOperations.d.ts +5 -5
  355. package/dist/useWorkflowValidation.d.ts +3 -3
  356. package/package.json +12 -7
  357. package/dist/C_ActionBar-DYG0Yf3A.css.map +0 -1
  358. package/dist/C_ActionBar-MAjRhqcI.css.map +0 -1
  359. package/dist/C_Cascade-Dq0S5Jkm.css.map +0 -1
  360. package/dist/C_Cascade-IUUHIh7r.css.map +0 -1
  361. package/dist/C_Code-BFi5xxnM.css.map +0 -1
  362. package/dist/C_Code-CZ7yfnri.css.map +0 -1
  363. package/dist/C_FullCalendar-BQA0OjGB.css.map +0 -1
  364. package/dist/C_FullCalendar-ChJUosAi.css.map +0 -1
  365. package/dist/C_Steps-Bk1krgiR.css.map +0 -1
  366. package/dist/C_Steps-Dn2CMpy1.css.map +0 -1
  367. package/dist/C_Time-BzyQVvTS.css.map +0 -1
  368. package/dist/C_Time-DmpKLQHc.css.map +0 -1
  369. package/dist/themes/glass-morphism.css +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"C_Login.js","names":[],"sources":["../src/components/C_Login/types.ts","../src/components/C_Login/index.vue"],"sourcesContent":["/**\n * @Description: C_Login 通用登录组件 - 类型定义\n */\n\n// ================= 功能开关 =================\nexport interface LoginFeatures {\n /** 是否显示密码登录(默认 true) */\n passwordLogin?: boolean\n /** 是否显示验证码登录(默认 true) */\n captchaLogin?: boolean\n /** 是否显示扫码登录(默认 true) */\n qrcodeLogin?: boolean\n /** 是否显示其他账号登录(默认 true) */\n socialLogin?: boolean\n /** 是否显示注册账号(默认 true) */\n register?: boolean\n /** 是否启用人机验证拼图(默认 true) */\n captchaVerify?: boolean\n /** 是否显示记住我(默认 true) */\n rememberMe?: boolean\n /** 是否显示忘记密码(默认 true) */\n forgotPassword?: boolean\n}\n\n// ================= 社交登录 =================\nexport interface SocialProvider {\n key: string\n label: string\n /** mdi 图标名称 */\n icon: string\n}\n\n// ================= 表单数据 =================\nexport interface PasswordFormData {\n username: string\n password: string\n}\n\nexport interface CaptchaFormData {\n account: string\n code: string\n}\n\nexport interface RegisterFormData {\n phone: string\n code: string\n password: string\n confirmPassword: string\n}\n\n// ================= 组件 Props =================\nexport interface LoginProps {\n /** 标题文字 */\n title?: string\n /** 副标题 */\n subtitle?: string\n /** Logo icon 名称(mdi:xxx),不传则不显示图标 */\n logoIcon?: string\n /** 功能开关配置 */\n features?: LoginFeatures\n /** 社交登录提供商列表 */\n socialProviders?: SocialProvider[]\n /** 扫码登录二维码 URL */\n qrcodeUrl?: string\n /** localStorage key(记住我) */\n storageKey?: string\n /** 外部传入的 loading 状态 */\n loading?: boolean\n /** 表单默认用户名(Demo / 预填场景) */\n defaultUsername?: string\n /** 表单默认密码(Demo / 预填场景) */\n defaultPassword?: string\n}\n\n// ================= 默认社交登录配置 =================\nexport const DEFAULT_SOCIAL_PROVIDERS: SocialProvider[] = [\n { key: 'github', label: 'GitHub', icon: 'mdi:github' },\n { key: 'wechat', label: '微信', icon: 'mdi:wechat' },\n { key: 'qq', label: 'QQ', icon: 'mdi:qqchat' },\n]\n\n// ================= 默认功能开关 =================\nexport const DEFAULT_FEATURES: Required<LoginFeatures> = {\n passwordLogin: true,\n captchaLogin: true,\n qrcodeLogin: true,\n socialLogin: true,\n register: true,\n captchaVerify: true,\n rememberMe: true,\n forgotPassword: true,\n}\n","<!--\n * @Description: C_Login - 通用插拔式登录组件\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\n-->\n<template>\n <div class=\"c-login-panel\">\n <!-- ===== Logo / 标题区 ===== -->\n <div class=\"c-login__header\">\n <slot name=\"logo\">\n <div\n v-if=\"mergedProps.logoIcon\"\n class=\"c-login__logo\"\n >\n <C_Icon\n :name=\"mergedProps.logoIcon\"\n :size=\"36\"\n class=\"c-login__logo-icon\"\n />\n </div>\n </slot>\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\n <p\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\n class=\"c-login__subtitle\"\n >\n {{ mergedProps.subtitle }}\n </p>\n <!-- 注册视图的副标题 -->\n <p\n v-if=\"activeTab === 'register'\"\n class=\"c-login__subtitle c-login__subtitle--register\"\n >\n {{ t('cl_register', '注册账号') }}\n </p>\n </div>\n\n <!-- ===== 密码登录表单(默认视图) ===== -->\n <NForm\n v-show=\"activeTab === 'password'\"\n ref=\"passwordFormRef\"\n :model=\"passwordForm\"\n :rules=\"passwordRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"username\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.username\"\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\n clearable\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:account-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n clearable\n @keyup.enter=\"handlePasswordSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <!-- 记住我 + 忘记密码 -->\n <div\n v-if=\"feat.rememberMe || feat.forgotPassword\"\n class=\"c-login__aux-row\"\n >\n <NCheckbox\n v-if=\"feat.rememberMe\"\n v-model:checked=\"rememberMe\"\n class=\"c-login__remember\"\n >\n {{ t('cl_remember', '记住我') }}\n </NCheckbox>\n <NText\n v-if=\"feat.forgotPassword\"\n class=\"c-login__forgot\"\n @click=\"emit('forgot-password')\"\n >\n {{ t('cl_forgot', '忘记密码?') }}\n </NText>\n </div>\n </NForm>\n\n <!-- ===== 验证码登录表单 ===== -->\n <NForm\n v-show=\"activeTab === 'captcha'\"\n ref=\"captchaFormRef\"\n :model=\"captchaForm\"\n :rules=\"captchaRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"account\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"captchaForm.account\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"captchaForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n @keyup.enter=\"handleCaptchaSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"countdown > 0 || !captchaForm.account\"\n class=\"c-login__send-code-btn\"\n @click=\"handleSendCode\"\n >\n {{\n countdown > 0\n ? `${countdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n </NForm>\n\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\n <NButton\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n :disabled=\"\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n \"\n class=\"c-login__submit-btn\"\n @click=\"\n activeTab === 'password'\n ? handlePasswordSubmit()\n : handleCaptchaSubmit()\n \"\n >\n {{\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n ? t('cl_verify_first', '请先完成人机验证')\n : t('cl_login_btn', '登 录')\n }}\n </NButton>\n\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\n <div\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\n class=\"c-login__captcha-wrap\"\n >\n <C_Captcha\n ref=\"captchaRef\"\n trigger-text=\"\"\n theme=\"dark\"\n @success=\"handleCaptchaSuccess\"\n @fail=\"handleCaptchaFail\"\n @change=\"handleCaptchaChange\"\n />\n </div>\n\n <!-- ===== 返回按钮(验证码子视图) ===== -->\n <NButton\n v-if=\"activeTab === 'captcha'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 扫码登录面板 ===== -->\n <div\n v-if=\"activeTab === 'qrcode'\"\n class=\"c-login__qr-panel\"\n >\n <div class=\"c-login__qr-frame\">\n <C_QRCode\n ref=\"qrcodeRef\"\n :value=\"qrcodeValue\"\n :size=\"148\"\n :margin=\"1\"\n :show-border=\"false\"\n color=\"#ffffff\"\n bg-color=\"#000000\"\n error-correction-level=\"M\"\n />\n <div class=\"c-login__qr-scan-line\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\n </div>\n <p class=\"c-login__qr-hint\">\n <C_Icon\n name=\"mdi:cellphone-check\"\n :size=\"13\"\n />\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\n </p>\n <NButton\n text\n size=\"tiny\"\n class=\"c-login__qr-refresh\"\n @click=\"handleQrcodeRefresh\"\n >\n <template #icon>\n <C_Icon\n name=\"mdi:refresh\"\n :size=\"12\"\n />\n </template>\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\n </NButton>\n </div>\n\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\n <NButton\n v-if=\"activeTab === 'qrcode'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 注册表单 ===== -->\n <NForm\n v-show=\"activeTab === 'register'\"\n ref=\"registerFormRef\"\n :model=\"registerForm\"\n :rules=\"registerRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"phone\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.phone\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"registerForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\n class=\"c-login__send-code-btn\"\n @click=\"handleRegSendCode\"\n >\n {{\n regCountdown > 0\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"confirmPassword\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.confirmPassword\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\n @keyup.enter=\"handleRegisterSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-check-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n </NForm>\n\n <!-- ===== 注册确认按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n class=\"c-login__submit-btn\"\n @click=\"handleRegisterSubmit\"\n >\n {{ t('cl_confirm', '确认') }}\n </NButton>\n\n <!-- ===== 注册视图返回按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\n class=\"c-login__mode-switch\"\n >\n <NButton\n v-if=\"feat.captchaLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'captcha'\"\n >\n {{ t('cl_captcha_login', '验证码登录') }}\n </NButton>\n <NButton\n v-if=\"feat.qrcodeLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'qrcode'\"\n >\n {{ t('cl_qr_login', '扫码登录') }}\n </NButton>\n </div>\n\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\n <template\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\n >\n <NDivider class=\"c-login__divider\">{{\n t('cl_social_login', '其他账号登录')\n }}</NDivider>\n <div class=\"c-login__social-row\">\n <NTooltip\n v-for=\"provider in socialList\"\n :key=\"provider.key\"\n trigger=\"hover\"\n placement=\"top\"\n >\n <template #trigger>\n <NButton\n circle\n class=\"c-login__social-btn\"\n @click=\"emit('social-login', provider.key)\"\n >\n <template #icon>\n <C_Icon\n :name=\"provider.icon\"\n :size=\"16\"\n />\n </template>\n </NButton>\n </template>\n {{ provider.label }}\n </NTooltip>\n </div>\n </template>\n\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && feat.register\"\n class=\"c-login__register-row\"\n >\n <NText class=\"c-login__register-hint\">{{\n t('cl_no_account', '还没有账号?')\n }}</NText>\n <NText\n class=\"c-login__register-link\"\n @click=\"activeTab = 'register'\"\n >\n {{ t('cl_register', '注册账号') }}\n </NText>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\n import type { FormInst, FormRules } from 'naive-ui/es'\n import C_Icon from '../C_Icon/index.vue'\n import C_Captcha from '../C_Captcha/index.vue'\n import C_QRCode from '../C_QRCode/index.vue'\n import { RULE_COMBOS } from '@robot-admin/form-validate'\n import {\n DEFAULT_FEATURES,\n DEFAULT_SOCIAL_PROVIDERS,\n type LoginProps,\n type PasswordFormData,\n type CaptchaFormData,\n type RegisterFormData,\n } from './types'\n\n // ===== i18n helper =====\n const t = (key: string, fallback: string) =>\n typeof (globalThis as any).$t === 'function'\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\n : fallback\n\n // ===== Props & Emits =====\n const props = withDefaults(defineProps<LoginProps>(), {\n title: '欢迎回来',\n loading: false,\n storageKey: 'c_login_remember',\n defaultUsername: '',\n defaultPassword: '',\n })\n\n const emit = defineEmits<{\n (\n e: 'submit',\n data: PasswordFormData & {\n captchaToken?: string\n captchaTimestamp?: number\n }\n ): void\n (e: 'captcha-submit', data: CaptchaFormData): void\n (e: 'send-code', account: string): void\n (e: 'social-login', provider: string): void\n (e: 'forgot-password'): void\n (e: 'qrcode-refresh'): void\n (e: 'register-submit', data: RegisterFormData): void\n (e: 'register-send-code', phone: string): void\n }>()\n\n // ===== 合并 Props(填充默认值) =====\n const mergedProps = computed(() => ({\n ...props,\n features: { ...DEFAULT_FEATURES, ...props.features },\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\n }))\n\n const feat = computed(\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\n )\n const socialList = computed(() => mergedProps.value.socialProviders!)\n\n // ===== Tab(默认密码登录) =====\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\n 'password'\n )\n\n // ===== 密码表单 =====\n const passwordFormRef = ref<FormInst | null>(null)\n const passwordForm = reactive<PasswordFormData>({\n username: props.defaultUsername ?? '',\n password: props.defaultPassword ?? '',\n })\n\n const passwordRules: FormRules = {\n username: [\n {\n required: true,\n message: t('cl_username_rule', '请输入用户名'),\n trigger: 'blur',\n },\n ],\n password: [\n {\n required: true,\n message: t('cl_password_rule', '请输入密码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 记住我 =====\n const rememberMe = ref(false)\n\n watch(rememberMe, val => {\n if (val) {\n localStorage.setItem(\n props.storageKey!,\n JSON.stringify({ username: passwordForm.username })\n )\n } else {\n localStorage.removeItem(props.storageKey!)\n }\n })\n\n onMounted(() => {\n try {\n const saved = localStorage.getItem(props.storageKey!)\n if (saved) {\n const data = JSON.parse(saved)\n if (data.username) {\n passwordForm.username = data.username\n rememberMe.value = true\n }\n }\n } catch {\n /* ignore */\n }\n })\n\n // ===== 密码登录提交 =====\n const handlePasswordSubmit = async () => {\n try {\n await passwordFormRef.value?.validate()\n } catch {\n return // 校验失败,表单会显示行内错误提示\n }\n emit('submit', {\n ...passwordForm,\n ...(captchaValid.value\n ? {\n captchaToken: captchaToken.value,\n captchaTimestamp: captchaTimestamp.value,\n }\n : {}),\n })\n }\n\n // ===== 验证码表单 =====\n const captchaFormRef = ref<FormInst | null>(null)\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\n const countdown = ref(0)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n\n const captchaRules: FormRules = {\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 人机验证 =====\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\n const captchaValid = ref(false)\n const captchaToken = ref('')\n const captchaTimestamp = ref(0)\n\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\n captchaValid.value = true\n captchaToken.value = data.token\n captchaTimestamp.value = data.timestamp\n }\n const handleCaptchaFail = () => {\n captchaValid.value = false\n }\n const handleCaptchaChange = (valid: boolean) => {\n captchaValid.value = valid\n if (!valid) {\n captchaToken.value = ''\n captchaTimestamp.value = 0\n }\n }\n\n const resetCaptcha = () => {\n captchaValid.value = false\n captchaToken.value = ''\n captchaTimestamp.value = 0\n captchaRef.value?.reset()\n }\n\n const handleCaptchaSubmit = async () => {\n try {\n await captchaFormRef.value?.validate()\n } catch {\n return\n }\n if (feat.value.captchaVerify && !captchaValid.value) return\n emit('captcha-submit', { ...captchaForm })\n }\n\n // ===== 发送验证码 =====\n const handleSendCode = () => {\n if (!captchaForm.account) return\n emit('send-code', captchaForm.account)\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n countdown.value = 60\n countdownTimer = setInterval(() => {\n countdown.value--\n if (countdown.value <= 0) {\n clearInterval(countdownTimer!)\n countdownTimer = null\n }\n }, 1000)\n }\n\n // ===== 扫码登录 =====\n const qrcodeRef = ref<any>(null)\n const qrcodeValue = ref(\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n )\n\n const handleQrcodeRefresh = () => {\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n emit('qrcode-refresh')\n }\n\n // ===== 注册表单 =====\n const registerFormRef = ref<FormInst | null>(null)\n const registerForm = reactive<RegisterFormData>({\n phone: '',\n code: '',\n password: '',\n confirmPassword: '',\n })\n const regCountdown = ref(0)\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\n\n const registerRules: FormRules = {\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n password: RULE_COMBOS.password('密码') as FormRules['password'],\n confirmPassword: [\n {\n required: true,\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\n trigger: 'blur',\n },\n {\n validator: (_rule: any, value: string) => {\n if (value !== registerForm.password) {\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\n }\n return true\n },\n trigger: 'blur',\n },\n ],\n }\n\n const handleRegSendCode = () => {\n if (!registerForm.phone) return\n emit('register-send-code', registerForm.phone)\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n regCountdown.value = 60\n regCountdownTimer = setInterval(() => {\n regCountdown.value--\n if (regCountdown.value <= 0) {\n clearInterval(regCountdownTimer!)\n regCountdownTimer = null\n }\n }, 1000)\n }\n\n const handleRegisterSubmit = async () => {\n try {\n await registerFormRef.value?.validate()\n } catch {\n return\n }\n emit('register-submit', { ...registerForm })\n }\n\n // ===== 清理定时器 =====\n onUnmounted(() => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n })\n\n // ===== 暴露方法给父组件 =====\n defineExpose({ resetCaptcha })\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"C_Login.js","names":[],"sources":["../src/components/C_Login/types.ts","../src/components/C_Login/index.vue","../src/components/C_Login/index.vue"],"sourcesContent":["/**\n * @Description: C_Login 通用登录组件 - 类型定义\n */\n\n// ================= 功能开关 =================\nexport interface LoginFeatures {\n /** 是否显示密码登录(默认 true) */\n passwordLogin?: boolean\n /** 是否显示验证码登录(默认 true) */\n captchaLogin?: boolean\n /** 是否显示扫码登录(默认 true) */\n qrcodeLogin?: boolean\n /** 是否显示其他账号登录(默认 true) */\n socialLogin?: boolean\n /** 是否显示注册账号(默认 true) */\n register?: boolean\n /** 是否启用人机验证拼图(默认 true) */\n captchaVerify?: boolean\n /** 是否显示记住我(默认 true) */\n rememberMe?: boolean\n /** 是否显示忘记密码(默认 true) */\n forgotPassword?: boolean\n}\n\n// ================= 社交登录 =================\nexport interface SocialProvider {\n key: string\n label: string\n /** mdi 图标名称 */\n icon: string\n}\n\n// ================= 表单数据 =================\nexport interface PasswordFormData {\n username: string\n password: string\n}\n\nexport interface CaptchaFormData {\n account: string\n code: string\n}\n\nexport interface RegisterFormData {\n phone: string\n code: string\n password: string\n confirmPassword: string\n}\n\n// ================= 组件 Props =================\nexport interface LoginProps {\n /** 标题文字 */\n title?: string\n /** 副标题 */\n subtitle?: string\n /** Logo icon 名称(mdi:xxx),不传则不显示图标 */\n logoIcon?: string\n /** 功能开关配置 */\n features?: LoginFeatures\n /** 社交登录提供商列表 */\n socialProviders?: SocialProvider[]\n /** 扫码登录二维码 URL */\n qrcodeUrl?: string\n /** localStorage key(记住我) */\n storageKey?: string\n /** 外部传入的 loading 状态 */\n loading?: boolean\n /** 表单默认用户名(Demo / 预填场景) */\n defaultUsername?: string\n /** 表单默认密码(Demo / 预填场景) */\n defaultPassword?: string\n}\n\n// ================= 默认社交登录配置 =================\nexport const DEFAULT_SOCIAL_PROVIDERS: SocialProvider[] = [\n { key: 'github', label: 'GitHub', icon: 'mdi:github' },\n { key: 'wechat', label: '微信', icon: 'mdi:wechat' },\n { key: 'qq', label: 'QQ', icon: 'mdi:qqchat' },\n]\n\n// ================= 默认功能开关 =================\nexport const DEFAULT_FEATURES: Required<LoginFeatures> = {\n passwordLogin: true,\n captchaLogin: true,\n qrcodeLogin: true,\n socialLogin: true,\n register: true,\n captchaVerify: true,\n rememberMe: true,\n forgotPassword: true,\n}\n","<!--\n * @Description: C_Login - 通用插拔式登录组件\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\n-->\n<template>\n <div class=\"c-login-panel\">\n <!-- ===== Logo / 标题区 ===== -->\n <div class=\"c-login__header\">\n <slot name=\"logo\">\n <div\n v-if=\"mergedProps.logoIcon\"\n class=\"c-login__logo\"\n >\n <C_Icon\n :name=\"mergedProps.logoIcon\"\n :size=\"36\"\n class=\"c-login__logo-icon\"\n />\n </div>\n </slot>\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\n <p\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\n class=\"c-login__subtitle\"\n >\n {{ mergedProps.subtitle }}\n </p>\n <!-- 注册视图的副标题 -->\n <p\n v-if=\"activeTab === 'register'\"\n class=\"c-login__subtitle c-login__subtitle--register\"\n >\n {{ t('cl_register', '注册账号') }}\n </p>\n </div>\n\n <!-- ===== 密码登录表单(默认视图) ===== -->\n <NForm\n v-show=\"activeTab === 'password'\"\n ref=\"passwordFormRef\"\n :model=\"passwordForm\"\n :rules=\"passwordRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"username\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.username\"\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\n clearable\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:account-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n clearable\n @keyup.enter=\"handlePasswordSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <!-- 记住我 + 忘记密码 -->\n <div\n v-if=\"feat.rememberMe || feat.forgotPassword\"\n class=\"c-login__aux-row\"\n >\n <NCheckbox\n v-if=\"feat.rememberMe\"\n v-model:checked=\"rememberMe\"\n class=\"c-login__remember\"\n >\n {{ t('cl_remember', '记住我') }}\n </NCheckbox>\n <NText\n v-if=\"feat.forgotPassword\"\n class=\"c-login__forgot\"\n @click=\"emit('forgot-password')\"\n >\n {{ t('cl_forgot', '忘记密码?') }}\n </NText>\n </div>\n </NForm>\n\n <!-- ===== 验证码登录表单 ===== -->\n <NForm\n v-show=\"activeTab === 'captcha'\"\n ref=\"captchaFormRef\"\n :model=\"captchaForm\"\n :rules=\"captchaRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"account\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"captchaForm.account\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"captchaForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n @keyup.enter=\"handleCaptchaSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"countdown > 0 || !captchaForm.account\"\n class=\"c-login__send-code-btn\"\n @click=\"handleSendCode\"\n >\n {{\n countdown > 0\n ? `${countdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n </NForm>\n\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\n <NButton\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n :disabled=\"\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n \"\n class=\"c-login__submit-btn\"\n @click=\"\n activeTab === 'password'\n ? handlePasswordSubmit()\n : handleCaptchaSubmit()\n \"\n >\n {{\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n ? t('cl_verify_first', '请先完成人机验证')\n : t('cl_login_btn', '登 录')\n }}\n </NButton>\n\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\n <div\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\n class=\"c-login__captcha-wrap\"\n >\n <C_Captcha\n ref=\"captchaRef\"\n trigger-text=\"\"\n theme=\"dark\"\n @success=\"handleCaptchaSuccess\"\n @fail=\"handleCaptchaFail\"\n @change=\"handleCaptchaChange\"\n />\n </div>\n\n <!-- ===== 返回按钮(验证码子视图) ===== -->\n <NButton\n v-if=\"activeTab === 'captcha'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 扫码登录面板 ===== -->\n <div\n v-if=\"activeTab === 'qrcode'\"\n class=\"c-login__qr-panel\"\n >\n <div class=\"c-login__qr-frame\">\n <C_QRCode\n ref=\"qrcodeRef\"\n :value=\"qrcodeValue\"\n :size=\"148\"\n :margin=\"1\"\n :show-border=\"false\"\n color=\"#ffffff\"\n bg-color=\"#000000\"\n error-correction-level=\"M\"\n />\n <div class=\"c-login__qr-scan-line\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\n </div>\n <p class=\"c-login__qr-hint\">\n <C_Icon\n name=\"mdi:cellphone-check\"\n :size=\"13\"\n />\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\n </p>\n <NButton\n text\n size=\"tiny\"\n class=\"c-login__qr-refresh\"\n @click=\"handleQrcodeRefresh\"\n >\n <template #icon>\n <C_Icon\n name=\"mdi:refresh\"\n :size=\"12\"\n />\n </template>\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\n </NButton>\n </div>\n\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\n <NButton\n v-if=\"activeTab === 'qrcode'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 注册表单 ===== -->\n <NForm\n v-show=\"activeTab === 'register'\"\n ref=\"registerFormRef\"\n :model=\"registerForm\"\n :rules=\"registerRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"phone\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.phone\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"registerForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\n class=\"c-login__send-code-btn\"\n @click=\"handleRegSendCode\"\n >\n {{\n regCountdown > 0\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"confirmPassword\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.confirmPassword\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\n @keyup.enter=\"handleRegisterSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-check-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n </NForm>\n\n <!-- ===== 注册确认按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n class=\"c-login__submit-btn\"\n @click=\"handleRegisterSubmit\"\n >\n {{ t('cl_confirm', '确认') }}\n </NButton>\n\n <!-- ===== 注册视图返回按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\n class=\"c-login__mode-switch\"\n >\n <NButton\n v-if=\"feat.captchaLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'captcha'\"\n >\n {{ t('cl_captcha_login', '验证码登录') }}\n </NButton>\n <NButton\n v-if=\"feat.qrcodeLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'qrcode'\"\n >\n {{ t('cl_qr_login', '扫码登录') }}\n </NButton>\n </div>\n\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\n <template\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\n >\n <NDivider class=\"c-login__divider\">{{\n t('cl_social_login', '其他账号登录')\n }}</NDivider>\n <div class=\"c-login__social-row\">\n <NTooltip\n v-for=\"provider in socialList\"\n :key=\"provider.key\"\n trigger=\"hover\"\n placement=\"top\"\n >\n <template #trigger>\n <NButton\n circle\n class=\"c-login__social-btn\"\n @click=\"emit('social-login', provider.key)\"\n >\n <template #icon>\n <C_Icon\n :name=\"provider.icon\"\n :size=\"16\"\n />\n </template>\n </NButton>\n </template>\n {{ provider.label }}\n </NTooltip>\n </div>\n </template>\n\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && feat.register\"\n class=\"c-login__register-row\"\n >\n <NText class=\"c-login__register-hint\">{{\n t('cl_no_account', '还没有账号?')\n }}</NText>\n <NText\n class=\"c-login__register-link\"\n @click=\"activeTab = 'register'\"\n >\n {{ t('cl_register', '注册账号') }}\n </NText>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\n import type { FormInst, FormRules } from 'naive-ui/es'\n import C_Icon from '../C_Icon/index.vue'\n import C_Captcha from '../C_Captcha/index.vue'\n import C_QRCode from '../C_QRCode/index.vue'\n import { RULE_COMBOS } from '@robot-admin/form-validate'\n import {\n DEFAULT_FEATURES,\n DEFAULT_SOCIAL_PROVIDERS,\n type LoginProps,\n type PasswordFormData,\n type CaptchaFormData,\n type RegisterFormData,\n } from './types'\n\n // ===== i18n helper =====\n const t = (key: string, fallback: string) =>\n typeof (globalThis as any).$t === 'function'\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\n : fallback\n\n // ===== Props & Emits =====\n const props = withDefaults(defineProps<LoginProps>(), {\n title: '欢迎回来',\n loading: false,\n storageKey: 'c_login_remember',\n defaultUsername: '',\n defaultPassword: '',\n })\n\n const emit = defineEmits<{\n (\n e: 'submit',\n data: PasswordFormData & {\n captchaToken?: string\n captchaTimestamp?: number\n }\n ): void\n (e: 'captcha-submit', data: CaptchaFormData): void\n (e: 'send-code', account: string): void\n (e: 'social-login', provider: string): void\n (e: 'forgot-password'): void\n (e: 'qrcode-refresh'): void\n (e: 'register-submit', data: RegisterFormData): void\n (e: 'register-send-code', phone: string): void\n }>()\n\n // ===== 合并 Props(填充默认值) =====\n const mergedProps = computed(() => ({\n ...props,\n features: { ...DEFAULT_FEATURES, ...props.features },\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\n }))\n\n const feat = computed(\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\n )\n const socialList = computed(() => mergedProps.value.socialProviders!)\n\n // ===== Tab(默认密码登录) =====\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\n 'password'\n )\n\n // ===== 密码表单 =====\n const passwordFormRef = ref<FormInst | null>(null)\n const passwordForm = reactive<PasswordFormData>({\n username: props.defaultUsername ?? '',\n password: props.defaultPassword ?? '',\n })\n\n const passwordRules: FormRules = {\n username: [\n {\n required: true,\n message: t('cl_username_rule', '请输入用户名'),\n trigger: 'blur',\n },\n ],\n password: [\n {\n required: true,\n message: t('cl_password_rule', '请输入密码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 记住我 =====\n const rememberMe = ref(false)\n\n watch(rememberMe, val => {\n if (val) {\n localStorage.setItem(\n props.storageKey!,\n JSON.stringify({ username: passwordForm.username })\n )\n } else {\n localStorage.removeItem(props.storageKey!)\n }\n })\n\n onMounted(() => {\n try {\n const saved = localStorage.getItem(props.storageKey!)\n if (saved) {\n const data = JSON.parse(saved)\n if (data.username) {\n passwordForm.username = data.username\n rememberMe.value = true\n }\n }\n } catch {\n /* ignore */\n }\n })\n\n // ===== 密码登录提交 =====\n const handlePasswordSubmit = async () => {\n try {\n await passwordFormRef.value?.validate()\n } catch {\n return // 校验失败,表单会显示行内错误提示\n }\n emit('submit', {\n ...passwordForm,\n ...(captchaValid.value\n ? {\n captchaToken: captchaToken.value,\n captchaTimestamp: captchaTimestamp.value,\n }\n : {}),\n })\n }\n\n // ===== 验证码表单 =====\n const captchaFormRef = ref<FormInst | null>(null)\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\n const countdown = ref(0)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n\n const captchaRules: FormRules = {\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 人机验证 =====\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\n const captchaValid = ref(false)\n const captchaToken = ref('')\n const captchaTimestamp = ref(0)\n\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\n captchaValid.value = true\n captchaToken.value = data.token\n captchaTimestamp.value = data.timestamp\n }\n const handleCaptchaFail = () => {\n captchaValid.value = false\n }\n const handleCaptchaChange = (valid: boolean) => {\n captchaValid.value = valid\n if (!valid) {\n captchaToken.value = ''\n captchaTimestamp.value = 0\n }\n }\n\n const resetCaptcha = () => {\n captchaValid.value = false\n captchaToken.value = ''\n captchaTimestamp.value = 0\n captchaRef.value?.reset()\n }\n\n const handleCaptchaSubmit = async () => {\n try {\n await captchaFormRef.value?.validate()\n } catch {\n return\n }\n if (feat.value.captchaVerify && !captchaValid.value) return\n emit('captcha-submit', { ...captchaForm })\n }\n\n // ===== 发送验证码 =====\n const handleSendCode = () => {\n if (!captchaForm.account) return\n emit('send-code', captchaForm.account)\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n countdown.value = 60\n countdownTimer = setInterval(() => {\n countdown.value--\n if (countdown.value <= 0) {\n clearInterval(countdownTimer!)\n countdownTimer = null\n }\n }, 1000)\n }\n\n // ===== 扫码登录 =====\n const qrcodeRef = ref<any>(null)\n const qrcodeValue = ref(\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n )\n\n const handleQrcodeRefresh = () => {\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n emit('qrcode-refresh')\n }\n\n // ===== 注册表单 =====\n const registerFormRef = ref<FormInst | null>(null)\n const registerForm = reactive<RegisterFormData>({\n phone: '',\n code: '',\n password: '',\n confirmPassword: '',\n })\n const regCountdown = ref(0)\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\n\n const registerRules: FormRules = {\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n password: RULE_COMBOS.password('密码') as FormRules['password'],\n confirmPassword: [\n {\n required: true,\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\n trigger: 'blur',\n },\n {\n validator: (_rule: any, value: string) => {\n if (value !== registerForm.password) {\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\n }\n return true\n },\n trigger: 'blur',\n },\n ],\n }\n\n const handleRegSendCode = () => {\n if (!registerForm.phone) return\n emit('register-send-code', registerForm.phone)\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n regCountdown.value = 60\n regCountdownTimer = setInterval(() => {\n regCountdown.value--\n if (regCountdown.value <= 0) {\n clearInterval(regCountdownTimer!)\n regCountdownTimer = null\n }\n }, 1000)\n }\n\n const handleRegisterSubmit = async () => {\n try {\n await registerFormRef.value?.validate()\n } catch {\n return\n }\n emit('register-submit', { ...registerForm })\n }\n\n // ===== 清理定时器 =====\n onUnmounted(() => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n })\n\n // ===== 暴露方法给父组件 =====\n defineExpose({ resetCaptcha })\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n","<!--\n * @Description: C_Login - 通用插拔式登录组件\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\n-->\n<template>\n <div class=\"c-login-panel\">\n <!-- ===== Logo / 标题区 ===== -->\n <div class=\"c-login__header\">\n <slot name=\"logo\">\n <div\n v-if=\"mergedProps.logoIcon\"\n class=\"c-login__logo\"\n >\n <C_Icon\n :name=\"mergedProps.logoIcon\"\n :size=\"36\"\n class=\"c-login__logo-icon\"\n />\n </div>\n </slot>\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\n <p\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\n class=\"c-login__subtitle\"\n >\n {{ mergedProps.subtitle }}\n </p>\n <!-- 注册视图的副标题 -->\n <p\n v-if=\"activeTab === 'register'\"\n class=\"c-login__subtitle c-login__subtitle--register\"\n >\n {{ t('cl_register', '注册账号') }}\n </p>\n </div>\n\n <!-- ===== 密码登录表单(默认视图) ===== -->\n <NForm\n v-show=\"activeTab === 'password'\"\n ref=\"passwordFormRef\"\n :model=\"passwordForm\"\n :rules=\"passwordRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"username\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.username\"\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\n clearable\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:account-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n clearable\n @keyup.enter=\"handlePasswordSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <!-- 记住我 + 忘记密码 -->\n <div\n v-if=\"feat.rememberMe || feat.forgotPassword\"\n class=\"c-login__aux-row\"\n >\n <NCheckbox\n v-if=\"feat.rememberMe\"\n v-model:checked=\"rememberMe\"\n class=\"c-login__remember\"\n >\n {{ t('cl_remember', '记住我') }}\n </NCheckbox>\n <NText\n v-if=\"feat.forgotPassword\"\n class=\"c-login__forgot\"\n @click=\"emit('forgot-password')\"\n >\n {{ t('cl_forgot', '忘记密码?') }}\n </NText>\n </div>\n </NForm>\n\n <!-- ===== 验证码登录表单 ===== -->\n <NForm\n v-show=\"activeTab === 'captcha'\"\n ref=\"captchaFormRef\"\n :model=\"captchaForm\"\n :rules=\"captchaRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"account\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"captchaForm.account\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"captchaForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n @keyup.enter=\"handleCaptchaSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"countdown > 0 || !captchaForm.account\"\n class=\"c-login__send-code-btn\"\n @click=\"handleSendCode\"\n >\n {{\n countdown > 0\n ? `${countdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n </NForm>\n\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\n <NButton\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n :disabled=\"\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n \"\n class=\"c-login__submit-btn\"\n @click=\"\n activeTab === 'password'\n ? handlePasswordSubmit()\n : handleCaptchaSubmit()\n \"\n >\n {{\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n ? t('cl_verify_first', '请先完成人机验证')\n : t('cl_login_btn', '登 录')\n }}\n </NButton>\n\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\n <div\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\n class=\"c-login__captcha-wrap\"\n >\n <C_Captcha\n ref=\"captchaRef\"\n trigger-text=\"\"\n theme=\"dark\"\n @success=\"handleCaptchaSuccess\"\n @fail=\"handleCaptchaFail\"\n @change=\"handleCaptchaChange\"\n />\n </div>\n\n <!-- ===== 返回按钮(验证码子视图) ===== -->\n <NButton\n v-if=\"activeTab === 'captcha'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 扫码登录面板 ===== -->\n <div\n v-if=\"activeTab === 'qrcode'\"\n class=\"c-login__qr-panel\"\n >\n <div class=\"c-login__qr-frame\">\n <C_QRCode\n ref=\"qrcodeRef\"\n :value=\"qrcodeValue\"\n :size=\"148\"\n :margin=\"1\"\n :show-border=\"false\"\n color=\"#ffffff\"\n bg-color=\"#000000\"\n error-correction-level=\"M\"\n />\n <div class=\"c-login__qr-scan-line\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\n </div>\n <p class=\"c-login__qr-hint\">\n <C_Icon\n name=\"mdi:cellphone-check\"\n :size=\"13\"\n />\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\n </p>\n <NButton\n text\n size=\"tiny\"\n class=\"c-login__qr-refresh\"\n @click=\"handleQrcodeRefresh\"\n >\n <template #icon>\n <C_Icon\n name=\"mdi:refresh\"\n :size=\"12\"\n />\n </template>\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\n </NButton>\n </div>\n\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\n <NButton\n v-if=\"activeTab === 'qrcode'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 注册表单 ===== -->\n <NForm\n v-show=\"activeTab === 'register'\"\n ref=\"registerFormRef\"\n :model=\"registerForm\"\n :rules=\"registerRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"phone\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.phone\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"registerForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\n class=\"c-login__send-code-btn\"\n @click=\"handleRegSendCode\"\n >\n {{\n regCountdown > 0\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"confirmPassword\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.confirmPassword\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\n @keyup.enter=\"handleRegisterSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-check-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n </NForm>\n\n <!-- ===== 注册确认按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n class=\"c-login__submit-btn\"\n @click=\"handleRegisterSubmit\"\n >\n {{ t('cl_confirm', '确认') }}\n </NButton>\n\n <!-- ===== 注册视图返回按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\n class=\"c-login__mode-switch\"\n >\n <NButton\n v-if=\"feat.captchaLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'captcha'\"\n >\n {{ t('cl_captcha_login', '验证码登录') }}\n </NButton>\n <NButton\n v-if=\"feat.qrcodeLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'qrcode'\"\n >\n {{ t('cl_qr_login', '扫码登录') }}\n </NButton>\n </div>\n\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\n <template\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\n >\n <NDivider class=\"c-login__divider\">{{\n t('cl_social_login', '其他账号登录')\n }}</NDivider>\n <div class=\"c-login__social-row\">\n <NTooltip\n v-for=\"provider in socialList\"\n :key=\"provider.key\"\n trigger=\"hover\"\n placement=\"top\"\n >\n <template #trigger>\n <NButton\n circle\n class=\"c-login__social-btn\"\n @click=\"emit('social-login', provider.key)\"\n >\n <template #icon>\n <C_Icon\n :name=\"provider.icon\"\n :size=\"16\"\n />\n </template>\n </NButton>\n </template>\n {{ provider.label }}\n </NTooltip>\n </div>\n </template>\n\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && feat.register\"\n class=\"c-login__register-row\"\n >\n <NText class=\"c-login__register-hint\">{{\n t('cl_no_account', '还没有账号?')\n }}</NText>\n <NText\n class=\"c-login__register-link\"\n @click=\"activeTab = 'register'\"\n >\n {{ t('cl_register', '注册账号') }}\n </NText>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\n import type { FormInst, FormRules } from 'naive-ui/es'\n import C_Icon from '../C_Icon/index.vue'\n import C_Captcha from '../C_Captcha/index.vue'\n import C_QRCode from '../C_QRCode/index.vue'\n import { RULE_COMBOS } from '@robot-admin/form-validate'\n import {\n DEFAULT_FEATURES,\n DEFAULT_SOCIAL_PROVIDERS,\n type LoginProps,\n type PasswordFormData,\n type CaptchaFormData,\n type RegisterFormData,\n } from './types'\n\n // ===== i18n helper =====\n const t = (key: string, fallback: string) =>\n typeof (globalThis as any).$t === 'function'\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\n : fallback\n\n // ===== Props & Emits =====\n const props = withDefaults(defineProps<LoginProps>(), {\n title: '欢迎回来',\n loading: false,\n storageKey: 'c_login_remember',\n defaultUsername: '',\n defaultPassword: '',\n })\n\n const emit = defineEmits<{\n (\n e: 'submit',\n data: PasswordFormData & {\n captchaToken?: string\n captchaTimestamp?: number\n }\n ): void\n (e: 'captcha-submit', data: CaptchaFormData): void\n (e: 'send-code', account: string): void\n (e: 'social-login', provider: string): void\n (e: 'forgot-password'): void\n (e: 'qrcode-refresh'): void\n (e: 'register-submit', data: RegisterFormData): void\n (e: 'register-send-code', phone: string): void\n }>()\n\n // ===== 合并 Props(填充默认值) =====\n const mergedProps = computed(() => ({\n ...props,\n features: { ...DEFAULT_FEATURES, ...props.features },\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\n }))\n\n const feat = computed(\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\n )\n const socialList = computed(() => mergedProps.value.socialProviders!)\n\n // ===== Tab(默认密码登录) =====\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\n 'password'\n )\n\n // ===== 密码表单 =====\n const passwordFormRef = ref<FormInst | null>(null)\n const passwordForm = reactive<PasswordFormData>({\n username: props.defaultUsername ?? '',\n password: props.defaultPassword ?? '',\n })\n\n const passwordRules: FormRules = {\n username: [\n {\n required: true,\n message: t('cl_username_rule', '请输入用户名'),\n trigger: 'blur',\n },\n ],\n password: [\n {\n required: true,\n message: t('cl_password_rule', '请输入密码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 记住我 =====\n const rememberMe = ref(false)\n\n watch(rememberMe, val => {\n if (val) {\n localStorage.setItem(\n props.storageKey!,\n JSON.stringify({ username: passwordForm.username })\n )\n } else {\n localStorage.removeItem(props.storageKey!)\n }\n })\n\n onMounted(() => {\n try {\n const saved = localStorage.getItem(props.storageKey!)\n if (saved) {\n const data = JSON.parse(saved)\n if (data.username) {\n passwordForm.username = data.username\n rememberMe.value = true\n }\n }\n } catch {\n /* ignore */\n }\n })\n\n // ===== 密码登录提交 =====\n const handlePasswordSubmit = async () => {\n try {\n await passwordFormRef.value?.validate()\n } catch {\n return // 校验失败,表单会显示行内错误提示\n }\n emit('submit', {\n ...passwordForm,\n ...(captchaValid.value\n ? {\n captchaToken: captchaToken.value,\n captchaTimestamp: captchaTimestamp.value,\n }\n : {}),\n })\n }\n\n // ===== 验证码表单 =====\n const captchaFormRef = ref<FormInst | null>(null)\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\n const countdown = ref(0)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n\n const captchaRules: FormRules = {\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 人机验证 =====\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\n const captchaValid = ref(false)\n const captchaToken = ref('')\n const captchaTimestamp = ref(0)\n\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\n captchaValid.value = true\n captchaToken.value = data.token\n captchaTimestamp.value = data.timestamp\n }\n const handleCaptchaFail = () => {\n captchaValid.value = false\n }\n const handleCaptchaChange = (valid: boolean) => {\n captchaValid.value = valid\n if (!valid) {\n captchaToken.value = ''\n captchaTimestamp.value = 0\n }\n }\n\n const resetCaptcha = () => {\n captchaValid.value = false\n captchaToken.value = ''\n captchaTimestamp.value = 0\n captchaRef.value?.reset()\n }\n\n const handleCaptchaSubmit = async () => {\n try {\n await captchaFormRef.value?.validate()\n } catch {\n return\n }\n if (feat.value.captchaVerify && !captchaValid.value) return\n emit('captcha-submit', { ...captchaForm })\n }\n\n // ===== 发送验证码 =====\n const handleSendCode = () => {\n if (!captchaForm.account) return\n emit('send-code', captchaForm.account)\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n countdown.value = 60\n countdownTimer = setInterval(() => {\n countdown.value--\n if (countdown.value <= 0) {\n clearInterval(countdownTimer!)\n countdownTimer = null\n }\n }, 1000)\n }\n\n // ===== 扫码登录 =====\n const qrcodeRef = ref<any>(null)\n const qrcodeValue = ref(\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n )\n\n const handleQrcodeRefresh = () => {\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n emit('qrcode-refresh')\n }\n\n // ===== 注册表单 =====\n const registerFormRef = ref<FormInst | null>(null)\n const registerForm = reactive<RegisterFormData>({\n phone: '',\n code: '',\n password: '',\n confirmPassword: '',\n })\n const regCountdown = ref(0)\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\n\n const registerRules: FormRules = {\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n password: RULE_COMBOS.password('密码') as FormRules['password'],\n confirmPassword: [\n {\n required: true,\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\n trigger: 'blur',\n },\n {\n validator: (_rule: any, value: string) => {\n if (value !== registerForm.password) {\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\n }\n return true\n },\n trigger: 'blur',\n },\n ],\n }\n\n const handleRegSendCode = () => {\n if (!registerForm.phone) return\n emit('register-send-code', registerForm.phone)\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n regCountdown.value = 60\n regCountdownTimer = setInterval(() => {\n regCountdown.value--\n if (regCountdown.value <= 0) {\n clearInterval(regCountdownTimer!)\n regCountdownTimer = null\n }\n }, 1000)\n }\n\n const handleRegisterSubmit = async () => {\n try {\n await registerFormRef.value?.validate()\n } catch {\n return\n }\n emit('register-submit', { ...registerForm })\n }\n\n // ===== 清理定时器 =====\n onUnmounted(() => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n })\n\n // ===== 暴露方法给父组件 =====\n defineExpose({ resetCaptcha })\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n"],"mappings":"iOA2EA,MAAa,EAA6C,CACxD,CAAE,IAAK,SAAU,MAAO,SAAU,KAAM,aAAc,CACtD,CAAE,IAAK,SAAU,MAAO,KAAM,KAAM,aAAc,CAClD,CAAE,IAAK,KAAM,MAAO,KAAM,KAAM,aAAc,CAC/C,CAGY,EAA4C,CACvD,cAAe,GACf,aAAc,GACd,YAAa,GACb,YAAa,GACb,SAAU,GACV,cAAe,GACf,WAAY,GACZ,eAAgB,GACjB,+9BC8YC,IAAM,GAAK,EAAa,IACtB,OAAQ,WAAmB,IAAO,WAC7B,WAAmB,GAAG,EAAK,EAAU,cAAa,CACnD,EAGA,EAAQ,EAQR,EAAO,EAkBP,GAAA,EAAA,EAAA,eAA8B,CAClC,GAAG,EACH,SAAU,CAAE,GAAG,EAAkB,GAAG,EAAM,SAAU,CACpD,gBAAiB,EAAM,iBAAmB,EAC3C,EAAC,CAEI,GAAA,EAAA,EAAA,cACE,EAAY,MAAM,SAC1B,CACM,GAAA,EAAA,EAAA,cAA4B,EAAY,MAAM,gBAAgB,CAG9D,GAAA,EAAA,EAAA,KACJ,WACF,CAGM,GAAA,EAAA,EAAA,KAAuC,KAAI,CAC3C,GAAA,EAAA,EAAA,UAA0C,CAC9C,SAAU,EAAM,iBAAmB,GACnC,SAAU,EAAM,iBAAmB,GACpC,CAAA,CAEK,GAA2B,CAC/B,SAAU,CACR,CACE,SAAU,GACV,QAAS,EAAE,mBAAoB,SAAS,CACxC,QAAS,OACV,CACF,CACD,SAAU,CACR,CACE,SAAU,GACV,QAAS,EAAE,mBAAoB,QAAQ,CACvC,QAAS,OACV,CAAA,CAEL,CAGM,GAAA,EAAA,EAAA,KAAiB,GAAK,EAE5B,EAAA,EAAA,OAAM,EAAY,GAAO,CACnB,EACF,aAAa,QACX,EAAM,WACN,KAAK,UAAU,CAAE,SAAU,EAAa,SAAU,CAAA,CACpD,CAEA,aAAa,WAAW,EAAM,WAAW,EAE5C,EAED,EAAA,EAAA,eAAgB,CACd,GAAI,CACF,IAAM,EAAQ,aAAa,QAAQ,EAAM,WAAW,CACpD,GAAI,EAAO,CACT,IAAM,EAAO,KAAK,MAAM,EAAK,CACzB,EAAK,WACP,EAAa,SAAW,EAAK,SAC7B,EAAW,MAAQ,UAGjB,IAGT,CAGD,IAAM,EAAuB,SAAY,CACvC,GAAI,CACF,MAAM,EAAgB,OAAO,UAAS,MAChC,CACN,OAEF,EAAK,SAAU,CACb,GAAG,EACH,GAAI,EAAa,MACb,CACE,aAAc,EAAa,MAC3B,iBAAkB,EAAiB,MACrC,CACA,EAAA,CACL,CAAA,EAIG,GAAA,EAAA,EAAA,KAAsC,KAAI,CAC1C,GAAA,EAAA,EAAA,UAAwC,CAAE,QAAS,GAAI,KAAM,GAAI,CAAA,CACjE,GAAA,EAAA,EAAA,KAAgB,EAAC,CACnB,EAAwD,KAEtD,EAA0B,CAC9B,QAAS,EAAA,YAAY,OAAO,MAAM,CAClC,KAAM,CACJ,CACE,SAAU,GACV,QAAS,EAAE,eAAgB,SAAS,CACpC,QAAS,OACV,CAAA,CAEL,CAGM,GAAA,EAAA,EAAA,MAAiD,CACjD,GAAA,EAAA,EAAA,KAAmB,GAAK,CACxB,GAAA,EAAA,EAAA,KAAmB,GAAE,CACrB,GAAA,EAAA,EAAA,KAAuB,EAAC,CAExB,EAAwB,GAA+C,CAC3E,EAAa,MAAQ,GACrB,EAAa,MAAQ,EAAK,MAC1B,EAAiB,MAAQ,EAAK,WAE1B,MAA0B,CAC9B,EAAa,MAAQ,IAEjB,EAAuB,GAAmB,CAC9C,EAAa,MAAQ,EAChB,IACH,EAAa,MAAQ,GACrB,EAAiB,MAAQ,IAIvB,MAAqB,CACzB,EAAa,MAAQ,GACrB,EAAa,MAAQ,GACrB,EAAiB,MAAQ,EACzB,EAAW,OAAO,OAAM,EAGpB,EAAsB,SAAY,CACtC,GAAI,CACF,MAAM,EAAe,OAAO,UAAS,MAC/B,CACN,OAEE,EAAK,MAAM,eAAiB,CAAC,EAAa,OAC9C,EAAK,iBAAkB,CAAE,GAAG,EAAa,CAAA,EAIrC,MAAuB,CACtB,EAAY,UACjB,EAAK,YAAa,EAAY,QAAO,CACrC,AAEE,KADA,cAAc,EAAc,CACX,MAEnB,EAAU,MAAQ,GAClB,EAAiB,gBAAkB,CACjC,EAAU,QACN,EAAU,OAAS,IACrB,cAAc,EAAe,CAC7B,EAAiB,OAElB,IAAI,GAIH,IAAA,EAAA,EAAA,KAAqB,KAAI,CACzB,GAAA,EAAA,EAAA,KACJ,0CAA0C,KAAK,KAAK,GACtD,CAEM,OAA4B,CAChC,EAAY,MAAQ,0CAA0C,KAAK,KAAK,GACxE,EAAK,iBAAgB,EAIjB,GAAA,EAAA,EAAA,KAAuC,KAAI,CAC3C,GAAA,EAAA,EAAA,UAA0C,CAC9C,MAAO,GACP,KAAM,GACN,SAAU,GACV,gBAAiB,GAClB,CAAA,CACK,GAAA,EAAA,EAAA,KAAmB,EAAC,CACtB,EAA2D,KAEzD,GAA2B,CAC/B,MAAO,EAAA,YAAY,OAAO,MAAM,CAChC,KAAM,CACJ,CACE,SAAU,GACV,QAAS,EAAE,eAAgB,SAAS,CACpC,QAAS,OACV,CACF,CACD,SAAU,EAAA,YAAY,SAAS,KAAK,CACpC,gBAAiB,CACf,CACE,SAAU,GACV,QAAS,EAAE,qBAAsB,UAAU,CAC3C,QAAS,OACV,CACD,CACE,WAAY,EAAY,IAClB,IAAU,EAAa,SAGpB,GAFM,MAAM,EAAE,iBAAkB,YAAY,CAAA,CAIrD,QAAS,OACV,CAAA,CAEL,CAEM,OAA0B,CACzB,EAAa,QAClB,EAAK,qBAAsB,EAAa,MAAK,CAC7C,AAEE,KADA,cAAc,EAAiB,CACX,MAEtB,EAAa,MAAQ,GACrB,EAAoB,gBAAkB,CACpC,EAAa,QACT,EAAa,OAAS,IACxB,cAAc,EAAkB,CAChC,EAAoB,OAErB,IAAI,GAGH,EAAuB,SAAY,CACvC,GAAI,CACF,MAAM,EAAgB,OAAO,UAAS,MAChC,CACN,OAEF,EAAK,kBAAmB,CAAE,GAAG,EAAc,CAAA,SAI7C,EAAA,EAAA,iBAAkB,CAChB,AAEE,KADA,cAAc,EAAc,CACX,MAEnB,AAEE,KADA,cAAc,EAAiB,CACX,OAEvB,CAGD,EAAa,CAAE,eAAc,CAAA,+JC9SvB,MAhdN,EAgdM,0BA/cJ,2BAA+B,0BA4BzB,MA3BN,EA2BM,kBAfG,EAAA,OAAA,OAAA,EAAA,KAAA,CATG,EAAA,MAAY,WAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAQd,MATN,EASM,EAAA,EAAA,EAAA,aADF,EAAA,EAAA,CAHC,KAAM,EAAA,MAAY,SAClB,KAAM,GACP,MAAM,4GAI2C,KAAvD,GAAA,EAAA,EAAA,iBAA8B,EAAA,MAAY,MAAK,CAAA,EAAA,CAEvC,EAAA,MAAY,UAAY,EAAA,QAAS,aAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAIrC,IALJ,GAAA,EAAA,EAAA,iBAIK,EAAA,MAAY,SAAQ,CAAA,EAAA,GAAA,EAAA,EAAA,oBAAA,OAAA,GAAA,0BAEzB,aAAiB,CAET,EAAA,QAAS,aAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAIb,IALJ,GAAA,EAAA,EAAA,iBAIK,EAAC,cAAA,OAAA,CAAA,CAAA,EAAA,GAAA,EAAA,EAAA,oBAAA,OAAA,GAAA,4BAIR,6BAAiC,wCAqEzB,EAAA,SAlEF,kBAAJ,IAAI,EACH,MAAO,EACP,MAAO,GACR,MAAM,4CAmBM,mBAAA,EAAA,CAhBV,KAAK,WACL,MAAM,iDAcG,EAAA,EAAA,EAAA,aAAA,EAAA,CAXC,MAAO,EAAa,oCAAb,EAAa,SAAQ,EACnC,YAAa,EAAC,iBAAA,SAAA,CACf,UAAA,KAEW,QAAA,EAAA,EAAA,aAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAHA,KAAK,sBACJ,KAAM,GACP,MAAM,oFA0BF,EAAA,CAnBV,KAAK,WACL,MAAM,iDAiBG,EAAA,EAAA,EAAA,aAAA,EAAA,CAdC,MAAO,EAAa,oCAAb,EAAa,SAAQ,EACpC,KAAK,WACL,mBAAiB,QAChB,YAAa,EAAC,iBAAA,QAAA,CACf,UAAA,GACC,SAAA,EAAA,EAAA,UAAa,EAAoB,CAAA,QAAA,CAAA,GAEvB,QAAA,EAAA,EAAA,aAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAHA,KAAK,mBACJ,KAAM,GACP,MAAM,2FAMd,eAAmB,CAEX,EAAA,MAAK,YAAc,EAAA,MAAK,iBAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAiB1B,MAlBN,EAkBM,CAbI,EAAA,MAAK,aAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAKD,EAAA,OAJF,QAAS,EAAA,mCAAA,EAAU,MAAA,EAC3B,MAAM,gDAEuB,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAA1B,EAAC,cAAA,MAAA,CAAA,CAAA,EAAA,CAAA,CAAA,0DAGE,EAAA,MAAK,iBAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAKL,EAAA,OAJN,MAAM,kBACL,QAAK,AAAA,EAAA,KAAA,GAAE,EAAI,kBAAA,6BAEiB,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAA1B,EAAC,YAAA,QAAA,CAAA,CAAA,EAAA,CAAA,CAAA,iHAhEA,EAAA,QAAS,WAAA,CAAA,CAAA,0BAqEnB,wBAA4B,wCA6DpB,EAAA,SA1DF,iBAAJ,IAAI,EACH,MAAO,EACP,MAAO,EACR,MAAM,4CAoBM,EAAA,EAAA,EAAA,aAAA,EAAA,CAjBV,KAAK,UACL,MAAM,iDAeG,EAAA,EAAA,EAAA,aAAA,EAAA,CAZC,MAAO,EAAY,mCAAZ,EAAY,QAAO,EACjC,YAAa,EAAC,cAAA,SAAA,CACf,UAAA,GACA,UAAU,OAEC,QAAA,EAAA,EAAA,aAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAHA,KAAK,gBACJ,KAAM,GACP,MAAM,oFAsCF,EAAA,CA/BV,KAAK,OACL,MAAM,iDA6BA,EAAA,EAAA,EAAA,oBAAA,MA3BN,GA2BM,EAAA,EAAA,EAAA,aAZK,EAAA,CAbC,MAAO,EAAY,gCAAZ,EAAY,KAAI,EAC9B,YAAa,EAAC,aAAA,SAAA,CACf,UAAA,GACA,MAAM,sBACL,SAAA,EAAA,EAAA,UAAa,EAAmB,CAAA,QAAA,CAAA,GAEtB,QAAA,EAAA,EAAA,aAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAHA,KAAK,yBACJ,KAAM,GACP,MAAM,4EAcF,EAAA,CATP,SAAU,EAAA,MAAS,GAAA,CAAS,EAAY,QACzC,MAAM,yBACL,QAAO,8BAMN,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAHA,EAAA,MAAS,EAAA,GAA0B,EAAA,MAAS,IAAK,EAAC,YAAA,MAAA,GAAyC,EAAC,cAAA,QAAA,CAAA,CAAA,EAAA,CAAA,CAAA,8DApD5F,EAAA,QAAS,UAAA,CAAA,CAAA,0BA6DnB,+BAAmC,CAE3B,EAAA,QAAS,YAAmB,EAAA,QAAS,YAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAmBnC,EAAA,OAlBR,KAAK,UACL,MAAA,GACC,QAAS,EAAM,QACf,SAAmB,EAAA,QAAS,YAAmB,EAAA,MAAK,eAAa,CAAK,EAAA,MAGvE,MAAM,sBACL,QAAK,AAAA,EAAA,KAAA,GAAW,EAAA,QAAS,WAA4B,GAAoB,CAAe,GAAmB,6BAU1G,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAHA,EAAA,QAAS,YAAmB,EAAA,MAAK,eAAa,CAAK,EAAA,MAAyB,EAAC,kBAAA,WAAA,CAA4C,EAAC,eAAA,MAAA,CAAA,CAAA,EAAA,CAAA,CAAA,8FAM9H,gCAAoC,CAE5B,EAAA,MAAK,eAAiB,EAAA,QAAS,aAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAWjC,MAZN,EAYM,EAAA,EAAA,EAAA,aADF,GAAA,EAAA,SANI,aAAJ,IAAI,EACJ,eAAa,GACb,MAAM,OACL,UAAS,EACT,OAAM,EACN,SAAQ,6EAIb,6BAAiC,CAEzB,EAAA,QAAS,YAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAMP,EAAA,OALR,MAAA,GACA,MAAM,oBACL,QAAK,AAAA,EAAA,KAAA,GAAE,EAAA,MAAS,uCAEO,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAArB,EAAC,UAAA,KAAA,CAAA,CAAA,EAAA,CAAA,CAAA,qEAGN,uBAA2B,CAEnB,EAAA,QAAS,WAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAyCX,MA1CN,EA0CM,0BAtBE,MAhBN,EAgBM,EAAA,EAAA,EAAA,aANF,GAAA,EAAA,SARI,YAAJ,IAAI,GACH,MAAO,EAAA,MACP,KAAM,IACN,OAAQ,EACR,cAAa,GACd,MAAM,UACN,WAAS,UACT,yBAAuB,qcAcvB,IANJ,EAMI,EAAA,EAAA,EAAA,aAFA,EAAA,EAAA,CAFA,KAAK,sBACJ,KAAM,2BACP,KAAA,EAAA,EAAA,iBACC,EAAC,aAAA,oBAAA,CAAA,CAAA,EAAA,CAAA,CAAA,mBAeI,EAAA,CAZR,KAAA,GACA,KAAK,OACL,MAAM,sBACL,QAAO,KAEG,MAAA,EAAA,EAAA,aAIP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAFA,KAAK,cACJ,KAAM,iCAGX,EAAA,EAAA,EAAA,iBADW,KAAA,EAAA,EAAA,iBACR,EAAC,gBAAA,cAAA,CAAA,CAAA,EAAA,CAAA,CAAA,uEAIR,mCAAuC,CAE/B,EAAA,QAAS,WAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAMP,EAAA,OALR,MAAA,GACA,MAAM,oBACL,QAAK,AAAA,EAAA,KAAA,GAAE,EAAA,MAAS,uCAEO,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAArB,EAAC,UAAA,KAAA,CAAA,CAAA,EAAA,CAAA,CAAA,qEAGN,qBAAyB,wCAqGjB,EAAA,SAlGF,kBAAJ,IAAI,EACH,MAAO,EACP,MAAO,GACR,MAAM,4CAoBM,mBAAA,EAAA,CAjBV,KAAK,QACL,MAAM,iDAeG,EAAA,EAAA,EAAA,aAAA,EAAA,CAZC,MAAO,EAAa,iCAAb,EAAa,MAAK,EAChC,YAAa,EAAC,cAAA,SAAA,CACf,UAAA,GACA,UAAU,OAEC,QAAA,EAAA,EAAA,aAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAHA,KAAK,gBACJ,KAAM,GACP,MAAM,oFAqCF,EAAA,CA9BV,KAAK,OACL,MAAM,iDA4BA,EAAA,EAAA,EAAA,oBAAA,MA1BN,EA0BM,EAAA,EAAA,EAAA,aAZK,EAAA,CAZC,MAAO,EAAa,iCAAb,EAAa,KAAI,EAC/B,YAAa,EAAC,aAAA,SAAA,CACf,UAAA,GACA,MAAM,wBAEK,QAAA,EAAA,EAAA,aAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAHA,KAAK,yBACJ,KAAM,GACP,MAAM,4EAcF,EAAA,CATP,SAAU,EAAA,MAAY,GAAA,CAAS,EAAa,MAC7C,MAAM,yBACL,QAAO,+BAMN,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAHA,EAAA,MAAY,EAAA,GAA0B,EAAA,MAAY,IAAK,EAAC,YAAA,MAAA,GAAyC,EAAC,cAAA,QAAA,CAAA,CAAA,EAAA,CAAA,CAAA,kDA0B9F,EAAA,CAjBV,KAAK,WACL,MAAM,iDAeG,EAAA,EAAA,EAAA,aAAA,EAAA,CAZC,MAAO,EAAa,qCAAb,EAAa,SAAQ,EACpC,KAAK,WACL,mBAAiB,QAChB,YAAa,EAAC,iBAAA,QAAA,GAEJ,QAAA,EAAA,EAAA,aAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAHA,KAAK,mBACJ,KAAM,GACP,MAAM,oFAyBF,EAAA,CAlBV,KAAK,kBACL,MAAM,iDAgBG,EAAA,EAAA,EAAA,aAAA,EAAA,CAbC,MAAO,EAAa,4CAAb,EAAa,gBAAe,EAC3C,KAAK,WACL,mBAAiB,QAChB,YAAa,EAAC,mBAAA,UAAA,CACd,SAAA,EAAA,EAAA,UAAa,EAAoB,CAAA,QAAA,CAAA,GAEvB,QAAA,EAAA,EAAA,aAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAHA,KAAK,yBACJ,KAAM,GACP,MAAM,gGA9FN,EAAA,QAAS,WAAA,CAAA,CAAA,0BAqGnB,uBAA2B,CAEnB,EAAA,QAAS,aAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAQP,EAAA,OAPR,KAAK,UACL,MAAA,GACC,QAAS,EAAM,QAChB,MAAM,sBACL,QAAO,8BAEmB,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAAxB,EAAC,aAAA,KAAA,CAAA,CAAA,EAAA,CAAA,CAAA,mFAGN,yBAA6B,CAErB,EAAA,QAAS,aAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAMP,EAAA,OALR,MAAA,GACA,MAAM,oBACL,QAAK,AAAA,EAAA,MAAA,GAAE,EAAA,MAAS,uCAEO,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAArB,EAAC,UAAA,KAAA,CAAA,CAAA,EAAA,CAAA,CAAA,qEAGN,oCAAwC,CAEhC,EAAA,QAAS,aAAoB,EAAA,MAAK,cAAgB,EAAA,MAAK,eAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAmBzD,MApBN,EAoBM,CAfI,EAAA,MAAK,eAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAMH,EAAA,OALR,MAAM,oBACN,KAAK,QACJ,QAAK,AAAA,EAAA,MAAA,GAAE,EAAA,MAAS,sCAEmB,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAAjC,EAAC,mBAAA,QAAA,CAAA,CAAA,EAAA,CAAA,CAAA,4CAGE,EAAA,MAAK,cAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAMH,EAAA,OALR,MAAM,oBACN,KAAK,QACJ,QAAK,AAAA,EAAA,MAAA,GAAE,EAAA,MAAS,qCAEa,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAA3B,EAAC,cAAA,OAAA,CAAA,CAAA,EAAA,CAAA,CAAA,4GAIR,oCAAwC,CAEhC,EAAA,QAAS,YAAmB,EAAA,MAAK,aAAe,EAAA,MAAW,SAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBA6BxD,EAAA,SAAA,CAAA,IAAA,EAAA,CAAA,EAAA,EAAA,EAAA,aAzBI,EAAA,CAFH,MAAM,mBAAkB,CAAA,2BAEhC,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBADA,EAAC,kBAAA,SAAA,CAAA,CAAA,EAAA,CAAA,CAAA,gCAyBG,MAvBN,GAuBM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADO,EAAA,SAAA,MAAA,EAAA,EAAA,YApBU,EAAA,MAAZ,wCAoBE,EAAA,CAnBR,IAAK,EAAS,IACf,QAAQ,QACR,UAAU,QAEC,SAAA,EAAA,EAAA,aAYC,EAAA,EAAA,EAAA,aAAA,EAAA,CAVR,OAAA,GACA,MAAM,sBACL,QAAK,GAAE,EAAI,eAAiB,EAAS,IAAA,GAE3B,MAAA,EAAA,EAAA,aAIP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAFC,KAAM,EAAS,KACf,KAAM,0EAKf,EAAA,EAAA,EAAA,iBADW,KAAA,EAAA,EAAA,iBACR,EAAS,MAAK,CAAA,EAAA,CAAA,CAAA,yFAKvB,kCAAsC,CAE9B,EAAA,QAAS,YAAmB,EAAA,MAAK,WAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAYnC,MAbN,GAaM,EAAA,EAAA,EAAA,aAPM,EAAA,CAFH,MAAM,yBAAwB,CAAA,2BAEnC,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBADA,EAAC,gBAAA,SAAA,CAAA,CAAA,EAAA,CAAA,CAAA,yBAOK,EAAA,CAJN,MAAM,yBACL,QAAK,AAAA,EAAA,MAAA,GAAE,EAAA,MAAS,uCAEa,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAA3B,EAAC,cAAA,OAAA,CAAA,CAAA,EAAA,CAAA,CAAA"}
package/dist/C_Login2.js CHANGED
@@ -1,2 +1,2 @@
1
- import{t as e}from"./C_Icon2.js";import{t}from"./export-helper.js";import{t as ee}from"./C_Captcha2.js";import{t as te}from"./C_QRCode2.js";import{Fragment as n,computed as r,createBlock as i,createCommentVNode as a,createElementBlock as o,createElementVNode as s,createStaticVNode as ne,createTextVNode as c,createVNode as l,defineComponent as u,onMounted as d,onUnmounted as f,openBlock as p,reactive as m,ref as h,renderList as re,renderSlot as ie,resolveComponent as g,toDisplayString as _,vShow as v,watch as y,withCtx as b,withDirectives as x,withKeys as S}from"vue";import{RULE_COMBOS as C}from"@robot-admin/form-validate";const w=[{key:`github`,label:`GitHub`,icon:`mdi:github`},{key:`wechat`,label:`微信`,icon:`mdi:wechat`},{key:`qq`,label:`QQ`,icon:`mdi:qqchat`}],T={passwordLogin:!0,captchaLogin:!0,qrcodeLogin:!0,socialLogin:!0,register:!0,captchaVerify:!0,rememberMe:!0,forgotPassword:!0},ae={class:`c-login-panel`},oe={class:`c-login__header`},se={key:0,class:`c-login__logo`},ce={class:`c-login__title`},le={key:0,class:`c-login__subtitle`},ue={key:1,class:`c-login__subtitle c-login__subtitle--register`},de={key:0,class:`c-login__aux-row`},fe={class:`c-login__code-row`},pe={key:1,class:`c-login__captcha-wrap`},me={key:3,class:`c-login__qr-panel`},he={class:`c-login__qr-frame`},ge={class:`c-login__qr-hint`},_e={class:`c-login__code-row`},ve={key:7,class:`c-login__mode-switch`},ye={class:`c-login__social-row`},be={key:9,class:`c-login__register-row`};var E=t(u({__name:`index`,props:{title:{default:`欢迎回来`},subtitle:{},logoIcon:{},features:{},socialProviders:{},qrcodeUrl:{},storageKey:{default:`c_login_remember`},loading:{type:Boolean,default:!1},defaultUsername:{default:``},defaultPassword:{default:``}},emits:[`submit`,`captcha-submit`,`send-code`,`social-login`,`forgot-password`,`qrcode-refresh`,`register-submit`,`register-send-code`],setup(t,{expose:u,emit:E}){let D=(e,t)=>typeof globalThis.$t==`function`?globalThis.$t(e,t,`robot_admin`):t,O=t,k=E,A=r(()=>({...O,features:{...T,...O.features},socialProviders:O.socialProviders??w})),j=r(()=>A.value.features),M=r(()=>A.value.socialProviders),N=h(`password`),P=h(null),F=m({username:O.defaultUsername??``,password:O.defaultPassword??``}),xe={username:[{required:!0,message:D(`cl_username_rule`,`请输入用户名`),trigger:`blur`}],password:[{required:!0,message:D(`cl_password_rule`,`请输入密码`),trigger:`blur`}]},I=h(!1);y(I,e=>{e?localStorage.setItem(O.storageKey,JSON.stringify({username:F.username})):localStorage.removeItem(O.storageKey)}),d(()=>{try{let e=localStorage.getItem(O.storageKey);if(e){let t=JSON.parse(e);t.username&&(F.username=t.username,I.value=!0)}}catch{}});let L=async()=>{try{await P.value?.validate()}catch{return}k(`submit`,{...F,...U.value?{captchaToken:W.value,captchaTimestamp:G.value}:{}})},R=h(null),z=m({account:``,code:``}),B=h(0),V=null,Se={account:C.mobile(`手机号`),code:[{required:!0,message:D(`cl_code_rule`,`请输入验证码`),trigger:`blur`}]},H=h(),U=h(!1),W=h(``),G=h(0),Ce=e=>{U.value=!0,W.value=e.token,G.value=e.timestamp},we=()=>{U.value=!1},Te=e=>{U.value=e,e||(W.value=``,G.value=0)},Ee=()=>{U.value=!1,W.value=``,G.value=0,H.value?.reset()},K=async()=>{try{await R.value?.validate()}catch{return}j.value.captchaVerify&&!U.value||k(`captcha-submit`,{...z})},q=()=>{z.account&&(k(`send-code`,z.account),V&&=(clearInterval(V),null),B.value=60,V=setInterval(()=>{B.value--,B.value<=0&&(clearInterval(V),V=null)},1e3))},De=h(null),J=h(`https://robot-admin.app/qrcode-login?t=${Date.now()}`),Oe=()=>{J.value=`https://robot-admin.app/qrcode-login?t=${Date.now()}`,k(`qrcode-refresh`)},Y=h(null),X=m({phone:``,code:``,password:``,confirmPassword:``}),Z=h(0),Q=null,ke={phone:C.mobile(`手机号`),code:[{required:!0,message:D(`cl_code_rule`,`请输入验证码`),trigger:`blur`}],password:C.password(`密码`),confirmPassword:[{required:!0,message:D(`cl_confirm_pw_rule`,`请再次输入密码`),trigger:`blur`},{validator:(e,t)=>t===X.password?!0:Error(D(`cl_pw_mismatch`,`两次密码输入不一致`)),trigger:`blur`}]},Ae=()=>{X.phone&&(k(`register-send-code`,X.phone),Q&&=(clearInterval(Q),null),Z.value=60,Q=setInterval(()=>{Z.value--,Z.value<=0&&(clearInterval(Q),Q=null)},1e3))},$=async()=>{try{await Y.value?.validate()}catch{return}k(`register-submit`,{...X})};return f(()=>{V&&=(clearInterval(V),null),Q&&=(clearInterval(Q),null)}),u({resetCaptcha:Ee}),(t,r)=>{let u=g(`NInput`),d=g(`NFormItem`),f=g(`NCheckbox`),m=g(`NText`),h=g(`NForm`),y=g(`NButton`),C=g(`NDivider`),w=g(`NTooltip`);return p(),o(`div`,ae,[a(` ===== Logo / 标题区 ===== `),s(`div`,oe,[ie(t.$slots,`logo`,{},()=>[A.value.logoIcon?(p(),o(`div`,se,[l(e,{name:A.value.logoIcon,size:36,class:`c-login__logo-icon`},null,8,[`name`])])):a(`v-if`,!0)],!0),s(`h2`,ce,_(A.value.title),1),A.value.subtitle&&N.value!==`register`?(p(),o(`p`,le,_(A.value.subtitle),1)):a(`v-if`,!0),a(` 注册视图的副标题 `),N.value===`register`?(p(),o(`p`,ue,_(D(`cl_register`,`注册账号`)),1)):a(`v-if`,!0)]),a(` ===== 密码登录表单(默认视图) ===== `),x(l(h,{ref_key:`passwordFormRef`,ref:P,model:F,rules:xe,class:`c-login__form`},{default:b(()=>[l(d,{path:`username`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:F.username,"onUpdate:value":r[0]||=e=>F.username=e,placeholder:D(`cl_username_ph`,`请输入用户名`),clearable:``},{prefix:b(()=>[l(e,{name:`mdi:account-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),l(d,{path:`password`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:F.password,"onUpdate:value":r[1]||=e=>F.password=e,type:`password`,"show-password-on":`click`,placeholder:D(`cl_password_ph`,`请输入密码`),clearable:``,onKeyup:S(L,[`enter`])},{prefix:b(()=>[l(e,{name:`mdi:lock-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),a(` 记住我 + 忘记密码 `),j.value.rememberMe||j.value.forgotPassword?(p(),o(`div`,de,[j.value.rememberMe?(p(),i(f,{key:0,checked:I.value,"onUpdate:checked":r[2]||=e=>I.value=e,class:`c-login__remember`},{default:b(()=>[c(_(D(`cl_remember`,`记住我`)),1)]),_:1},8,[`checked`])):a(`v-if`,!0),j.value.forgotPassword?(p(),i(m,{key:1,class:`c-login__forgot`,onClick:r[3]||=e=>k(`forgot-password`)},{default:b(()=>[c(_(D(`cl_forgot`,`忘记密码?`)),1)]),_:1})):a(`v-if`,!0)])):a(`v-if`,!0)]),_:1},8,[`model`]),[[v,N.value===`password`]]),a(` ===== 验证码登录表单 ===== `),x(l(h,{ref_key:`captchaFormRef`,ref:R,model:z,rules:Se,class:`c-login__form`},{default:b(()=>[l(d,{path:`account`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:z.account,"onUpdate:value":r[4]||=e=>z.account=e,placeholder:D(`cl_phone_ph`,`请输入手机号`),clearable:``,maxlength:`11`},{prefix:b(()=>[l(e,{name:`mdi:cellphone`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),l(d,{path:`code`,class:`c-login__form-item`},{default:b(()=>[s(`div`,fe,[l(u,{value:z.code,"onUpdate:value":r[5]||=e=>z.code=e,placeholder:D(`cl_code_ph`,`请输入验证码`),clearable:``,class:`c-login__code-input`,onKeyup:S(K,[`enter`])},{prefix:b(()=>[l(e,{name:`mdi:shield-key-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`]),l(y,{disabled:B.value>0||!z.account,class:`c-login__send-code-btn`,onClick:q},{default:b(()=>[c(_(B.value>0?`${B.value}s ${D(`cl_resend`,`后重发`)}`:D(`cl_get_code`,`获取验证码`)),1)]),_:1},8,[`disabled`])])]),_:1})]),_:1},8,[`model`]),[[v,N.value===`captcha`]]),a(` ===== 登录按钮(密码和验证码模式) ===== `),N.value===`password`||N.value===`captcha`?(p(),i(y,{key:0,type:`primary`,block:``,loading:O.loading,disabled:N.value===`password`&&j.value.captchaVerify&&!U.value,class:`c-login__submit-btn`,onClick:r[6]||=e=>N.value===`password`?L():K()},{default:b(()=>[c(_(N.value===`password`&&j.value.captchaVerify&&!U.value?D(`cl_verify_first`,`请先完成人机验证`):D(`cl_login_btn`,`登 录`)),1)]),_:1},8,[`loading`,`disabled`])):a(`v-if`,!0),a(` ===== 人机验证拼图(仅密码登录模式) ===== `),j.value.captchaVerify&&N.value===`password`?(p(),o(`div`,pe,[l(ee,{ref_key:`captchaRef`,ref:H,"trigger-text":``,theme:`dark`,onSuccess:Ce,onFail:we,onChange:Te},null,512)])):a(`v-if`,!0),a(` ===== 返回按钮(验证码子视图) ===== `),N.value===`captcha`?(p(),i(y,{key:2,block:``,class:`c-login__back-btn`,onClick:r[7]||=e=>N.value=`password`},{default:b(()=>[c(_(D(`cl_back`,`返回`)),1)]),_:1})):a(`v-if`,!0),a(` ===== 扫码登录面板 ===== `),N.value===`qrcode`?(p(),o(`div`,me,[s(`div`,he,[l(te,{ref_key:`qrcodeRef`,ref:De,value:J.value,size:148,margin:1,"show-border":!1,color:`#ffffff`,"bg-color":`#000000`,"error-correction-level":`M`},null,8,[`value`]),r[17]||=ne(`<div class="c-login__qr-scan-line" data-v-d6255d97></div><div class="c-login__qr-corner c-login__qr-corner--tl" data-v-d6255d97></div><div class="c-login__qr-corner c-login__qr-corner--tr" data-v-d6255d97></div><div class="c-login__qr-corner c-login__qr-corner--bl" data-v-d6255d97></div><div class="c-login__qr-corner c-login__qr-corner--br" data-v-d6255d97></div>`,5)]),s(`p`,ge,[l(e,{name:`mdi:cellphone-check`,size:13}),c(` `+_(D(`cl_qr_hint`,`打开手机扫码后点击「确认」即可登录`)),1)]),l(y,{text:``,size:`tiny`,class:`c-login__qr-refresh`,onClick:Oe},{icon:b(()=>[l(e,{name:`mdi:refresh`,size:12})]),default:b(()=>[c(` `+_(D(`cl_qr_refresh`,`二维码已失效?点击刷新`)),1)]),_:1})])):a(`v-if`,!0),a(` ===== 返回按钮(扫码子视图,在二维码下方) ===== `),N.value===`qrcode`?(p(),i(y,{key:4,block:``,class:`c-login__back-btn`,onClick:r[8]||=e=>N.value=`password`},{default:b(()=>[c(_(D(`cl_back`,`返回`)),1)]),_:1})):a(`v-if`,!0),a(` ===== 注册表单 ===== `),x(l(h,{ref_key:`registerFormRef`,ref:Y,model:X,rules:ke,class:`c-login__form`},{default:b(()=>[l(d,{path:`phone`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:X.phone,"onUpdate:value":r[9]||=e=>X.phone=e,placeholder:D(`cl_phone_ph`,`请输入手机号`),clearable:``,maxlength:`11`},{prefix:b(()=>[l(e,{name:`mdi:cellphone`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),l(d,{path:`code`,class:`c-login__form-item`},{default:b(()=>[s(`div`,_e,[l(u,{value:X.code,"onUpdate:value":r[10]||=e=>X.code=e,placeholder:D(`cl_code_ph`,`请输入验证码`),clearable:``,class:`c-login__code-input`},{prefix:b(()=>[l(e,{name:`mdi:shield-key-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`]),l(y,{disabled:Z.value>0||!X.phone,class:`c-login__send-code-btn`,onClick:Ae},{default:b(()=>[c(_(Z.value>0?`${Z.value}s ${D(`cl_resend`,`后重发`)}`:D(`cl_get_code`,`获取验证码`)),1)]),_:1},8,[`disabled`])])]),_:1}),l(d,{path:`password`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:X.password,"onUpdate:value":r[11]||=e=>X.password=e,type:`password`,"show-password-on":`click`,placeholder:D(`cl_password_ph`,`请输入密码`)},{prefix:b(()=>[l(e,{name:`mdi:lock-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),l(d,{path:`confirmPassword`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:X.confirmPassword,"onUpdate:value":r[12]||=e=>X.confirmPassword=e,type:`password`,"show-password-on":`click`,placeholder:D(`cl_confirm_pw_ph`,`请再次输入密码`),onKeyup:S($,[`enter`])},{prefix:b(()=>[l(e,{name:`mdi:lock-check-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1})]),_:1},8,[`model`]),[[v,N.value===`register`]]),a(` ===== 注册确认按钮 ===== `),N.value===`register`?(p(),i(y,{key:5,type:`primary`,block:``,loading:O.loading,class:`c-login__submit-btn`,onClick:$},{default:b(()=>[c(_(D(`cl_confirm`,`确认`)),1)]),_:1},8,[`loading`])):a(`v-if`,!0),a(` ===== 注册视图返回按钮 ===== `),N.value===`register`?(p(),i(y,{key:6,block:``,class:`c-login__back-btn`,onClick:r[13]||=e=>N.value=`password`},{default:b(()=>[c(_(D(`cl_back`,`返回`)),1)]),_:1})):a(`v-if`,!0),a(` ===== 登录方式切换(仅密码登录默认视图显示) ===== `),N.value===`password`&&(j.value.captchaLogin||j.value.qrcodeLogin)?(p(),o(`div`,ve,[j.value.captchaLogin?(p(),i(y,{key:0,class:`c-login__mode-btn`,size:`small`,onClick:r[14]||=e=>N.value=`captcha`},{default:b(()=>[c(_(D(`cl_captcha_login`,`验证码登录`)),1)]),_:1})):a(`v-if`,!0),j.value.qrcodeLogin?(p(),i(y,{key:1,class:`c-login__mode-btn`,size:`small`,onClick:r[15]||=e=>N.value=`qrcode`},{default:b(()=>[c(_(D(`cl_qr_login`,`扫码登录`)),1)]),_:1})):a(`v-if`,!0)])):a(`v-if`,!0),a(` ===== 其他登录方式(仅密码登录默认视图显示) ===== `),N.value===`password`&&j.value.socialLogin&&M.value.length?(p(),o(n,{key:8},[l(C,{class:`c-login__divider`},{default:b(()=>[c(_(D(`cl_social_login`,`其他账号登录`)),1)]),_:1}),s(`div`,ye,[(p(!0),o(n,null,re(M.value,t=>(p(),i(w,{key:t.key,trigger:`hover`,placement:`top`},{trigger:b(()=>[l(y,{circle:``,class:`c-login__social-btn`,onClick:e=>k(`social-login`,t.key)},{icon:b(()=>[l(e,{name:t.icon,size:16},null,8,[`name`])]),_:2},1032,[`onClick`])]),default:b(()=>[c(` `+_(t.label),1)]),_:2},1024))),128))])],64)):a(`v-if`,!0),a(` ===== 注册入口(仅密码登录默认视图显示) ===== `),N.value===`password`&&j.value.register?(p(),o(`div`,be,[l(m,{class:`c-login__register-hint`},{default:b(()=>[c(_(D(`cl_no_account`,`还没有账号?`)),1)]),_:1}),l(m,{class:`c-login__register-link`,onClick:r[16]||=e=>N.value=`register`},{default:b(()=>[c(_(D(`cl_register`,`注册账号`)),1)]),_:1})])):a(`v-if`,!0)])}}}),[[`__scopeId`,`data-v-d6255d97`]]);export{T as n,w as r,E as t};
1
+ import{t as e}from"./C_Icon2.js";import{t}from"./export-helper.js";import{t as ee}from"./C_Captcha2.js";import{t as te}from"./C_QRCode2.js";import{NButton as ne,NCheckbox as re,NDivider as ie,NForm as ae,NFormItem as oe,NInput as se,NText as ce,NTooltip as le}from"naive-ui";import{Fragment as n,computed as r,createBlock as i,createCommentVNode as a,createElementBlock as o,createElementVNode as s,createStaticVNode as ue,createTextVNode as c,createVNode as l,defineComponent as u,onMounted as d,onUnmounted as f,openBlock as p,reactive as m,ref as h,renderList as de,renderSlot as fe,resolveComponent as g,toDisplayString as _,vShow as v,watch as y,withCtx as b,withDirectives as x,withKeys as S}from"vue";import{RULE_COMBOS as C}from"@robot-admin/form-validate";const w=[{key:`github`,label:`GitHub`,icon:`mdi:github`},{key:`wechat`,label:`微信`,icon:`mdi:wechat`},{key:`qq`,label:`QQ`,icon:`mdi:qqchat`}],T={passwordLogin:!0,captchaLogin:!0,qrcodeLogin:!0,socialLogin:!0,register:!0,captchaVerify:!0,rememberMe:!0,forgotPassword:!0},pe={class:`c-login-panel`},me={class:`c-login__header`},he={key:0,class:`c-login__logo`},ge={class:`c-login__title`},_e={key:0,class:`c-login__subtitle`},ve={key:1,class:`c-login__subtitle c-login__subtitle--register`},ye={key:0,class:`c-login__aux-row`},be={class:`c-login__code-row`},xe={key:1,class:`c-login__captcha-wrap`},E={key:3,class:`c-login__qr-panel`},Se={class:`c-login__qr-frame`},Ce={class:`c-login__qr-hint`},we={class:`c-login__code-row`},Te={key:7,class:`c-login__mode-switch`},Ee={class:`c-login__social-row`},D={key:9,class:`c-login__register-row`};var O=t(u({__name:`index`,props:{title:{default:`欢迎回来`},subtitle:{},logoIcon:{},features:{},socialProviders:{},qrcodeUrl:{},storageKey:{default:`c_login_remember`},loading:{type:Boolean,default:!1},defaultUsername:{default:``},defaultPassword:{default:``}},emits:[`submit`,`captcha-submit`,`send-code`,`social-login`,`forgot-password`,`qrcode-refresh`,`register-submit`,`register-send-code`],setup(t,{expose:u,emit:g}){let O=(e,t)=>typeof globalThis.$t==`function`?globalThis.$t(e,t,`robot_admin`):t,k=t,A=g,j=r(()=>({...k,features:{...T,...k.features},socialProviders:k.socialProviders??w})),M=r(()=>j.value.features),N=r(()=>j.value.socialProviders),P=h(`password`),F=h(null),I=m({username:k.defaultUsername??``,password:k.defaultPassword??``}),De={username:[{required:!0,message:O(`cl_username_rule`,`请输入用户名`),trigger:`blur`}],password:[{required:!0,message:O(`cl_password_rule`,`请输入密码`),trigger:`blur`}]},L=h(!1);y(L,e=>{e?localStorage.setItem(k.storageKey,JSON.stringify({username:I.username})):localStorage.removeItem(k.storageKey)}),d(()=>{try{let e=localStorage.getItem(k.storageKey);if(e){let t=JSON.parse(e);t.username&&(I.username=t.username,L.value=!0)}}catch{}});let R=async()=>{try{await F.value?.validate()}catch{return}A(`submit`,{...I,...W.value?{captchaToken:G.value,captchaTimestamp:K.value}:{}})},z=h(null),B=m({account:``,code:``}),V=h(0),H=null,Oe={account:C.mobile(`手机号`),code:[{required:!0,message:O(`cl_code_rule`,`请输入验证码`),trigger:`blur`}]},U=h(),W=h(!1),G=h(``),K=h(0),ke=e=>{W.value=!0,G.value=e.token,K.value=e.timestamp},Ae=()=>{W.value=!1},je=e=>{W.value=e,e||(G.value=``,K.value=0)},Me=()=>{W.value=!1,G.value=``,K.value=0,U.value?.reset()},q=async()=>{try{await z.value?.validate()}catch{return}M.value.captchaVerify&&!W.value||A(`captcha-submit`,{...B})},Ne=()=>{B.account&&(A(`send-code`,B.account),H&&=(clearInterval(H),null),V.value=60,H=setInterval(()=>{V.value--,V.value<=0&&(clearInterval(H),H=null)},1e3))},Pe=h(null),J=h(`https://robot-admin.app/qrcode-login?t=${Date.now()}`),Fe=()=>{J.value=`https://robot-admin.app/qrcode-login?t=${Date.now()}`,A(`qrcode-refresh`)},Y=h(null),X=m({phone:``,code:``,password:``,confirmPassword:``}),Z=h(0),Q=null,Ie={phone:C.mobile(`手机号`),code:[{required:!0,message:O(`cl_code_rule`,`请输入验证码`),trigger:`blur`}],password:C.password(`密码`),confirmPassword:[{required:!0,message:O(`cl_confirm_pw_rule`,`请再次输入密码`),trigger:`blur`},{validator:(e,t)=>t===X.password?!0:Error(O(`cl_pw_mismatch`,`两次密码输入不一致`)),trigger:`blur`}]},Le=()=>{X.phone&&(A(`register-send-code`,X.phone),Q&&=(clearInterval(Q),null),Z.value=60,Q=setInterval(()=>{Z.value--,Z.value<=0&&(clearInterval(Q),Q=null)},1e3))},$=async()=>{try{await Y.value?.validate()}catch{return}A(`register-submit`,{...X})};return f(()=>{H&&=(clearInterval(H),null),Q&&=(clearInterval(Q),null)}),u({resetCaptcha:Me}),(t,r)=>{let u=se,d=oe,f=re,m=ce,h=ae,g=ne,y=ie,C=le;return p(),o(`div`,pe,[a(` ===== Logo / 标题区 ===== `),s(`div`,me,[fe(t.$slots,`logo`,{},()=>[j.value.logoIcon?(p(),o(`div`,he,[l(e,{name:j.value.logoIcon,size:36,class:`c-login__logo-icon`},null,8,[`name`])])):a(`v-if`,!0)],!0),s(`h2`,ge,_(j.value.title),1),j.value.subtitle&&P.value!==`register`?(p(),o(`p`,_e,_(j.value.subtitle),1)):a(`v-if`,!0),a(` 注册视图的副标题 `),P.value===`register`?(p(),o(`p`,ve,_(O(`cl_register`,`注册账号`)),1)):a(`v-if`,!0)]),a(` ===== 密码登录表单(默认视图) ===== `),x(l(h,{ref_key:`passwordFormRef`,ref:F,model:I,rules:De,class:`c-login__form`},{default:b(()=>[l(d,{path:`username`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:I.username,"onUpdate:value":r[0]||=e=>I.username=e,placeholder:O(`cl_username_ph`,`请输入用户名`),clearable:``},{prefix:b(()=>[l(e,{name:`mdi:account-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),l(d,{path:`password`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:I.password,"onUpdate:value":r[1]||=e=>I.password=e,type:`password`,"show-password-on":`click`,placeholder:O(`cl_password_ph`,`请输入密码`),clearable:``,onKeyup:S(R,[`enter`])},{prefix:b(()=>[l(e,{name:`mdi:lock-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),a(` 记住我 + 忘记密码 `),M.value.rememberMe||M.value.forgotPassword?(p(),o(`div`,ye,[M.value.rememberMe?(p(),i(f,{key:0,checked:L.value,"onUpdate:checked":r[2]||=e=>L.value=e,class:`c-login__remember`},{default:b(()=>[c(_(O(`cl_remember`,`记住我`)),1)]),_:1},8,[`checked`])):a(`v-if`,!0),M.value.forgotPassword?(p(),i(m,{key:1,class:`c-login__forgot`,onClick:r[3]||=e=>A(`forgot-password`)},{default:b(()=>[c(_(O(`cl_forgot`,`忘记密码?`)),1)]),_:1})):a(`v-if`,!0)])):a(`v-if`,!0)]),_:1},8,[`model`]),[[v,P.value===`password`]]),a(` ===== 验证码登录表单 ===== `),x(l(h,{ref_key:`captchaFormRef`,ref:z,model:B,rules:Oe,class:`c-login__form`},{default:b(()=>[l(d,{path:`account`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:B.account,"onUpdate:value":r[4]||=e=>B.account=e,placeholder:O(`cl_phone_ph`,`请输入手机号`),clearable:``,maxlength:`11`},{prefix:b(()=>[l(e,{name:`mdi:cellphone`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),l(d,{path:`code`,class:`c-login__form-item`},{default:b(()=>[s(`div`,be,[l(u,{value:B.code,"onUpdate:value":r[5]||=e=>B.code=e,placeholder:O(`cl_code_ph`,`请输入验证码`),clearable:``,class:`c-login__code-input`,onKeyup:S(q,[`enter`])},{prefix:b(()=>[l(e,{name:`mdi:shield-key-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`]),l(g,{disabled:V.value>0||!B.account,class:`c-login__send-code-btn`,onClick:Ne},{default:b(()=>[c(_(V.value>0?`${V.value}s ${O(`cl_resend`,`后重发`)}`:O(`cl_get_code`,`获取验证码`)),1)]),_:1},8,[`disabled`])])]),_:1})]),_:1},8,[`model`]),[[v,P.value===`captcha`]]),a(` ===== 登录按钮(密码和验证码模式) ===== `),P.value===`password`||P.value===`captcha`?(p(),i(g,{key:0,type:`primary`,block:``,loading:k.loading,disabled:P.value===`password`&&M.value.captchaVerify&&!W.value,class:`c-login__submit-btn`,onClick:r[6]||=e=>P.value===`password`?R():q()},{default:b(()=>[c(_(P.value===`password`&&M.value.captchaVerify&&!W.value?O(`cl_verify_first`,`请先完成人机验证`):O(`cl_login_btn`,`登 录`)),1)]),_:1},8,[`loading`,`disabled`])):a(`v-if`,!0),a(` ===== 人机验证拼图(仅密码登录模式) ===== `),M.value.captchaVerify&&P.value===`password`?(p(),o(`div`,xe,[l(ee,{ref_key:`captchaRef`,ref:U,"trigger-text":``,theme:`dark`,onSuccess:ke,onFail:Ae,onChange:je},null,512)])):a(`v-if`,!0),a(` ===== 返回按钮(验证码子视图) ===== `),P.value===`captcha`?(p(),i(g,{key:2,block:``,class:`c-login__back-btn`,onClick:r[7]||=e=>P.value=`password`},{default:b(()=>[c(_(O(`cl_back`,`返回`)),1)]),_:1})):a(`v-if`,!0),a(` ===== 扫码登录面板 ===== `),P.value===`qrcode`?(p(),o(`div`,E,[s(`div`,Se,[l(te,{ref_key:`qrcodeRef`,ref:Pe,value:J.value,size:148,margin:1,"show-border":!1,color:`#ffffff`,"bg-color":`#000000`,"error-correction-level":`M`},null,8,[`value`]),r[17]||=ue(`<div class="c-login__qr-scan-line" data-v-c3995da6></div><div class="c-login__qr-corner c-login__qr-corner--tl" data-v-c3995da6></div><div class="c-login__qr-corner c-login__qr-corner--tr" data-v-c3995da6></div><div class="c-login__qr-corner c-login__qr-corner--bl" data-v-c3995da6></div><div class="c-login__qr-corner c-login__qr-corner--br" data-v-c3995da6></div>`,5)]),s(`p`,Ce,[l(e,{name:`mdi:cellphone-check`,size:13}),c(` `+_(O(`cl_qr_hint`,`打开手机扫码后点击「确认」即可登录`)),1)]),l(g,{text:``,size:`tiny`,class:`c-login__qr-refresh`,onClick:Fe},{icon:b(()=>[l(e,{name:`mdi:refresh`,size:12})]),default:b(()=>[c(` `+_(O(`cl_qr_refresh`,`二维码已失效?点击刷新`)),1)]),_:1})])):a(`v-if`,!0),a(` ===== 返回按钮(扫码子视图,在二维码下方) ===== `),P.value===`qrcode`?(p(),i(g,{key:4,block:``,class:`c-login__back-btn`,onClick:r[8]||=e=>P.value=`password`},{default:b(()=>[c(_(O(`cl_back`,`返回`)),1)]),_:1})):a(`v-if`,!0),a(` ===== 注册表单 ===== `),x(l(h,{ref_key:`registerFormRef`,ref:Y,model:X,rules:Ie,class:`c-login__form`},{default:b(()=>[l(d,{path:`phone`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:X.phone,"onUpdate:value":r[9]||=e=>X.phone=e,placeholder:O(`cl_phone_ph`,`请输入手机号`),clearable:``,maxlength:`11`},{prefix:b(()=>[l(e,{name:`mdi:cellphone`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),l(d,{path:`code`,class:`c-login__form-item`},{default:b(()=>[s(`div`,we,[l(u,{value:X.code,"onUpdate:value":r[10]||=e=>X.code=e,placeholder:O(`cl_code_ph`,`请输入验证码`),clearable:``,class:`c-login__code-input`},{prefix:b(()=>[l(e,{name:`mdi:shield-key-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`]),l(g,{disabled:Z.value>0||!X.phone,class:`c-login__send-code-btn`,onClick:Le},{default:b(()=>[c(_(Z.value>0?`${Z.value}s ${O(`cl_resend`,`后重发`)}`:O(`cl_get_code`,`获取验证码`)),1)]),_:1},8,[`disabled`])])]),_:1}),l(d,{path:`password`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:X.password,"onUpdate:value":r[11]||=e=>X.password=e,type:`password`,"show-password-on":`click`,placeholder:O(`cl_password_ph`,`请输入密码`)},{prefix:b(()=>[l(e,{name:`mdi:lock-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),l(d,{path:`confirmPassword`,class:`c-login__form-item`},{default:b(()=>[l(u,{value:X.confirmPassword,"onUpdate:value":r[12]||=e=>X.confirmPassword=e,type:`password`,"show-password-on":`click`,placeholder:O(`cl_confirm_pw_ph`,`请再次输入密码`),onKeyup:S($,[`enter`])},{prefix:b(()=>[l(e,{name:`mdi:lock-check-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1})]),_:1},8,[`model`]),[[v,P.value===`register`]]),a(` ===== 注册确认按钮 ===== `),P.value===`register`?(p(),i(g,{key:5,type:`primary`,block:``,loading:k.loading,class:`c-login__submit-btn`,onClick:$},{default:b(()=>[c(_(O(`cl_confirm`,`确认`)),1)]),_:1},8,[`loading`])):a(`v-if`,!0),a(` ===== 注册视图返回按钮 ===== `),P.value===`register`?(p(),i(g,{key:6,block:``,class:`c-login__back-btn`,onClick:r[13]||=e=>P.value=`password`},{default:b(()=>[c(_(O(`cl_back`,`返回`)),1)]),_:1})):a(`v-if`,!0),a(` ===== 登录方式切换(仅密码登录默认视图显示) ===== `),P.value===`password`&&(M.value.captchaLogin||M.value.qrcodeLogin)?(p(),o(`div`,Te,[M.value.captchaLogin?(p(),i(g,{key:0,class:`c-login__mode-btn`,size:`small`,onClick:r[14]||=e=>P.value=`captcha`},{default:b(()=>[c(_(O(`cl_captcha_login`,`验证码登录`)),1)]),_:1})):a(`v-if`,!0),M.value.qrcodeLogin?(p(),i(g,{key:1,class:`c-login__mode-btn`,size:`small`,onClick:r[15]||=e=>P.value=`qrcode`},{default:b(()=>[c(_(O(`cl_qr_login`,`扫码登录`)),1)]),_:1})):a(`v-if`,!0)])):a(`v-if`,!0),a(` ===== 其他登录方式(仅密码登录默认视图显示) ===== `),P.value===`password`&&M.value.socialLogin&&N.value.length?(p(),o(n,{key:8},[l(y,{class:`c-login__divider`},{default:b(()=>[c(_(O(`cl_social_login`,`其他账号登录`)),1)]),_:1}),s(`div`,Ee,[(p(!0),o(n,null,de(N.value,t=>(p(),i(C,{key:t.key,trigger:`hover`,placement:`top`},{trigger:b(()=>[l(g,{circle:``,class:`c-login__social-btn`,onClick:e=>A(`social-login`,t.key)},{icon:b(()=>[l(e,{name:t.icon,size:16},null,8,[`name`])]),_:2},1032,[`onClick`])]),default:b(()=>[c(` `+_(t.label),1)]),_:2},1024))),128))])],64)):a(`v-if`,!0),a(` ===== 注册入口(仅密码登录默认视图显示) ===== `),P.value===`password`&&M.value.register?(p(),o(`div`,D,[l(m,{class:`c-login__register-hint`},{default:b(()=>[c(_(O(`cl_no_account`,`还没有账号?`)),1)]),_:1}),l(m,{class:`c-login__register-link`,onClick:r[16]||=e=>P.value=`register`},{default:b(()=>[c(_(O(`cl_register`,`注册账号`)),1)]),_:1})])):a(`v-if`,!0)])}}}),[[`__scopeId`,`data-v-c3995da6`]]);export{T as n,w as r,O as t};
2
2
  //# sourceMappingURL=C_Login2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"C_Login2.js","names":[],"sources":["../src/components/C_Login/types.ts","../src/components/C_Login/index.vue"],"sourcesContent":["/**\n * @Description: C_Login 通用登录组件 - 类型定义\n */\n\n// ================= 功能开关 =================\nexport interface LoginFeatures {\n /** 是否显示密码登录(默认 true) */\n passwordLogin?: boolean\n /** 是否显示验证码登录(默认 true) */\n captchaLogin?: boolean\n /** 是否显示扫码登录(默认 true) */\n qrcodeLogin?: boolean\n /** 是否显示其他账号登录(默认 true) */\n socialLogin?: boolean\n /** 是否显示注册账号(默认 true) */\n register?: boolean\n /** 是否启用人机验证拼图(默认 true) */\n captchaVerify?: boolean\n /** 是否显示记住我(默认 true) */\n rememberMe?: boolean\n /** 是否显示忘记密码(默认 true) */\n forgotPassword?: boolean\n}\n\n// ================= 社交登录 =================\nexport interface SocialProvider {\n key: string\n label: string\n /** mdi 图标名称 */\n icon: string\n}\n\n// ================= 表单数据 =================\nexport interface PasswordFormData {\n username: string\n password: string\n}\n\nexport interface CaptchaFormData {\n account: string\n code: string\n}\n\nexport interface RegisterFormData {\n phone: string\n code: string\n password: string\n confirmPassword: string\n}\n\n// ================= 组件 Props =================\nexport interface LoginProps {\n /** 标题文字 */\n title?: string\n /** 副标题 */\n subtitle?: string\n /** Logo icon 名称(mdi:xxx),不传则不显示图标 */\n logoIcon?: string\n /** 功能开关配置 */\n features?: LoginFeatures\n /** 社交登录提供商列表 */\n socialProviders?: SocialProvider[]\n /** 扫码登录二维码 URL */\n qrcodeUrl?: string\n /** localStorage key(记住我) */\n storageKey?: string\n /** 外部传入的 loading 状态 */\n loading?: boolean\n /** 表单默认用户名(Demo / 预填场景) */\n defaultUsername?: string\n /** 表单默认密码(Demo / 预填场景) */\n defaultPassword?: string\n}\n\n// ================= 默认社交登录配置 =================\nexport const DEFAULT_SOCIAL_PROVIDERS: SocialProvider[] = [\n { key: 'github', label: 'GitHub', icon: 'mdi:github' },\n { key: 'wechat', label: '微信', icon: 'mdi:wechat' },\n { key: 'qq', label: 'QQ', icon: 'mdi:qqchat' },\n]\n\n// ================= 默认功能开关 =================\nexport const DEFAULT_FEATURES: Required<LoginFeatures> = {\n passwordLogin: true,\n captchaLogin: true,\n qrcodeLogin: true,\n socialLogin: true,\n register: true,\n captchaVerify: true,\n rememberMe: true,\n forgotPassword: true,\n}\n","<!--\n * @Description: C_Login - 通用插拔式登录组件\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\n-->\n<template>\n <div class=\"c-login-panel\">\n <!-- ===== Logo / 标题区 ===== -->\n <div class=\"c-login__header\">\n <slot name=\"logo\">\n <div\n v-if=\"mergedProps.logoIcon\"\n class=\"c-login__logo\"\n >\n <C_Icon\n :name=\"mergedProps.logoIcon\"\n :size=\"36\"\n class=\"c-login__logo-icon\"\n />\n </div>\n </slot>\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\n <p\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\n class=\"c-login__subtitle\"\n >\n {{ mergedProps.subtitle }}\n </p>\n <!-- 注册视图的副标题 -->\n <p\n v-if=\"activeTab === 'register'\"\n class=\"c-login__subtitle c-login__subtitle--register\"\n >\n {{ t('cl_register', '注册账号') }}\n </p>\n </div>\n\n <!-- ===== 密码登录表单(默认视图) ===== -->\n <NForm\n v-show=\"activeTab === 'password'\"\n ref=\"passwordFormRef\"\n :model=\"passwordForm\"\n :rules=\"passwordRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"username\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.username\"\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\n clearable\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:account-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n clearable\n @keyup.enter=\"handlePasswordSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <!-- 记住我 + 忘记密码 -->\n <div\n v-if=\"feat.rememberMe || feat.forgotPassword\"\n class=\"c-login__aux-row\"\n >\n <NCheckbox\n v-if=\"feat.rememberMe\"\n v-model:checked=\"rememberMe\"\n class=\"c-login__remember\"\n >\n {{ t('cl_remember', '记住我') }}\n </NCheckbox>\n <NText\n v-if=\"feat.forgotPassword\"\n class=\"c-login__forgot\"\n @click=\"emit('forgot-password')\"\n >\n {{ t('cl_forgot', '忘记密码?') }}\n </NText>\n </div>\n </NForm>\n\n <!-- ===== 验证码登录表单 ===== -->\n <NForm\n v-show=\"activeTab === 'captcha'\"\n ref=\"captchaFormRef\"\n :model=\"captchaForm\"\n :rules=\"captchaRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"account\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"captchaForm.account\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"captchaForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n @keyup.enter=\"handleCaptchaSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"countdown > 0 || !captchaForm.account\"\n class=\"c-login__send-code-btn\"\n @click=\"handleSendCode\"\n >\n {{\n countdown > 0\n ? `${countdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n </NForm>\n\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\n <NButton\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n :disabled=\"\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n \"\n class=\"c-login__submit-btn\"\n @click=\"\n activeTab === 'password'\n ? handlePasswordSubmit()\n : handleCaptchaSubmit()\n \"\n >\n {{\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n ? t('cl_verify_first', '请先完成人机验证')\n : t('cl_login_btn', '登 录')\n }}\n </NButton>\n\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\n <div\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\n class=\"c-login__captcha-wrap\"\n >\n <C_Captcha\n ref=\"captchaRef\"\n trigger-text=\"\"\n theme=\"dark\"\n @success=\"handleCaptchaSuccess\"\n @fail=\"handleCaptchaFail\"\n @change=\"handleCaptchaChange\"\n />\n </div>\n\n <!-- ===== 返回按钮(验证码子视图) ===== -->\n <NButton\n v-if=\"activeTab === 'captcha'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 扫码登录面板 ===== -->\n <div\n v-if=\"activeTab === 'qrcode'\"\n class=\"c-login__qr-panel\"\n >\n <div class=\"c-login__qr-frame\">\n <C_QRCode\n ref=\"qrcodeRef\"\n :value=\"qrcodeValue\"\n :size=\"148\"\n :margin=\"1\"\n :show-border=\"false\"\n color=\"#ffffff\"\n bg-color=\"#000000\"\n error-correction-level=\"M\"\n />\n <div class=\"c-login__qr-scan-line\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\n </div>\n <p class=\"c-login__qr-hint\">\n <C_Icon\n name=\"mdi:cellphone-check\"\n :size=\"13\"\n />\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\n </p>\n <NButton\n text\n size=\"tiny\"\n class=\"c-login__qr-refresh\"\n @click=\"handleQrcodeRefresh\"\n >\n <template #icon>\n <C_Icon\n name=\"mdi:refresh\"\n :size=\"12\"\n />\n </template>\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\n </NButton>\n </div>\n\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\n <NButton\n v-if=\"activeTab === 'qrcode'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 注册表单 ===== -->\n <NForm\n v-show=\"activeTab === 'register'\"\n ref=\"registerFormRef\"\n :model=\"registerForm\"\n :rules=\"registerRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"phone\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.phone\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"registerForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\n class=\"c-login__send-code-btn\"\n @click=\"handleRegSendCode\"\n >\n {{\n regCountdown > 0\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"confirmPassword\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.confirmPassword\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\n @keyup.enter=\"handleRegisterSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-check-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n </NForm>\n\n <!-- ===== 注册确认按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n class=\"c-login__submit-btn\"\n @click=\"handleRegisterSubmit\"\n >\n {{ t('cl_confirm', '确认') }}\n </NButton>\n\n <!-- ===== 注册视图返回按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\n class=\"c-login__mode-switch\"\n >\n <NButton\n v-if=\"feat.captchaLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'captcha'\"\n >\n {{ t('cl_captcha_login', '验证码登录') }}\n </NButton>\n <NButton\n v-if=\"feat.qrcodeLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'qrcode'\"\n >\n {{ t('cl_qr_login', '扫码登录') }}\n </NButton>\n </div>\n\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\n <template\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\n >\n <NDivider class=\"c-login__divider\">{{\n t('cl_social_login', '其他账号登录')\n }}</NDivider>\n <div class=\"c-login__social-row\">\n <NTooltip\n v-for=\"provider in socialList\"\n :key=\"provider.key\"\n trigger=\"hover\"\n placement=\"top\"\n >\n <template #trigger>\n <NButton\n circle\n class=\"c-login__social-btn\"\n @click=\"emit('social-login', provider.key)\"\n >\n <template #icon>\n <C_Icon\n :name=\"provider.icon\"\n :size=\"16\"\n />\n </template>\n </NButton>\n </template>\n {{ provider.label }}\n </NTooltip>\n </div>\n </template>\n\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && feat.register\"\n class=\"c-login__register-row\"\n >\n <NText class=\"c-login__register-hint\">{{\n t('cl_no_account', '还没有账号?')\n }}</NText>\n <NText\n class=\"c-login__register-link\"\n @click=\"activeTab = 'register'\"\n >\n {{ t('cl_register', '注册账号') }}\n </NText>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\n import type { FormInst, FormRules } from 'naive-ui/es'\n import C_Icon from '../C_Icon/index.vue'\n import C_Captcha from '../C_Captcha/index.vue'\n import C_QRCode from '../C_QRCode/index.vue'\n import { RULE_COMBOS } from '@robot-admin/form-validate'\n import {\n DEFAULT_FEATURES,\n DEFAULT_SOCIAL_PROVIDERS,\n type LoginProps,\n type PasswordFormData,\n type CaptchaFormData,\n type RegisterFormData,\n } from './types'\n\n // ===== i18n helper =====\n const t = (key: string, fallback: string) =>\n typeof (globalThis as any).$t === 'function'\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\n : fallback\n\n // ===== Props & Emits =====\n const props = withDefaults(defineProps<LoginProps>(), {\n title: '欢迎回来',\n loading: false,\n storageKey: 'c_login_remember',\n defaultUsername: '',\n defaultPassword: '',\n })\n\n const emit = defineEmits<{\n (\n e: 'submit',\n data: PasswordFormData & {\n captchaToken?: string\n captchaTimestamp?: number\n }\n ): void\n (e: 'captcha-submit', data: CaptchaFormData): void\n (e: 'send-code', account: string): void\n (e: 'social-login', provider: string): void\n (e: 'forgot-password'): void\n (e: 'qrcode-refresh'): void\n (e: 'register-submit', data: RegisterFormData): void\n (e: 'register-send-code', phone: string): void\n }>()\n\n // ===== 合并 Props(填充默认值) =====\n const mergedProps = computed(() => ({\n ...props,\n features: { ...DEFAULT_FEATURES, ...props.features },\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\n }))\n\n const feat = computed(\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\n )\n const socialList = computed(() => mergedProps.value.socialProviders!)\n\n // ===== Tab(默认密码登录) =====\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\n 'password'\n )\n\n // ===== 密码表单 =====\n const passwordFormRef = ref<FormInst | null>(null)\n const passwordForm = reactive<PasswordFormData>({\n username: props.defaultUsername ?? '',\n password: props.defaultPassword ?? '',\n })\n\n const passwordRules: FormRules = {\n username: [\n {\n required: true,\n message: t('cl_username_rule', '请输入用户名'),\n trigger: 'blur',\n },\n ],\n password: [\n {\n required: true,\n message: t('cl_password_rule', '请输入密码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 记住我 =====\n const rememberMe = ref(false)\n\n watch(rememberMe, val => {\n if (val) {\n localStorage.setItem(\n props.storageKey!,\n JSON.stringify({ username: passwordForm.username })\n )\n } else {\n localStorage.removeItem(props.storageKey!)\n }\n })\n\n onMounted(() => {\n try {\n const saved = localStorage.getItem(props.storageKey!)\n if (saved) {\n const data = JSON.parse(saved)\n if (data.username) {\n passwordForm.username = data.username\n rememberMe.value = true\n }\n }\n } catch {\n /* ignore */\n }\n })\n\n // ===== 密码登录提交 =====\n const handlePasswordSubmit = async () => {\n try {\n await passwordFormRef.value?.validate()\n } catch {\n return // 校验失败,表单会显示行内错误提示\n }\n emit('submit', {\n ...passwordForm,\n ...(captchaValid.value\n ? {\n captchaToken: captchaToken.value,\n captchaTimestamp: captchaTimestamp.value,\n }\n : {}),\n })\n }\n\n // ===== 验证码表单 =====\n const captchaFormRef = ref<FormInst | null>(null)\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\n const countdown = ref(0)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n\n const captchaRules: FormRules = {\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 人机验证 =====\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\n const captchaValid = ref(false)\n const captchaToken = ref('')\n const captchaTimestamp = ref(0)\n\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\n captchaValid.value = true\n captchaToken.value = data.token\n captchaTimestamp.value = data.timestamp\n }\n const handleCaptchaFail = () => {\n captchaValid.value = false\n }\n const handleCaptchaChange = (valid: boolean) => {\n captchaValid.value = valid\n if (!valid) {\n captchaToken.value = ''\n captchaTimestamp.value = 0\n }\n }\n\n const resetCaptcha = () => {\n captchaValid.value = false\n captchaToken.value = ''\n captchaTimestamp.value = 0\n captchaRef.value?.reset()\n }\n\n const handleCaptchaSubmit = async () => {\n try {\n await captchaFormRef.value?.validate()\n } catch {\n return\n }\n if (feat.value.captchaVerify && !captchaValid.value) return\n emit('captcha-submit', { ...captchaForm })\n }\n\n // ===== 发送验证码 =====\n const handleSendCode = () => {\n if (!captchaForm.account) return\n emit('send-code', captchaForm.account)\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n countdown.value = 60\n countdownTimer = setInterval(() => {\n countdown.value--\n if (countdown.value <= 0) {\n clearInterval(countdownTimer!)\n countdownTimer = null\n }\n }, 1000)\n }\n\n // ===== 扫码登录 =====\n const qrcodeRef = ref<any>(null)\n const qrcodeValue = ref(\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n )\n\n const handleQrcodeRefresh = () => {\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n emit('qrcode-refresh')\n }\n\n // ===== 注册表单 =====\n const registerFormRef = ref<FormInst | null>(null)\n const registerForm = reactive<RegisterFormData>({\n phone: '',\n code: '',\n password: '',\n confirmPassword: '',\n })\n const regCountdown = ref(0)\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\n\n const registerRules: FormRules = {\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n password: RULE_COMBOS.password('密码') as FormRules['password'],\n confirmPassword: [\n {\n required: true,\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\n trigger: 'blur',\n },\n {\n validator: (_rule: any, value: string) => {\n if (value !== registerForm.password) {\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\n }\n return true\n },\n trigger: 'blur',\n },\n ],\n }\n\n const handleRegSendCode = () => {\n if (!registerForm.phone) return\n emit('register-send-code', registerForm.phone)\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n regCountdown.value = 60\n regCountdownTimer = setInterval(() => {\n regCountdown.value--\n if (regCountdown.value <= 0) {\n clearInterval(regCountdownTimer!)\n regCountdownTimer = null\n }\n }, 1000)\n }\n\n const handleRegisterSubmit = async () => {\n try {\n await registerFormRef.value?.validate()\n } catch {\n return\n }\n emit('register-submit', { ...registerForm })\n }\n\n // ===== 清理定时器 =====\n onUnmounted(() => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n })\n\n // ===== 暴露方法给父组件 =====\n defineExpose({ resetCaptcha })\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"C_Login2.js","names":[],"sources":["../src/components/C_Login/types.ts","../src/components/C_Login/index.vue","../src/components/C_Login/index.vue"],"sourcesContent":["/**\n * @Description: C_Login 通用登录组件 - 类型定义\n */\n\n// ================= 功能开关 =================\nexport interface LoginFeatures {\n /** 是否显示密码登录(默认 true) */\n passwordLogin?: boolean\n /** 是否显示验证码登录(默认 true) */\n captchaLogin?: boolean\n /** 是否显示扫码登录(默认 true) */\n qrcodeLogin?: boolean\n /** 是否显示其他账号登录(默认 true) */\n socialLogin?: boolean\n /** 是否显示注册账号(默认 true) */\n register?: boolean\n /** 是否启用人机验证拼图(默认 true) */\n captchaVerify?: boolean\n /** 是否显示记住我(默认 true) */\n rememberMe?: boolean\n /** 是否显示忘记密码(默认 true) */\n forgotPassword?: boolean\n}\n\n// ================= 社交登录 =================\nexport interface SocialProvider {\n key: string\n label: string\n /** mdi 图标名称 */\n icon: string\n}\n\n// ================= 表单数据 =================\nexport interface PasswordFormData {\n username: string\n password: string\n}\n\nexport interface CaptchaFormData {\n account: string\n code: string\n}\n\nexport interface RegisterFormData {\n phone: string\n code: string\n password: string\n confirmPassword: string\n}\n\n// ================= 组件 Props =================\nexport interface LoginProps {\n /** 标题文字 */\n title?: string\n /** 副标题 */\n subtitle?: string\n /** Logo icon 名称(mdi:xxx),不传则不显示图标 */\n logoIcon?: string\n /** 功能开关配置 */\n features?: LoginFeatures\n /** 社交登录提供商列表 */\n socialProviders?: SocialProvider[]\n /** 扫码登录二维码 URL */\n qrcodeUrl?: string\n /** localStorage key(记住我) */\n storageKey?: string\n /** 外部传入的 loading 状态 */\n loading?: boolean\n /** 表单默认用户名(Demo / 预填场景) */\n defaultUsername?: string\n /** 表单默认密码(Demo / 预填场景) */\n defaultPassword?: string\n}\n\n// ================= 默认社交登录配置 =================\nexport const DEFAULT_SOCIAL_PROVIDERS: SocialProvider[] = [\n { key: 'github', label: 'GitHub', icon: 'mdi:github' },\n { key: 'wechat', label: '微信', icon: 'mdi:wechat' },\n { key: 'qq', label: 'QQ', icon: 'mdi:qqchat' },\n]\n\n// ================= 默认功能开关 =================\nexport const DEFAULT_FEATURES: Required<LoginFeatures> = {\n passwordLogin: true,\n captchaLogin: true,\n qrcodeLogin: true,\n socialLogin: true,\n register: true,\n captchaVerify: true,\n rememberMe: true,\n forgotPassword: true,\n}\n","<!--\n * @Description: C_Login - 通用插拔式登录组件\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\n-->\n<template>\n <div class=\"c-login-panel\">\n <!-- ===== Logo / 标题区 ===== -->\n <div class=\"c-login__header\">\n <slot name=\"logo\">\n <div\n v-if=\"mergedProps.logoIcon\"\n class=\"c-login__logo\"\n >\n <C_Icon\n :name=\"mergedProps.logoIcon\"\n :size=\"36\"\n class=\"c-login__logo-icon\"\n />\n </div>\n </slot>\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\n <p\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\n class=\"c-login__subtitle\"\n >\n {{ mergedProps.subtitle }}\n </p>\n <!-- 注册视图的副标题 -->\n <p\n v-if=\"activeTab === 'register'\"\n class=\"c-login__subtitle c-login__subtitle--register\"\n >\n {{ t('cl_register', '注册账号') }}\n </p>\n </div>\n\n <!-- ===== 密码登录表单(默认视图) ===== -->\n <NForm\n v-show=\"activeTab === 'password'\"\n ref=\"passwordFormRef\"\n :model=\"passwordForm\"\n :rules=\"passwordRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"username\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.username\"\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\n clearable\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:account-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n clearable\n @keyup.enter=\"handlePasswordSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <!-- 记住我 + 忘记密码 -->\n <div\n v-if=\"feat.rememberMe || feat.forgotPassword\"\n class=\"c-login__aux-row\"\n >\n <NCheckbox\n v-if=\"feat.rememberMe\"\n v-model:checked=\"rememberMe\"\n class=\"c-login__remember\"\n >\n {{ t('cl_remember', '记住我') }}\n </NCheckbox>\n <NText\n v-if=\"feat.forgotPassword\"\n class=\"c-login__forgot\"\n @click=\"emit('forgot-password')\"\n >\n {{ t('cl_forgot', '忘记密码?') }}\n </NText>\n </div>\n </NForm>\n\n <!-- ===== 验证码登录表单 ===== -->\n <NForm\n v-show=\"activeTab === 'captcha'\"\n ref=\"captchaFormRef\"\n :model=\"captchaForm\"\n :rules=\"captchaRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"account\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"captchaForm.account\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"captchaForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n @keyup.enter=\"handleCaptchaSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"countdown > 0 || !captchaForm.account\"\n class=\"c-login__send-code-btn\"\n @click=\"handleSendCode\"\n >\n {{\n countdown > 0\n ? `${countdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n </NForm>\n\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\n <NButton\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n :disabled=\"\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n \"\n class=\"c-login__submit-btn\"\n @click=\"\n activeTab === 'password'\n ? handlePasswordSubmit()\n : handleCaptchaSubmit()\n \"\n >\n {{\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n ? t('cl_verify_first', '请先完成人机验证')\n : t('cl_login_btn', '登 录')\n }}\n </NButton>\n\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\n <div\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\n class=\"c-login__captcha-wrap\"\n >\n <C_Captcha\n ref=\"captchaRef\"\n trigger-text=\"\"\n theme=\"dark\"\n @success=\"handleCaptchaSuccess\"\n @fail=\"handleCaptchaFail\"\n @change=\"handleCaptchaChange\"\n />\n </div>\n\n <!-- ===== 返回按钮(验证码子视图) ===== -->\n <NButton\n v-if=\"activeTab === 'captcha'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 扫码登录面板 ===== -->\n <div\n v-if=\"activeTab === 'qrcode'\"\n class=\"c-login__qr-panel\"\n >\n <div class=\"c-login__qr-frame\">\n <C_QRCode\n ref=\"qrcodeRef\"\n :value=\"qrcodeValue\"\n :size=\"148\"\n :margin=\"1\"\n :show-border=\"false\"\n color=\"#ffffff\"\n bg-color=\"#000000\"\n error-correction-level=\"M\"\n />\n <div class=\"c-login__qr-scan-line\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\n </div>\n <p class=\"c-login__qr-hint\">\n <C_Icon\n name=\"mdi:cellphone-check\"\n :size=\"13\"\n />\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\n </p>\n <NButton\n text\n size=\"tiny\"\n class=\"c-login__qr-refresh\"\n @click=\"handleQrcodeRefresh\"\n >\n <template #icon>\n <C_Icon\n name=\"mdi:refresh\"\n :size=\"12\"\n />\n </template>\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\n </NButton>\n </div>\n\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\n <NButton\n v-if=\"activeTab === 'qrcode'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 注册表单 ===== -->\n <NForm\n v-show=\"activeTab === 'register'\"\n ref=\"registerFormRef\"\n :model=\"registerForm\"\n :rules=\"registerRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"phone\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.phone\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"registerForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\n class=\"c-login__send-code-btn\"\n @click=\"handleRegSendCode\"\n >\n {{\n regCountdown > 0\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"confirmPassword\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.confirmPassword\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\n @keyup.enter=\"handleRegisterSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-check-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n </NForm>\n\n <!-- ===== 注册确认按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n class=\"c-login__submit-btn\"\n @click=\"handleRegisterSubmit\"\n >\n {{ t('cl_confirm', '确认') }}\n </NButton>\n\n <!-- ===== 注册视图返回按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\n class=\"c-login__mode-switch\"\n >\n <NButton\n v-if=\"feat.captchaLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'captcha'\"\n >\n {{ t('cl_captcha_login', '验证码登录') }}\n </NButton>\n <NButton\n v-if=\"feat.qrcodeLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'qrcode'\"\n >\n {{ t('cl_qr_login', '扫码登录') }}\n </NButton>\n </div>\n\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\n <template\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\n >\n <NDivider class=\"c-login__divider\">{{\n t('cl_social_login', '其他账号登录')\n }}</NDivider>\n <div class=\"c-login__social-row\">\n <NTooltip\n v-for=\"provider in socialList\"\n :key=\"provider.key\"\n trigger=\"hover\"\n placement=\"top\"\n >\n <template #trigger>\n <NButton\n circle\n class=\"c-login__social-btn\"\n @click=\"emit('social-login', provider.key)\"\n >\n <template #icon>\n <C_Icon\n :name=\"provider.icon\"\n :size=\"16\"\n />\n </template>\n </NButton>\n </template>\n {{ provider.label }}\n </NTooltip>\n </div>\n </template>\n\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && feat.register\"\n class=\"c-login__register-row\"\n >\n <NText class=\"c-login__register-hint\">{{\n t('cl_no_account', '还没有账号?')\n }}</NText>\n <NText\n class=\"c-login__register-link\"\n @click=\"activeTab = 'register'\"\n >\n {{ t('cl_register', '注册账号') }}\n </NText>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\n import type { FormInst, FormRules } from 'naive-ui/es'\n import C_Icon from '../C_Icon/index.vue'\n import C_Captcha from '../C_Captcha/index.vue'\n import C_QRCode from '../C_QRCode/index.vue'\n import { RULE_COMBOS } from '@robot-admin/form-validate'\n import {\n DEFAULT_FEATURES,\n DEFAULT_SOCIAL_PROVIDERS,\n type LoginProps,\n type PasswordFormData,\n type CaptchaFormData,\n type RegisterFormData,\n } from './types'\n\n // ===== i18n helper =====\n const t = (key: string, fallback: string) =>\n typeof (globalThis as any).$t === 'function'\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\n : fallback\n\n // ===== Props & Emits =====\n const props = withDefaults(defineProps<LoginProps>(), {\n title: '欢迎回来',\n loading: false,\n storageKey: 'c_login_remember',\n defaultUsername: '',\n defaultPassword: '',\n })\n\n const emit = defineEmits<{\n (\n e: 'submit',\n data: PasswordFormData & {\n captchaToken?: string\n captchaTimestamp?: number\n }\n ): void\n (e: 'captcha-submit', data: CaptchaFormData): void\n (e: 'send-code', account: string): void\n (e: 'social-login', provider: string): void\n (e: 'forgot-password'): void\n (e: 'qrcode-refresh'): void\n (e: 'register-submit', data: RegisterFormData): void\n (e: 'register-send-code', phone: string): void\n }>()\n\n // ===== 合并 Props(填充默认值) =====\n const mergedProps = computed(() => ({\n ...props,\n features: { ...DEFAULT_FEATURES, ...props.features },\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\n }))\n\n const feat = computed(\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\n )\n const socialList = computed(() => mergedProps.value.socialProviders!)\n\n // ===== Tab(默认密码登录) =====\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\n 'password'\n )\n\n // ===== 密码表单 =====\n const passwordFormRef = ref<FormInst | null>(null)\n const passwordForm = reactive<PasswordFormData>({\n username: props.defaultUsername ?? '',\n password: props.defaultPassword ?? '',\n })\n\n const passwordRules: FormRules = {\n username: [\n {\n required: true,\n message: t('cl_username_rule', '请输入用户名'),\n trigger: 'blur',\n },\n ],\n password: [\n {\n required: true,\n message: t('cl_password_rule', '请输入密码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 记住我 =====\n const rememberMe = ref(false)\n\n watch(rememberMe, val => {\n if (val) {\n localStorage.setItem(\n props.storageKey!,\n JSON.stringify({ username: passwordForm.username })\n )\n } else {\n localStorage.removeItem(props.storageKey!)\n }\n })\n\n onMounted(() => {\n try {\n const saved = localStorage.getItem(props.storageKey!)\n if (saved) {\n const data = JSON.parse(saved)\n if (data.username) {\n passwordForm.username = data.username\n rememberMe.value = true\n }\n }\n } catch {\n /* ignore */\n }\n })\n\n // ===== 密码登录提交 =====\n const handlePasswordSubmit = async () => {\n try {\n await passwordFormRef.value?.validate()\n } catch {\n return // 校验失败,表单会显示行内错误提示\n }\n emit('submit', {\n ...passwordForm,\n ...(captchaValid.value\n ? {\n captchaToken: captchaToken.value,\n captchaTimestamp: captchaTimestamp.value,\n }\n : {}),\n })\n }\n\n // ===== 验证码表单 =====\n const captchaFormRef = ref<FormInst | null>(null)\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\n const countdown = ref(0)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n\n const captchaRules: FormRules = {\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 人机验证 =====\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\n const captchaValid = ref(false)\n const captchaToken = ref('')\n const captchaTimestamp = ref(0)\n\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\n captchaValid.value = true\n captchaToken.value = data.token\n captchaTimestamp.value = data.timestamp\n }\n const handleCaptchaFail = () => {\n captchaValid.value = false\n }\n const handleCaptchaChange = (valid: boolean) => {\n captchaValid.value = valid\n if (!valid) {\n captchaToken.value = ''\n captchaTimestamp.value = 0\n }\n }\n\n const resetCaptcha = () => {\n captchaValid.value = false\n captchaToken.value = ''\n captchaTimestamp.value = 0\n captchaRef.value?.reset()\n }\n\n const handleCaptchaSubmit = async () => {\n try {\n await captchaFormRef.value?.validate()\n } catch {\n return\n }\n if (feat.value.captchaVerify && !captchaValid.value) return\n emit('captcha-submit', { ...captchaForm })\n }\n\n // ===== 发送验证码 =====\n const handleSendCode = () => {\n if (!captchaForm.account) return\n emit('send-code', captchaForm.account)\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n countdown.value = 60\n countdownTimer = setInterval(() => {\n countdown.value--\n if (countdown.value <= 0) {\n clearInterval(countdownTimer!)\n countdownTimer = null\n }\n }, 1000)\n }\n\n // ===== 扫码登录 =====\n const qrcodeRef = ref<any>(null)\n const qrcodeValue = ref(\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n )\n\n const handleQrcodeRefresh = () => {\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n emit('qrcode-refresh')\n }\n\n // ===== 注册表单 =====\n const registerFormRef = ref<FormInst | null>(null)\n const registerForm = reactive<RegisterFormData>({\n phone: '',\n code: '',\n password: '',\n confirmPassword: '',\n })\n const regCountdown = ref(0)\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\n\n const registerRules: FormRules = {\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n password: RULE_COMBOS.password('密码') as FormRules['password'],\n confirmPassword: [\n {\n required: true,\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\n trigger: 'blur',\n },\n {\n validator: (_rule: any, value: string) => {\n if (value !== registerForm.password) {\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\n }\n return true\n },\n trigger: 'blur',\n },\n ],\n }\n\n const handleRegSendCode = () => {\n if (!registerForm.phone) return\n emit('register-send-code', registerForm.phone)\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n regCountdown.value = 60\n regCountdownTimer = setInterval(() => {\n regCountdown.value--\n if (regCountdown.value <= 0) {\n clearInterval(regCountdownTimer!)\n regCountdownTimer = null\n }\n }, 1000)\n }\n\n const handleRegisterSubmit = async () => {\n try {\n await registerFormRef.value?.validate()\n } catch {\n return\n }\n emit('register-submit', { ...registerForm })\n }\n\n // ===== 清理定时器 =====\n onUnmounted(() => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n })\n\n // ===== 暴露方法给父组件 =====\n defineExpose({ resetCaptcha })\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n","<!--\n * @Description: C_Login - 通用插拔式登录组件\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\n-->\n<template>\n <div class=\"c-login-panel\">\n <!-- ===== Logo / 标题区 ===== -->\n <div class=\"c-login__header\">\n <slot name=\"logo\">\n <div\n v-if=\"mergedProps.logoIcon\"\n class=\"c-login__logo\"\n >\n <C_Icon\n :name=\"mergedProps.logoIcon\"\n :size=\"36\"\n class=\"c-login__logo-icon\"\n />\n </div>\n </slot>\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\n <p\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\n class=\"c-login__subtitle\"\n >\n {{ mergedProps.subtitle }}\n </p>\n <!-- 注册视图的副标题 -->\n <p\n v-if=\"activeTab === 'register'\"\n class=\"c-login__subtitle c-login__subtitle--register\"\n >\n {{ t('cl_register', '注册账号') }}\n </p>\n </div>\n\n <!-- ===== 密码登录表单(默认视图) ===== -->\n <NForm\n v-show=\"activeTab === 'password'\"\n ref=\"passwordFormRef\"\n :model=\"passwordForm\"\n :rules=\"passwordRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"username\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.username\"\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\n clearable\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:account-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"passwordForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n clearable\n @keyup.enter=\"handlePasswordSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <!-- 记住我 + 忘记密码 -->\n <div\n v-if=\"feat.rememberMe || feat.forgotPassword\"\n class=\"c-login__aux-row\"\n >\n <NCheckbox\n v-if=\"feat.rememberMe\"\n v-model:checked=\"rememberMe\"\n class=\"c-login__remember\"\n >\n {{ t('cl_remember', '记住我') }}\n </NCheckbox>\n <NText\n v-if=\"feat.forgotPassword\"\n class=\"c-login__forgot\"\n @click=\"emit('forgot-password')\"\n >\n {{ t('cl_forgot', '忘记密码?') }}\n </NText>\n </div>\n </NForm>\n\n <!-- ===== 验证码登录表单 ===== -->\n <NForm\n v-show=\"activeTab === 'captcha'\"\n ref=\"captchaFormRef\"\n :model=\"captchaForm\"\n :rules=\"captchaRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"account\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"captchaForm.account\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"captchaForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n @keyup.enter=\"handleCaptchaSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"countdown > 0 || !captchaForm.account\"\n class=\"c-login__send-code-btn\"\n @click=\"handleSendCode\"\n >\n {{\n countdown > 0\n ? `${countdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n </NForm>\n\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\n <NButton\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n :disabled=\"\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n \"\n class=\"c-login__submit-btn\"\n @click=\"\n activeTab === 'password'\n ? handlePasswordSubmit()\n : handleCaptchaSubmit()\n \"\n >\n {{\n activeTab === 'password' && feat.captchaVerify && !captchaValid\n ? t('cl_verify_first', '请先完成人机验证')\n : t('cl_login_btn', '登 录')\n }}\n </NButton>\n\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\n <div\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\n class=\"c-login__captcha-wrap\"\n >\n <C_Captcha\n ref=\"captchaRef\"\n trigger-text=\"\"\n theme=\"dark\"\n @success=\"handleCaptchaSuccess\"\n @fail=\"handleCaptchaFail\"\n @change=\"handleCaptchaChange\"\n />\n </div>\n\n <!-- ===== 返回按钮(验证码子视图) ===== -->\n <NButton\n v-if=\"activeTab === 'captcha'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 扫码登录面板 ===== -->\n <div\n v-if=\"activeTab === 'qrcode'\"\n class=\"c-login__qr-panel\"\n >\n <div class=\"c-login__qr-frame\">\n <C_QRCode\n ref=\"qrcodeRef\"\n :value=\"qrcodeValue\"\n :size=\"148\"\n :margin=\"1\"\n :show-border=\"false\"\n color=\"#ffffff\"\n bg-color=\"#000000\"\n error-correction-level=\"M\"\n />\n <div class=\"c-login__qr-scan-line\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\n </div>\n <p class=\"c-login__qr-hint\">\n <C_Icon\n name=\"mdi:cellphone-check\"\n :size=\"13\"\n />\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\n </p>\n <NButton\n text\n size=\"tiny\"\n class=\"c-login__qr-refresh\"\n @click=\"handleQrcodeRefresh\"\n >\n <template #icon>\n <C_Icon\n name=\"mdi:refresh\"\n :size=\"12\"\n />\n </template>\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\n </NButton>\n </div>\n\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\n <NButton\n v-if=\"activeTab === 'qrcode'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 注册表单 ===== -->\n <NForm\n v-show=\"activeTab === 'register'\"\n ref=\"registerFormRef\"\n :model=\"registerForm\"\n :rules=\"registerRules\"\n class=\"c-login__form\"\n >\n <NFormItem\n path=\"phone\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.phone\"\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\n clearable\n maxlength=\"11\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:cellphone\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"code\"\n class=\"c-login__form-item\"\n >\n <div class=\"c-login__code-row\">\n <NInput\n v-model:value=\"registerForm.code\"\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\n clearable\n class=\"c-login__code-input\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:shield-key-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n <NButton\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\n class=\"c-login__send-code-btn\"\n @click=\"handleRegSendCode\"\n >\n {{\n regCountdown > 0\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\n : t('cl_get_code', '获取验证码')\n }}\n </NButton>\n </div>\n </NFormItem>\n\n <NFormItem\n path=\"password\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.password\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n\n <NFormItem\n path=\"confirmPassword\"\n class=\"c-login__form-item\"\n >\n <NInput\n v-model:value=\"registerForm.confirmPassword\"\n type=\"password\"\n show-password-on=\"click\"\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\n @keyup.enter=\"handleRegisterSubmit\"\n >\n <template #prefix>\n <C_Icon\n name=\"mdi:lock-check-outline\"\n :size=\"16\"\n class=\"c-login__input-icon\"\n />\n </template>\n </NInput>\n </NFormItem>\n </NForm>\n\n <!-- ===== 注册确认按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n type=\"primary\"\n block\n :loading=\"props.loading\"\n class=\"c-login__submit-btn\"\n @click=\"handleRegisterSubmit\"\n >\n {{ t('cl_confirm', '确认') }}\n </NButton>\n\n <!-- ===== 注册视图返回按钮 ===== -->\n <NButton\n v-if=\"activeTab === 'register'\"\n block\n class=\"c-login__back-btn\"\n @click=\"activeTab = 'password'\"\n >\n {{ t('cl_back', '返回') }}\n </NButton>\n\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\n class=\"c-login__mode-switch\"\n >\n <NButton\n v-if=\"feat.captchaLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'captcha'\"\n >\n {{ t('cl_captcha_login', '验证码登录') }}\n </NButton>\n <NButton\n v-if=\"feat.qrcodeLogin\"\n class=\"c-login__mode-btn\"\n size=\"small\"\n @click=\"activeTab = 'qrcode'\"\n >\n {{ t('cl_qr_login', '扫码登录') }}\n </NButton>\n </div>\n\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\n <template\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\n >\n <NDivider class=\"c-login__divider\">{{\n t('cl_social_login', '其他账号登录')\n }}</NDivider>\n <div class=\"c-login__social-row\">\n <NTooltip\n v-for=\"provider in socialList\"\n :key=\"provider.key\"\n trigger=\"hover\"\n placement=\"top\"\n >\n <template #trigger>\n <NButton\n circle\n class=\"c-login__social-btn\"\n @click=\"emit('social-login', provider.key)\"\n >\n <template #icon>\n <C_Icon\n :name=\"provider.icon\"\n :size=\"16\"\n />\n </template>\n </NButton>\n </template>\n {{ provider.label }}\n </NTooltip>\n </div>\n </template>\n\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\n <div\n v-if=\"activeTab === 'password' && feat.register\"\n class=\"c-login__register-row\"\n >\n <NText class=\"c-login__register-hint\">{{\n t('cl_no_account', '还没有账号?')\n }}</NText>\n <NText\n class=\"c-login__register-link\"\n @click=\"activeTab = 'register'\"\n >\n {{ t('cl_register', '注册账号') }}\n </NText>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\n import type { FormInst, FormRules } from 'naive-ui/es'\n import C_Icon from '../C_Icon/index.vue'\n import C_Captcha from '../C_Captcha/index.vue'\n import C_QRCode from '../C_QRCode/index.vue'\n import { RULE_COMBOS } from '@robot-admin/form-validate'\n import {\n DEFAULT_FEATURES,\n DEFAULT_SOCIAL_PROVIDERS,\n type LoginProps,\n type PasswordFormData,\n type CaptchaFormData,\n type RegisterFormData,\n } from './types'\n\n // ===== i18n helper =====\n const t = (key: string, fallback: string) =>\n typeof (globalThis as any).$t === 'function'\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\n : fallback\n\n // ===== Props & Emits =====\n const props = withDefaults(defineProps<LoginProps>(), {\n title: '欢迎回来',\n loading: false,\n storageKey: 'c_login_remember',\n defaultUsername: '',\n defaultPassword: '',\n })\n\n const emit = defineEmits<{\n (\n e: 'submit',\n data: PasswordFormData & {\n captchaToken?: string\n captchaTimestamp?: number\n }\n ): void\n (e: 'captcha-submit', data: CaptchaFormData): void\n (e: 'send-code', account: string): void\n (e: 'social-login', provider: string): void\n (e: 'forgot-password'): void\n (e: 'qrcode-refresh'): void\n (e: 'register-submit', data: RegisterFormData): void\n (e: 'register-send-code', phone: string): void\n }>()\n\n // ===== 合并 Props(填充默认值) =====\n const mergedProps = computed(() => ({\n ...props,\n features: { ...DEFAULT_FEATURES, ...props.features },\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\n }))\n\n const feat = computed(\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\n )\n const socialList = computed(() => mergedProps.value.socialProviders!)\n\n // ===== Tab(默认密码登录) =====\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\n 'password'\n )\n\n // ===== 密码表单 =====\n const passwordFormRef = ref<FormInst | null>(null)\n const passwordForm = reactive<PasswordFormData>({\n username: props.defaultUsername ?? '',\n password: props.defaultPassword ?? '',\n })\n\n const passwordRules: FormRules = {\n username: [\n {\n required: true,\n message: t('cl_username_rule', '请输入用户名'),\n trigger: 'blur',\n },\n ],\n password: [\n {\n required: true,\n message: t('cl_password_rule', '请输入密码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 记住我 =====\n const rememberMe = ref(false)\n\n watch(rememberMe, val => {\n if (val) {\n localStorage.setItem(\n props.storageKey!,\n JSON.stringify({ username: passwordForm.username })\n )\n } else {\n localStorage.removeItem(props.storageKey!)\n }\n })\n\n onMounted(() => {\n try {\n const saved = localStorage.getItem(props.storageKey!)\n if (saved) {\n const data = JSON.parse(saved)\n if (data.username) {\n passwordForm.username = data.username\n rememberMe.value = true\n }\n }\n } catch {\n /* ignore */\n }\n })\n\n // ===== 密码登录提交 =====\n const handlePasswordSubmit = async () => {\n try {\n await passwordFormRef.value?.validate()\n } catch {\n return // 校验失败,表单会显示行内错误提示\n }\n emit('submit', {\n ...passwordForm,\n ...(captchaValid.value\n ? {\n captchaToken: captchaToken.value,\n captchaTimestamp: captchaTimestamp.value,\n }\n : {}),\n })\n }\n\n // ===== 验证码表单 =====\n const captchaFormRef = ref<FormInst | null>(null)\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\n const countdown = ref(0)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n\n const captchaRules: FormRules = {\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n }\n\n // ===== 人机验证 =====\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\n const captchaValid = ref(false)\n const captchaToken = ref('')\n const captchaTimestamp = ref(0)\n\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\n captchaValid.value = true\n captchaToken.value = data.token\n captchaTimestamp.value = data.timestamp\n }\n const handleCaptchaFail = () => {\n captchaValid.value = false\n }\n const handleCaptchaChange = (valid: boolean) => {\n captchaValid.value = valid\n if (!valid) {\n captchaToken.value = ''\n captchaTimestamp.value = 0\n }\n }\n\n const resetCaptcha = () => {\n captchaValid.value = false\n captchaToken.value = ''\n captchaTimestamp.value = 0\n captchaRef.value?.reset()\n }\n\n const handleCaptchaSubmit = async () => {\n try {\n await captchaFormRef.value?.validate()\n } catch {\n return\n }\n if (feat.value.captchaVerify && !captchaValid.value) return\n emit('captcha-submit', { ...captchaForm })\n }\n\n // ===== 发送验证码 =====\n const handleSendCode = () => {\n if (!captchaForm.account) return\n emit('send-code', captchaForm.account)\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n countdown.value = 60\n countdownTimer = setInterval(() => {\n countdown.value--\n if (countdown.value <= 0) {\n clearInterval(countdownTimer!)\n countdownTimer = null\n }\n }, 1000)\n }\n\n // ===== 扫码登录 =====\n const qrcodeRef = ref<any>(null)\n const qrcodeValue = ref(\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n )\n\n const handleQrcodeRefresh = () => {\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\n emit('qrcode-refresh')\n }\n\n // ===== 注册表单 =====\n const registerFormRef = ref<FormInst | null>(null)\n const registerForm = reactive<RegisterFormData>({\n phone: '',\n code: '',\n password: '',\n confirmPassword: '',\n })\n const regCountdown = ref(0)\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\n\n const registerRules: FormRules = {\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\n code: [\n {\n required: true,\n message: t('cl_code_rule', '请输入验证码'),\n trigger: 'blur',\n },\n ],\n password: RULE_COMBOS.password('密码') as FormRules['password'],\n confirmPassword: [\n {\n required: true,\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\n trigger: 'blur',\n },\n {\n validator: (_rule: any, value: string) => {\n if (value !== registerForm.password) {\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\n }\n return true\n },\n trigger: 'blur',\n },\n ],\n }\n\n const handleRegSendCode = () => {\n if (!registerForm.phone) return\n emit('register-send-code', registerForm.phone)\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n regCountdown.value = 60\n regCountdownTimer = setInterval(() => {\n regCountdown.value--\n if (regCountdown.value <= 0) {\n clearInterval(regCountdownTimer!)\n regCountdownTimer = null\n }\n }, 1000)\n }\n\n const handleRegisterSubmit = async () => {\n try {\n await registerFormRef.value?.validate()\n } catch {\n return\n }\n emit('register-submit', { ...registerForm })\n }\n\n // ===== 清理定时器 =====\n onUnmounted(() => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n if (regCountdownTimer) {\n clearInterval(regCountdownTimer)\n regCountdownTimer = null\n }\n })\n\n // ===== 暴露方法给父组件 =====\n defineExpose({ resetCaptcha })\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"C_Map-DNnBcY70.css","names":[],"sources":["../src/components/C_Map/index.vue?vue&type=style&index=0&scoped=340816cd&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled */.c-map[data-v-340816cd] {\n position: relative;\n width: 100%;\n min-height: 300px;\n background: #f5f5f5;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-container[data-v-340816cd] {\n width: 100%;\n height: 100%;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-loading[data-v-340816cd] {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n border-radius: 4px;\n z-index: 1000;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"C_Map-CWMMN58_.css","names":[],"sources":["../src/components/C_Map/index.vue?vue&type=style&index=0&scoped=35363772&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled */.c-map[data-v-35363772] {\n position: relative;\n width: 100%;\n min-height: 300px;\n background: #f5f5f5;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-container[data-v-35363772] {\n width: 100%;\n height: 100%;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-loading[data-v-35363772] {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n border-radius: 4px;\n z-index: 1000;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}