@robot-admin/naive-ui-components 0.8.1 → 0.8.2

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 (375) hide show
  1. package/README.md +279 -279
  2. package/dist/C_ActionBar-DYG0Yf3A.css.map +1 -0
  3. package/dist/C_ActionBar-MAjRhqcI.css.map +1 -0
  4. package/dist/C_ActionBar.cjs +1 -1
  5. package/dist/C_ActionBar.css +47 -47
  6. package/dist/C_ActionBar.js +1 -1
  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-A3NXx_sF.css.map → C_AntV-BY8jqqmi.css.map} +1 -1
  11. package/dist/{C_AntV-D2Zp2qQF.css.map → C_AntV-BbkHc-_a.css.map} +1 -1
  12. package/dist/C_AntV.cjs +1 -1
  13. package/dist/C_AntV.css +78 -78
  14. package/dist/C_AntV.js +1 -1
  15. package/dist/C_AntV.js.map +1 -1
  16. package/dist/C_AntV2.js +3 -3
  17. package/dist/C_AntV2.js.map +1 -1
  18. package/dist/{C_AudioPlayer-CXl4yP_m.css.map → C_AudioPlayer-DKRIGhi7.css.map} +1 -1
  19. package/dist/{C_AudioPlayer-CcBtt71m.css.map → C_AudioPlayer-DlB6eNsa.css.map} +1 -1
  20. package/dist/C_AudioPlayer.cjs +1 -1
  21. package/dist/C_AudioPlayer.css +47 -47
  22. package/dist/C_AudioPlayer.js +1 -1
  23. package/dist/C_AudioPlayer.js.map +1 -1
  24. package/dist/C_AudioPlayer2.js +1 -1
  25. package/dist/C_AudioPlayer2.js.map +1 -1
  26. package/dist/{C_AvatarGroup-CtPhfH9h.css.map → C_AvatarGroup-D02zci-i.css.map} +1 -1
  27. package/dist/{C_AvatarGroup-M1dNPH3E.css.map → C_AvatarGroup-DatHaUlY.css.map} +1 -1
  28. package/dist/C_AvatarGroup.css +21 -21
  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-DjTmDkbQ.css.map → C_Barcode-DYDRDe2O.css.map} +1 -1
  33. package/dist/{C_Barcode-Do1Ozsmh.css.map → C_Barcode-muJdc_O4.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.cjs +1 -1
  39. package/dist/C_Breadcrumb.js +1 -1
  40. package/dist/C_Breadcrumb.js.map +1 -1
  41. package/dist/C_Breadcrumb2.js +1 -1
  42. package/dist/C_Breadcrumb2.js.map +1 -1
  43. package/dist/{C_Captcha-CZmAETly.css.map → C_Captcha-9_hk6VmY.css.map} +1 -1
  44. package/dist/{C_Captcha-Ccq3DMrR.css.map → C_Captcha-MtASlJ0f.css.map} +1 -1
  45. package/dist/C_Captcha.css +19 -19
  46. package/dist/C_Captcha.js.map +1 -1
  47. package/dist/C_Captcha2.js +1 -1
  48. package/dist/C_Captcha2.js.map +1 -1
  49. package/dist/C_Cascade.js.map +1 -1
  50. package/dist/C_Cascade2.js +1 -1
  51. package/dist/C_Cascade2.js.map +1 -1
  52. package/dist/{C_Chat-CJ8KESTM.css.map → C_Chat-Csjz_BFw.css.map} +1 -1
  53. package/dist/{C_Chat-DzV4AFLd.css.map → C_Chat-DO1ZBA_0.css.map} +1 -1
  54. package/dist/C_Chat.cjs +1 -1
  55. package/dist/C_Chat.css +65 -65
  56. package/dist/C_Chat.js +1 -1
  57. package/dist/C_Chat.js.map +1 -1
  58. package/dist/C_Chat2.js +1 -1
  59. package/dist/C_Chat2.js.map +1 -1
  60. package/dist/{C_City-Cv5BESaN.css.map → C_City-Bam08Qv3.css.map} +1 -1
  61. package/dist/{C_City-DNKv4MpH.css.map → C_City-Bb3W-KaN.css.map} +1 -1
  62. package/dist/C_City.css +29 -29
  63. package/dist/C_City.js.map +1 -1
  64. package/dist/C_City2.js +1 -1
  65. package/dist/C_City2.js.map +1 -1
  66. package/dist/C_Code-BFi5xxnM.css.map +1 -0
  67. package/dist/C_Code-CZ7yfnri.css.map +1 -0
  68. package/dist/C_Code.cjs +1 -1
  69. package/dist/C_Code.css +3 -3
  70. package/dist/C_Code.js +1 -1
  71. package/dist/C_Code.js.map +1 -1
  72. package/dist/C_Code2.js +1 -1
  73. package/dist/C_Code2.js.map +1 -1
  74. package/dist/{C_CollapsePanel-CedsEM0N.css.map → C_CollapsePanel-CWkFCqNQ.css.map} +1 -1
  75. package/dist/{C_CollapsePanel-BkOivqn3.css.map → C_CollapsePanel-pcVJA_4z.css.map} +1 -1
  76. package/dist/C_CollapsePanel.cjs +1 -1
  77. package/dist/C_CollapsePanel.css +35 -35
  78. package/dist/C_CollapsePanel.js +1 -1
  79. package/dist/C_CollapsePanel.js.map +1 -1
  80. package/dist/C_CollapsePanel2.js +1 -1
  81. package/dist/C_CollapsePanel2.js.map +1 -1
  82. package/dist/{C_ContextMenu-CCsCeI39.css.map → C_ContextMenu-B3Cgz_wJ.css.map} +1 -1
  83. package/dist/{C_ContextMenu-DOTBeltf.css.map → C_ContextMenu-u1LixF3_.css.map} +1 -1
  84. package/dist/C_ContextMenu.cjs +1 -1
  85. package/dist/C_ContextMenu.css +17 -17
  86. package/dist/C_ContextMenu.js +1 -1
  87. package/dist/C_ContextMenu.js.map +1 -1
  88. package/dist/C_ContextMenu2.js +1 -1
  89. package/dist/C_ContextMenu2.js.map +1 -1
  90. package/dist/{C_Cron-C0-8b5af.css.map → C_Cron-Civir5-I.css.map} +1 -1
  91. package/dist/{C_Cron-DqHQ92LN.css.map → C_Cron-iw3qpMsy.css.map} +1 -1
  92. package/dist/C_Cron.cjs +1 -1
  93. package/dist/C_Cron.css +37 -37
  94. package/dist/C_Cron.js +1 -1
  95. package/dist/C_Cron.js.map +1 -1
  96. package/dist/C_Cron2.js +1 -1
  97. package/dist/C_Cron2.js.map +1 -1
  98. package/dist/C_Date.js.map +1 -1
  99. package/dist/C_Date2.js +1 -1
  100. package/dist/C_Date2.js.map +1 -1
  101. package/dist/{C_Draggable-Bq6o0qXn.css.map → C_Draggable-BCjIvIwT.css.map} +1 -1
  102. package/dist/{C_Draggable-r6nhx3yM.css.map → C_Draggable-DwkSlCOS.css.map} +1 -1
  103. package/dist/C_Draggable.css +46 -46
  104. package/dist/C_Draggable.js.map +1 -1
  105. package/dist/C_Draggable2.js +1 -1
  106. package/dist/C_Draggable2.js.map +1 -1
  107. package/dist/C_Editor-BfjjYQ9B.css.map +1 -0
  108. package/dist/C_Editor-Do2StvoJ.css.map +1 -0
  109. package/dist/C_Editor.css +55 -55
  110. package/dist/C_Editor.js.map +1 -1
  111. package/dist/C_Editor2.js +1 -1
  112. package/dist/C_Editor2.js.map +1 -1
  113. package/dist/{C_FilePreview-BVarVvnD.css.map → C_FilePreview-BhcbiXsV.css.map} +1 -1
  114. package/dist/{C_FilePreview-DHGlV4OR.css.map → C_FilePreview-DJudcIbW.css.map} +1 -1
  115. package/dist/C_FilePreview.cjs +1 -1
  116. package/dist/C_FilePreview.css +91 -91
  117. package/dist/C_FilePreview.js +1 -1
  118. package/dist/C_FilePreview.js.map +1 -1
  119. package/dist/C_FilePreview2.js +1 -1
  120. package/dist/C_FilePreview2.js.map +1 -1
  121. package/dist/{C_Form-BciQUPpS.css.map → C_Form-BtcAPIRi.css.map} +1 -1
  122. package/dist/{C_Form-CKHvXL3O.css.map → C_Form-D_gwQ7hC.css.map} +1 -1
  123. package/dist/C_Form.cjs +1 -1
  124. package/dist/C_Form.css +222 -222
  125. package/dist/C_Form.js +1 -1
  126. package/dist/C_Form.js.map +1 -1
  127. package/dist/C_Form2.js +1 -1
  128. package/dist/C_Form2.js.map +1 -1
  129. package/dist/{C_FormSearch-Bmd2CmG6.css.map → C_FormSearch-Bi4XYjjk.css.map} +1 -1
  130. package/dist/{C_FormSearch-DBNtnjaL.css.map → C_FormSearch-DUGOoX0u.css.map} +1 -1
  131. package/dist/C_FormSearch.css +45 -45
  132. package/dist/C_FormSearch.js.map +1 -1
  133. package/dist/C_FormSearch2.js +1 -1
  134. package/dist/C_FormSearch2.js.map +1 -1
  135. package/dist/{C_FormulaEditor-3WG2vtQl.css.map → C_FormulaEditor-CJk3SSUJ.css.map} +1 -1
  136. package/dist/{C_FormulaEditor-Cm0CokN5.css.map → C_FormulaEditor-Cy7mjfVO.css.map} +1 -1
  137. package/dist/C_FormulaEditor.cjs +1 -1
  138. package/dist/C_FormulaEditor.css +78 -78
  139. package/dist/C_FormulaEditor.js +1 -1
  140. package/dist/C_FormulaEditor.js.map +1 -1
  141. package/dist/C_FormulaEditor2.js +1 -1
  142. package/dist/C_FormulaEditor2.js.map +1 -1
  143. package/dist/C_FullCalendar-BQA0OjGB.css.map +1 -0
  144. package/dist/C_FullCalendar-ChJUosAi.css.map +1 -0
  145. package/dist/C_FullCalendar.css +3 -3
  146. package/dist/C_FullCalendar.js.map +1 -1
  147. package/dist/C_FullCalendar2.js +1 -1
  148. package/dist/C_FullCalendar2.js.map +1 -1
  149. package/dist/C_GlobalSearch.js.map +1 -1
  150. package/dist/C_GlobalSearch2.js.map +1 -1
  151. package/dist/C_Guide.cjs +1 -1
  152. package/dist/C_Guide.js +1 -1
  153. package/dist/C_Guide.js.map +1 -1
  154. package/dist/C_Guide2.js +1 -1
  155. package/dist/C_Guide2.js.map +1 -1
  156. package/dist/C_Icon.js.map +1 -1
  157. package/dist/C_Icon2.js +1 -1
  158. package/dist/C_Icon2.js.map +1 -1
  159. package/dist/{C_ImageCropper-D3L2-J_J.css.map → C_ImageCropper-BqZpkZfU.css.map} +1 -1
  160. package/dist/{C_ImageCropper-DrmUlaLi.css.map → C_ImageCropper-CylwGuzS.css.map} +1 -1
  161. package/dist/C_ImageCropper.cjs +1 -1
  162. package/dist/C_ImageCropper.css +10 -10
  163. package/dist/C_ImageCropper.js +1 -1
  164. package/dist/C_ImageCropper.js.map +1 -1
  165. package/dist/C_ImageCropper2.js +1 -1
  166. package/dist/C_ImageCropper2.js.map +1 -1
  167. package/dist/C_Language.cjs +1 -1
  168. package/dist/C_Language.js +1 -1
  169. package/dist/C_Language.js.map +1 -1
  170. package/dist/C_Language2.js +1 -1
  171. package/dist/C_Language2.js.map +1 -1
  172. package/dist/{C_Login-Cst_W1XC.css.map → C_Login-BkdnkBKX.css.map} +1 -1
  173. package/dist/{C_Login-DWZ_6K1x.css.map → C_Login-VkMM6OJ2.css.map} +1 -1
  174. package/dist/C_Login.cjs +1 -1
  175. package/dist/C_Login.css +72 -72
  176. package/dist/C_Login.js +1 -1
  177. package/dist/C_Login.js.map +1 -1
  178. package/dist/C_Login2.js +1 -1
  179. package/dist/C_Login2.js.map +1 -1
  180. package/dist/{C_Map-BCJmDnJT.css.map → C_Map-DNnBcY70.css.map} +1 -1
  181. package/dist/{C_Map-WUMXSAfy.css.map → C_Map-LEXIx6zM.css.map} +1 -1
  182. package/dist/C_Map.css +4 -4
  183. package/dist/C_Map.js.map +1 -1
  184. package/dist/C_Map2.js +1 -1
  185. package/dist/C_Map2.js.map +1 -1
  186. package/dist/{C_Markdown-7J0uPEuk.css.map → C_Markdown-DwmI0_0Q.css.map} +1 -1
  187. package/dist/{C_Markdown-C7Lg0_9z.css.map → C_Markdown-k0794RW0.css.map} +1 -1
  188. package/dist/C_Markdown.css +33 -33
  189. package/dist/C_Markdown.js.map +1 -1
  190. package/dist/C_Markdown2.js +1 -1
  191. package/dist/C_Markdown2.js.map +1 -1
  192. package/dist/C_Menu.js.map +1 -1
  193. package/dist/C_Menu2.js +1 -1
  194. package/dist/C_Menu2.js.map +1 -1
  195. package/dist/C_NotificationCenter.cjs +1 -1
  196. package/dist/C_NotificationCenter.js +1 -1
  197. package/dist/C_NotificationCenter.js.map +1 -1
  198. package/dist/C_NotificationCenter2.js +1 -1
  199. package/dist/C_NotificationCenter2.js.map +1 -1
  200. package/dist/C_OrgChart.js.map +1 -1
  201. package/dist/C_OrgChart2.js +1 -1
  202. package/dist/C_OrgChart2.js.map +1 -1
  203. package/dist/C_Progress.js.map +1 -1
  204. package/dist/C_Progress2.js +1 -1
  205. package/dist/C_Progress2.js.map +1 -1
  206. package/dist/{C_QRCode-DMxSyogU.css.map → C_QRCode-CFNTda_1.css.map} +1 -1
  207. package/dist/{C_QRCode-G7fiAkm4.css.map → C_QRCode-CmWr9lK-.css.map} +1 -1
  208. package/dist/C_QRCode.css +9 -9
  209. package/dist/C_QRCode.js.map +1 -1
  210. package/dist/C_QRCode2.js +1 -1
  211. package/dist/C_QRCode2.js.map +1 -1
  212. package/dist/{C_Signature-BJMvVPWT.css.map → C_Signature-BDx4ctsE.css.map} +1 -1
  213. package/dist/{C_Signature-BJUHx2BF.css.map → C_Signature-Bz_uNbwv.css.map} +1 -1
  214. package/dist/C_Signature.cjs +1 -1
  215. package/dist/C_Signature.css +15 -15
  216. package/dist/C_Signature.js +1 -1
  217. package/dist/C_Signature.js.map +1 -1
  218. package/dist/C_Signature2.js +1 -1
  219. package/dist/C_Signature2.js.map +1 -1
  220. package/dist/C_Skeleton.js.map +1 -1
  221. package/dist/C_Skeleton2.js +1 -1
  222. package/dist/C_Skeleton2.js.map +1 -1
  223. package/dist/{C_SplitPane-Br2eK8IG.css.map → C_SplitPane-BOwr1C7K.css.map} +1 -1
  224. package/dist/{C_SplitPane-DuyzUhKB.css.map → C_SplitPane-N-a_D-7F.css.map} +1 -1
  225. package/dist/C_SplitPane.cjs +1 -1
  226. package/dist/C_SplitPane.css +31 -31
  227. package/dist/C_SplitPane.js +1 -1
  228. package/dist/C_SplitPane.js.map +1 -1
  229. package/dist/C_SplitPane2.js +1 -1
  230. package/dist/C_SplitPane2.js.map +1 -1
  231. package/dist/C_Steps-Bk1krgiR.css.map +1 -0
  232. package/dist/C_Steps-Dn2CMpy1.css.map +1 -0
  233. package/dist/C_Steps.css +38 -38
  234. package/dist/C_Steps.js.map +1 -1
  235. package/dist/C_Steps2.js +1 -1
  236. package/dist/C_Steps2.js.map +1 -1
  237. package/dist/C_Table.cjs +1 -1
  238. package/dist/C_Table.js +1 -1
  239. package/dist/C_Table.js.map +1 -1
  240. package/dist/C_Table2.js +2 -2
  241. package/dist/C_Table2.js.map +1 -1
  242. package/dist/{C_TagsView-BsutyC3U.css.map → C_TagsView-DX3LecK1.css.map} +1 -1
  243. package/dist/{C_TagsView-Crm7yWg9.css.map → C_TagsView-DqOTa8bL.css.map} +1 -1
  244. package/dist/C_TagsView.cjs +1 -1
  245. package/dist/C_TagsView.css +36 -36
  246. package/dist/C_TagsView.js +1 -1
  247. package/dist/C_TagsView.js.map +1 -1
  248. package/dist/C_TagsView2.js +1 -1
  249. package/dist/C_TagsView2.js.map +1 -1
  250. package/dist/C_Theme.cjs +1 -1
  251. package/dist/C_Theme.js +1 -1
  252. package/dist/C_Theme.js.map +1 -1
  253. package/dist/C_Theme2.js +1 -1
  254. package/dist/C_Theme2.js.map +1 -1
  255. package/dist/C_Time-BzyQVvTS.css.map +1 -0
  256. package/dist/C_Time-DmpKLQHc.css.map +1 -0
  257. package/dist/C_Time.css +5 -5
  258. package/dist/C_Time.js.map +1 -1
  259. package/dist/C_Time2.js +1 -1
  260. package/dist/C_Time2.js.map +1 -1
  261. package/dist/{C_Timeline-DG70WtJP.css.map → C_Timeline-BKkimrZx.css.map} +1 -1
  262. package/dist/{C_Timeline-Dzxmmdjg.css.map → C_Timeline-DTuSCX6p.css.map} +1 -1
  263. package/dist/C_Timeline.cjs +1 -1
  264. package/dist/C_Timeline.css +45 -45
  265. package/dist/C_Timeline.js +1 -1
  266. package/dist/C_Timeline.js.map +1 -1
  267. package/dist/C_Timeline2.js +1 -1
  268. package/dist/C_Timeline2.js.map +1 -1
  269. package/dist/{C_Transfer-CCEqnC-v.css.map → C_Transfer-BOB6OBqS.css.map} +1 -1
  270. package/dist/{C_Transfer-CStPm4NP.css.map → C_Transfer-Dpq7ezWC.css.map} +1 -1
  271. package/dist/C_Transfer.cjs +1 -1
  272. package/dist/C_Transfer.css +31 -31
  273. package/dist/C_Transfer.js +1 -1
  274. package/dist/C_Transfer.js.map +1 -1
  275. package/dist/C_Transfer2.js +1 -1
  276. package/dist/C_Transfer2.js.map +1 -1
  277. package/dist/{C_Tree-B-Le4hjQ.css.map → C_Tree-D98nX9qT.css.map} +1 -1
  278. package/dist/{C_Tree-DnGc_MPb.css.map → C_Tree-ulfm7b-P.css.map} +1 -1
  279. package/dist/C_Tree.cjs +1 -1
  280. package/dist/C_Tree.css +33 -33
  281. package/dist/C_Tree.js +1 -1
  282. package/dist/C_Tree.js.map +1 -1
  283. package/dist/C_Tree2.js +1 -1
  284. package/dist/C_Tree2.js.map +1 -1
  285. package/dist/{C_Upload-CFAxVGVz.css.map → C_Upload-BR9fg5r4.css.map} +1 -1
  286. package/dist/{C_Upload-D8_QeSV0.css.map → C_Upload-CPqf74Ea.css.map} +1 -1
  287. package/dist/C_Upload.cjs +1 -1
  288. package/dist/C_Upload.css +4 -4
  289. package/dist/C_Upload.js +1 -1
  290. package/dist/C_Upload.js.map +1 -1
  291. package/dist/C_Upload2.js +2 -2
  292. package/dist/C_Upload2.js.map +1 -1
  293. package/dist/{C_VideoPlayer-Cdgv4Zq0.css.map → C_VideoPlayer-4J96tKfK.css.map} +1 -1
  294. package/dist/{C_VideoPlayer-rm0MODUv.css.map → C_VideoPlayer-D35seHkm.css.map} +1 -1
  295. package/dist/C_VideoPlayer.css +13 -13
  296. package/dist/C_VideoPlayer.js.map +1 -1
  297. package/dist/C_VideoPlayer2.js +3 -3
  298. package/dist/C_VideoPlayer2.js.map +1 -1
  299. package/dist/{C_VtableGantt-BpY-Rng3.css.map → C_VtableGantt-C2lxFmWg.css.map} +1 -1
  300. package/dist/{C_VtableGantt-DB87-jdS.css.map → C_VtableGantt-Dwnkw_WL.css.map} +1 -1
  301. package/dist/C_VtableGantt.cjs +1 -1
  302. package/dist/C_VtableGantt.css +15 -15
  303. package/dist/C_VtableGantt.js +1 -1
  304. package/dist/C_VtableGantt.js.map +1 -1
  305. package/dist/C_VtableGantt2.js +1 -1
  306. package/dist/C_VtableGantt2.js.map +1 -1
  307. package/dist/C_WaterFall.js.map +1 -1
  308. package/dist/C_WaterFall2.js +1 -1
  309. package/dist/C_WaterFall2.js.map +1 -1
  310. package/dist/{C_WorkFlow-7F6op6qN.css.map → C_WorkFlow-2hJfvvTK.css.map} +1 -1
  311. package/dist/{C_WorkFlow-Cy3HWA6A.css.map → C_WorkFlow-DHoNjl8-.css.map} +1 -1
  312. package/dist/C_WorkFlow.cjs +1 -1
  313. package/dist/C_WorkFlow.css +108 -108
  314. package/dist/C_WorkFlow.js +1 -1
  315. package/dist/C_WorkFlow.js.map +1 -1
  316. package/dist/C_WorkFlow2.js +1 -1
  317. package/dist/C_WorkFlow2.js.map +1 -1
  318. package/dist/constants.d.ts +2 -2
  319. package/dist/constants2.d.ts +4 -4
  320. package/dist/constants3.d.ts +2 -2
  321. package/dist/constants4.d.ts +6 -6
  322. package/dist/constants5.d.ts +4 -4
  323. package/dist/index.cjs.map +1 -1
  324. package/dist/index.js.map +1 -1
  325. package/dist/index.vue.d.ts +2 -2
  326. package/dist/index10.vue.d.ts +1 -1
  327. package/dist/index12.vue.d.ts +2 -2
  328. package/dist/index13.vue.d.ts +3 -3
  329. package/dist/index14.vue.d.ts +3 -3
  330. package/dist/index15.vue.d.ts +7 -7
  331. package/dist/index17.vue.d.ts +1 -1
  332. package/dist/index18.vue.d.ts +5 -5
  333. package/dist/index2.vue.d.ts +2 -2
  334. package/dist/index20.vue.d.ts +1 -1
  335. package/dist/index21.vue.d.ts +3 -3
  336. package/dist/index22.vue.d.ts +1 -1
  337. package/dist/index26.vue.d.ts +4 -4
  338. package/dist/index27.vue.d.ts +2 -2
  339. package/dist/index28.vue.d.ts +3 -3
  340. package/dist/index4.vue.d.ts +7 -7
  341. package/dist/index6.vue.d.ts +3 -3
  342. package/dist/index8.vue.d.ts +4 -4
  343. package/dist/menuAdapter.js.map +1 -1
  344. package/dist/resolver.cjs.map +1 -1
  345. package/dist/resolver.js.map +1 -1
  346. package/dist/style.css +1483 -1483
  347. package/dist/useCalendarEvents.d.ts +1 -1
  348. package/dist/useCollapsePanel.d.ts +3 -3
  349. package/dist/useCropperCore.d.ts +5 -5
  350. package/dist/useDraggableLayout.d.ts +5 -5
  351. package/dist/useDynamicFormState.d.ts +21 -21
  352. package/dist/useEdgeInteraction.d.ts +3 -3
  353. package/dist/useFullscreen.d.ts +2 -2
  354. package/dist/useInfiniteScroll.d.ts +3 -3
  355. package/dist/useModalEdit.d.ts +1 -1
  356. package/dist/useQRCode.d.ts +5 -5
  357. package/dist/useSearchState.d.ts +2 -2
  358. package/dist/useSignatureHistory.d.ts +5 -5
  359. package/dist/useSplitResize.d.ts +4 -4
  360. package/dist/useTimeSelection.d.ts +2 -2
  361. package/dist/useTreeOperations.d.ts +3 -3
  362. package/dist/useWorkflowValidation.d.ts +2 -2
  363. package/package.json +5 -5
  364. package/dist/C_ActionBar-jZPsiXtT.css.map +0 -1
  365. package/dist/C_ActionBar-nnfbZCea.css.map +0 -1
  366. package/dist/C_Code-DPZlNSxL.css.map +0 -1
  367. package/dist/C_Code-avZjAnHD.css.map +0 -1
  368. package/dist/C_Editor-C49qURgb.css.map +0 -1
  369. package/dist/C_Editor-DuxBD9Qg.css.map +0 -1
  370. package/dist/C_FullCalendar-1U4VlG7X.css.map +0 -1
  371. package/dist/C_FullCalendar-DLUdCtqf.css.map +0 -1
  372. package/dist/C_Steps-BHYNJI1l.css.map +0 -1
  373. package/dist/C_Steps-DY7vl-4I.css.map +0 -1
  374. package/dist/C_Time-Bd_e1YDN.css.map +0 -1
  375. package/dist/C_Time-CKb_fwdm.css.map +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","../src/components/C_Login/index.vue","../src/components/C_Login/index.vue"],"sourcesContent":["/**\r\n * @Description: C_Login 通用登录组件 - 类型定义\r\n */\r\n\r\n// ================= 功能开关 =================\r\nexport interface LoginFeatures {\r\n /** 是否显示密码登录(默认 true) */\r\n passwordLogin?: boolean\r\n /** 是否显示验证码登录(默认 true) */\r\n captchaLogin?: boolean\r\n /** 是否显示扫码登录(默认 true) */\r\n qrcodeLogin?: boolean\r\n /** 是否显示其他账号登录(默认 true) */\r\n socialLogin?: boolean\r\n /** 是否显示注册账号(默认 true) */\r\n register?: boolean\r\n /** 是否启用人机验证拼图(默认 true) */\r\n captchaVerify?: boolean\r\n /** 是否显示记住我(默认 true) */\r\n rememberMe?: boolean\r\n /** 是否显示忘记密码(默认 true) */\r\n forgotPassword?: boolean\r\n}\r\n\r\n// ================= 社交登录 =================\r\nexport interface SocialProvider {\r\n key: string\r\n label: string\r\n /** mdi 图标名称 */\r\n icon: string\r\n}\r\n\r\n// ================= 表单数据 =================\r\nexport interface PasswordFormData {\r\n username: string\r\n password: string\r\n}\r\n\r\nexport interface CaptchaFormData {\r\n account: string\r\n code: string\r\n}\r\n\r\nexport interface RegisterFormData {\r\n phone: string\r\n code: string\r\n password: string\r\n confirmPassword: string\r\n}\r\n\r\n// ================= 组件 Props =================\r\nexport interface LoginProps {\r\n /** 标题文字 */\r\n title?: string\r\n /** 副标题 */\r\n subtitle?: string\r\n /** Logo icon 名称(mdi:xxx),不传则不显示图标 */\r\n logoIcon?: string\r\n /** 功能开关配置 */\r\n features?: LoginFeatures\r\n /** 社交登录提供商列表 */\r\n socialProviders?: SocialProvider[]\r\n /** 扫码登录二维码 URL */\r\n qrcodeUrl?: string\r\n /** localStorage key(记住我) */\r\n storageKey?: string\r\n /** 外部传入的 loading 状态 */\r\n loading?: boolean\r\n /** 表单默认用户名(Demo / 预填场景) */\r\n defaultUsername?: string\r\n /** 表单默认密码(Demo / 预填场景) */\r\n defaultPassword?: string\r\n}\r\n\r\n// ================= 默认社交登录配置 =================\r\nexport const DEFAULT_SOCIAL_PROVIDERS: SocialProvider[] = [\r\n { key: 'github', label: 'GitHub', icon: 'mdi:github' },\r\n { key: 'wechat', label: '微信', icon: 'mdi:wechat' },\r\n { key: 'qq', label: 'QQ', icon: 'mdi:qqchat' },\r\n]\r\n\r\n// ================= 默认功能开关 =================\r\nexport const DEFAULT_FEATURES: Required<LoginFeatures> = {\r\n passwordLogin: true,\r\n captchaLogin: true,\r\n qrcodeLogin: true,\r\n socialLogin: true,\r\n register: true,\r\n captchaVerify: true,\r\n rememberMe: true,\r\n forgotPassword: true,\r\n}\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Description: C_Login - 通用插拔式登录组件\r\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\r\n-->\r\n<template>\r\n <div class=\"c-login-panel\">\r\n <!-- ===== Logo / 标题区 ===== -->\r\n <div class=\"c-login__header\">\r\n <slot name=\"logo\">\r\n <div\r\n v-if=\"mergedProps.logoIcon\"\r\n class=\"c-login__logo\"\r\n >\r\n <C_Icon\r\n :name=\"mergedProps.logoIcon\"\r\n :size=\"36\"\r\n class=\"c-login__logo-icon\"\r\n />\r\n </div>\r\n </slot>\r\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\r\n <p\r\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\r\n class=\"c-login__subtitle\"\r\n >\r\n {{ mergedProps.subtitle }}\r\n </p>\r\n <!-- 注册视图的副标题 -->\r\n <p\r\n v-if=\"activeTab === 'register'\"\r\n class=\"c-login__subtitle c-login__subtitle--register\"\r\n >\r\n {{ t('cl_register', '注册账号') }}\r\n </p>\r\n </div>\r\n\r\n <!-- ===== 密码登录表单(默认视图) ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'password'\"\r\n ref=\"passwordFormRef\"\r\n :model=\"passwordForm\"\r\n :rules=\"passwordRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"username\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"passwordForm.username\"\r\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\r\n clearable\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:account-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"password\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"passwordForm.password\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\r\n clearable\r\n @keyup.enter=\"handlePasswordSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <!-- 记住我 + 忘记密码 -->\r\n <div\r\n v-if=\"feat.rememberMe || feat.forgotPassword\"\r\n class=\"c-login__aux-row\"\r\n >\r\n <NCheckbox\r\n v-if=\"feat.rememberMe\"\r\n v-model:checked=\"rememberMe\"\r\n class=\"c-login__remember\"\r\n >\r\n {{ t('cl_remember', '记住我') }}\r\n </NCheckbox>\r\n <NText\r\n v-if=\"feat.forgotPassword\"\r\n class=\"c-login__forgot\"\r\n @click=\"emit('forgot-password')\"\r\n >\r\n {{ t('cl_forgot', '忘记密码?') }}\r\n </NText>\r\n </div>\r\n </NForm>\r\n\r\n <!-- ===== 验证码登录表单 ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'captcha'\"\r\n ref=\"captchaFormRef\"\r\n :model=\"captchaForm\"\r\n :rules=\"captchaRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"account\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"captchaForm.account\"\r\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\r\n clearable\r\n maxlength=\"11\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:cellphone\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"code\"\r\n class=\"c-login__form-item\"\r\n >\r\n <div class=\"c-login__code-row\">\r\n <NInput\r\n v-model:value=\"captchaForm.code\"\r\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\r\n clearable\r\n class=\"c-login__code-input\"\r\n @keyup.enter=\"handleCaptchaSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:shield-key-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n <NButton\r\n :disabled=\"countdown > 0 || !captchaForm.account\"\r\n class=\"c-login__send-code-btn\"\r\n @click=\"handleSendCode\"\r\n >\r\n {{\r\n countdown > 0\r\n ? `${countdown}s ${t('cl_resend', '后重发')}`\r\n : t('cl_get_code', '获取验证码')\r\n }}\r\n </NButton>\r\n </div>\r\n </NFormItem>\r\n </NForm>\r\n\r\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\r\n type=\"primary\"\r\n block\r\n :loading=\"props.loading\"\r\n :disabled=\"\r\n activeTab === 'password' && feat.captchaVerify && !captchaValid\r\n \"\r\n class=\"c-login__submit-btn\"\r\n @click=\"\r\n activeTab === 'password'\r\n ? handlePasswordSubmit()\r\n : handleCaptchaSubmit()\r\n \"\r\n >\r\n {{\r\n activeTab === 'password' && feat.captchaVerify && !captchaValid\r\n ? t('cl_verify_first', '请先完成人机验证')\r\n : t('cl_login_btn', '登 录')\r\n }}\r\n </NButton>\r\n\r\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\r\n <div\r\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\r\n class=\"c-login__captcha-wrap\"\r\n >\r\n <C_Captcha\r\n ref=\"captchaRef\"\r\n trigger-text=\"\"\r\n theme=\"dark\"\r\n @success=\"handleCaptchaSuccess\"\r\n @fail=\"handleCaptchaFail\"\r\n @change=\"handleCaptchaChange\"\r\n />\r\n </div>\r\n\r\n <!-- ===== 返回按钮(验证码子视图) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'captcha'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 扫码登录面板 ===== -->\r\n <div\r\n v-if=\"activeTab === 'qrcode'\"\r\n class=\"c-login__qr-panel\"\r\n >\r\n <div class=\"c-login__qr-frame\">\r\n <C_QRCode\r\n ref=\"qrcodeRef\"\r\n :value=\"qrcodeValue\"\r\n :size=\"148\"\r\n :margin=\"1\"\r\n :show-border=\"false\"\r\n color=\"#ffffff\"\r\n bg-color=\"#000000\"\r\n error-correction-level=\"M\"\r\n />\r\n <div class=\"c-login__qr-scan-line\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\r\n </div>\r\n <p class=\"c-login__qr-hint\">\r\n <C_Icon\r\n name=\"mdi:cellphone-check\"\r\n :size=\"13\"\r\n />\r\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\r\n </p>\r\n <NButton\r\n text\r\n size=\"tiny\"\r\n class=\"c-login__qr-refresh\"\r\n @click=\"handleQrcodeRefresh\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n name=\"mdi:refresh\"\r\n :size=\"12\"\r\n />\r\n </template>\r\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\r\n </NButton>\r\n </div>\r\n\r\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'qrcode'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 注册表单 ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'register'\"\r\n ref=\"registerFormRef\"\r\n :model=\"registerForm\"\r\n :rules=\"registerRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"phone\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.phone\"\r\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\r\n clearable\r\n maxlength=\"11\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:cellphone\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"code\"\r\n class=\"c-login__form-item\"\r\n >\r\n <div class=\"c-login__code-row\">\r\n <NInput\r\n v-model:value=\"registerForm.code\"\r\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\r\n clearable\r\n class=\"c-login__code-input\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:shield-key-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n <NButton\r\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\r\n class=\"c-login__send-code-btn\"\r\n @click=\"handleRegSendCode\"\r\n >\r\n {{\r\n regCountdown > 0\r\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\r\n : t('cl_get_code', '获取验证码')\r\n }}\r\n </NButton>\r\n </div>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"password\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.password\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"confirmPassword\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.confirmPassword\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\r\n @keyup.enter=\"handleRegisterSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-check-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n </NForm>\r\n\r\n <!-- ===== 注册确认按钮 ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'register'\"\r\n type=\"primary\"\r\n block\r\n :loading=\"props.loading\"\r\n class=\"c-login__submit-btn\"\r\n @click=\"handleRegisterSubmit\"\r\n >\r\n {{ t('cl_confirm', '确认') }}\r\n </NButton>\r\n\r\n <!-- ===== 注册视图返回按钮 ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'register'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\r\n <div\r\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\r\n class=\"c-login__mode-switch\"\r\n >\r\n <NButton\r\n v-if=\"feat.captchaLogin\"\r\n class=\"c-login__mode-btn\"\r\n size=\"small\"\r\n @click=\"activeTab = 'captcha'\"\r\n >\r\n {{ t('cl_captcha_login', '验证码登录') }}\r\n </NButton>\r\n <NButton\r\n v-if=\"feat.qrcodeLogin\"\r\n class=\"c-login__mode-btn\"\r\n size=\"small\"\r\n @click=\"activeTab = 'qrcode'\"\r\n >\r\n {{ t('cl_qr_login', '扫码登录') }}\r\n </NButton>\r\n </div>\r\n\r\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\r\n <template\r\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\r\n >\r\n <NDivider class=\"c-login__divider\">{{\r\n t('cl_social_login', '其他账号登录')\r\n }}</NDivider>\r\n <div class=\"c-login__social-row\">\r\n <NTooltip\r\n v-for=\"provider in socialList\"\r\n :key=\"provider.key\"\r\n trigger=\"hover\"\r\n placement=\"top\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n circle\r\n class=\"c-login__social-btn\"\r\n @click=\"emit('social-login', provider.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"provider.icon\"\r\n :size=\"16\"\r\n />\r\n </template>\r\n </NButton>\r\n </template>\r\n {{ provider.label }}\r\n </NTooltip>\r\n </div>\r\n </template>\r\n\r\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\r\n <div\r\n v-if=\"activeTab === 'password' && feat.register\"\r\n class=\"c-login__register-row\"\r\n >\r\n <NText class=\"c-login__register-hint\">{{\r\n t('cl_no_account', '还没有账号?')\r\n }}</NText>\r\n <NText\r\n class=\"c-login__register-link\"\r\n @click=\"activeTab = 'register'\"\r\n >\r\n {{ t('cl_register', '注册账号') }}\r\n </NText>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\r\n import type { FormInst, FormRules } from 'naive-ui/es'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import C_Captcha from '../C_Captcha/index.vue'\r\n import C_QRCode from '../C_QRCode/index.vue'\r\n import { RULE_COMBOS } from '@robot-admin/form-validate'\r\n import {\r\n DEFAULT_FEATURES,\r\n DEFAULT_SOCIAL_PROVIDERS,\r\n type LoginProps,\r\n type PasswordFormData,\r\n type CaptchaFormData,\r\n type RegisterFormData,\r\n } from './types'\r\n\r\n // ===== i18n helper =====\r\n const t = (key: string, fallback: string) =>\r\n typeof (globalThis as any).$t === 'function'\r\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\r\n : fallback\r\n\r\n // ===== Props & Emits =====\r\n const props = withDefaults(defineProps<LoginProps>(), {\r\n title: '欢迎回来',\r\n loading: false,\r\n storageKey: 'c_login_remember',\r\n defaultUsername: '',\r\n defaultPassword: '',\r\n })\r\n\r\n const emit = defineEmits<{\r\n (\r\n e: 'submit',\r\n data: PasswordFormData & {\r\n captchaToken?: string\r\n captchaTimestamp?: number\r\n }\r\n ): void\r\n (e: 'captcha-submit', data: CaptchaFormData): void\r\n (e: 'send-code', account: string): void\r\n (e: 'social-login', provider: string): void\r\n (e: 'forgot-password'): void\r\n (e: 'qrcode-refresh'): void\r\n (e: 'register-submit', data: RegisterFormData): void\r\n (e: 'register-send-code', phone: string): void\r\n }>()\r\n\r\n // ===== 合并 Props(填充默认值) =====\r\n const mergedProps = computed(() => ({\r\n ...props,\r\n features: { ...DEFAULT_FEATURES, ...props.features },\r\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\r\n }))\r\n\r\n const feat = computed(\r\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\r\n )\r\n const socialList = computed(() => mergedProps.value.socialProviders!)\r\n\r\n // ===== Tab(默认密码登录) =====\r\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\r\n 'password'\r\n )\r\n\r\n // ===== 密码表单 =====\r\n const passwordFormRef = ref<FormInst | null>(null)\r\n const passwordForm = reactive<PasswordFormData>({\r\n username: props.defaultUsername ?? '',\r\n password: props.defaultPassword ?? '',\r\n })\r\n\r\n const passwordRules: FormRules = {\r\n username: [\r\n {\r\n required: true,\r\n message: t('cl_username_rule', '请输入用户名'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n password: [\r\n {\r\n required: true,\r\n message: t('cl_password_rule', '请输入密码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n // ===== 记住我 =====\r\n const rememberMe = ref(false)\r\n\r\n watch(rememberMe, val => {\r\n if (val) {\r\n localStorage.setItem(\r\n props.storageKey!,\r\n JSON.stringify({ username: passwordForm.username })\r\n )\r\n } else {\r\n localStorage.removeItem(props.storageKey!)\r\n }\r\n })\r\n\r\n onMounted(() => {\r\n try {\r\n const saved = localStorage.getItem(props.storageKey!)\r\n if (saved) {\r\n const data = JSON.parse(saved)\r\n if (data.username) {\r\n passwordForm.username = data.username\r\n rememberMe.value = true\r\n }\r\n }\r\n } catch {\r\n /* ignore */\r\n }\r\n })\r\n\r\n // ===== 密码登录提交 =====\r\n const handlePasswordSubmit = async () => {\r\n try {\r\n await passwordFormRef.value?.validate()\r\n } catch {\r\n return // 校验失败,表单会显示行内错误提示\r\n }\r\n emit('submit', {\r\n ...passwordForm,\r\n ...(captchaValid.value\r\n ? {\r\n captchaToken: captchaToken.value,\r\n captchaTimestamp: captchaTimestamp.value,\r\n }\r\n : {}),\r\n })\r\n }\r\n\r\n // ===== 验证码表单 =====\r\n const captchaFormRef = ref<FormInst | null>(null)\r\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\r\n const countdown = ref(0)\r\n let countdownTimer: ReturnType<typeof setInterval> | null = null\r\n\r\n const captchaRules: FormRules = {\r\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\r\n code: [\r\n {\r\n required: true,\r\n message: t('cl_code_rule', '请输入验证码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n // ===== 人机验证 =====\r\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\r\n const captchaValid = ref(false)\r\n const captchaToken = ref('')\r\n const captchaTimestamp = ref(0)\r\n\r\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\r\n captchaValid.value = true\r\n captchaToken.value = data.token\r\n captchaTimestamp.value = data.timestamp\r\n }\r\n const handleCaptchaFail = () => {\r\n captchaValid.value = false\r\n }\r\n const handleCaptchaChange = (valid: boolean) => {\r\n captchaValid.value = valid\r\n if (!valid) {\r\n captchaToken.value = ''\r\n captchaTimestamp.value = 0\r\n }\r\n }\r\n\r\n const resetCaptcha = () => {\r\n captchaValid.value = false\r\n captchaToken.value = ''\r\n captchaTimestamp.value = 0\r\n captchaRef.value?.reset()\r\n }\r\n\r\n const handleCaptchaSubmit = async () => {\r\n try {\r\n await captchaFormRef.value?.validate()\r\n } catch {\r\n return\r\n }\r\n if (feat.value.captchaVerify && !captchaValid.value) return\r\n emit('captcha-submit', { ...captchaForm })\r\n }\r\n\r\n // ===== 发送验证码 =====\r\n const handleSendCode = () => {\r\n if (!captchaForm.account) return\r\n emit('send-code', captchaForm.account)\r\n if (countdownTimer) {\r\n clearInterval(countdownTimer)\r\n countdownTimer = null\r\n }\r\n countdown.value = 60\r\n countdownTimer = setInterval(() => {\r\n countdown.value--\r\n if (countdown.value <= 0) {\r\n clearInterval(countdownTimer!)\r\n countdownTimer = null\r\n }\r\n }, 1000)\r\n }\r\n\r\n // ===== 扫码登录 =====\r\n const qrcodeRef = ref<any>(null)\r\n const qrcodeValue = ref(\r\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\r\n )\r\n\r\n const handleQrcodeRefresh = () => {\r\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\r\n emit('qrcode-refresh')\r\n }\r\n\r\n // ===== 注册表单 =====\r\n const registerFormRef = ref<FormInst | null>(null)\r\n const registerForm = reactive<RegisterFormData>({\r\n phone: '',\r\n code: '',\r\n password: '',\r\n confirmPassword: '',\r\n })\r\n const regCountdown = ref(0)\r\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\r\n\r\n const registerRules: FormRules = {\r\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\r\n code: [\r\n {\r\n required: true,\r\n message: t('cl_code_rule', '请输入验证码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n password: RULE_COMBOS.password('密码') as FormRules['password'],\r\n confirmPassword: [\r\n {\r\n required: true,\r\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\r\n trigger: 'blur',\r\n },\r\n {\r\n validator: (_rule: any, value: string) => {\r\n if (value !== registerForm.password) {\r\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\r\n }\r\n return true\r\n },\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n const handleRegSendCode = () => {\r\n if (!registerForm.phone) return\r\n emit('register-send-code', registerForm.phone)\r\n if (regCountdownTimer) {\r\n clearInterval(regCountdownTimer)\r\n regCountdownTimer = null\r\n }\r\n regCountdown.value = 60\r\n regCountdownTimer = setInterval(() => {\r\n regCountdown.value--\r\n if (regCountdown.value <= 0) {\r\n clearInterval(regCountdownTimer!)\r\n regCountdownTimer = null\r\n }\r\n }, 1000)\r\n }\r\n\r\n const handleRegisterSubmit = async () => {\r\n try {\r\n await registerFormRef.value?.validate()\r\n } catch {\r\n return\r\n }\r\n emit('register-submit', { ...registerForm })\r\n }\r\n\r\n // ===== 清理定时器 =====\r\n onUnmounted(() => {\r\n if (countdownTimer) {\r\n clearInterval(countdownTimer)\r\n countdownTimer = null\r\n }\r\n if (regCountdownTimer) {\r\n clearInterval(regCountdownTimer)\r\n regCountdownTimer = null\r\n }\r\n })\r\n\r\n // ===== 暴露方法给父组件 =====\r\n defineExpose({ resetCaptcha })\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n @use './index.scss';\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Description: C_Login - 通用插拔式登录组件\r\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\r\n-->\r\n<template>\r\n <div class=\"c-login-panel\">\r\n <!-- ===== Logo / 标题区 ===== -->\r\n <div class=\"c-login__header\">\r\n <slot name=\"logo\">\r\n <div\r\n v-if=\"mergedProps.logoIcon\"\r\n class=\"c-login__logo\"\r\n >\r\n <C_Icon\r\n :name=\"mergedProps.logoIcon\"\r\n :size=\"36\"\r\n class=\"c-login__logo-icon\"\r\n />\r\n </div>\r\n </slot>\r\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\r\n <p\r\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\r\n class=\"c-login__subtitle\"\r\n >\r\n {{ mergedProps.subtitle }}\r\n </p>\r\n <!-- 注册视图的副标题 -->\r\n <p\r\n v-if=\"activeTab === 'register'\"\r\n class=\"c-login__subtitle c-login__subtitle--register\"\r\n >\r\n {{ t('cl_register', '注册账号') }}\r\n </p>\r\n </div>\r\n\r\n <!-- ===== 密码登录表单(默认视图) ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'password'\"\r\n ref=\"passwordFormRef\"\r\n :model=\"passwordForm\"\r\n :rules=\"passwordRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"username\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"passwordForm.username\"\r\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\r\n clearable\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:account-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"password\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"passwordForm.password\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\r\n clearable\r\n @keyup.enter=\"handlePasswordSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <!-- 记住我 + 忘记密码 -->\r\n <div\r\n v-if=\"feat.rememberMe || feat.forgotPassword\"\r\n class=\"c-login__aux-row\"\r\n >\r\n <NCheckbox\r\n v-if=\"feat.rememberMe\"\r\n v-model:checked=\"rememberMe\"\r\n class=\"c-login__remember\"\r\n >\r\n {{ t('cl_remember', '记住我') }}\r\n </NCheckbox>\r\n <NText\r\n v-if=\"feat.forgotPassword\"\r\n class=\"c-login__forgot\"\r\n @click=\"emit('forgot-password')\"\r\n >\r\n {{ t('cl_forgot', '忘记密码?') }}\r\n </NText>\r\n </div>\r\n </NForm>\r\n\r\n <!-- ===== 验证码登录表单 ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'captcha'\"\r\n ref=\"captchaFormRef\"\r\n :model=\"captchaForm\"\r\n :rules=\"captchaRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"account\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"captchaForm.account\"\r\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\r\n clearable\r\n maxlength=\"11\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:cellphone\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"code\"\r\n class=\"c-login__form-item\"\r\n >\r\n <div class=\"c-login__code-row\">\r\n <NInput\r\n v-model:value=\"captchaForm.code\"\r\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\r\n clearable\r\n class=\"c-login__code-input\"\r\n @keyup.enter=\"handleCaptchaSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:shield-key-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n <NButton\r\n :disabled=\"countdown > 0 || !captchaForm.account\"\r\n class=\"c-login__send-code-btn\"\r\n @click=\"handleSendCode\"\r\n >\r\n {{\r\n countdown > 0\r\n ? `${countdown}s ${t('cl_resend', '后重发')}`\r\n : t('cl_get_code', '获取验证码')\r\n }}\r\n </NButton>\r\n </div>\r\n </NFormItem>\r\n </NForm>\r\n\r\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\r\n type=\"primary\"\r\n block\r\n :loading=\"props.loading\"\r\n :disabled=\"\r\n activeTab === 'password' && feat.captchaVerify && !captchaValid\r\n \"\r\n class=\"c-login__submit-btn\"\r\n @click=\"\r\n activeTab === 'password'\r\n ? handlePasswordSubmit()\r\n : handleCaptchaSubmit()\r\n \"\r\n >\r\n {{\r\n activeTab === 'password' && feat.captchaVerify && !captchaValid\r\n ? t('cl_verify_first', '请先完成人机验证')\r\n : t('cl_login_btn', '登 录')\r\n }}\r\n </NButton>\r\n\r\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\r\n <div\r\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\r\n class=\"c-login__captcha-wrap\"\r\n >\r\n <C_Captcha\r\n ref=\"captchaRef\"\r\n trigger-text=\"\"\r\n theme=\"dark\"\r\n @success=\"handleCaptchaSuccess\"\r\n @fail=\"handleCaptchaFail\"\r\n @change=\"handleCaptchaChange\"\r\n />\r\n </div>\r\n\r\n <!-- ===== 返回按钮(验证码子视图) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'captcha'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 扫码登录面板 ===== -->\r\n <div\r\n v-if=\"activeTab === 'qrcode'\"\r\n class=\"c-login__qr-panel\"\r\n >\r\n <div class=\"c-login__qr-frame\">\r\n <C_QRCode\r\n ref=\"qrcodeRef\"\r\n :value=\"qrcodeValue\"\r\n :size=\"148\"\r\n :margin=\"1\"\r\n :show-border=\"false\"\r\n color=\"#ffffff\"\r\n bg-color=\"#000000\"\r\n error-correction-level=\"M\"\r\n />\r\n <div class=\"c-login__qr-scan-line\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\r\n </div>\r\n <p class=\"c-login__qr-hint\">\r\n <C_Icon\r\n name=\"mdi:cellphone-check\"\r\n :size=\"13\"\r\n />\r\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\r\n </p>\r\n <NButton\r\n text\r\n size=\"tiny\"\r\n class=\"c-login__qr-refresh\"\r\n @click=\"handleQrcodeRefresh\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n name=\"mdi:refresh\"\r\n :size=\"12\"\r\n />\r\n </template>\r\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\r\n </NButton>\r\n </div>\r\n\r\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'qrcode'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 注册表单 ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'register'\"\r\n ref=\"registerFormRef\"\r\n :model=\"registerForm\"\r\n :rules=\"registerRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"phone\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.phone\"\r\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\r\n clearable\r\n maxlength=\"11\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:cellphone\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"code\"\r\n class=\"c-login__form-item\"\r\n >\r\n <div class=\"c-login__code-row\">\r\n <NInput\r\n v-model:value=\"registerForm.code\"\r\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\r\n clearable\r\n class=\"c-login__code-input\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:shield-key-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n <NButton\r\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\r\n class=\"c-login__send-code-btn\"\r\n @click=\"handleRegSendCode\"\r\n >\r\n {{\r\n regCountdown > 0\r\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\r\n : t('cl_get_code', '获取验证码')\r\n }}\r\n </NButton>\r\n </div>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"password\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.password\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"confirmPassword\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.confirmPassword\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\r\n @keyup.enter=\"handleRegisterSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-check-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n </NForm>\r\n\r\n <!-- ===== 注册确认按钮 ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'register'\"\r\n type=\"primary\"\r\n block\r\n :loading=\"props.loading\"\r\n class=\"c-login__submit-btn\"\r\n @click=\"handleRegisterSubmit\"\r\n >\r\n {{ t('cl_confirm', '确认') }}\r\n </NButton>\r\n\r\n <!-- ===== 注册视图返回按钮 ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'register'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\r\n <div\r\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\r\n class=\"c-login__mode-switch\"\r\n >\r\n <NButton\r\n v-if=\"feat.captchaLogin\"\r\n class=\"c-login__mode-btn\"\r\n size=\"small\"\r\n @click=\"activeTab = 'captcha'\"\r\n >\r\n {{ t('cl_captcha_login', '验证码登录') }}\r\n </NButton>\r\n <NButton\r\n v-if=\"feat.qrcodeLogin\"\r\n class=\"c-login__mode-btn\"\r\n size=\"small\"\r\n @click=\"activeTab = 'qrcode'\"\r\n >\r\n {{ t('cl_qr_login', '扫码登录') }}\r\n </NButton>\r\n </div>\r\n\r\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\r\n <template\r\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\r\n >\r\n <NDivider class=\"c-login__divider\">{{\r\n t('cl_social_login', '其他账号登录')\r\n }}</NDivider>\r\n <div class=\"c-login__social-row\">\r\n <NTooltip\r\n v-for=\"provider in socialList\"\r\n :key=\"provider.key\"\r\n trigger=\"hover\"\r\n placement=\"top\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n circle\r\n class=\"c-login__social-btn\"\r\n @click=\"emit('social-login', provider.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"provider.icon\"\r\n :size=\"16\"\r\n />\r\n </template>\r\n </NButton>\r\n </template>\r\n {{ provider.label }}\r\n </NTooltip>\r\n </div>\r\n </template>\r\n\r\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\r\n <div\r\n v-if=\"activeTab === 'password' && feat.register\"\r\n class=\"c-login__register-row\"\r\n >\r\n <NText class=\"c-login__register-hint\">{{\r\n t('cl_no_account', '还没有账号?')\r\n }}</NText>\r\n <NText\r\n class=\"c-login__register-link\"\r\n @click=\"activeTab = 'register'\"\r\n >\r\n {{ t('cl_register', '注册账号') }}\r\n </NText>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\r\n import type { FormInst, FormRules } from 'naive-ui/es'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import C_Captcha from '../C_Captcha/index.vue'\r\n import C_QRCode from '../C_QRCode/index.vue'\r\n import { RULE_COMBOS } from '@robot-admin/form-validate'\r\n import {\r\n DEFAULT_FEATURES,\r\n DEFAULT_SOCIAL_PROVIDERS,\r\n type LoginProps,\r\n type PasswordFormData,\r\n type CaptchaFormData,\r\n type RegisterFormData,\r\n } from './types'\r\n\r\n // ===== i18n helper =====\r\n const t = (key: string, fallback: string) =>\r\n typeof (globalThis as any).$t === 'function'\r\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\r\n : fallback\r\n\r\n // ===== Props & Emits =====\r\n const props = withDefaults(defineProps<LoginProps>(), {\r\n title: '欢迎回来',\r\n loading: false,\r\n storageKey: 'c_login_remember',\r\n defaultUsername: '',\r\n defaultPassword: '',\r\n })\r\n\r\n const emit = defineEmits<{\r\n (\r\n e: 'submit',\r\n data: PasswordFormData & {\r\n captchaToken?: string\r\n captchaTimestamp?: number\r\n }\r\n ): void\r\n (e: 'captcha-submit', data: CaptchaFormData): void\r\n (e: 'send-code', account: string): void\r\n (e: 'social-login', provider: string): void\r\n (e: 'forgot-password'): void\r\n (e: 'qrcode-refresh'): void\r\n (e: 'register-submit', data: RegisterFormData): void\r\n (e: 'register-send-code', phone: string): void\r\n }>()\r\n\r\n // ===== 合并 Props(填充默认值) =====\r\n const mergedProps = computed(() => ({\r\n ...props,\r\n features: { ...DEFAULT_FEATURES, ...props.features },\r\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\r\n }))\r\n\r\n const feat = computed(\r\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\r\n )\r\n const socialList = computed(() => mergedProps.value.socialProviders!)\r\n\r\n // ===== Tab(默认密码登录) =====\r\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\r\n 'password'\r\n )\r\n\r\n // ===== 密码表单 =====\r\n const passwordFormRef = ref<FormInst | null>(null)\r\n const passwordForm = reactive<PasswordFormData>({\r\n username: props.defaultUsername ?? '',\r\n password: props.defaultPassword ?? '',\r\n })\r\n\r\n const passwordRules: FormRules = {\r\n username: [\r\n {\r\n required: true,\r\n message: t('cl_username_rule', '请输入用户名'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n password: [\r\n {\r\n required: true,\r\n message: t('cl_password_rule', '请输入密码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n // ===== 记住我 =====\r\n const rememberMe = ref(false)\r\n\r\n watch(rememberMe, val => {\r\n if (val) {\r\n localStorage.setItem(\r\n props.storageKey!,\r\n JSON.stringify({ username: passwordForm.username })\r\n )\r\n } else {\r\n localStorage.removeItem(props.storageKey!)\r\n }\r\n })\r\n\r\n onMounted(() => {\r\n try {\r\n const saved = localStorage.getItem(props.storageKey!)\r\n if (saved) {\r\n const data = JSON.parse(saved)\r\n if (data.username) {\r\n passwordForm.username = data.username\r\n rememberMe.value = true\r\n }\r\n }\r\n } catch {\r\n /* ignore */\r\n }\r\n })\r\n\r\n // ===== 密码登录提交 =====\r\n const handlePasswordSubmit = async () => {\r\n try {\r\n await passwordFormRef.value?.validate()\r\n } catch {\r\n return // 校验失败,表单会显示行内错误提示\r\n }\r\n emit('submit', {\r\n ...passwordForm,\r\n ...(captchaValid.value\r\n ? {\r\n captchaToken: captchaToken.value,\r\n captchaTimestamp: captchaTimestamp.value,\r\n }\r\n : {}),\r\n })\r\n }\r\n\r\n // ===== 验证码表单 =====\r\n const captchaFormRef = ref<FormInst | null>(null)\r\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\r\n const countdown = ref(0)\r\n let countdownTimer: ReturnType<typeof setInterval> | null = null\r\n\r\n const captchaRules: FormRules = {\r\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\r\n code: [\r\n {\r\n required: true,\r\n message: t('cl_code_rule', '请输入验证码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n // ===== 人机验证 =====\r\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\r\n const captchaValid = ref(false)\r\n const captchaToken = ref('')\r\n const captchaTimestamp = ref(0)\r\n\r\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\r\n captchaValid.value = true\r\n captchaToken.value = data.token\r\n captchaTimestamp.value = data.timestamp\r\n }\r\n const handleCaptchaFail = () => {\r\n captchaValid.value = false\r\n }\r\n const handleCaptchaChange = (valid: boolean) => {\r\n captchaValid.value = valid\r\n if (!valid) {\r\n captchaToken.value = ''\r\n captchaTimestamp.value = 0\r\n }\r\n }\r\n\r\n const resetCaptcha = () => {\r\n captchaValid.value = false\r\n captchaToken.value = ''\r\n captchaTimestamp.value = 0\r\n captchaRef.value?.reset()\r\n }\r\n\r\n const handleCaptchaSubmit = async () => {\r\n try {\r\n await captchaFormRef.value?.validate()\r\n } catch {\r\n return\r\n }\r\n if (feat.value.captchaVerify && !captchaValid.value) return\r\n emit('captcha-submit', { ...captchaForm })\r\n }\r\n\r\n // ===== 发送验证码 =====\r\n const handleSendCode = () => {\r\n if (!captchaForm.account) return\r\n emit('send-code', captchaForm.account)\r\n if (countdownTimer) {\r\n clearInterval(countdownTimer)\r\n countdownTimer = null\r\n }\r\n countdown.value = 60\r\n countdownTimer = setInterval(() => {\r\n countdown.value--\r\n if (countdown.value <= 0) {\r\n clearInterval(countdownTimer!)\r\n countdownTimer = null\r\n }\r\n }, 1000)\r\n }\r\n\r\n // ===== 扫码登录 =====\r\n const qrcodeRef = ref<any>(null)\r\n const qrcodeValue = ref(\r\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\r\n )\r\n\r\n const handleQrcodeRefresh = () => {\r\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\r\n emit('qrcode-refresh')\r\n }\r\n\r\n // ===== 注册表单 =====\r\n const registerFormRef = ref<FormInst | null>(null)\r\n const registerForm = reactive<RegisterFormData>({\r\n phone: '',\r\n code: '',\r\n password: '',\r\n confirmPassword: '',\r\n })\r\n const regCountdown = ref(0)\r\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\r\n\r\n const registerRules: FormRules = {\r\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\r\n code: [\r\n {\r\n required: true,\r\n message: t('cl_code_rule', '请输入验证码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n password: RULE_COMBOS.password('密码') as FormRules['password'],\r\n confirmPassword: [\r\n {\r\n required: true,\r\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\r\n trigger: 'blur',\r\n },\r\n {\r\n validator: (_rule: any, value: string) => {\r\n if (value !== registerForm.password) {\r\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\r\n }\r\n return true\r\n },\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n const handleRegSendCode = () => {\r\n if (!registerForm.phone) return\r\n emit('register-send-code', registerForm.phone)\r\n if (regCountdownTimer) {\r\n clearInterval(regCountdownTimer)\r\n regCountdownTimer = null\r\n }\r\n regCountdown.value = 60\r\n regCountdownTimer = setInterval(() => {\r\n regCountdown.value--\r\n if (regCountdown.value <= 0) {\r\n clearInterval(regCountdownTimer!)\r\n regCountdownTimer = null\r\n }\r\n }, 1000)\r\n }\r\n\r\n const handleRegisterSubmit = async () => {\r\n try {\r\n await registerFormRef.value?.validate()\r\n } catch {\r\n return\r\n }\r\n emit('register-submit', { ...registerForm })\r\n }\r\n\r\n // ===== 清理定时器 =====\r\n onUnmounted(() => {\r\n if (countdownTimer) {\r\n clearInterval(countdownTimer)\r\n countdownTimer = null\r\n }\r\n if (regCountdownTimer) {\r\n clearInterval(regCountdownTimer)\r\n regCountdownTimer = null\r\n }\r\n })\r\n\r\n // ===== 暴露方法给父组件 =====\r\n defineExpose({ resetCaptcha })\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n @use './index.scss';\r\n</style>\r\n","<!--\r\n * @Description: C_Login - 通用插拔式登录组件\r\n * 通过 features prop 控制功能模块的显隐,样式通过 CSS 变量覆盖\r\n-->\r\n<template>\r\n <div class=\"c-login-panel\">\r\n <!-- ===== Logo / 标题区 ===== -->\r\n <div class=\"c-login__header\">\r\n <slot name=\"logo\">\r\n <div\r\n v-if=\"mergedProps.logoIcon\"\r\n class=\"c-login__logo\"\r\n >\r\n <C_Icon\r\n :name=\"mergedProps.logoIcon\"\r\n :size=\"36\"\r\n class=\"c-login__logo-icon\"\r\n />\r\n </div>\r\n </slot>\r\n <h2 class=\"c-login__title\">{{ mergedProps.title }}</h2>\r\n <p\r\n v-if=\"mergedProps.subtitle && activeTab !== 'register'\"\r\n class=\"c-login__subtitle\"\r\n >\r\n {{ mergedProps.subtitle }}\r\n </p>\r\n <!-- 注册视图的副标题 -->\r\n <p\r\n v-if=\"activeTab === 'register'\"\r\n class=\"c-login__subtitle c-login__subtitle--register\"\r\n >\r\n {{ t('cl_register', '注册账号') }}\r\n </p>\r\n </div>\r\n\r\n <!-- ===== 密码登录表单(默认视图) ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'password'\"\r\n ref=\"passwordFormRef\"\r\n :model=\"passwordForm\"\r\n :rules=\"passwordRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"username\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"passwordForm.username\"\r\n :placeholder=\"t('cl_username_ph', '请输入用户名')\"\r\n clearable\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:account-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"password\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"passwordForm.password\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\r\n clearable\r\n @keyup.enter=\"handlePasswordSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <!-- 记住我 + 忘记密码 -->\r\n <div\r\n v-if=\"feat.rememberMe || feat.forgotPassword\"\r\n class=\"c-login__aux-row\"\r\n >\r\n <NCheckbox\r\n v-if=\"feat.rememberMe\"\r\n v-model:checked=\"rememberMe\"\r\n class=\"c-login__remember\"\r\n >\r\n {{ t('cl_remember', '记住我') }}\r\n </NCheckbox>\r\n <NText\r\n v-if=\"feat.forgotPassword\"\r\n class=\"c-login__forgot\"\r\n @click=\"emit('forgot-password')\"\r\n >\r\n {{ t('cl_forgot', '忘记密码?') }}\r\n </NText>\r\n </div>\r\n </NForm>\r\n\r\n <!-- ===== 验证码登录表单 ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'captcha'\"\r\n ref=\"captchaFormRef\"\r\n :model=\"captchaForm\"\r\n :rules=\"captchaRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"account\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"captchaForm.account\"\r\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\r\n clearable\r\n maxlength=\"11\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:cellphone\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"code\"\r\n class=\"c-login__form-item\"\r\n >\r\n <div class=\"c-login__code-row\">\r\n <NInput\r\n v-model:value=\"captchaForm.code\"\r\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\r\n clearable\r\n class=\"c-login__code-input\"\r\n @keyup.enter=\"handleCaptchaSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:shield-key-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n <NButton\r\n :disabled=\"countdown > 0 || !captchaForm.account\"\r\n class=\"c-login__send-code-btn\"\r\n @click=\"handleSendCode\"\r\n >\r\n {{\r\n countdown > 0\r\n ? `${countdown}s ${t('cl_resend', '后重发')}`\r\n : t('cl_get_code', '获取验证码')\r\n }}\r\n </NButton>\r\n </div>\r\n </NFormItem>\r\n </NForm>\r\n\r\n <!-- ===== 登录按钮(密码和验证码模式) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'password' || activeTab === 'captcha'\"\r\n type=\"primary\"\r\n block\r\n :loading=\"props.loading\"\r\n :disabled=\"\r\n activeTab === 'password' && feat.captchaVerify && !captchaValid\r\n \"\r\n class=\"c-login__submit-btn\"\r\n @click=\"\r\n activeTab === 'password'\r\n ? handlePasswordSubmit()\r\n : handleCaptchaSubmit()\r\n \"\r\n >\r\n {{\r\n activeTab === 'password' && feat.captchaVerify && !captchaValid\r\n ? t('cl_verify_first', '请先完成人机验证')\r\n : t('cl_login_btn', '登 录')\r\n }}\r\n </NButton>\r\n\r\n <!-- ===== 人机验证拼图(仅密码登录模式) ===== -->\r\n <div\r\n v-if=\"feat.captchaVerify && activeTab === 'password'\"\r\n class=\"c-login__captcha-wrap\"\r\n >\r\n <C_Captcha\r\n ref=\"captchaRef\"\r\n trigger-text=\"\"\r\n theme=\"dark\"\r\n @success=\"handleCaptchaSuccess\"\r\n @fail=\"handleCaptchaFail\"\r\n @change=\"handleCaptchaChange\"\r\n />\r\n </div>\r\n\r\n <!-- ===== 返回按钮(验证码子视图) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'captcha'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 扫码登录面板 ===== -->\r\n <div\r\n v-if=\"activeTab === 'qrcode'\"\r\n class=\"c-login__qr-panel\"\r\n >\r\n <div class=\"c-login__qr-frame\">\r\n <C_QRCode\r\n ref=\"qrcodeRef\"\r\n :value=\"qrcodeValue\"\r\n :size=\"148\"\r\n :margin=\"1\"\r\n :show-border=\"false\"\r\n color=\"#ffffff\"\r\n bg-color=\"#000000\"\r\n error-correction-level=\"M\"\r\n />\r\n <div class=\"c-login__qr-scan-line\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--tl\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--tr\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--bl\" />\r\n <div class=\"c-login__qr-corner c-login__qr-corner--br\" />\r\n </div>\r\n <p class=\"c-login__qr-hint\">\r\n <C_Icon\r\n name=\"mdi:cellphone-check\"\r\n :size=\"13\"\r\n />\r\n {{ t('cl_qr_hint', '打开手机扫码后点击「确认」即可登录') }}\r\n </p>\r\n <NButton\r\n text\r\n size=\"tiny\"\r\n class=\"c-login__qr-refresh\"\r\n @click=\"handleQrcodeRefresh\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n name=\"mdi:refresh\"\r\n :size=\"12\"\r\n />\r\n </template>\r\n {{ t('cl_qr_refresh', '二维码已失效?点击刷新') }}\r\n </NButton>\r\n </div>\r\n\r\n <!-- ===== 返回按钮(扫码子视图,在二维码下方) ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'qrcode'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 注册表单 ===== -->\r\n <NForm\r\n v-show=\"activeTab === 'register'\"\r\n ref=\"registerFormRef\"\r\n :model=\"registerForm\"\r\n :rules=\"registerRules\"\r\n class=\"c-login__form\"\r\n >\r\n <NFormItem\r\n path=\"phone\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.phone\"\r\n :placeholder=\"t('cl_phone_ph', '请输入手机号')\"\r\n clearable\r\n maxlength=\"11\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:cellphone\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"code\"\r\n class=\"c-login__form-item\"\r\n >\r\n <div class=\"c-login__code-row\">\r\n <NInput\r\n v-model:value=\"registerForm.code\"\r\n :placeholder=\"t('cl_code_ph', '请输入验证码')\"\r\n clearable\r\n class=\"c-login__code-input\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:shield-key-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n <NButton\r\n :disabled=\"regCountdown > 0 || !registerForm.phone\"\r\n class=\"c-login__send-code-btn\"\r\n @click=\"handleRegSendCode\"\r\n >\r\n {{\r\n regCountdown > 0\r\n ? `${regCountdown}s ${t('cl_resend', '后重发')}`\r\n : t('cl_get_code', '获取验证码')\r\n }}\r\n </NButton>\r\n </div>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"password\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.password\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_password_ph', '请输入密码')\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n\r\n <NFormItem\r\n path=\"confirmPassword\"\r\n class=\"c-login__form-item\"\r\n >\r\n <NInput\r\n v-model:value=\"registerForm.confirmPassword\"\r\n type=\"password\"\r\n show-password-on=\"click\"\r\n :placeholder=\"t('cl_confirm_pw_ph', '请再次输入密码')\"\r\n @keyup.enter=\"handleRegisterSubmit\"\r\n >\r\n <template #prefix>\r\n <C_Icon\r\n name=\"mdi:lock-check-outline\"\r\n :size=\"16\"\r\n class=\"c-login__input-icon\"\r\n />\r\n </template>\r\n </NInput>\r\n </NFormItem>\r\n </NForm>\r\n\r\n <!-- ===== 注册确认按钮 ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'register'\"\r\n type=\"primary\"\r\n block\r\n :loading=\"props.loading\"\r\n class=\"c-login__submit-btn\"\r\n @click=\"handleRegisterSubmit\"\r\n >\r\n {{ t('cl_confirm', '确认') }}\r\n </NButton>\r\n\r\n <!-- ===== 注册视图返回按钮 ===== -->\r\n <NButton\r\n v-if=\"activeTab === 'register'\"\r\n block\r\n class=\"c-login__back-btn\"\r\n @click=\"activeTab = 'password'\"\r\n >\r\n {{ t('cl_back', '返回') }}\r\n </NButton>\r\n\r\n <!-- ===== 登录方式切换(仅密码登录默认视图显示) ===== -->\r\n <div\r\n v-if=\"activeTab === 'password' && (feat.captchaLogin || feat.qrcodeLogin)\"\r\n class=\"c-login__mode-switch\"\r\n >\r\n <NButton\r\n v-if=\"feat.captchaLogin\"\r\n class=\"c-login__mode-btn\"\r\n size=\"small\"\r\n @click=\"activeTab = 'captcha'\"\r\n >\r\n {{ t('cl_captcha_login', '验证码登录') }}\r\n </NButton>\r\n <NButton\r\n v-if=\"feat.qrcodeLogin\"\r\n class=\"c-login__mode-btn\"\r\n size=\"small\"\r\n @click=\"activeTab = 'qrcode'\"\r\n >\r\n {{ t('cl_qr_login', '扫码登录') }}\r\n </NButton>\r\n </div>\r\n\r\n <!-- ===== 其他登录方式(仅密码登录默认视图显示) ===== -->\r\n <template\r\n v-if=\"activeTab === 'password' && feat.socialLogin && socialList.length\"\r\n >\r\n <NDivider class=\"c-login__divider\">{{\r\n t('cl_social_login', '其他账号登录')\r\n }}</NDivider>\r\n <div class=\"c-login__social-row\">\r\n <NTooltip\r\n v-for=\"provider in socialList\"\r\n :key=\"provider.key\"\r\n trigger=\"hover\"\r\n placement=\"top\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n circle\r\n class=\"c-login__social-btn\"\r\n @click=\"emit('social-login', provider.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"provider.icon\"\r\n :size=\"16\"\r\n />\r\n </template>\r\n </NButton>\r\n </template>\r\n {{ provider.label }}\r\n </NTooltip>\r\n </div>\r\n </template>\r\n\r\n <!-- ===== 注册入口(仅密码登录默认视图显示) ===== -->\r\n <div\r\n v-if=\"activeTab === 'password' && feat.register\"\r\n class=\"c-login__register-row\"\r\n >\r\n <NText class=\"c-login__register-hint\">{{\r\n t('cl_no_account', '还没有账号?')\r\n }}</NText>\r\n <NText\r\n class=\"c-login__register-link\"\r\n @click=\"activeTab = 'register'\"\r\n >\r\n {{ t('cl_register', '注册账号') }}\r\n </NText>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'\r\n import type { FormInst, FormRules } from 'naive-ui/es'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import C_Captcha from '../C_Captcha/index.vue'\r\n import C_QRCode from '../C_QRCode/index.vue'\r\n import { RULE_COMBOS } from '@robot-admin/form-validate'\r\n import {\r\n DEFAULT_FEATURES,\r\n DEFAULT_SOCIAL_PROVIDERS,\r\n type LoginProps,\r\n type PasswordFormData,\r\n type CaptchaFormData,\r\n type RegisterFormData,\r\n } from './types'\r\n\r\n // ===== i18n helper =====\r\n const t = (key: string, fallback: string) =>\r\n typeof (globalThis as any).$t === 'function'\r\n ? (globalThis as any).$t(key, fallback, 'robot_admin')\r\n : fallback\r\n\r\n // ===== Props & Emits =====\r\n const props = withDefaults(defineProps<LoginProps>(), {\r\n title: '欢迎回来',\r\n loading: false,\r\n storageKey: 'c_login_remember',\r\n defaultUsername: '',\r\n defaultPassword: '',\r\n })\r\n\r\n const emit = defineEmits<{\r\n (\r\n e: 'submit',\r\n data: PasswordFormData & {\r\n captchaToken?: string\r\n captchaTimestamp?: number\r\n }\r\n ): void\r\n (e: 'captcha-submit', data: CaptchaFormData): void\r\n (e: 'send-code', account: string): void\r\n (e: 'social-login', provider: string): void\r\n (e: 'forgot-password'): void\r\n (e: 'qrcode-refresh'): void\r\n (e: 'register-submit', data: RegisterFormData): void\r\n (e: 'register-send-code', phone: string): void\r\n }>()\r\n\r\n // ===== 合并 Props(填充默认值) =====\r\n const mergedProps = computed(() => ({\r\n ...props,\r\n features: { ...DEFAULT_FEATURES, ...props.features },\r\n socialProviders: props.socialProviders ?? DEFAULT_SOCIAL_PROVIDERS,\r\n }))\r\n\r\n const feat = computed(\r\n () => mergedProps.value.features as Required<typeof DEFAULT_FEATURES>\r\n )\r\n const socialList = computed(() => mergedProps.value.socialProviders!)\r\n\r\n // ===== Tab(默认密码登录) =====\r\n const activeTab = ref<'password' | 'captcha' | 'qrcode' | 'register'>(\r\n 'password'\r\n )\r\n\r\n // ===== 密码表单 =====\r\n const passwordFormRef = ref<FormInst | null>(null)\r\n const passwordForm = reactive<PasswordFormData>({\r\n username: props.defaultUsername ?? '',\r\n password: props.defaultPassword ?? '',\r\n })\r\n\r\n const passwordRules: FormRules = {\r\n username: [\r\n {\r\n required: true,\r\n message: t('cl_username_rule', '请输入用户名'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n password: [\r\n {\r\n required: true,\r\n message: t('cl_password_rule', '请输入密码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n // ===== 记住我 =====\r\n const rememberMe = ref(false)\r\n\r\n watch(rememberMe, val => {\r\n if (val) {\r\n localStorage.setItem(\r\n props.storageKey!,\r\n JSON.stringify({ username: passwordForm.username })\r\n )\r\n } else {\r\n localStorage.removeItem(props.storageKey!)\r\n }\r\n })\r\n\r\n onMounted(() => {\r\n try {\r\n const saved = localStorage.getItem(props.storageKey!)\r\n if (saved) {\r\n const data = JSON.parse(saved)\r\n if (data.username) {\r\n passwordForm.username = data.username\r\n rememberMe.value = true\r\n }\r\n }\r\n } catch {\r\n /* ignore */\r\n }\r\n })\r\n\r\n // ===== 密码登录提交 =====\r\n const handlePasswordSubmit = async () => {\r\n try {\r\n await passwordFormRef.value?.validate()\r\n } catch {\r\n return // 校验失败,表单会显示行内错误提示\r\n }\r\n emit('submit', {\r\n ...passwordForm,\r\n ...(captchaValid.value\r\n ? {\r\n captchaToken: captchaToken.value,\r\n captchaTimestamp: captchaTimestamp.value,\r\n }\r\n : {}),\r\n })\r\n }\r\n\r\n // ===== 验证码表单 =====\r\n const captchaFormRef = ref<FormInst | null>(null)\r\n const captchaForm = reactive<CaptchaFormData>({ account: '', code: '' })\r\n const countdown = ref(0)\r\n let countdownTimer: ReturnType<typeof setInterval> | null = null\r\n\r\n const captchaRules: FormRules = {\r\n account: RULE_COMBOS.mobile('手机号') as FormRules['account'],\r\n code: [\r\n {\r\n required: true,\r\n message: t('cl_code_rule', '请输入验证码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n // ===== 人机验证 =====\r\n const captchaRef = ref<InstanceType<typeof C_Captcha>>()\r\n const captchaValid = ref(false)\r\n const captchaToken = ref('')\r\n const captchaTimestamp = ref(0)\r\n\r\n const handleCaptchaSuccess = (data: { token: string; timestamp: number }) => {\r\n captchaValid.value = true\r\n captchaToken.value = data.token\r\n captchaTimestamp.value = data.timestamp\r\n }\r\n const handleCaptchaFail = () => {\r\n captchaValid.value = false\r\n }\r\n const handleCaptchaChange = (valid: boolean) => {\r\n captchaValid.value = valid\r\n if (!valid) {\r\n captchaToken.value = ''\r\n captchaTimestamp.value = 0\r\n }\r\n }\r\n\r\n const resetCaptcha = () => {\r\n captchaValid.value = false\r\n captchaToken.value = ''\r\n captchaTimestamp.value = 0\r\n captchaRef.value?.reset()\r\n }\r\n\r\n const handleCaptchaSubmit = async () => {\r\n try {\r\n await captchaFormRef.value?.validate()\r\n } catch {\r\n return\r\n }\r\n if (feat.value.captchaVerify && !captchaValid.value) return\r\n emit('captcha-submit', { ...captchaForm })\r\n }\r\n\r\n // ===== 发送验证码 =====\r\n const handleSendCode = () => {\r\n if (!captchaForm.account) return\r\n emit('send-code', captchaForm.account)\r\n if (countdownTimer) {\r\n clearInterval(countdownTimer)\r\n countdownTimer = null\r\n }\r\n countdown.value = 60\r\n countdownTimer = setInterval(() => {\r\n countdown.value--\r\n if (countdown.value <= 0) {\r\n clearInterval(countdownTimer!)\r\n countdownTimer = null\r\n }\r\n }, 1000)\r\n }\r\n\r\n // ===== 扫码登录 =====\r\n const qrcodeRef = ref<any>(null)\r\n const qrcodeValue = ref(\r\n `https://robot-admin.app/qrcode-login?t=${Date.now()}`\r\n )\r\n\r\n const handleQrcodeRefresh = () => {\r\n qrcodeValue.value = `https://robot-admin.app/qrcode-login?t=${Date.now()}`\r\n emit('qrcode-refresh')\r\n }\r\n\r\n // ===== 注册表单 =====\r\n const registerFormRef = ref<FormInst | null>(null)\r\n const registerForm = reactive<RegisterFormData>({\r\n phone: '',\r\n code: '',\r\n password: '',\r\n confirmPassword: '',\r\n })\r\n const regCountdown = ref(0)\r\n let regCountdownTimer: ReturnType<typeof setInterval> | null = null\r\n\r\n const registerRules: FormRules = {\r\n phone: RULE_COMBOS.mobile('手机号') as FormRules['phone'],\r\n code: [\r\n {\r\n required: true,\r\n message: t('cl_code_rule', '请输入验证码'),\r\n trigger: 'blur',\r\n },\r\n ],\r\n password: RULE_COMBOS.password('密码') as FormRules['password'],\r\n confirmPassword: [\r\n {\r\n required: true,\r\n message: t('cl_confirm_pw_rule', '请再次输入密码'),\r\n trigger: 'blur',\r\n },\r\n {\r\n validator: (_rule: any, value: string) => {\r\n if (value !== registerForm.password) {\r\n return new Error(t('cl_pw_mismatch', '两次密码输入不一致'))\r\n }\r\n return true\r\n },\r\n trigger: 'blur',\r\n },\r\n ],\r\n }\r\n\r\n const handleRegSendCode = () => {\r\n if (!registerForm.phone) return\r\n emit('register-send-code', registerForm.phone)\r\n if (regCountdownTimer) {\r\n clearInterval(regCountdownTimer)\r\n regCountdownTimer = null\r\n }\r\n regCountdown.value = 60\r\n regCountdownTimer = setInterval(() => {\r\n regCountdown.value--\r\n if (regCountdown.value <= 0) {\r\n clearInterval(regCountdownTimer!)\r\n regCountdownTimer = null\r\n }\r\n }, 1000)\r\n }\r\n\r\n const handleRegisterSubmit = async () => {\r\n try {\r\n await registerFormRef.value?.validate()\r\n } catch {\r\n return\r\n }\r\n emit('register-submit', { ...registerForm })\r\n }\r\n\r\n // ===== 清理定时器 =====\r\n onUnmounted(() => {\r\n if (countdownTimer) {\r\n clearInterval(countdownTimer)\r\n countdownTimer = null\r\n }\r\n if (regCountdownTimer) {\r\n clearInterval(regCountdownTimer)\r\n regCountdownTimer = null\r\n }\r\n })\r\n\r\n // ===== 暴露方法给父组件 =====\r\n defineExpose({ resetCaptcha })\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n @use './index.scss';\r\n</style>\r\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"],"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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"}
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{NButton as ne,NCheckbox as n,NDivider as re,NForm as ie,NFormItem as ae,NInput as oe,NText as se,NTooltip as ce}from"naive-ui";import{Fragment as r,computed as i,createBlock as a,createCommentVNode as o,createElementBlock as s,createElementVNode as c,createStaticVNode as le,createTextVNode as l,createVNode as u,defineComponent as d,onMounted as f,onUnmounted as p,openBlock as m,reactive as h,ref as g,renderList as ue,renderSlot as de,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},fe={class:`c-login-panel`},pe={class:`c-login__header`},me={key:0,class:`c-login__logo`},he={class:`c-login__title`},ge={key:0,class:`c-login__subtitle`},_e={key:1,class:`c-login__subtitle c-login__subtitle--register`},ve={key:0,class:`c-login__aux-row`},ye={class:`c-login__code-row`},be={key:1,class:`c-login__captcha-wrap`},xe={key:3,class:`c-login__qr-panel`},E={class:`c-login__qr-frame`},Se={class:`c-login__qr-hint`},Ce={class:`c-login__code-row`},we={key:7,class:`c-login__mode-switch`},Te={class:`c-login__social-row`},Ee={key:9,class:`c-login__register-row`};var D=t(d({__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:d,emit:D}){let O=(e,t)=>typeof globalThis.$t==`function`?globalThis.$t(e,t,`robot_admin`):t,k=t,A=D,j=i(()=>({...k,features:{...T,...k.features},socialProviders:k.socialProviders??w})),M=i(()=>j.value.features),N=i(()=>j.value.socialProviders),P=g(`password`),F=g(null),I=h({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=g(!1);y(L,e=>{e?localStorage.setItem(k.storageKey,JSON.stringify({username:I.username})):localStorage.removeItem(k.storageKey)}),f(()=>{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=g(null),B=h({account:``,code:``}),V=g(0),H=null,Oe={account:C.mobile(`手机号`),code:[{required:!0,message:O(`cl_code_rule`,`请输入验证码`),trigger:`blur`}]},U=g(),W=g(!1),G=g(``),K=g(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=g(null),J=g(`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=g(null),X=h({phone:``,code:``,password:``,confirmPassword:``}),Z=g(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 p(()=>{H&&=(clearInterval(H),null),Q&&=(clearInterval(Q),null)}),d({resetCaptcha:Me}),(t,i)=>{let d=oe,f=ae,p=n,h=se,g=ie,y=ne,C=re,w=ce;return m(),s(`div`,fe,[o(` ===== Logo / 标题区 ===== `),c(`div`,pe,[de(t.$slots,`logo`,{},()=>[j.value.logoIcon?(m(),s(`div`,me,[u(e,{name:j.value.logoIcon,size:36,class:`c-login__logo-icon`},null,8,[`name`])])):o(`v-if`,!0)],!0),c(`h2`,he,_(j.value.title),1),j.value.subtitle&&P.value!==`register`?(m(),s(`p`,ge,_(j.value.subtitle),1)):o(`v-if`,!0),o(` 注册视图的副标题 `),P.value===`register`?(m(),s(`p`,_e,_(O(`cl_register`,`注册账号`)),1)):o(`v-if`,!0)]),o(` ===== 密码登录表单(默认视图) ===== `),x(u(g,{ref_key:`passwordFormRef`,ref:F,model:I,rules:De,class:`c-login__form`},{default:b(()=>[u(f,{path:`username`,class:`c-login__form-item`},{default:b(()=>[u(d,{value:I.username,"onUpdate:value":i[0]||=e=>I.username=e,placeholder:O(`cl_username_ph`,`请输入用户名`),clearable:``},{prefix:b(()=>[u(e,{name:`mdi:account-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),u(f,{path:`password`,class:`c-login__form-item`},{default:b(()=>[u(d,{value:I.password,"onUpdate:value":i[1]||=e=>I.password=e,type:`password`,"show-password-on":`click`,placeholder:O(`cl_password_ph`,`请输入密码`),clearable:``,onKeyup:S(R,[`enter`])},{prefix:b(()=>[u(e,{name:`mdi:lock-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),o(` 记住我 + 忘记密码 `),M.value.rememberMe||M.value.forgotPassword?(m(),s(`div`,ve,[M.value.rememberMe?(m(),a(p,{key:0,checked:L.value,"onUpdate:checked":i[2]||=e=>L.value=e,class:`c-login__remember`},{default:b(()=>[l(_(O(`cl_remember`,`记住我`)),1)]),_:1},8,[`checked`])):o(`v-if`,!0),M.value.forgotPassword?(m(),a(h,{key:1,class:`c-login__forgot`,onClick:i[3]||=e=>A(`forgot-password`)},{default:b(()=>[l(_(O(`cl_forgot`,`忘记密码?`)),1)]),_:1})):o(`v-if`,!0)])):o(`v-if`,!0)]),_:1},8,[`model`]),[[v,P.value===`password`]]),o(` ===== 验证码登录表单 ===== `),x(u(g,{ref_key:`captchaFormRef`,ref:z,model:B,rules:Oe,class:`c-login__form`},{default:b(()=>[u(f,{path:`account`,class:`c-login__form-item`},{default:b(()=>[u(d,{value:B.account,"onUpdate:value":i[4]||=e=>B.account=e,placeholder:O(`cl_phone_ph`,`请输入手机号`),clearable:``,maxlength:`11`},{prefix:b(()=>[u(e,{name:`mdi:cellphone`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),u(f,{path:`code`,class:`c-login__form-item`},{default:b(()=>[c(`div`,ye,[u(d,{value:B.code,"onUpdate:value":i[5]||=e=>B.code=e,placeholder:O(`cl_code_ph`,`请输入验证码`),clearable:``,class:`c-login__code-input`,onKeyup:S(q,[`enter`])},{prefix:b(()=>[u(e,{name:`mdi:shield-key-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`]),u(y,{disabled:V.value>0||!B.account,class:`c-login__send-code-btn`,onClick:Ne},{default:b(()=>[l(_(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`]]),o(` ===== 登录按钮(密码和验证码模式) ===== `),P.value===`password`||P.value===`captcha`?(m(),a(y,{key:0,type:`primary`,block:``,loading:k.loading,disabled:P.value===`password`&&M.value.captchaVerify&&!W.value,class:`c-login__submit-btn`,onClick:i[6]||=e=>P.value===`password`?R():q()},{default:b(()=>[l(_(P.value===`password`&&M.value.captchaVerify&&!W.value?O(`cl_verify_first`,`请先完成人机验证`):O(`cl_login_btn`,`登 录`)),1)]),_:1},8,[`loading`,`disabled`])):o(`v-if`,!0),o(` ===== 人机验证拼图(仅密码登录模式) ===== `),M.value.captchaVerify&&P.value===`password`?(m(),s(`div`,be,[u(ee,{ref_key:`captchaRef`,ref:U,"trigger-text":``,theme:`dark`,onSuccess:ke,onFail:Ae,onChange:je},null,512)])):o(`v-if`,!0),o(` ===== 返回按钮(验证码子视图) ===== `),P.value===`captcha`?(m(),a(y,{key:2,block:``,class:`c-login__back-btn`,onClick:i[7]||=e=>P.value=`password`},{default:b(()=>[l(_(O(`cl_back`,`返回`)),1)]),_:1})):o(`v-if`,!0),o(` ===== 扫码登录面板 ===== `),P.value===`qrcode`?(m(),s(`div`,xe,[c(`div`,E,[u(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`]),i[17]||=le(`<div class="c-login__qr-scan-line" data-v-ff3e5593></div><div class="c-login__qr-corner c-login__qr-corner--tl" data-v-ff3e5593></div><div class="c-login__qr-corner c-login__qr-corner--tr" data-v-ff3e5593></div><div class="c-login__qr-corner c-login__qr-corner--bl" data-v-ff3e5593></div><div class="c-login__qr-corner c-login__qr-corner--br" data-v-ff3e5593></div>`,5)]),c(`p`,Se,[u(e,{name:`mdi:cellphone-check`,size:13}),l(` `+_(O(`cl_qr_hint`,`打开手机扫码后点击「确认」即可登录`)),1)]),u(y,{text:``,size:`tiny`,class:`c-login__qr-refresh`,onClick:Fe},{icon:b(()=>[u(e,{name:`mdi:refresh`,size:12})]),default:b(()=>[l(` `+_(O(`cl_qr_refresh`,`二维码已失效?点击刷新`)),1)]),_:1})])):o(`v-if`,!0),o(` ===== 返回按钮(扫码子视图,在二维码下方) ===== `),P.value===`qrcode`?(m(),a(y,{key:4,block:``,class:`c-login__back-btn`,onClick:i[8]||=e=>P.value=`password`},{default:b(()=>[l(_(O(`cl_back`,`返回`)),1)]),_:1})):o(`v-if`,!0),o(` ===== 注册表单 ===== `),x(u(g,{ref_key:`registerFormRef`,ref:Y,model:X,rules:Ie,class:`c-login__form`},{default:b(()=>[u(f,{path:`phone`,class:`c-login__form-item`},{default:b(()=>[u(d,{value:X.phone,"onUpdate:value":i[9]||=e=>X.phone=e,placeholder:O(`cl_phone_ph`,`请输入手机号`),clearable:``,maxlength:`11`},{prefix:b(()=>[u(e,{name:`mdi:cellphone`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),u(f,{path:`code`,class:`c-login__form-item`},{default:b(()=>[c(`div`,Ce,[u(d,{value:X.code,"onUpdate:value":i[10]||=e=>X.code=e,placeholder:O(`cl_code_ph`,`请输入验证码`),clearable:``,class:`c-login__code-input`},{prefix:b(()=>[u(e,{name:`mdi:shield-key-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`]),u(y,{disabled:Z.value>0||!X.phone,class:`c-login__send-code-btn`,onClick:Le},{default:b(()=>[l(_(Z.value>0?`${Z.value}s ${O(`cl_resend`,`后重发`)}`:O(`cl_get_code`,`获取验证码`)),1)]),_:1},8,[`disabled`])])]),_:1}),u(f,{path:`password`,class:`c-login__form-item`},{default:b(()=>[u(d,{value:X.password,"onUpdate:value":i[11]||=e=>X.password=e,type:`password`,"show-password-on":`click`,placeholder:O(`cl_password_ph`,`请输入密码`)},{prefix:b(()=>[u(e,{name:`mdi:lock-outline`,size:16,class:`c-login__input-icon`})]),_:1},8,[`value`,`placeholder`])]),_:1}),u(f,{path:`confirmPassword`,class:`c-login__form-item`},{default:b(()=>[u(d,{value:X.confirmPassword,"onUpdate:value":i[12]||=e=>X.confirmPassword=e,type:`password`,"show-password-on":`click`,placeholder:O(`cl_confirm_pw_ph`,`请再次输入密码`),onKeyup:S($,[`enter`])},{prefix:b(()=>[u(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`]]),o(` ===== 注册确认按钮 ===== `),P.value===`register`?(m(),a(y,{key:5,type:`primary`,block:``,loading:k.loading,class:`c-login__submit-btn`,onClick:$},{default:b(()=>[l(_(O(`cl_confirm`,`确认`)),1)]),_:1},8,[`loading`])):o(`v-if`,!0),o(` ===== 注册视图返回按钮 ===== `),P.value===`register`?(m(),a(y,{key:6,block:``,class:`c-login__back-btn`,onClick:i[13]||=e=>P.value=`password`},{default:b(()=>[l(_(O(`cl_back`,`返回`)),1)]),_:1})):o(`v-if`,!0),o(` ===== 登录方式切换(仅密码登录默认视图显示) ===== `),P.value===`password`&&(M.value.captchaLogin||M.value.qrcodeLogin)?(m(),s(`div`,we,[M.value.captchaLogin?(m(),a(y,{key:0,class:`c-login__mode-btn`,size:`small`,onClick:i[14]||=e=>P.value=`captcha`},{default:b(()=>[l(_(O(`cl_captcha_login`,`验证码登录`)),1)]),_:1})):o(`v-if`,!0),M.value.qrcodeLogin?(m(),a(y,{key:1,class:`c-login__mode-btn`,size:`small`,onClick:i[15]||=e=>P.value=`qrcode`},{default:b(()=>[l(_(O(`cl_qr_login`,`扫码登录`)),1)]),_:1})):o(`v-if`,!0)])):o(`v-if`,!0),o(` ===== 其他登录方式(仅密码登录默认视图显示) ===== `),P.value===`password`&&M.value.socialLogin&&N.value.length?(m(),s(r,{key:8},[u(C,{class:`c-login__divider`},{default:b(()=>[l(_(O(`cl_social_login`,`其他账号登录`)),1)]),_:1}),c(`div`,Te,[(m(!0),s(r,null,ue(N.value,t=>(m(),a(w,{key:t.key,trigger:`hover`,placement:`top`},{trigger:b(()=>[u(y,{circle:``,class:`c-login__social-btn`,onClick:e=>A(`social-login`,t.key)},{icon:b(()=>[u(e,{name:t.icon,size:16},null,8,[`name`])]),_:2},1032,[`onClick`])]),default:b(()=>[l(` `+_(t.label),1)]),_:2},1024))),128))])],64)):o(`v-if`,!0),o(` ===== 注册入口(仅密码登录默认视图显示) ===== `),P.value===`password`&&M.value.register?(m(),s(`div`,Ee,[u(h,{class:`c-login__register-hint`},{default:b(()=>[l(_(O(`cl_no_account`,`还没有账号?`)),1)]),_:1}),u(h,{class:`c-login__register-link`,onClick:i[16]||=e=>P.value=`register`},{default:b(()=>[l(_(O(`cl_register`,`注册账号`)),1)]),_:1})])):o(`v-if`,!0)])}}}),[[`__scopeId`,`data-v-ff3e5593`]]);export{T as n,w as r,D 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{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};
2
2
  //# sourceMappingURL=C_Login2.js.map