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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/README.md +294 -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 +3 -3
  319. package/dist/constants2.d.ts +4 -4
  320. package/dist/constants3.d.ts +3 -3
  321. package/dist/constants4.d.ts +4 -4
  322. package/dist/constants5.d.ts +2 -2
  323. package/dist/data.d.ts +2 -2
  324. package/dist/index.cjs.map +1 -1
  325. package/dist/index.js.map +1 -1
  326. package/dist/index.vue.d.ts +1 -1
  327. package/dist/index10.vue.d.ts +1 -1
  328. package/dist/index11.vue.d.ts +4 -4
  329. package/dist/index12.vue.d.ts +1 -1
  330. package/dist/index13.vue.d.ts +2 -2
  331. package/dist/index14.vue.d.ts +3 -3
  332. package/dist/index15.vue.d.ts +3 -3
  333. package/dist/index16.vue.d.ts +2 -2
  334. package/dist/index17.vue.d.ts +1 -1
  335. package/dist/index18.vue.d.ts +5 -5
  336. package/dist/index2.vue.d.ts +1 -1
  337. package/dist/index20.vue.d.ts +1 -1
  338. package/dist/index22.vue.d.ts +1 -1
  339. package/dist/index23.vue.d.ts +2 -2
  340. package/dist/index24.vue.d.ts +1 -1
  341. package/dist/index26.vue.d.ts +3 -3
  342. package/dist/index27.vue.d.ts +2 -2
  343. package/dist/index28.vue.d.ts +3 -3
  344. package/dist/index3.vue.d.ts +3 -3
  345. package/dist/index4.vue.d.ts +2 -2
  346. package/dist/index6.vue.d.ts +6 -6
  347. package/dist/index7.vue.d.ts +2 -2
  348. package/dist/index8.vue.d.ts +1 -1
  349. package/dist/index9.vue.d.ts +2 -2
  350. package/dist/menuAdapter.js.map +1 -1
  351. package/dist/resolver.cjs.map +1 -1
  352. package/dist/resolver.js.map +1 -1
  353. package/dist/style.css +1483 -1483
  354. package/dist/themes/glass-morphism.css +1 -0
  355. package/dist/useCalendarEvents.d.ts +1 -1
  356. package/dist/useCollapsePanel.d.ts +1 -1
  357. package/dist/useCropperCore.d.ts +2 -2
  358. package/dist/useDraggableLayout.d.ts +4 -4
  359. package/dist/useDynamicFormState.d.ts +21 -21
  360. package/dist/useEdgeInteraction.d.ts +2 -2
  361. package/dist/useModalEdit.d.ts +1 -1
  362. package/dist/useQRCode.d.ts +3 -3
  363. package/dist/useSearchState.d.ts +2 -2
  364. package/dist/useSignatureHistory.d.ts +4 -4
  365. package/dist/useSplitResize.d.ts +2 -2
  366. package/dist/useTimeSelection.d.ts +2 -2
  367. package/dist/useTreeOperations.d.ts +3 -3
  368. package/dist/useWorkflowValidation.d.ts +6 -6
  369. package/package.json +11 -8
  370. package/dist/C_ActionBar-jZPsiXtT.css.map +0 -1
  371. package/dist/C_ActionBar-nnfbZCea.css.map +0 -1
  372. package/dist/C_Code-DPZlNSxL.css.map +0 -1
  373. package/dist/C_Code-avZjAnHD.css.map +0 -1
  374. package/dist/C_Editor-C49qURgb.css.map +0 -1
  375. package/dist/C_Editor-DuxBD9Qg.css.map +0 -1
  376. package/dist/C_FullCalendar-1U4VlG7X.css.map +0 -1
  377. package/dist/C_FullCalendar-DLUdCtqf.css.map +0 -1
  378. package/dist/C_Steps-BHYNJI1l.css.map +0 -1
  379. package/dist/C_Steps-DY7vl-4I.css.map +0 -1
  380. package/dist/C_Time-Bd_e1YDN.css.map +0 -1
  381. package/dist/C_Time-CKb_fwdm.css.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"C_GlobalSearch2.js","names":["placeholder","emptyText","resultGroupTitle"],"sources":["../src/components/C_GlobalSearch/composables/useGlobalSearch.ts","../src/components/C_GlobalSearch/index.vue","../src/components/C_GlobalSearch/index.vue","../src/components/C_GlobalSearch/index.vue"],"sourcesContent":["import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'\nimport type { SearchMenuItem, HistoryItem, GlobalSearchOptions } from '../types'\n\n/**\n * 全局搜索逻辑 — 管理菜单搜索、历史记录、键盘导航\n *\n * 已解耦:不依赖任何项目级 store / router,\n * 所有外部数据通过 options 注入。\n */\nexport function useGlobalSearch(options: GlobalSearchOptions) {\n const {\n menuItems: getMenuItems,\n isDark: getIsDark,\n onSelect,\n storageKey = 'robot-search-history',\n maxHistory = 10,\n maxDisplayHistory = 5,\n } = options\n\n // ==================== 响应式状态 ====================\n const showDialog = ref(false)\n const searchValue = ref('')\n const selectedIndex = ref(0)\n const searchInputRef = ref<HTMLInputElement>()\n const searchHistory = ref<HistoryItem[]>([])\n\n // ==================== 计算属性 ====================\n const isDark = computed(() => getIsDark?.() ?? false)\n\n const filteredMenuItems = computed(() => {\n if (!searchValue.value.trim()) return []\n const query = searchValue.value.toLowerCase()\n const items = getMenuItems()\n return items.filter(\n item =>\n item.label.toLowerCase().includes(query) ||\n formatMenuPath(item.key).toLowerCase().includes(query)\n )\n })\n\n const displayHistory = computed(() =>\n searchHistory.value.slice(0, maxDisplayHistory)\n )\n const hasResults = computed(() => filteredMenuItems.value.length > 0)\n const hasContent = computed(\n () => hasResults.value || displayHistory.value.length > 0\n )\n\n // ==================== 工具函数 ====================\n const getItemClasses = (index: number, isHistory: boolean) => [\n 'robot-result-item',\n isHistory && 'robot-history-item',\n { 'robot-result-item-selected': selectedIndex.value === index },\n ]\n\n const highlightMatch = (text: string, search: string): string => {\n if (!search) return text\n const regex = new RegExp(\n `(${search.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`,\n 'gi'\n )\n return text.replace(regex, '<mark class=\"robot-highlight\">$1</mark>')\n }\n\n const formatMenuPath = (key: string): string =>\n key.replace(/^\\//, '').replace(/\\//g, ' > ') || '首页'\n\n // ==================== 搜索历史管理 ====================\n const addToHistory = (menuItem: SearchMenuItem) => {\n if (!menuItem.label.trim()) return\n const query = menuItem.label\n const filtered = searchHistory.value.filter(\n (item: HistoryItem) =>\n !(item.query === query && item.menuItem?.key === menuItem.key)\n )\n searchHistory.value = [\n { query, menuItem, timestamp: Date.now() },\n ...filtered,\n ].slice(0, maxHistory)\n localStorage.setItem(storageKey, JSON.stringify(searchHistory.value))\n }\n\n const loadHistory = () => {\n try {\n const saved = localStorage.getItem(storageKey)\n if (saved) {\n const parsed = JSON.parse(saved)\n searchHistory.value = parsed.map((item: any) =>\n typeof item === 'string'\n ? { query: item, menuItem: null, timestamp: 0 }\n : item\n )\n }\n } catch (error) {\n console.error('加载搜索历史失败:', error)\n }\n }\n\n const clearHistory = () => {\n searchHistory.value = []\n localStorage.removeItem(storageKey)\n }\n\n const removeHistoryItem = (index: number) => {\n searchHistory.value.splice(index, 1)\n localStorage.setItem(storageKey, JSON.stringify(searchHistory.value))\n if (selectedIndex.value >= searchHistory.value.length) {\n selectedIndex.value = Math.max(0, searchHistory.value.length - 1)\n }\n }\n\n // ==================== 对话框控制 ====================\n const openDialog = async () => {\n showDialog.value = true\n searchValue.value = ''\n selectedIndex.value = 0\n await nextTick()\n searchInputRef.value?.focus()\n }\n\n const closeDialog = () => {\n showDialog.value = false\n searchValue.value = ''\n selectedIndex.value = 0\n }\n\n // ==================== 选择项目 ====================\n const selectHistoryItem = (historyItem: HistoryItem) => {\n if (historyItem.menuItem) {\n const hasChildren = !!historyItem.menuItem.children?.length\n onSelect?.(historyItem.menuItem.key, hasChildren)\n closeDialog()\n } else {\n searchValue.value = historyItem.query\n selectedIndex.value = 0\n }\n }\n\n const selectItem = (item: SearchMenuItem) => {\n addToHistory(item)\n const hasChildren = !!item.children?.length\n onSelect?.(item.key, hasChildren)\n closeDialog()\n }\n\n // ==================== 键盘导航 ====================\n const handleEnter = () => {\n const isHistoryMode =\n !searchValue.value.trim() && displayHistory.value.length > 0\n const isSearchMode =\n searchValue.value.trim() && filteredMenuItems.value.length > 0\n\n if (isHistoryMode) {\n const historyItem = displayHistory.value[selectedIndex.value]\n if (historyItem) selectHistoryItem(historyItem)\n } else if (isSearchMode) {\n const item = filteredMenuItems.value[selectedIndex.value]\n if (item) selectItem(item)\n }\n }\n\n const focusNext = () => {\n const maxIndex = !searchValue.value.trim()\n ? displayHistory.value.length - 1\n : filteredMenuItems.value.length - 1\n selectedIndex.value = Math.min(selectedIndex.value + 1, maxIndex)\n }\n\n const focusPrev = () => {\n selectedIndex.value = Math.max(selectedIndex.value - 1, 0)\n }\n\n // ==================== 监听器和生命周期 ====================\n watch(searchValue, () => {\n selectedIndex.value = 0\n })\n\n onMounted(() => {\n loadHistory()\n const handleKeydown = (e: KeyboardEvent) => {\n if ((e.ctrlKey || e.metaKey) && e.key === 'k') {\n e.preventDefault()\n openDialog()\n }\n }\n document.addEventListener('keydown', handleKeydown)\n onUnmounted(() => document.removeEventListener('keydown', handleKeydown))\n })\n\n return {\n showDialog,\n searchValue,\n selectedIndex,\n searchInputRef,\n isDark,\n filteredMenuItems,\n displayHistory,\n hasResults,\n hasContent,\n getItemClasses,\n highlightMatch,\n formatMenuPath,\n clearHistory,\n removeHistoryItem,\n openDialog,\n closeDialog,\n selectHistoryItem,\n selectItem,\n handleEnter,\n focusNext,\n focusPrev,\n }\n}\n","/* unplugin-vue-components disabled */<!--\n * @Description: 全局搜索组件 - 命令面板式搜索\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"robot-search-palette\">\n <!-- 搜索触发按钮 -->\n <div\n class=\"robot-search-trigger\"\n :class=\"{ 'trigger-dark': isDark }\"\n @click=\"openDialog\"\n >\n <div class=\"i-mdi:search robot-trigger-icon\" />\n <span class=\"robot-trigger-text\">搜索</span>\n <span class=\"robot-trigger-shortcut\">Ctrl K</span>\n </div>\n\n <!-- 搜索对话框 -->\n <Teleport to=\"body\">\n <Transition name=\"robot-dialog\">\n <div\n v-if=\"showDialog\"\n class=\"robot-dialog-overlay\"\n @click=\"closeDialog\"\n @keydown.esc=\"closeDialog\"\n >\n <div\n class=\"robot-dialog-container\"\n :class=\"{ 'dialog-dark': isDark }\"\n @click.stop\n >\n <!-- 搜索头部 -->\n <div class=\"robot-search-header\">\n <div class=\"i-mdi:magnify robot-search-icon\" />\n <input\n ref=\"searchInputRef\"\n v-model=\"searchValue\"\n type=\"text\"\n :placeholder=\"placeholder\"\n class=\"robot-search-input\"\n @keydown.esc=\"closeDialog\"\n @keydown.enter=\"handleEnter\"\n @keydown.arrow-down=\"focusNext\"\n @keydown.arrow-up=\"focusPrev\"\n />\n <button\n class=\"robot-close-btn\"\n @click=\"closeDialog\"\n >\n <div class=\"i-mdi:close\" />\n </button>\n </div>\n\n <!-- 搜索内容 -->\n <div class=\"robot-search-content\">\n <!-- 空状态 -->\n <div\n v-if=\"!hasContent\"\n class=\"robot-empty-state\"\n >\n <div class=\"i-mdi:magnify robot-empty-icon\" />\n <p>{{ emptyText }}</p>\n </div>\n\n <!-- 搜索历史 -->\n <template\n v-else-if=\"!searchValue.trim() && displayHistory.length > 0\"\n >\n <div class=\"robot-search-results\">\n <div class=\"robot-result-group\">\n <div class=\"robot-group-title\">\n <div\n class=\"i-mdi:clock-time-four-outline robot-group-icon\"\n />\n 最近搜索\n <span class=\"robot-group-count\">{{\n displayHistory.length\n }}</span>\n <button\n class=\"robot-clear-history\"\n @click=\"clearHistory\"\n title=\"清空历史\"\n >\n <div class=\"i-mdi:delete-outline\" />\n </button>\n </div>\n <div class=\"robot-group-items\">\n <div\n v-for=\"(historyItem, index) in displayHistory\"\n :key=\"`history-${index}`\"\n :class=\"getItemClasses(index, true)\"\n @click=\"selectHistoryItem(historyItem)\"\n @mouseenter=\"selectedIndex = index\"\n >\n <component\n v-if=\"historyItem.menuItem?.icon\"\n :is=\"historyItem.menuItem.icon\"\n class=\"robot-item-icon\"\n />\n <div\n v-else\n class=\"robot-item-icon robot-default-icon\"\n >\n <div class=\"i-mdi:file-document-outline\" />\n </div>\n <div class=\"robot-item-content\">\n <div class=\"robot-item-label\">\n {{ historyItem.query }}\n </div>\n <div class=\"robot-item-desc\">\n {{\n formatMenuPath(historyItem.menuItem?.key || '')\n }}\n </div>\n </div>\n <button\n class=\"robot-history-delete\"\n @click.stop=\"removeHistoryItem(index)\"\n title=\"删除此记录\"\n >\n <div class=\"i-mdi:close\" />\n </button>\n </div>\n </div>\n </div>\n </div>\n </template>\n\n <!-- 搜索结果 -->\n <template v-else-if=\"searchValue.trim()\">\n <div class=\"robot-search-results\">\n <!-- 无结果状态 -->\n <div\n v-if=\"!hasResults\"\n class=\"robot-empty-state\"\n >\n <div class=\"i-mdi:magnify robot-empty-icon\" />\n <p>未找到 \"{{ searchValue }}\" 相关内容</p>\n </div>\n\n <!-- 搜索结果列表 -->\n <div\n v-else\n class=\"robot-result-group\"\n >\n <div class=\"robot-group-title\">\n <div class=\"i-mdi:menu robot-group-icon\" />\n {{ resultGroupTitle }}\n <span class=\"robot-group-count\">{{\n filteredMenuItems.length\n }}</span>\n </div>\n <div class=\"robot-group-items\">\n <div\n v-for=\"(item, index) in filteredMenuItems\"\n :key=\"item.key\"\n :class=\"getItemClasses(index, false)\"\n @click=\"selectItem(item)\"\n @mouseenter=\"selectedIndex = index\"\n >\n <component\n v-if=\"item.icon\"\n :is=\"item.icon\"\n class=\"robot-item-icon\"\n />\n <div\n v-else\n class=\"robot-item-icon robot-default-icon\"\n >\n <div class=\"i-mdi:file-document-outline\" />\n </div>\n <div class=\"robot-item-content\">\n <div\n class=\"robot-item-label\"\n v-html=\"highlightMatch(item.label, searchValue)\"\n ></div>\n <div class=\"robot-item-desc\">\n {{ formatMenuPath(item.key) }}\n </div>\n </div>\n <div\n v-if=\"item.children?.length\"\n class=\"robot-item-badge\"\n >\n {{ item.children.length }}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </div>\n\n <!-- 搜索底部 -->\n <div class=\"robot-search-footer\">\n <div class=\"robot-footer-actions\">\n <div class=\"robot-action-item\">\n <kbd>↵</kbd><span>选择</span>\n </div>\n <div class=\"robot-action-item\">\n <kbd>↑↓</kbd><span>导航</span>\n </div>\n <div class=\"robot-action-item\">\n <kbd>esc</kbd><span>关闭</span>\n </div>\n </div>\n <div\n v-if=\"hasContent\"\n class=\"robot-footer-info\"\n >\n <span v-if=\"searchValue.trim() && hasResults\">\n 共 {{ filteredMenuItems.length }} 项结果\n </span>\n <span\n v-else-if=\"!searchValue.trim() && displayHistory.length > 0\"\n >\n {{ displayHistory.length }} 条历史记录\n </span>\n </div>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { useGlobalSearch } from './composables/useGlobalSearch'\n import type { GlobalSearchOptions } from './types'\n\n defineOptions({ name: 'C_GlobalSearch' })\n\n interface Props {\n /** useGlobalSearch 配置(必须传入 menuItems + onSelect) */\n options: GlobalSearchOptions\n /** 搜索框 placeholder */\n placeholder?: string\n /** 无内容提示文字 */\n emptyText?: string\n /** 结果分组标题 */\n resultGroupTitle?: string\n }\n\n const props = withDefaults(defineProps<Props>(), {\n placeholder: '搜索导航菜单...',\n emptyText: '输入关键词搜索菜单',\n resultGroupTitle: '菜单导航',\n })\n\n const {\n showDialog,\n searchValue,\n selectedIndex,\n searchInputRef,\n isDark,\n filteredMenuItems,\n displayHistory,\n hasResults,\n hasContent,\n getItemClasses,\n highlightMatch,\n formatMenuPath,\n clearHistory,\n removeHistoryItem,\n openDialog,\n closeDialog,\n selectHistoryItem,\n selectItem,\n handleEnter,\n focusNext,\n focusPrev,\n } = useGlobalSearch(props.options)\n\n defineExpose({ openDialog, closeDialog })\n</script>\n\n<style lang=\"scss\" scoped>\n @use './index.scss';\n</style>\n","/* unplugin-vue-components disabled */<!--\n * @Description: 全局搜索组件 - 命令面板式搜索\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"robot-search-palette\">\n <!-- 搜索触发按钮 -->\n <div\n class=\"robot-search-trigger\"\n :class=\"{ 'trigger-dark': isDark }\"\n @click=\"openDialog\"\n >\n <div class=\"i-mdi:search robot-trigger-icon\" />\n <span class=\"robot-trigger-text\">搜索</span>\n <span class=\"robot-trigger-shortcut\">Ctrl K</span>\n </div>\n\n <!-- 搜索对话框 -->\n <Teleport to=\"body\">\n <Transition name=\"robot-dialog\">\n <div\n v-if=\"showDialog\"\n class=\"robot-dialog-overlay\"\n @click=\"closeDialog\"\n @keydown.esc=\"closeDialog\"\n >\n <div\n class=\"robot-dialog-container\"\n :class=\"{ 'dialog-dark': isDark }\"\n @click.stop\n >\n <!-- 搜索头部 -->\n <div class=\"robot-search-header\">\n <div class=\"i-mdi:magnify robot-search-icon\" />\n <input\n ref=\"searchInputRef\"\n v-model=\"searchValue\"\n type=\"text\"\n :placeholder=\"placeholder\"\n class=\"robot-search-input\"\n @keydown.esc=\"closeDialog\"\n @keydown.enter=\"handleEnter\"\n @keydown.arrow-down=\"focusNext\"\n @keydown.arrow-up=\"focusPrev\"\n />\n <button\n class=\"robot-close-btn\"\n @click=\"closeDialog\"\n >\n <div class=\"i-mdi:close\" />\n </button>\n </div>\n\n <!-- 搜索内容 -->\n <div class=\"robot-search-content\">\n <!-- 空状态 -->\n <div\n v-if=\"!hasContent\"\n class=\"robot-empty-state\"\n >\n <div class=\"i-mdi:magnify robot-empty-icon\" />\n <p>{{ emptyText }}</p>\n </div>\n\n <!-- 搜索历史 -->\n <template\n v-else-if=\"!searchValue.trim() && displayHistory.length > 0\"\n >\n <div class=\"robot-search-results\">\n <div class=\"robot-result-group\">\n <div class=\"robot-group-title\">\n <div\n class=\"i-mdi:clock-time-four-outline robot-group-icon\"\n />\n 最近搜索\n <span class=\"robot-group-count\">{{\n displayHistory.length\n }}</span>\n <button\n class=\"robot-clear-history\"\n @click=\"clearHistory\"\n title=\"清空历史\"\n >\n <div class=\"i-mdi:delete-outline\" />\n </button>\n </div>\n <div class=\"robot-group-items\">\n <div\n v-for=\"(historyItem, index) in displayHistory\"\n :key=\"`history-${index}`\"\n :class=\"getItemClasses(index, true)\"\n @click=\"selectHistoryItem(historyItem)\"\n @mouseenter=\"selectedIndex = index\"\n >\n <component\n v-if=\"historyItem.menuItem?.icon\"\n :is=\"historyItem.menuItem.icon\"\n class=\"robot-item-icon\"\n />\n <div\n v-else\n class=\"robot-item-icon robot-default-icon\"\n >\n <div class=\"i-mdi:file-document-outline\" />\n </div>\n <div class=\"robot-item-content\">\n <div class=\"robot-item-label\">\n {{ historyItem.query }}\n </div>\n <div class=\"robot-item-desc\">\n {{\n formatMenuPath(historyItem.menuItem?.key || '')\n }}\n </div>\n </div>\n <button\n class=\"robot-history-delete\"\n @click.stop=\"removeHistoryItem(index)\"\n title=\"删除此记录\"\n >\n <div class=\"i-mdi:close\" />\n </button>\n </div>\n </div>\n </div>\n </div>\n </template>\n\n <!-- 搜索结果 -->\n <template v-else-if=\"searchValue.trim()\">\n <div class=\"robot-search-results\">\n <!-- 无结果状态 -->\n <div\n v-if=\"!hasResults\"\n class=\"robot-empty-state\"\n >\n <div class=\"i-mdi:magnify robot-empty-icon\" />\n <p>未找到 \"{{ searchValue }}\" 相关内容</p>\n </div>\n\n <!-- 搜索结果列表 -->\n <div\n v-else\n class=\"robot-result-group\"\n >\n <div class=\"robot-group-title\">\n <div class=\"i-mdi:menu robot-group-icon\" />\n {{ resultGroupTitle }}\n <span class=\"robot-group-count\">{{\n filteredMenuItems.length\n }}</span>\n </div>\n <div class=\"robot-group-items\">\n <div\n v-for=\"(item, index) in filteredMenuItems\"\n :key=\"item.key\"\n :class=\"getItemClasses(index, false)\"\n @click=\"selectItem(item)\"\n @mouseenter=\"selectedIndex = index\"\n >\n <component\n v-if=\"item.icon\"\n :is=\"item.icon\"\n class=\"robot-item-icon\"\n />\n <div\n v-else\n class=\"robot-item-icon robot-default-icon\"\n >\n <div class=\"i-mdi:file-document-outline\" />\n </div>\n <div class=\"robot-item-content\">\n <div\n class=\"robot-item-label\"\n v-html=\"highlightMatch(item.label, searchValue)\"\n ></div>\n <div class=\"robot-item-desc\">\n {{ formatMenuPath(item.key) }}\n </div>\n </div>\n <div\n v-if=\"item.children?.length\"\n class=\"robot-item-badge\"\n >\n {{ item.children.length }}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </div>\n\n <!-- 搜索底部 -->\n <div class=\"robot-search-footer\">\n <div class=\"robot-footer-actions\">\n <div class=\"robot-action-item\">\n <kbd>↵</kbd><span>选择</span>\n </div>\n <div class=\"robot-action-item\">\n <kbd>↑↓</kbd><span>导航</span>\n </div>\n <div class=\"robot-action-item\">\n <kbd>esc</kbd><span>关闭</span>\n </div>\n </div>\n <div\n v-if=\"hasContent\"\n class=\"robot-footer-info\"\n >\n <span v-if=\"searchValue.trim() && hasResults\">\n 共 {{ filteredMenuItems.length }} 项结果\n </span>\n <span\n v-else-if=\"!searchValue.trim() && displayHistory.length > 0\"\n >\n {{ displayHistory.length }} 条历史记录\n </span>\n </div>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { useGlobalSearch } from './composables/useGlobalSearch'\n import type { GlobalSearchOptions } from './types'\n\n defineOptions({ name: 'C_GlobalSearch' })\n\n interface Props {\n /** useGlobalSearch 配置(必须传入 menuItems + onSelect) */\n options: GlobalSearchOptions\n /** 搜索框 placeholder */\n placeholder?: string\n /** 无内容提示文字 */\n emptyText?: string\n /** 结果分组标题 */\n resultGroupTitle?: string\n }\n\n const props = withDefaults(defineProps<Props>(), {\n placeholder: '搜索导航菜单...',\n emptyText: '输入关键词搜索菜单',\n resultGroupTitle: '菜单导航',\n })\n\n const {\n showDialog,\n searchValue,\n selectedIndex,\n searchInputRef,\n isDark,\n filteredMenuItems,\n displayHistory,\n hasResults,\n hasContent,\n getItemClasses,\n highlightMatch,\n formatMenuPath,\n clearHistory,\n removeHistoryItem,\n openDialog,\n closeDialog,\n selectHistoryItem,\n selectItem,\n handleEnter,\n focusNext,\n focusPrev,\n } = useGlobalSearch(props.options)\n\n defineExpose({ openDialog, closeDialog })\n</script>\n\n<style lang=\"scss\" scoped>\n @use './index.scss';\n</style>\n","<!--\n * @Description: 全局搜索组件 - 命令面板式搜索\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"robot-search-palette\">\n <!-- 搜索触发按钮 -->\n <div\n class=\"robot-search-trigger\"\n :class=\"{ 'trigger-dark': isDark }\"\n @click=\"openDialog\"\n >\n <div class=\"i-mdi:search robot-trigger-icon\" />\n <span class=\"robot-trigger-text\">搜索</span>\n <span class=\"robot-trigger-shortcut\">Ctrl K</span>\n </div>\n\n <!-- 搜索对话框 -->\n <Teleport to=\"body\">\n <Transition name=\"robot-dialog\">\n <div\n v-if=\"showDialog\"\n class=\"robot-dialog-overlay\"\n @click=\"closeDialog\"\n @keydown.esc=\"closeDialog\"\n >\n <div\n class=\"robot-dialog-container\"\n :class=\"{ 'dialog-dark': isDark }\"\n @click.stop\n >\n <!-- 搜索头部 -->\n <div class=\"robot-search-header\">\n <div class=\"i-mdi:magnify robot-search-icon\" />\n <input\n ref=\"searchInputRef\"\n v-model=\"searchValue\"\n type=\"text\"\n :placeholder=\"placeholder\"\n class=\"robot-search-input\"\n @keydown.esc=\"closeDialog\"\n @keydown.enter=\"handleEnter\"\n @keydown.arrow-down=\"focusNext\"\n @keydown.arrow-up=\"focusPrev\"\n />\n <button\n class=\"robot-close-btn\"\n @click=\"closeDialog\"\n >\n <div class=\"i-mdi:close\" />\n </button>\n </div>\n\n <!-- 搜索内容 -->\n <div class=\"robot-search-content\">\n <!-- 空状态 -->\n <div\n v-if=\"!hasContent\"\n class=\"robot-empty-state\"\n >\n <div class=\"i-mdi:magnify robot-empty-icon\" />\n <p>{{ emptyText }}</p>\n </div>\n\n <!-- 搜索历史 -->\n <template\n v-else-if=\"!searchValue.trim() && displayHistory.length > 0\"\n >\n <div class=\"robot-search-results\">\n <div class=\"robot-result-group\">\n <div class=\"robot-group-title\">\n <div\n class=\"i-mdi:clock-time-four-outline robot-group-icon\"\n />\n 最近搜索\n <span class=\"robot-group-count\">{{\n displayHistory.length\n }}</span>\n <button\n class=\"robot-clear-history\"\n @click=\"clearHistory\"\n title=\"清空历史\"\n >\n <div class=\"i-mdi:delete-outline\" />\n </button>\n </div>\n <div class=\"robot-group-items\">\n <div\n v-for=\"(historyItem, index) in displayHistory\"\n :key=\"`history-${index}`\"\n :class=\"getItemClasses(index, true)\"\n @click=\"selectHistoryItem(historyItem)\"\n @mouseenter=\"selectedIndex = index\"\n >\n <component\n v-if=\"historyItem.menuItem?.icon\"\n :is=\"historyItem.menuItem.icon\"\n class=\"robot-item-icon\"\n />\n <div\n v-else\n class=\"robot-item-icon robot-default-icon\"\n >\n <div class=\"i-mdi:file-document-outline\" />\n </div>\n <div class=\"robot-item-content\">\n <div class=\"robot-item-label\">\n {{ historyItem.query }}\n </div>\n <div class=\"robot-item-desc\">\n {{\n formatMenuPath(historyItem.menuItem?.key || '')\n }}\n </div>\n </div>\n <button\n class=\"robot-history-delete\"\n @click.stop=\"removeHistoryItem(index)\"\n title=\"删除此记录\"\n >\n <div class=\"i-mdi:close\" />\n </button>\n </div>\n </div>\n </div>\n </div>\n </template>\n\n <!-- 搜索结果 -->\n <template v-else-if=\"searchValue.trim()\">\n <div class=\"robot-search-results\">\n <!-- 无结果状态 -->\n <div\n v-if=\"!hasResults\"\n class=\"robot-empty-state\"\n >\n <div class=\"i-mdi:magnify robot-empty-icon\" />\n <p>未找到 \"{{ searchValue }}\" 相关内容</p>\n </div>\n\n <!-- 搜索结果列表 -->\n <div\n v-else\n class=\"robot-result-group\"\n >\n <div class=\"robot-group-title\">\n <div class=\"i-mdi:menu robot-group-icon\" />\n {{ resultGroupTitle }}\n <span class=\"robot-group-count\">{{\n filteredMenuItems.length\n }}</span>\n </div>\n <div class=\"robot-group-items\">\n <div\n v-for=\"(item, index) in filteredMenuItems\"\n :key=\"item.key\"\n :class=\"getItemClasses(index, false)\"\n @click=\"selectItem(item)\"\n @mouseenter=\"selectedIndex = index\"\n >\n <component\n v-if=\"item.icon\"\n :is=\"item.icon\"\n class=\"robot-item-icon\"\n />\n <div\n v-else\n class=\"robot-item-icon robot-default-icon\"\n >\n <div class=\"i-mdi:file-document-outline\" />\n </div>\n <div class=\"robot-item-content\">\n <div\n class=\"robot-item-label\"\n v-html=\"highlightMatch(item.label, searchValue)\"\n ></div>\n <div class=\"robot-item-desc\">\n {{ formatMenuPath(item.key) }}\n </div>\n </div>\n <div\n v-if=\"item.children?.length\"\n class=\"robot-item-badge\"\n >\n {{ item.children.length }}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </div>\n\n <!-- 搜索底部 -->\n <div class=\"robot-search-footer\">\n <div class=\"robot-footer-actions\">\n <div class=\"robot-action-item\">\n <kbd>↵</kbd><span>选择</span>\n </div>\n <div class=\"robot-action-item\">\n <kbd>↑↓</kbd><span>导航</span>\n </div>\n <div class=\"robot-action-item\">\n <kbd>esc</kbd><span>关闭</span>\n </div>\n </div>\n <div\n v-if=\"hasContent\"\n class=\"robot-footer-info\"\n >\n <span v-if=\"searchValue.trim() && hasResults\">\n 共 {{ filteredMenuItems.length }} 项结果\n </span>\n <span\n v-else-if=\"!searchValue.trim() && displayHistory.length > 0\"\n >\n {{ displayHistory.length }} 条历史记录\n </span>\n </div>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { useGlobalSearch } from './composables/useGlobalSearch'\n import type { GlobalSearchOptions } from './types'\n\n defineOptions({ name: 'C_GlobalSearch' })\n\n interface Props {\n /** useGlobalSearch 配置(必须传入 menuItems + onSelect) */\n options: GlobalSearchOptions\n /** 搜索框 placeholder */\n placeholder?: string\n /** 无内容提示文字 */\n emptyText?: string\n /** 结果分组标题 */\n resultGroupTitle?: string\n }\n\n const props = withDefaults(defineProps<Props>(), {\n placeholder: '搜索导航菜单...',\n emptyText: '输入关键词搜索菜单',\n resultGroupTitle: '菜单导航',\n })\n\n const {\n showDialog,\n searchValue,\n selectedIndex,\n searchInputRef,\n isDark,\n filteredMenuItems,\n displayHistory,\n hasResults,\n hasContent,\n getItemClasses,\n highlightMatch,\n formatMenuPath,\n clearHistory,\n removeHistoryItem,\n openDialog,\n closeDialog,\n selectHistoryItem,\n selectItem,\n handleEnter,\n focusNext,\n focusPrev,\n } = useGlobalSearch(props.options)\n\n defineExpose({ openDialog, closeDialog })\n</script>\n\n<style lang=\"scss\" scoped>\n @use './index.scss';\n</style>\n"],"mappings":"qhBASA,SAAgB,EAAgB,EAA8B,CAC5D,GAAM,CACJ,UAAW,EACX,OAAQ,EACR,WACA,aAAa,uBACb,aAAa,GACb,oBAAoB,GAClB,EAGE,EAAa,EAAI,GAAM,CACvB,EAAc,EAAI,GAAG,CACrB,EAAgB,EAAI,EAAE,CACtB,EAAiB,GAAuB,CACxC,EAAgB,EAAmB,EAAE,CAAC,CAGtC,EAAS,MAAe,KAAa,EAAI,GAAM,CAE/C,EAAoB,MAAe,CACvC,GAAI,CAAC,EAAY,MAAM,MAAM,CAAE,MAAO,EAAE,CACxC,IAAM,EAAQ,EAAY,MAAM,aAAa,CAE7C,OADc,GAAc,CACf,OACX,GACE,EAAK,MAAM,aAAa,CAAC,SAAS,EAAM,EACxC,EAAe,EAAK,IAAI,CAAC,aAAa,CAAC,SAAS,EAAM,CACzD,EACD,CAEI,EAAiB,MACrB,EAAc,MAAM,MAAM,EAAG,EAAkB,CAChD,CACK,EAAa,MAAe,EAAkB,MAAM,OAAS,EAAE,CAC/D,EAAa,MACX,EAAW,OAAS,EAAe,MAAM,OAAS,EACzD,CAGK,GAAkB,EAAe,IAAuB,CAC5D,oBACA,GAAa,qBACb,CAAE,6BAA8B,EAAc,QAAU,EAAO,CAChE,CAEK,GAAkB,EAAc,IAA2B,CAC/D,GAAI,CAAC,EAAQ,OAAO,EACpB,IAAM,EAAY,OAChB,IAAI,EAAO,QAAQ,sBAAuB,OAAO,CAAC,GAClD,KACD,CACD,OAAO,EAAK,QAAQ,EAAO,0CAA0C,EAGjE,EAAkB,GACtB,EAAI,QAAQ,MAAO,GAAG,CAAC,QAAQ,MAAO,MAAM,EAAI,KAG5C,EAAgB,GAA6B,CACjD,GAAI,CAAC,EAAS,MAAM,MAAM,CAAE,OAC5B,IAAM,EAAQ,EAAS,MACjB,EAAW,EAAc,MAAM,OAClC,GACC,EAAE,EAAK,QAAU,GAAS,EAAK,UAAU,MAAQ,EAAS,KAC7D,CACD,EAAc,MAAQ,CACpB,CAAE,QAAO,WAAU,UAAW,KAAK,KAAK,CAAE,CAC1C,GAAG,EACJ,CAAC,MAAM,EAAG,EAAW,CACtB,aAAa,QAAQ,EAAY,KAAK,UAAU,EAAc,MAAM,CAAC,EAGjE,MAAoB,CACxB,GAAI,CACF,IAAM,EAAQ,aAAa,QAAQ,EAAW,CAC1C,IAEF,EAAc,MADC,KAAK,MAAM,EAAM,CACH,IAAK,GAChC,OAAO,GAAS,SACZ,CAAE,MAAO,EAAM,SAAU,KAAM,UAAW,EAAG,CAC7C,EACL,QAEI,EAAO,CACd,QAAQ,MAAM,YAAa,EAAM,GAI/B,MAAqB,CACzB,EAAc,MAAQ,EAAE,CACxB,aAAa,WAAW,EAAW,EAG/B,EAAqB,GAAkB,CAC3C,EAAc,MAAM,OAAO,EAAO,EAAE,CACpC,aAAa,QAAQ,EAAY,KAAK,UAAU,EAAc,MAAM,CAAC,CACjE,EAAc,OAAS,EAAc,MAAM,SAC7C,EAAc,MAAQ,KAAK,IAAI,EAAG,EAAc,MAAM,OAAS,EAAE,GAK/D,EAAa,SAAY,CAC7B,EAAW,MAAQ,GACnB,EAAY,MAAQ,GACpB,EAAc,MAAQ,EACtB,MAAM,GAAU,CAChB,EAAe,OAAO,OAAO,EAGzB,MAAoB,CACxB,EAAW,MAAQ,GACnB,EAAY,MAAQ,GACpB,EAAc,MAAQ,GAIlB,EAAqB,GAA6B,CACtD,GAAI,EAAY,SAAU,CACxB,IAAM,EAAc,CAAC,CAAC,EAAY,SAAS,UAAU,OACrD,IAAW,EAAY,SAAS,IAAK,EAAY,CACjD,GAAa,MAEb,EAAY,MAAQ,EAAY,MAChC,EAAc,MAAQ,GAIpB,EAAc,GAAyB,CAC3C,EAAa,EAAK,CAClB,IAAM,EAAc,CAAC,CAAC,EAAK,UAAU,OACrC,IAAW,EAAK,IAAK,EAAY,CACjC,GAAa,EA+Cf,OAhBA,EAAM,MAAmB,CACvB,EAAc,MAAQ,GACtB,CAEF,MAAgB,CACd,GAAa,CACb,IAAM,EAAiB,GAAqB,EACrC,EAAE,SAAW,EAAE,UAAY,EAAE,MAAQ,MACxC,EAAE,gBAAgB,CAClB,GAAY,GAGhB,SAAS,iBAAiB,UAAW,EAAc,CACnD,MAAkB,SAAS,oBAAoB,UAAW,EAAc,CAAC,EACzE,CAEK,CACL,aACA,cACA,gBACA,iBACA,SACA,oBACA,iBACA,aACA,aACA,iBACA,iBACA,iBACA,eACA,oBACA,aACA,cACA,oBACA,aACA,gBA9DwB,CACxB,IAAM,EACJ,CAAC,EAAY,MAAM,MAAM,EAAI,EAAe,MAAM,OAAS,EACvD,EACJ,EAAY,MAAM,MAAM,EAAI,EAAkB,MAAM,OAAS,EAE/D,GAAI,EAAe,CACjB,IAAM,EAAc,EAAe,MAAM,EAAc,OACnD,GAAa,EAAkB,EAAY,SACtC,EAAc,CACvB,IAAM,EAAO,EAAkB,MAAM,EAAc,OAC/C,GAAM,EAAW,EAAK,GAoD5B,cAhDsB,CACtB,IAAM,EAAY,EAAY,MAAM,MAAM,CAEtC,EAAkB,MAAM,OAAS,EADjC,EAAe,MAAM,OAAS,EAElC,EAAc,MAAQ,KAAK,IAAI,EAAc,MAAQ,EAAG,EAAS,EA6CjE,cA1CsB,CACtB,EAAc,MAAQ,KAAK,IAAI,EAAc,MAAQ,EAAG,EAAE,EA0C3D,qpCGwCD,GAAM,CACJ,aACA,cACA,gBACA,iBACA,SACA,oBACA,iBACA,aACA,aACA,iBACA,kBACA,iBACA,eACA,qBACA,aACA,cACA,qBACA,cACA,cACA,YACA,aACE,EA5BU,EA4BY,QAAO,QAEjC,EAAa,CAAE,aAAY,cAAa,CAAA,aA7QxC,EA2NM,MA3NN,EA2NM,CA1NJ,EAAA,WAAe,CACf,EAQM,MAAA,CAPJ,MAAK,EAAA,CAAC,uBAAsB,CAAA,eACF,EAAA,EAAM,CAAA,CAAA,CAAA,CAC/B,QAAK,AAAA,EAAA,MAAA,GAAA,IAAE,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAU,WAElB,EAA+C,MAAA,CAA1C,MAAM,kCAAiC,CAAA,KAAA,GAAA,CAC5C,EAA0C,OAAA,CAApC,MAAM,qBAAoB,CAAC,KAAE,GAAA,CACnC,EAAkD,OAAA,CAA5C,MAAM,yBAAwB,CAAC,SAAM,GAAA,KAG7C,EAAA,UAAc,MACd,EA6MW,EAAA,CA7MD,GAAG,OAAM,CAAA,CACjB,EA2Ma,EAAA,CA3MD,KAAK,eAAc,CAAA,eA6MpC,CA3Me,EAAA,EAAU,EAAA,GAAA,CADlB,EAyMM,MAAA,OAvMJ,MAAM,uBACL,QAAK,AAAA,EAAA,MAAA,GAAA,IAAE,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,CAClB,UAAO,AAAA,EAAA,MAAA,GAAA,GAAA,IAAM,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,CAAA,CAAA,MAAA,CAAA,GAEzB,EAkMM,MAAA,CAjMJ,MAAK,EAAA,CAAC,yBAAwB,CAAA,cACL,EAAA,EAAM,CAAA,CAAA,CAAA,CAC9B,QAAK,AAAA,EAAA,KAAA,MAAN,GAAW,CAAA,OAAA,CAAA,GAEX,EAAA,SAAa,CACb,EAmBM,MAnBN,EAmBM,SAlBJ,EAA+C,MAAA,CAA1C,MAAM,kCAAiC,CAAA,KAAA,GAAA,GAC5C,EAUE,QAAA,SATI,iBAAJ,IAAI,uCACK,EAAW,MAAA,EAAA,KACpB,KAAK,OACJ,YAAaA,EAAAA,YACd,MAAM,qBACL,UAAO,kBAAM,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,CAAA,CAAA,MAAA,CAAA,kBACT,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,CAAA,CAAA,QAAA,CAAA,kBACN,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAS,CAAA,CAAA,aAAA,CAAA,kBACX,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAS,CAAA,CAAA,WAAA,CAAA,kBAPnB,EAAA,EAAW,CAAA,CAAA,CAAA,CAStB,EAKS,SAAA,CAJP,MAAM,kBACL,QAAK,AAAA,EAAA,MAAA,GAAA,IAAE,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,WAEnB,EAA2B,MAAA,CAAtB,MAAM,cAAa,CAAA,KAAA,GAAA,CAAA,CAAA,GAI5B,EAAA,SAAa,CACb,EAyIM,MAzIN,EAyIM,CAxIJ,EAAA,QAAY,CAEH,EAAA,EAAU,CAIF,CAKH,EAAA,EAAW,CAAC,MAAI,EAAM,EAAA,EAAc,CAAC,OAAM,GAAA,GAAA,CADzD,EA6DW,EAAA,CAAA,IAAA,EAAA,CAAA,CA9DX,EAAA,SAAa,CAIX,EAyDM,MAzDN,GAyDM,CAxDJ,EAuDM,MAvDN,EAuDM,CAtDJ,EAeM,MAfN,GAeM,SAdJ,EAEE,MAAA,CADA,MAAM,iDAAgD,CAAA,KAAA,GAAA,WACtD,SAEF,GAAA,CAAA,EAES,OAFT,GAES,EADP,EAAA,EAAc,CAAC,OAAM,CAAA,EAAA,CAEvB,EAMS,SAAA,CALP,MAAM,sBACL,QAAK,AAAA,EAAA,MAAA,GAAA,IAAE,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAY,CACpB,MAAM,iBAEN,EAAoC,MAAA,CAA/B,MAAM,uBAAsB,CAAA,KAAA,GAAA,CAAA,CAAA,GAGrC,EAqCM,MArCN,GAqCM,EAAA,EAAA,GAAA,CApCJ,EAmCM,EAAA,KAAA,EAlC2B,EAAA,EAAc,EAArC,EAAa,SADvB,EAmCM,MAAA,CAjCH,IAAG,WAAa,IAChB,MAAK,EAAE,EAAA,EAAc,CAAC,EAAK,GAAA,CAAA,CAC3B,QAAK,GAAE,EAAA,GAAiB,CAAC,EAAW,CACpC,aAAU,GAAE,EAAA,MAAgB,IAGrB,EAAY,UAAU,MAAA,GAAA,CAD9B,EAIE,EAFK,EAAY,SAAS,KAAI,CAAA,OAC9B,MAAM,0BAER,EAKM,MALN,GAKM,AAAA,EAAA,MAAA,CADJ,EAA2C,MAAA,CAAtC,MAAM,8BAA6B,CAAA,KAAA,GAAA,CAAA,CAAA,EAE1C,EASM,MATN,GASM,CARJ,EAEM,MAFN,GAEM,EADD,EAAY,MAAK,CAAA,EAAA,CAEtB,EAIM,MAJN,GAIM,EAFF,EAAA,EAAc,CAAC,EAAY,UAAU,KAAG,GAAA,CAAA,CAAA,EAAA,CAAA,CAAA,CAI9C,EAMS,SAAA,CALP,MAAM,uBACL,QAAK,EAAA,GAAO,EAAA,GAAiB,CAAC,EAAK,CAAA,CAAA,OAAA,CAAA,CACpC,MAAM,kBAEN,EAA2B,MAAA,CAAtB,MAAM,cAAa,CAAA,KAAA,GAAA,CAAA,CAAA,EAAA,GAAA,6BASf,EAAA,EAAW,CAAC,MAAI,EAAA,GAAA,CAArC,EA6DW,EAAA,CAAA,IAAA,EAAA,CAAA,CA9DX,EAAA,SAAa,CAEX,EA2DM,MA3DN,GA2DM,CA1DJ,EAAA,UAAc,CAEL,EAAA,EAAU,EAIc,GAAA,CAIjC,EA+CM,EAAA,CAAA,IAAA,EAAA,CAAA,CAhDN,EAAA,WAAe,CACf,EA+CM,MA/CN,EA+CM,CA3CJ,EAMM,MANN,EAMM,SALJ,EAA2C,MAAA,CAAtC,MAAM,8BAA6B,CAAA,KAAA,GAAA,GAAG,IAC3C,EAAGE,EAAAA,iBAAgB,CAAG,IACtB,EAAA,CAAA,EAES,OAFT,EAES,EADP,EAAA,EAAiB,CAAC,OAAM,CAAA,EAAA,GAG5B,EAmCM,MAnCN,EAmCM,EAAA,EAAA,GAAA,CAlCJ,EAiCM,EAAA,KAAA,EAhCoB,EAAA,EAAiB,EAAjC,EAAM,SADhB,EAiCM,MAAA,CA/BH,IAAK,EAAK,IACV,MAAK,EAAE,EAAA,EAAc,CAAC,EAAK,GAAA,CAAA,CAC3B,QAAK,GAAE,EAAA,GAAU,CAAC,EAAI,CACtB,aAAU,GAAE,EAAA,MAAgB,IAGrB,EAAK,MAAA,GAAA,CADb,EAIE,EAFK,EAAK,KAAI,CAAA,OACd,MAAM,0BAER,EAKM,MALN,EAKM,AAAA,EAAA,MAAA,CADJ,EAA2C,MAAA,CAAtC,MAAM,8BAA6B,CAAA,KAAA,GAAA,CAAA,CAAA,EAE1C,EAQM,MARN,GAQM,CAPJ,EAGO,MAAA,CAFL,MAAM,mBACN,UAAQ,EAAA,GAAc,CAAC,EAAK,MAAO,EAAA,EAAW,CAAA,YAEhD,EAEM,MAFN,GAEM,EADD,EAAA,EAAc,CAAC,EAAK,IAAG,CAAA,CAAA,EAAA,CAAA,CAAA,CAItB,EAAK,UAAU,QAAA,GAAA,CADvB,EAKM,MALN,GAKM,EADD,EAAK,SAAS,OAAM,CAAA,EAAA,EAAA,EAAA,OAAA,GAAA,6BAnDZ,GAAA,CADnB,EAMM,MANN,EAMM,CAAA,AAAA,EAAA,MAFJ,EAA8C,MAAA,CAAzC,MAAM,iCAAgC,CAAA,KAAA,GAAA,CAC3C,EAAmC,IAAA,KAAhC,QAAK,EAAG,EAAA,EAAW,CAAA,CAAG,SAAM,EAAA,CAAA,CAAA,wBAhFlB,GAAA,CADnB,EAMM,MANN,GAMM,CAAA,AAAA,EAAA,MAFJ,EAA8C,MAAA,CAAzC,MAAM,iCAAgC,CAAA,KAAA,GAAA,CAC3C,EAAsB,IAAA,KAAA,EAAhBD,EAAAA,UAAS,CAAA,EAAA,CAAA,CAAA,IAoInB,EAAA,SAAa,CACb,EAyBM,MAzBN,GAyBM,CAAA,AAAA,EAAA,MAxBJ,EAUM,MAAA,CAVD,MAAM,uBAAsB,CAAA,CAC/B,EAEM,MAAA,CAFD,MAAM,oBAAmB,CAAA,CAC5B,EAAY,MAAA,KAAP,IAAC,CAAM,EAAe,OAAA,KAAT,KAAE,CAAA,CAAA,CAEtB,EAEM,MAAA,CAFD,MAAM,oBAAmB,CAAA,CAC5B,EAAa,MAAA,KAAR,KAAE,CAAM,EAAe,OAAA,KAAT,KAAE,CAAA,CAAA,CAEvB,EAEM,MAAA,CAFD,MAAM,oBAAmB,CAAA,CAC5B,EAAc,MAAA,KAAT,MAAG,CAAM,EAAe,OAAA,KAAT,KAAE,CAAA,CAAA,MAIlB,EAAA,EAAU,EAAA,GAAA,CADlB,EAYM,MAZN,GAYM,CARQ,EAAA,EAAW,CAAC,MAAI,EAAM,EAAA,EAAU,EAAA,GAAA,CAA5C,EAEO,OAAA,GAFuC,MAC1C,EAAG,EAAA,EAAiB,CAAC,OAAM,CAAG,QAClC,EAAA,EAAA,CAEc,EAAA,EAAW,CAAC,MAAI,EAAM,EAAA,EAAc,CAAC,OAAM,GAAA,GAAA,CADzD,EAIO,OAAA,GAAA,EADF,EAAA,EAAc,CAAC,OAAM,CAAG,UAC7B,EAAA,EAAA,EAAA,OAAA,GAAA,CAAA,CAAA,EAAA,EAAA,OAAA,GAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"C_GlobalSearch2.js","names":[],"sources":["../src/components/C_GlobalSearch/composables/useGlobalSearch.ts","../src/components/C_GlobalSearch/index.vue"],"sourcesContent":["import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'\nimport type { SearchMenuItem, HistoryItem, GlobalSearchOptions } from '../types'\n\n/**\n * 全局搜索逻辑 — 管理菜单搜索、历史记录、键盘导航\n *\n * 已解耦:不依赖任何项目级 store / router,\n * 所有外部数据通过 options 注入。\n */\nexport function useGlobalSearch(options: GlobalSearchOptions) {\n const {\n menuItems: getMenuItems,\n isDark: getIsDark,\n onSelect,\n storageKey = 'robot-search-history',\n maxHistory = 10,\n maxDisplayHistory = 5,\n } = options\n\n // ==================== 响应式状态 ====================\n const showDialog = ref(false)\n const searchValue = ref('')\n const selectedIndex = ref(0)\n const searchInputRef = ref<HTMLInputElement>()\n const searchHistory = ref<HistoryItem[]>([])\n\n // ==================== 计算属性 ====================\n const isDark = computed(() => getIsDark?.() ?? false)\n\n const filteredMenuItems = computed(() => {\n if (!searchValue.value.trim()) return []\n const query = searchValue.value.toLowerCase()\n const items = getMenuItems()\n return items.filter(\n item =>\n item.label.toLowerCase().includes(query) ||\n formatMenuPath(item.key).toLowerCase().includes(query)\n )\n })\n\n const displayHistory = computed(() =>\n searchHistory.value.slice(0, maxDisplayHistory)\n )\n const hasResults = computed(() => filteredMenuItems.value.length > 0)\n const hasContent = computed(\n () => hasResults.value || displayHistory.value.length > 0\n )\n\n // ==================== 工具函数 ====================\n const getItemClasses = (index: number, isHistory: boolean) => [\n 'robot-result-item',\n isHistory && 'robot-history-item',\n { 'robot-result-item-selected': selectedIndex.value === index },\n ]\n\n const highlightMatch = (text: string, search: string): string => {\n if (!search) return text\n const regex = new RegExp(\n `(${search.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`,\n 'gi'\n )\n return text.replace(regex, '<mark class=\"robot-highlight\">$1</mark>')\n }\n\n const formatMenuPath = (key: string): string =>\n key.replace(/^\\//, '').replace(/\\//g, ' > ') || '首页'\n\n // ==================== 搜索历史管理 ====================\n const addToHistory = (menuItem: SearchMenuItem) => {\n if (!menuItem.label.trim()) return\n const query = menuItem.label\n const filtered = searchHistory.value.filter(\n (item: HistoryItem) =>\n !(item.query === query && item.menuItem?.key === menuItem.key)\n )\n searchHistory.value = [\n { query, menuItem, timestamp: Date.now() },\n ...filtered,\n ].slice(0, maxHistory)\n localStorage.setItem(storageKey, JSON.stringify(searchHistory.value))\n }\n\n const loadHistory = () => {\n try {\n const saved = localStorage.getItem(storageKey)\n if (saved) {\n const parsed = JSON.parse(saved)\n searchHistory.value = parsed.map((item: any) =>\n typeof item === 'string'\n ? { query: item, menuItem: null, timestamp: 0 }\n : item\n )\n }\n } catch (error) {\n console.error('加载搜索历史失败:', error)\n }\n }\n\n const clearHistory = () => {\n searchHistory.value = []\n localStorage.removeItem(storageKey)\n }\n\n const removeHistoryItem = (index: number) => {\n searchHistory.value.splice(index, 1)\n localStorage.setItem(storageKey, JSON.stringify(searchHistory.value))\n if (selectedIndex.value >= searchHistory.value.length) {\n selectedIndex.value = Math.max(0, searchHistory.value.length - 1)\n }\n }\n\n // ==================== 对话框控制 ====================\n const openDialog = async () => {\n showDialog.value = true\n searchValue.value = ''\n selectedIndex.value = 0\n await nextTick()\n searchInputRef.value?.focus()\n }\n\n const closeDialog = () => {\n showDialog.value = false\n searchValue.value = ''\n selectedIndex.value = 0\n }\n\n // ==================== 选择项目 ====================\n const selectHistoryItem = (historyItem: HistoryItem) => {\n if (historyItem.menuItem) {\n const hasChildren = !!historyItem.menuItem.children?.length\n onSelect?.(historyItem.menuItem.key, hasChildren)\n closeDialog()\n } else {\n searchValue.value = historyItem.query\n selectedIndex.value = 0\n }\n }\n\n const selectItem = (item: SearchMenuItem) => {\n addToHistory(item)\n const hasChildren = !!item.children?.length\n onSelect?.(item.key, hasChildren)\n closeDialog()\n }\n\n // ==================== 键盘导航 ====================\n const handleEnter = () => {\n const isHistoryMode =\n !searchValue.value.trim() && displayHistory.value.length > 0\n const isSearchMode =\n searchValue.value.trim() && filteredMenuItems.value.length > 0\n\n if (isHistoryMode) {\n const historyItem = displayHistory.value[selectedIndex.value]\n if (historyItem) selectHistoryItem(historyItem)\n } else if (isSearchMode) {\n const item = filteredMenuItems.value[selectedIndex.value]\n if (item) selectItem(item)\n }\n }\n\n const focusNext = () => {\n const maxIndex = !searchValue.value.trim()\n ? displayHistory.value.length - 1\n : filteredMenuItems.value.length - 1\n selectedIndex.value = Math.min(selectedIndex.value + 1, maxIndex)\n }\n\n const focusPrev = () => {\n selectedIndex.value = Math.max(selectedIndex.value - 1, 0)\n }\n\n // ==================== 监听器和生命周期 ====================\n watch(searchValue, () => {\n selectedIndex.value = 0\n })\n\n onMounted(() => {\n loadHistory()\n const handleKeydown = (e: KeyboardEvent) => {\n if ((e.ctrlKey || e.metaKey) && e.key === 'k') {\n e.preventDefault()\n openDialog()\n }\n }\n document.addEventListener('keydown', handleKeydown)\n onUnmounted(() => document.removeEventListener('keydown', handleKeydown))\n })\n\n return {\n showDialog,\n searchValue,\n selectedIndex,\n searchInputRef,\n isDark,\n filteredMenuItems,\n displayHistory,\n hasResults,\n hasContent,\n getItemClasses,\n highlightMatch,\n formatMenuPath,\n clearHistory,\n removeHistoryItem,\n openDialog,\n closeDialog,\n selectHistoryItem,\n selectItem,\n handleEnter,\n focusNext,\n focusPrev,\n }\n}\n","<!--\n * @Description: 全局搜索组件 - 命令面板式搜索\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"robot-search-palette\">\n <!-- 搜索触发按钮 -->\n <div\n class=\"robot-search-trigger\"\n :class=\"{ 'trigger-dark': isDark }\"\n @click=\"openDialog\"\n >\n <div class=\"i-mdi:search robot-trigger-icon\" />\n <span class=\"robot-trigger-text\">搜索</span>\n <span class=\"robot-trigger-shortcut\">Ctrl K</span>\n </div>\n\n <!-- 搜索对话框 -->\n <Teleport to=\"body\">\n <Transition name=\"robot-dialog\">\n <div\n v-if=\"showDialog\"\n class=\"robot-dialog-overlay\"\n @click=\"closeDialog\"\n @keydown.esc=\"closeDialog\"\n >\n <div\n class=\"robot-dialog-container\"\n :class=\"{ 'dialog-dark': isDark }\"\n @click.stop\n >\n <!-- 搜索头部 -->\n <div class=\"robot-search-header\">\n <div class=\"i-mdi:magnify robot-search-icon\" />\n <input\n ref=\"searchInputRef\"\n v-model=\"searchValue\"\n type=\"text\"\n :placeholder=\"placeholder\"\n class=\"robot-search-input\"\n @keydown.esc=\"closeDialog\"\n @keydown.enter=\"handleEnter\"\n @keydown.arrow-down=\"focusNext\"\n @keydown.arrow-up=\"focusPrev\"\n />\n <button\n class=\"robot-close-btn\"\n @click=\"closeDialog\"\n >\n <div class=\"i-mdi:close\" />\n </button>\n </div>\n\n <!-- 搜索内容 -->\n <div class=\"robot-search-content\">\n <!-- 空状态 -->\n <div\n v-if=\"!hasContent\"\n class=\"robot-empty-state\"\n >\n <div class=\"i-mdi:magnify robot-empty-icon\" />\n <p>{{ emptyText }}</p>\n </div>\n\n <!-- 搜索历史 -->\n <template\n v-else-if=\"!searchValue.trim() && displayHistory.length > 0\"\n >\n <div class=\"robot-search-results\">\n <div class=\"robot-result-group\">\n <div class=\"robot-group-title\">\n <div\n class=\"i-mdi:clock-time-four-outline robot-group-icon\"\n />\n 最近搜索\n <span class=\"robot-group-count\">{{\n displayHistory.length\n }}</span>\n <button\n class=\"robot-clear-history\"\n @click=\"clearHistory\"\n title=\"清空历史\"\n >\n <div class=\"i-mdi:delete-outline\" />\n </button>\n </div>\n <div class=\"robot-group-items\">\n <div\n v-for=\"(historyItem, index) in displayHistory\"\n :key=\"`history-${index}`\"\n :class=\"getItemClasses(index, true)\"\n @click=\"selectHistoryItem(historyItem)\"\n @mouseenter=\"selectedIndex = index\"\n >\n <component\n v-if=\"historyItem.menuItem?.icon\"\n :is=\"historyItem.menuItem.icon\"\n class=\"robot-item-icon\"\n />\n <div\n v-else\n class=\"robot-item-icon robot-default-icon\"\n >\n <div class=\"i-mdi:file-document-outline\" />\n </div>\n <div class=\"robot-item-content\">\n <div class=\"robot-item-label\">\n {{ historyItem.query }}\n </div>\n <div class=\"robot-item-desc\">\n {{\n formatMenuPath(historyItem.menuItem?.key || '')\n }}\n </div>\n </div>\n <button\n class=\"robot-history-delete\"\n @click.stop=\"removeHistoryItem(index)\"\n title=\"删除此记录\"\n >\n <div class=\"i-mdi:close\" />\n </button>\n </div>\n </div>\n </div>\n </div>\n </template>\n\n <!-- 搜索结果 -->\n <template v-else-if=\"searchValue.trim()\">\n <div class=\"robot-search-results\">\n <!-- 无结果状态 -->\n <div\n v-if=\"!hasResults\"\n class=\"robot-empty-state\"\n >\n <div class=\"i-mdi:magnify robot-empty-icon\" />\n <p>未找到 \"{{ searchValue }}\" 相关内容</p>\n </div>\n\n <!-- 搜索结果列表 -->\n <div\n v-else\n class=\"robot-result-group\"\n >\n <div class=\"robot-group-title\">\n <div class=\"i-mdi:menu robot-group-icon\" />\n {{ resultGroupTitle }}\n <span class=\"robot-group-count\">{{\n filteredMenuItems.length\n }}</span>\n </div>\n <div class=\"robot-group-items\">\n <div\n v-for=\"(item, index) in filteredMenuItems\"\n :key=\"item.key\"\n :class=\"getItemClasses(index, false)\"\n @click=\"selectItem(item)\"\n @mouseenter=\"selectedIndex = index\"\n >\n <component\n v-if=\"item.icon\"\n :is=\"item.icon\"\n class=\"robot-item-icon\"\n />\n <div\n v-else\n class=\"robot-item-icon robot-default-icon\"\n >\n <div class=\"i-mdi:file-document-outline\" />\n </div>\n <div class=\"robot-item-content\">\n <div\n class=\"robot-item-label\"\n v-html=\"highlightMatch(item.label, searchValue)\"\n ></div>\n <div class=\"robot-item-desc\">\n {{ formatMenuPath(item.key) }}\n </div>\n </div>\n <div\n v-if=\"item.children?.length\"\n class=\"robot-item-badge\"\n >\n {{ item.children.length }}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </div>\n\n <!-- 搜索底部 -->\n <div class=\"robot-search-footer\">\n <div class=\"robot-footer-actions\">\n <div class=\"robot-action-item\">\n <kbd>↵</kbd><span>选择</span>\n </div>\n <div class=\"robot-action-item\">\n <kbd>↑↓</kbd><span>导航</span>\n </div>\n <div class=\"robot-action-item\">\n <kbd>esc</kbd><span>关闭</span>\n </div>\n </div>\n <div\n v-if=\"hasContent\"\n class=\"robot-footer-info\"\n >\n <span v-if=\"searchValue.trim() && hasResults\">\n 共 {{ filteredMenuItems.length }} 项结果\n </span>\n <span\n v-else-if=\"!searchValue.trim() && displayHistory.length > 0\"\n >\n {{ displayHistory.length }} 条历史记录\n </span>\n </div>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { useGlobalSearch } from './composables/useGlobalSearch'\n import type { GlobalSearchOptions } from './types'\n\n defineOptions({ name: 'C_GlobalSearch' })\n\n interface Props {\n /** useGlobalSearch 配置(必须传入 menuItems + onSelect) */\n options: GlobalSearchOptions\n /** 搜索框 placeholder */\n placeholder?: string\n /** 无内容提示文字 */\n emptyText?: string\n /** 结果分组标题 */\n resultGroupTitle?: string\n }\n\n const props = withDefaults(defineProps<Props>(), {\n placeholder: '搜索导航菜单...',\n emptyText: '输入关键词搜索菜单',\n resultGroupTitle: '菜单导航',\n })\n\n const {\n showDialog,\n searchValue,\n selectedIndex,\n searchInputRef,\n isDark,\n filteredMenuItems,\n displayHistory,\n hasResults,\n hasContent,\n getItemClasses,\n highlightMatch,\n formatMenuPath,\n clearHistory,\n removeHistoryItem,\n openDialog,\n closeDialog,\n selectHistoryItem,\n selectItem,\n handleEnter,\n focusNext,\n focusPrev,\n } = useGlobalSearch(props.options)\n\n defineExpose({ openDialog, closeDialog })\n</script>\n\n<style lang=\"scss\" scoped>\n @use './index.scss';\n</style>\n"],"mappings":"qhBASA,SAAgB,EAAgB,EAA8B,CAC5D,GAAM,CACJ,UAAW,EACX,OAAQ,EACR,WACA,aAAa,uBACb,aAAa,GACb,oBAAoB,GAClB,EAGE,EAAa,EAAI,GAAM,CACvB,EAAc,EAAI,GAAG,CACrB,EAAgB,EAAI,EAAE,CACtB,EAAiB,GAAuB,CACxC,EAAgB,EAAmB,EAAE,CAAC,CAGtC,EAAS,MAAe,KAAa,EAAI,GAAM,CAE/C,EAAoB,MAAe,CACvC,GAAI,CAAC,EAAY,MAAM,MAAM,CAAE,MAAO,EAAE,CACxC,IAAM,EAAQ,EAAY,MAAM,aAAa,CAE7C,OADc,GAAc,CACf,OACX,GACE,EAAK,MAAM,aAAa,CAAC,SAAS,EAAM,EACxC,EAAe,EAAK,IAAI,CAAC,aAAa,CAAC,SAAS,EAAM,CACzD,EACD,CAEI,EAAiB,MACrB,EAAc,MAAM,MAAM,EAAG,EAAkB,CAChD,CACK,EAAa,MAAe,EAAkB,MAAM,OAAS,EAAE,CAC/D,EAAa,MACX,EAAW,OAAS,EAAe,MAAM,OAAS,EACzD,CAGK,GAAkB,EAAe,IAAuB,CAC5D,oBACA,GAAa,qBACb,CAAE,6BAA8B,EAAc,QAAU,EAAO,CAChE,CAEK,GAAkB,EAAc,IAA2B,CAC/D,GAAI,CAAC,EAAQ,OAAO,EACpB,IAAM,EAAY,OAChB,IAAI,EAAO,QAAQ,sBAAuB,OAAO,CAAC,GAClD,KACD,CACD,OAAO,EAAK,QAAQ,EAAO,0CAA0C,EAGjE,EAAkB,GACtB,EAAI,QAAQ,MAAO,GAAG,CAAC,QAAQ,MAAO,MAAM,EAAI,KAG5C,EAAgB,GAA6B,CACjD,GAAI,CAAC,EAAS,MAAM,MAAM,CAAE,OAC5B,IAAM,EAAQ,EAAS,MACjB,EAAW,EAAc,MAAM,OAClC,GACC,EAAE,EAAK,QAAU,GAAS,EAAK,UAAU,MAAQ,EAAS,KAC7D,CACD,EAAc,MAAQ,CACpB,CAAE,QAAO,WAAU,UAAW,KAAK,KAAK,CAAE,CAC1C,GAAG,EACJ,CAAC,MAAM,EAAG,EAAW,CACtB,aAAa,QAAQ,EAAY,KAAK,UAAU,EAAc,MAAM,CAAC,EAGjE,MAAoB,CACxB,GAAI,CACF,IAAM,EAAQ,aAAa,QAAQ,EAAW,CAC1C,IAEF,EAAc,MADC,KAAK,MAAM,EAAM,CACH,IAAK,GAChC,OAAO,GAAS,SACZ,CAAE,MAAO,EAAM,SAAU,KAAM,UAAW,EAAG,CAC7C,EACL,QAEI,EAAO,CACd,QAAQ,MAAM,YAAa,EAAM,GAI/B,MAAqB,CACzB,EAAc,MAAQ,EAAE,CACxB,aAAa,WAAW,EAAW,EAG/B,EAAqB,GAAkB,CAC3C,EAAc,MAAM,OAAO,EAAO,EAAE,CACpC,aAAa,QAAQ,EAAY,KAAK,UAAU,EAAc,MAAM,CAAC,CACjE,EAAc,OAAS,EAAc,MAAM,SAC7C,EAAc,MAAQ,KAAK,IAAI,EAAG,EAAc,MAAM,OAAS,EAAE,GAK/D,EAAa,SAAY,CAC7B,EAAW,MAAQ,GACnB,EAAY,MAAQ,GACpB,EAAc,MAAQ,EACtB,MAAM,GAAU,CAChB,EAAe,OAAO,OAAO,EAGzB,MAAoB,CACxB,EAAW,MAAQ,GACnB,EAAY,MAAQ,GACpB,EAAc,MAAQ,GAIlB,EAAqB,GAA6B,CACtD,GAAI,EAAY,SAAU,CACxB,IAAM,EAAc,CAAC,CAAC,EAAY,SAAS,UAAU,OACrD,IAAW,EAAY,SAAS,IAAK,EAAY,CACjD,GAAa,MAEb,EAAY,MAAQ,EAAY,MAChC,EAAc,MAAQ,GAIpB,EAAc,GAAyB,CAC3C,EAAa,EAAK,CAClB,IAAM,EAAc,CAAC,CAAC,EAAK,UAAU,OACrC,IAAW,EAAK,IAAK,EAAY,CACjC,GAAa,EA+Cf,OAhBA,EAAM,MAAmB,CACvB,EAAc,MAAQ,GACtB,CAEF,MAAgB,CACd,GAAa,CACb,IAAM,EAAiB,GAAqB,EACrC,EAAE,SAAW,EAAE,UAAY,EAAE,MAAQ,MACxC,EAAE,gBAAgB,CAClB,GAAY,GAGhB,SAAS,iBAAiB,UAAW,EAAc,CACnD,MAAkB,SAAS,oBAAoB,UAAW,EAAc,CAAC,EACzE,CAEK,CACL,aACA,cACA,gBACA,iBACA,SACA,oBACA,iBACA,aACA,aACA,iBACA,iBACA,iBACA,eACA,oBACA,aACA,cACA,oBACA,aACA,gBA9DwB,CACxB,IAAM,EACJ,CAAC,EAAY,MAAM,MAAM,EAAI,EAAe,MAAM,OAAS,EACvD,EACJ,EAAY,MAAM,MAAM,EAAI,EAAkB,MAAM,OAAS,EAE/D,GAAI,EAAe,CACjB,IAAM,EAAc,EAAe,MAAM,EAAc,OACnD,GAAa,EAAkB,EAAY,SACtC,EAAc,CACvB,IAAM,EAAO,EAAkB,MAAM,EAAc,OAC/C,GAAM,EAAW,EAAK,GAoD5B,cAhDsB,CACtB,IAAM,EAAY,EAAY,MAAM,MAAM,CAEtC,EAAkB,MAAM,OAAS,EADjC,EAAe,MAAM,OAAS,EAElC,EAAc,MAAQ,KAAK,IAAI,EAAc,MAAQ,EAAG,EAAS,EA6CjE,cA1CsB,CACtB,EAAc,MAAQ,KAAK,IAAI,EAAc,MAAQ,EAAG,EAAE,EA0C3D,qpCCwCD,GAAM,CACJ,aACA,cACA,gBACA,iBACA,SACA,oBACA,iBACA,aACA,aACA,iBACA,kBACA,iBACA,eACA,qBACA,aACA,cACA,qBACA,cACA,cACA,YACA,aACE,EA5BU,EA4BY,QAAO,QAEjC,EAAa,CAAE,aAAY,cAAa,CAAA,aA7QxC,EA2NK,MA3NL,EA2NK,CA1NH,EAAA,WAAc,CACd,EAQK,MAAA,CAPH,MAAK,EAAA,CAAC,uBAAqB,CAAA,eACD,EAAA,EAAM,CAAA,CAAA,CAAA,CAC/B,QAAK,AAAA,EAAA,MAAA,GAAA,IAAE,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAU,WAElB,EAA8C,MAAA,CAAzC,MAAM,kCAAiC,CAAA,KAAA,GAAA,CAC5C,EAAyC,OAAA,CAAnC,MAAM,qBAAoB,CAAC,KAAE,GAAA,CACnC,EAAiD,OAAA,CAA3C,MAAM,yBAAwB,CAAC,SAAM,GAAA,KAG7C,EAAA,UAAa,MACb,EA6MU,EAAA,CA7MA,GAAG,OAAM,CAAA,CACjB,EA2MY,EAAA,CA3MA,KAAK,eAAc,CAAA,eA6MpC,CA3Me,EAAA,EAAU,EAAA,GAAA,CADlB,EAyMK,MAAA,OAvMH,MAAM,uBACL,QAAK,AAAA,EAAA,MAAA,GAAA,IAAE,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,CAClB,UAAO,AAAA,EAAA,MAAA,GAAA,GAAA,IAAM,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,CAAA,CAAA,MAAA,CAAA,GAEzB,EAkMK,MAAA,CAjMH,MAAK,EAAA,CAAC,yBAAuB,CAAA,cACJ,EAAA,EAAM,CAAA,CAAA,CAAA,CAC9B,QAAK,AAAA,EAAA,KAAA,MAAN,GAAO,CAAA,OAAA,CAAA,GAEP,EAAA,SAAY,CACZ,EAmBK,MAnBL,EAmBK,SAlBH,EAA8C,MAAA,CAAzC,MAAM,kCAAiC,CAAA,KAAA,GAAA,GAC5C,EAUC,QAAA,SATK,iBAAJ,IAAI,uCACK,EAAW,MAAA,EAAA,KACpB,KAAK,OACJ,YAAa,EAAA,YACd,MAAM,qBACL,UAAO,kBAAM,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,CAAA,CAAA,MAAA,CAAA,kBACT,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,CAAA,CAAA,QAAA,CAAA,kBACN,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAS,CAAA,CAAA,aAAA,CAAA,kBACX,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAS,CAAA,CAAA,WAAA,CAAA,kBAPnB,EAAA,EAAW,CAAA,CAAA,CAAA,CAStB,EAKQ,SAAA,CAJN,MAAM,kBACL,QAAK,AAAA,EAAA,MAAA,GAAA,IAAE,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAW,WAEnB,EAA0B,MAAA,CAArB,MAAM,cAAa,CAAA,KAAA,GAAA,CAAA,CAAA,GAI5B,EAAA,SAAY,CACZ,EAyIK,MAzIL,EAyIK,CAxIH,EAAA,QAAW,CAEF,EAAA,EAAU,CAIF,CAKH,EAAA,EAAW,CAAC,MAAI,EAAM,EAAA,EAAc,CAAC,OAAM,GAAA,GAAA,CADzD,EA6DU,EAAA,CAAA,IAAA,EAAA,CAAA,CA9DV,EAAA,SAAY,CAIV,EAyDK,MAzDL,GAyDK,CAxDH,EAuDK,MAvDL,EAuDK,CAtDH,EAeK,MAfL,GAeK,SAdH,EAEC,MAAA,CADC,MAAM,iDAA+C,CAAA,KAAA,GAAA,WACtD,SAED,GAAA,CAAA,EAEQ,OAFR,GAEQ,EADN,EAAA,EAAc,CAAC,OAAA,CAAA,EAAA,CAEjB,EAMQ,SAAA,CALN,MAAM,sBACL,QAAK,AAAA,EAAA,MAAA,GAAA,IAAE,EAAA,EAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAAY,CACpB,MAAM,iBAEN,EAAmC,MAAA,CAA9B,MAAM,uBAAsB,CAAA,KAAA,GAAA,CAAA,CAAA,GAGrC,EAqCK,MArCL,GAqCK,EAAA,EAAA,GAAA,CApCH,EAmCK,EAAA,KAAA,EAlC4B,EAAA,EAAc,EAArC,EAAa,SADvB,EAmCK,MAAA,CAjCF,IAAG,WAAa,IAChB,MAAK,EAAE,EAAA,EAAc,CAAC,EAAK,GAAA,CAAA,CAC3B,QAAK,GAAE,EAAA,GAAiB,CAAC,EAAW,CACpC,aAAU,GAAE,EAAA,MAAgB,IAGrB,EAAY,UAAU,MAAA,GAAA,CAD9B,EAIC,EAFM,EAAY,SAAS,KAAI,CAAA,OAC9B,MAAM,0BAER,EAKK,MALL,GAKK,AAAA,EAAA,MAAA,CADH,EAA0C,MAAA,CAArC,MAAM,8BAA6B,CAAA,KAAA,GAAA,CAAA,CAAA,EAE1C,EASK,MATL,GASK,CARH,EAEK,MAFL,GAEK,EADA,EAAY,MAAK,CAAA,EAAA,CAEtB,EAIK,MAJL,GAIK,EAFD,EAAA,EAAc,CAAC,EAAY,UAAU,KAAG,GAAA,CAAA,CAAA,EAAA,CAAA,CAAA,CAI9C,EAMQ,SAAA,CALN,MAAM,uBACL,QAAK,EAAA,GAAO,EAAA,GAAiB,CAAC,EAAK,CAAA,CAAA,OAAA,CAAA,CACpC,MAAM,kBAEN,EAA0B,MAAA,CAArB,MAAM,cAAa,CAAA,KAAA,GAAA,CAAA,CAAA,EAAA,GAAA,6BASf,EAAA,EAAW,CAAC,MAAI,EAAA,GAAA,CAArC,EA6DU,EAAA,CAAA,IAAA,EAAA,CAAA,CA9DV,EAAA,SAAY,CAEV,EA2DK,MA3DL,GA2DK,CA1DH,EAAA,UAAa,CAEJ,EAAA,EAAU,EAIc,GAAA,CAIjC,EA+CK,EAAA,CAAA,IAAA,EAAA,CAAA,CAhDL,EAAA,WAAc,CACd,EA+CK,MA/CL,EA+CK,CA3CH,EAMK,MANL,EAMK,SALH,EAA0C,MAAA,CAArC,MAAM,8BAA6B,CAAA,KAAA,GAAA,GAAE,IAC1C,EAAG,EAAA,iBAAgB,CAAE,IACrB,EAAA,CAAA,EAEQ,OAFR,EAEQ,EADN,EAAA,EAAiB,CAAC,OAAA,CAAA,EAAA,GAGtB,EAmCK,MAnCL,EAmCK,EAAA,EAAA,GAAA,CAlCH,EAiCK,EAAA,KAAA,EAhCqB,EAAA,EAAiB,EAAjC,EAAM,SADhB,EAiCK,MAAA,CA/BF,IAAK,EAAK,IACV,MAAK,EAAE,EAAA,EAAc,CAAC,EAAK,GAAA,CAAA,CAC3B,QAAK,GAAE,EAAA,GAAU,CAAC,EAAI,CACtB,aAAU,GAAE,EAAA,MAAgB,IAGrB,EAAK,MAAA,GAAA,CADb,EAIC,EAFM,EAAK,KAAI,CAAA,OACd,MAAM,0BAER,EAKK,MALL,EAKK,AAAA,EAAA,MAAA,CADH,EAA0C,MAAA,CAArC,MAAM,8BAA6B,CAAA,KAAA,GAAA,CAAA,CAAA,EAE1C,EAQK,MARL,GAQK,CAPH,EAGM,MAAA,CAFJ,MAAM,mBACN,UAAQ,EAAA,GAAc,CAAC,EAAK,MAAO,EAAA,EAAW,CAAA,YAEhD,EAEK,MAFL,GAEK,EADA,EAAA,EAAc,CAAC,EAAK,IAAG,CAAA,CAAA,EAAA,CAAA,CAAA,CAItB,EAAK,UAAU,QAAA,GAAA,CADvB,EAKK,MALL,GAKK,EADA,EAAK,SAAS,OAAM,CAAA,EAAA,EAAA,EAAA,OAAA,GAAA,6BAnDZ,GAAA,CADnB,EAMK,MANL,EAMK,CAAA,AAAA,EAAA,MAFH,EAA6C,MAAA,CAAxC,MAAM,iCAAgC,CAAA,KAAA,GAAA,CAC3C,EAAkC,IAAA,KAA/B,QAAK,EAAG,EAAA,EAAW,CAAA,CAAG,SAAM,EAAA,CAAA,CAAA,wBAhFlB,GAAA,CADnB,EAMK,MANL,GAMK,CAAA,AAAA,EAAA,MAFH,EAA6C,MAAA,CAAxC,MAAM,iCAAgC,CAAA,KAAA,GAAA,CAC3C,EAAqB,IAAA,KAAA,EAAf,EAAA,UAAS,CAAA,EAAA,CAAA,CAAA,IAoInB,EAAA,SAAY,CACZ,EAyBK,MAzBL,GAyBK,CAAA,AAAA,EAAA,MAxBH,EAUK,MAAA,CAVA,MAAM,uBAAsB,CAAA,CAC/B,EAEK,MAAA,CAFA,MAAM,oBAAmB,CAAA,CAC5B,EAAY,MAAA,KAAP,IAAC,CAAM,EAAc,OAAA,KAAR,KAAE,CAAA,CAAA,CAEtB,EAEK,MAAA,CAFA,MAAM,oBAAmB,CAAA,CAC5B,EAAa,MAAA,KAAR,KAAE,CAAM,EAAc,OAAA,KAAR,KAAE,CAAA,CAAA,CAEvB,EAEK,MAAA,CAFA,MAAM,oBAAmB,CAAA,CAC5B,EAAc,MAAA,KAAT,MAAG,CAAM,EAAc,OAAA,KAAR,KAAE,CAAA,CAAA,MAIlB,EAAA,EAAU,EAAA,GAAA,CADlB,EAYK,MAZL,GAYK,CARS,EAAA,EAAW,CAAC,MAAI,EAAM,EAAA,EAAU,EAAA,GAAA,CAA5C,EAEM,OAAA,GAFuC,MACzC,EAAG,EAAA,EAAiB,CAAC,OAAM,CAAG,QAClC,EAAA,EAAA,CAEc,EAAA,EAAW,CAAC,MAAI,EAAM,EAAA,EAAc,CAAC,OAAM,GAAA,GAAA,CADzD,EAIM,OAAA,GAAA,EADD,EAAA,EAAc,CAAC,OAAM,CAAG,UAC7B,EAAA,EAAA,EAAA,OAAA,GAAA,CAAA,CAAA,EAAA,EAAA,OAAA,GAAA,CAAA,CAAA"}
package/dist/C_Guide.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./C_Icon.js`);const e=require(`./C_Guide.js`);exports.C_Guide=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./C_Guide.js`);exports.C_Guide=e.t;
package/dist/C_Guide.js CHANGED
@@ -1 +1 @@
1
- import"./C_Icon2.js";import{t as e}from"./C_Guide2.js";export{e as C_Guide};
1
+ import{t as e}from"./C_Guide2.js";export{e as C_Guide};
@@ -1 +1 @@
1
- {"version":3,"file":"C_Guide.js","names":[],"sources":["../src/components/C_Guide/index.vue","../src/components/C_Guide/index.vue","../src/components/C_Guide/index.vue"],"sourcesContent":["/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @LastEditors: ChenYu ycyplus@gmail.com\r\n * @LastEditTime: 2026-03-06\r\n * @Description: 用户引导组件(基于 driver.js)— 增强版\r\n * 支持:步骤分组 / 键盘导航 / 步骤回调 / 主题自定义 / 完成持久化\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\r\n-->\r\n<template>\r\n <NTooltip\r\n v-if=\"props.showTrigger\"\r\n placement=\"bottom\"\r\n trigger=\"hover\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n text\r\n @click=\"startGuide\"\r\n >\r\n <C_Icon\r\n :name=\"props.triggerIcon\"\r\n :size=\"18\"\r\n />\r\n </NButton>\r\n </template>\r\n <span>{{ props.triggerTooltip }}</span>\r\n </NTooltip>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed } from 'vue'\r\n import { NTooltip, NButton } from 'naive-ui'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import { driver } from 'driver.js'\r\n import 'driver.js/dist/driver.css'\r\n import type { GuideProps, GuideStep } from './types'\r\n\r\n defineOptions({ name: 'C_Guide' })\r\n\r\n const props = withDefaults(defineProps<GuideProps>(), {\r\n steps: () => [],\r\n doneBtnText: '完成',\r\n nextBtnText: '下一步',\r\n prevBtnText: '上一步',\r\n showProgress: true,\r\n keyboard: true,\r\n animate: true,\r\n allowClose: true,\r\n popoverClass: 'driverjs-theme',\r\n showTrigger: true,\r\n triggerTooltip: '功能引导',\r\n triggerIcon: 'mdi:sign-routes',\r\n })\r\n\r\n const emit = defineEmits<{\r\n /** 引导开始 */\r\n start: []\r\n /** 引导完成(走完全部步骤) */\r\n complete: []\r\n /** 引导被用户关闭(未完成) */\r\n close: [currentStep: number]\r\n /** 步骤切换 */\r\n 'step-change': [stepIndex: number, step: GuideStep]\r\n }>()\r\n\r\n /** 持久化 key */\r\n const persistKey = computed(() => {\r\n const prefix = props.persistence?.keyPrefix ?? 'c_guide'\r\n return `${prefix}_completed`\r\n })\r\n\r\n /** 是否已完成引导 */\r\n const isCompleted = (): boolean => {\r\n if (!props.persistence?.enabled) return false\r\n try {\r\n return localStorage.getItem(persistKey.value) === 'true'\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /** 标记引导已完成 */\r\n const markCompleted = () => {\r\n if (!props.persistence?.enabled) return\r\n try {\r\n localStorage.setItem(persistKey.value, 'true')\r\n } catch {\r\n // localStorage 不可用时静默忽略\r\n }\r\n }\r\n\r\n /** 重置完成状态 */\r\n const resetCompleted = () => {\r\n try {\r\n localStorage.removeItem(persistKey.value)\r\n } catch {\r\n // 静默忽略\r\n }\r\n }\r\n\r\n /**\r\n * * @description: 过滤掉需要跳过的步骤\r\n * ! @return 有效步骤列表\r\n */\r\n const getActiveSteps = (): GuideStep[] => {\r\n return props.steps.filter(step => !step.skipIf?.())\r\n }\r\n\r\n /**\r\n * * @description: 构建主题相关的 CSS 变量\r\n * ! @return popoverClass 字符串\r\n */\r\n const buildPopoverClass = (): string => {\r\n const classes = [props.popoverClass]\r\n if (props.theme) {\r\n // 通过 CSS 变量注入主题\r\n const root = document.documentElement\r\n if (props.theme.popoverBgColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-bg',\r\n props.theme.popoverBgColor\r\n )\r\n if (props.theme.popoverTextColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-text',\r\n props.theme.popoverTextColor\r\n )\r\n if (props.theme.primaryColor)\r\n root.style.setProperty(\r\n '--c-guide-primary',\r\n props.theme.primaryColor\r\n )\r\n if (props.theme.overlayOpacity !== undefined)\r\n root.style.setProperty(\r\n '--c-guide-overlay-opacity',\r\n String(props.theme.overlayOpacity)\r\n )\r\n if (props.theme.borderRadius)\r\n root.style.setProperty(\r\n '--c-guide-radius',\r\n props.theme.borderRadius\r\n )\r\n }\r\n return classes.filter(Boolean).join(' ')\r\n }\r\n\r\n /**\r\n * * @description: 启动引导流程\r\n * ? @param {boolean} force 是否强制启动(忽略持久化状态)\r\n */\r\n const startGuide = (force = false) => {\r\n if (!force && isCompleted()) return\r\n\r\n const activeSteps = getActiveSteps()\r\n if (!activeSteps.length) {\r\n console.warn(\r\n '[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。'\r\n )\r\n return\r\n }\r\n\r\n let currentStepIndex = 0\r\n\r\n const driverObj = driver({\r\n popoverClass: buildPopoverClass(),\r\n animate: props.animate,\r\n showProgress: props.showProgress,\r\n allowClose: props.allowClose,\r\n doneBtnText: props.doneBtnText,\r\n nextBtnText: props.nextBtnText,\r\n prevBtnText: props.prevBtnText,\r\n steps: activeSteps.map(step => ({\r\n element: step.element,\r\n popover: step.popover,\r\n })),\r\n onHighlightStarted: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n if (step) {\r\n step.onHighlightStarted?.(_el as Element | undefined, step)\r\n emit('step-change', currentStepIndex, step)\r\n }\r\n },\r\n onDeselected: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n step?.onDeselected?.(_el as Element | undefined, step)\r\n currentStepIndex++\r\n },\r\n onDestroyStarted: () => {\r\n if (currentStepIndex >= activeSteps.length) {\r\n markCompleted()\r\n emit('complete')\r\n } else {\r\n emit('close', currentStepIndex)\r\n }\r\n driverObj.destroy()\r\n },\r\n })\r\n\r\n emit('start')\r\n driverObj.drive()\r\n }\r\n\r\n defineExpose({\r\n startGuide,\r\n resetCompleted,\r\n isCompleted,\r\n })\r\n</script>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @LastEditors: ChenYu ycyplus@gmail.com\r\n * @LastEditTime: 2026-03-06\r\n * @Description: 用户引导组件(基于 driver.js)— 增强版\r\n * 支持:步骤分组 / 键盘导航 / 步骤回调 / 主题自定义 / 完成持久化\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\r\n-->\r\n<template>\r\n <NTooltip\r\n v-if=\"props.showTrigger\"\r\n placement=\"bottom\"\r\n trigger=\"hover\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n text\r\n @click=\"startGuide\"\r\n >\r\n <C_Icon\r\n :name=\"props.triggerIcon\"\r\n :size=\"18\"\r\n />\r\n </NButton>\r\n </template>\r\n <span>{{ props.triggerTooltip }}</span>\r\n </NTooltip>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed } from 'vue'\r\n import { NTooltip, NButton } from 'naive-ui'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import { driver } from 'driver.js'\r\n import 'driver.js/dist/driver.css'\r\n import type { GuideProps, GuideStep } from './types'\r\n\r\n defineOptions({ name: 'C_Guide' })\r\n\r\n const props = withDefaults(defineProps<GuideProps>(), {\r\n steps: () => [],\r\n doneBtnText: '完成',\r\n nextBtnText: '下一步',\r\n prevBtnText: '上一步',\r\n showProgress: true,\r\n keyboard: true,\r\n animate: true,\r\n allowClose: true,\r\n popoverClass: 'driverjs-theme',\r\n showTrigger: true,\r\n triggerTooltip: '功能引导',\r\n triggerIcon: 'mdi:sign-routes',\r\n })\r\n\r\n const emit = defineEmits<{\r\n /** 引导开始 */\r\n start: []\r\n /** 引导完成(走完全部步骤) */\r\n complete: []\r\n /** 引导被用户关闭(未完成) */\r\n close: [currentStep: number]\r\n /** 步骤切换 */\r\n 'step-change': [stepIndex: number, step: GuideStep]\r\n }>()\r\n\r\n /** 持久化 key */\r\n const persistKey = computed(() => {\r\n const prefix = props.persistence?.keyPrefix ?? 'c_guide'\r\n return `${prefix}_completed`\r\n })\r\n\r\n /** 是否已完成引导 */\r\n const isCompleted = (): boolean => {\r\n if (!props.persistence?.enabled) return false\r\n try {\r\n return localStorage.getItem(persistKey.value) === 'true'\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /** 标记引导已完成 */\r\n const markCompleted = () => {\r\n if (!props.persistence?.enabled) return\r\n try {\r\n localStorage.setItem(persistKey.value, 'true')\r\n } catch {\r\n // localStorage 不可用时静默忽略\r\n }\r\n }\r\n\r\n /** 重置完成状态 */\r\n const resetCompleted = () => {\r\n try {\r\n localStorage.removeItem(persistKey.value)\r\n } catch {\r\n // 静默忽略\r\n }\r\n }\r\n\r\n /**\r\n * * @description: 过滤掉需要跳过的步骤\r\n * ! @return 有效步骤列表\r\n */\r\n const getActiveSteps = (): GuideStep[] => {\r\n return props.steps.filter(step => !step.skipIf?.())\r\n }\r\n\r\n /**\r\n * * @description: 构建主题相关的 CSS 变量\r\n * ! @return popoverClass 字符串\r\n */\r\n const buildPopoverClass = (): string => {\r\n const classes = [props.popoverClass]\r\n if (props.theme) {\r\n // 通过 CSS 变量注入主题\r\n const root = document.documentElement\r\n if (props.theme.popoverBgColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-bg',\r\n props.theme.popoverBgColor\r\n )\r\n if (props.theme.popoverTextColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-text',\r\n props.theme.popoverTextColor\r\n )\r\n if (props.theme.primaryColor)\r\n root.style.setProperty(\r\n '--c-guide-primary',\r\n props.theme.primaryColor\r\n )\r\n if (props.theme.overlayOpacity !== undefined)\r\n root.style.setProperty(\r\n '--c-guide-overlay-opacity',\r\n String(props.theme.overlayOpacity)\r\n )\r\n if (props.theme.borderRadius)\r\n root.style.setProperty(\r\n '--c-guide-radius',\r\n props.theme.borderRadius\r\n )\r\n }\r\n return classes.filter(Boolean).join(' ')\r\n }\r\n\r\n /**\r\n * * @description: 启动引导流程\r\n * ? @param {boolean} force 是否强制启动(忽略持久化状态)\r\n */\r\n const startGuide = (force = false) => {\r\n if (!force && isCompleted()) return\r\n\r\n const activeSteps = getActiveSteps()\r\n if (!activeSteps.length) {\r\n console.warn(\r\n '[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。'\r\n )\r\n return\r\n }\r\n\r\n let currentStepIndex = 0\r\n\r\n const driverObj = driver({\r\n popoverClass: buildPopoverClass(),\r\n animate: props.animate,\r\n showProgress: props.showProgress,\r\n allowClose: props.allowClose,\r\n doneBtnText: props.doneBtnText,\r\n nextBtnText: props.nextBtnText,\r\n prevBtnText: props.prevBtnText,\r\n steps: activeSteps.map(step => ({\r\n element: step.element,\r\n popover: step.popover,\r\n })),\r\n onHighlightStarted: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n if (step) {\r\n step.onHighlightStarted?.(_el as Element | undefined, step)\r\n emit('step-change', currentStepIndex, step)\r\n }\r\n },\r\n onDeselected: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n step?.onDeselected?.(_el as Element | undefined, step)\r\n currentStepIndex++\r\n },\r\n onDestroyStarted: () => {\r\n if (currentStepIndex >= activeSteps.length) {\r\n markCompleted()\r\n emit('complete')\r\n } else {\r\n emit('close', currentStepIndex)\r\n }\r\n driverObj.destroy()\r\n },\r\n })\r\n\r\n emit('start')\r\n driverObj.drive()\r\n }\r\n\r\n defineExpose({\r\n startGuide,\r\n resetCompleted,\r\n isCompleted,\r\n })\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @LastEditors: ChenYu ycyplus@gmail.com\r\n * @LastEditTime: 2026-03-06\r\n * @Description: 用户引导组件(基于 driver.js)— 增强版\r\n * 支持:步骤分组 / 键盘导航 / 步骤回调 / 主题自定义 / 完成持久化\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\r\n-->\r\n<template>\r\n <NTooltip\r\n v-if=\"props.showTrigger\"\r\n placement=\"bottom\"\r\n trigger=\"hover\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n text\r\n @click=\"startGuide\"\r\n >\r\n <C_Icon\r\n :name=\"props.triggerIcon\"\r\n :size=\"18\"\r\n />\r\n </NButton>\r\n </template>\r\n <span>{{ props.triggerTooltip }}</span>\r\n </NTooltip>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed } from 'vue'\r\n import { NTooltip, NButton } from 'naive-ui'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import { driver } from 'driver.js'\r\n import 'driver.js/dist/driver.css'\r\n import type { GuideProps, GuideStep } from './types'\r\n\r\n defineOptions({ name: 'C_Guide' })\r\n\r\n const props = withDefaults(defineProps<GuideProps>(), {\r\n steps: () => [],\r\n doneBtnText: '完成',\r\n nextBtnText: '下一步',\r\n prevBtnText: '上一步',\r\n showProgress: true,\r\n keyboard: true,\r\n animate: true,\r\n allowClose: true,\r\n popoverClass: 'driverjs-theme',\r\n showTrigger: true,\r\n triggerTooltip: '功能引导',\r\n triggerIcon: 'mdi:sign-routes',\r\n })\r\n\r\n const emit = defineEmits<{\r\n /** 引导开始 */\r\n start: []\r\n /** 引导完成(走完全部步骤) */\r\n complete: []\r\n /** 引导被用户关闭(未完成) */\r\n close: [currentStep: number]\r\n /** 步骤切换 */\r\n 'step-change': [stepIndex: number, step: GuideStep]\r\n }>()\r\n\r\n /** 持久化 key */\r\n const persistKey = computed(() => {\r\n const prefix = props.persistence?.keyPrefix ?? 'c_guide'\r\n return `${prefix}_completed`\r\n })\r\n\r\n /** 是否已完成引导 */\r\n const isCompleted = (): boolean => {\r\n if (!props.persistence?.enabled) return false\r\n try {\r\n return localStorage.getItem(persistKey.value) === 'true'\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /** 标记引导已完成 */\r\n const markCompleted = () => {\r\n if (!props.persistence?.enabled) return\r\n try {\r\n localStorage.setItem(persistKey.value, 'true')\r\n } catch {\r\n // localStorage 不可用时静默忽略\r\n }\r\n }\r\n\r\n /** 重置完成状态 */\r\n const resetCompleted = () => {\r\n try {\r\n localStorage.removeItem(persistKey.value)\r\n } catch {\r\n // 静默忽略\r\n }\r\n }\r\n\r\n /**\r\n * * @description: 过滤掉需要跳过的步骤\r\n * ! @return 有效步骤列表\r\n */\r\n const getActiveSteps = (): GuideStep[] => {\r\n return props.steps.filter(step => !step.skipIf?.())\r\n }\r\n\r\n /**\r\n * * @description: 构建主题相关的 CSS 变量\r\n * ! @return popoverClass 字符串\r\n */\r\n const buildPopoverClass = (): string => {\r\n const classes = [props.popoverClass]\r\n if (props.theme) {\r\n // 通过 CSS 变量注入主题\r\n const root = document.documentElement\r\n if (props.theme.popoverBgColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-bg',\r\n props.theme.popoverBgColor\r\n )\r\n if (props.theme.popoverTextColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-text',\r\n props.theme.popoverTextColor\r\n )\r\n if (props.theme.primaryColor)\r\n root.style.setProperty(\r\n '--c-guide-primary',\r\n props.theme.primaryColor\r\n )\r\n if (props.theme.overlayOpacity !== undefined)\r\n root.style.setProperty(\r\n '--c-guide-overlay-opacity',\r\n String(props.theme.overlayOpacity)\r\n )\r\n if (props.theme.borderRadius)\r\n root.style.setProperty(\r\n '--c-guide-radius',\r\n props.theme.borderRadius\r\n )\r\n }\r\n return classes.filter(Boolean).join(' ')\r\n }\r\n\r\n /**\r\n * * @description: 启动引导流程\r\n * ? @param {boolean} force 是否强制启动(忽略持久化状态)\r\n */\r\n const startGuide = (force = false) => {\r\n if (!force && isCompleted()) return\r\n\r\n const activeSteps = getActiveSteps()\r\n if (!activeSteps.length) {\r\n console.warn(\r\n '[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。'\r\n )\r\n return\r\n }\r\n\r\n let currentStepIndex = 0\r\n\r\n const driverObj = driver({\r\n popoverClass: buildPopoverClass(),\r\n animate: props.animate,\r\n showProgress: props.showProgress,\r\n allowClose: props.allowClose,\r\n doneBtnText: props.doneBtnText,\r\n nextBtnText: props.nextBtnText,\r\n prevBtnText: props.prevBtnText,\r\n steps: activeSteps.map(step => ({\r\n element: step.element,\r\n popover: step.popover,\r\n })),\r\n onHighlightStarted: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n if (step) {\r\n step.onHighlightStarted?.(_el as Element | undefined, step)\r\n emit('step-change', currentStepIndex, step)\r\n }\r\n },\r\n onDeselected: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n step?.onDeselected?.(_el as Element | undefined, step)\r\n currentStepIndex++\r\n },\r\n onDestroyStarted: () => {\r\n if (currentStepIndex >= activeSteps.length) {\r\n markCompleted()\r\n emit('complete')\r\n } else {\r\n emit('close', currentStepIndex)\r\n }\r\n driverObj.destroy()\r\n },\r\n })\r\n\r\n emit('start')\r\n driverObj.drive()\r\n }\r\n\r\n defineExpose({\r\n startGuide,\r\n resetCompleted,\r\n isCompleted,\r\n })\r\n</script>\r\n"],"mappings":"otBEwCE,IAAM,EAAQ,EAeR,EAAO,EAYP,GAAA,EAAA,EAAA,cAEG,GADQ,EAAM,aAAa,WAAa,UAC9B,YACjB,CAGI,MAA6B,CACjC,GAAI,CAAC,EAAM,aAAa,QAAS,MAAO,GACxC,GAAI,CACF,OAAO,aAAa,QAAQ,EAAW,MAAM,GAAK,YAC5C,CACN,MAAO,KAKL,MAAsB,CACrB,KAAM,aAAa,QACxB,GAAI,CACF,aAAa,QAAQ,EAAW,MAAO,OAAO,MACxC,IAMJ,MAAuB,CAC3B,GAAI,CACF,aAAa,WAAW,EAAW,MAAM,MACnC,IASJ,MACG,EAAM,MAAM,OAAO,GAAQ,CAAC,EAAK,UAAU,CAAC,CAO/C,MAAkC,CACtC,IAAM,EAAU,CAAC,EAAM,aAAa,CACpC,GAAI,EAAM,MAAO,CAEf,IAAM,EAAO,SAAS,gBAClB,EAAM,MAAM,gBACd,EAAK,MAAM,YACT,uBACA,EAAM,MAAM,eACb,CACC,EAAM,MAAM,kBACd,EAAK,MAAM,YACT,yBACA,EAAM,MAAM,iBACb,CACC,EAAM,MAAM,cACd,EAAK,MAAM,YACT,oBACA,EAAM,MAAM,aACb,CACC,EAAM,MAAM,iBAAmB,IAAA,IACjC,EAAK,MAAM,YACT,4BACA,OAAO,EAAM,MAAM,eAAe,CACnC,CACC,EAAM,MAAM,cACd,EAAK,MAAM,YACT,mBACA,EAAM,MAAM,aACb,CAEL,OAAO,EAAQ,OAAO,QAAQ,CAAC,KAAK,IAAI,EAOpC,GAAc,EAAQ,KAAU,CACpC,GAAI,CAAC,GAAS,GAAa,CAAE,OAE7B,IAAM,EAAc,GAAgB,CACpC,GAAI,CAAC,EAAY,OAAQ,CACvB,QAAQ,KACN,+CACD,CACD,OAGF,IAAI,EAAmB,EAEjB,GAAA,EAAA,EAAA,QAAmB,CACvB,aAAc,GAAmB,CACjC,QAAS,EAAM,QACf,aAAc,EAAM,aACpB,WAAY,EAAM,WAClB,YAAa,EAAM,YACnB,YAAa,EAAM,YACnB,YAAa,EAAM,YACnB,MAAO,EAAY,IAAI,IAAS,CAC9B,QAAS,EAAK,QACd,QAAS,EAAK,QACf,EAAE,CACH,mBAAqB,GAAQ,CAC3B,IAAM,EAAO,EAAY,GACrB,IACF,EAAK,qBAAqB,EAA4B,EAAK,CAC3D,EAAK,cAAe,EAAkB,EAAK,GAG/C,aAAe,GAAQ,CACrB,IAAM,EAAO,EAAY,GACzB,GAAM,eAAe,EAA4B,EAAK,CACtD,KAEF,qBAAwB,CAClB,GAAoB,EAAY,QAClC,GAAe,CACf,EAAK,WAAW,EAEhB,EAAK,QAAS,EAAiB,CAEjC,EAAU,SAAS,EAEtB,CAAC,CAEF,EAAK,QAAQ,CACb,EAAU,OAAO,SAGnB,EAAa,CACX,aACA,iBACA,cACD,CAAC,QApMM,EAAM,cAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAgBH,EAAA,SAAA,CAAA,OAfT,UAAU,SACV,QAAQ,UAEG,SAAA,EAAA,EAAA,aASC,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,QAAA,CAAA,CAPR,KAAA,GACC,QAAO,8BAKN,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAFC,KAAM,EAAM,YACZ,KAAM,yDAI0B,EAAA,EAAA,EAAA,oBAAA,OAAA,MAAA,EAAA,EAAA,iBAA9B,EAAM,eAAc,CAAA,EAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"C_Guide.js","names":[],"sources":["../src/components/C_Guide/index.vue"],"sourcesContent":["<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-06-01\n * @LastEditors: ChenYu ycyplus@gmail.com\n * @LastEditTime: 2026-03-06\n * @Description: 用户引导组件(基于 driver.js)— 增强版\n * 支持:步骤分组 / 键盘导航 / 步骤回调 / 主题自定义 / 完成持久化\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\n-->\n<template>\n <NTooltip\n v-if=\"props.showTrigger\"\n placement=\"bottom\"\n trigger=\"hover\"\n >\n <template #trigger>\n <NButton\n text\n @click=\"startGuide\"\n >\n <C_Icon\n :name=\"props.triggerIcon\"\n :size=\"18\"\n />\n </NButton>\n </template>\n <span>{{ props.triggerTooltip }}</span>\n </NTooltip>\n</template>\n\n<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { NTooltip, NButton } from 'naive-ui'\n import C_Icon from '../C_Icon/index.vue'\n import { driver } from 'driver.js'\n import 'driver.js/dist/driver.css'\n import type { GuideProps, GuideStep } from './types'\n\n defineOptions({ name: 'C_Guide' })\n\n const props = withDefaults(defineProps<GuideProps>(), {\n steps: () => [],\n doneBtnText: '完成',\n nextBtnText: '下一步',\n prevBtnText: '上一步',\n showProgress: true,\n keyboard: true,\n animate: true,\n allowClose: true,\n popoverClass: 'driverjs-theme',\n showTrigger: true,\n triggerTooltip: '功能引导',\n triggerIcon: 'mdi:sign-routes',\n })\n\n const emit = defineEmits<{\n /** 引导开始 */\n start: []\n /** 引导完成(走完全部步骤) */\n complete: []\n /** 引导被用户关闭(未完成) */\n close: [currentStep: number]\n /** 步骤切换 */\n 'step-change': [stepIndex: number, step: GuideStep]\n }>()\n\n /** 持久化 key */\n const persistKey = computed(() => {\n const prefix = props.persistence?.keyPrefix ?? 'c_guide'\n return `${prefix}_completed`\n })\n\n /** 是否已完成引导 */\n const isCompleted = (): boolean => {\n if (!props.persistence?.enabled) return false\n try {\n return localStorage.getItem(persistKey.value) === 'true'\n } catch {\n return false\n }\n }\n\n /** 标记引导已完成 */\n const markCompleted = () => {\n if (!props.persistence?.enabled) return\n try {\n localStorage.setItem(persistKey.value, 'true')\n } catch {\n // localStorage 不可用时静默忽略\n }\n }\n\n /** 重置完成状态 */\n const resetCompleted = () => {\n try {\n localStorage.removeItem(persistKey.value)\n } catch {\n // 静默忽略\n }\n }\n\n /**\n * * @description: 过滤掉需要跳过的步骤\n * ! @return 有效步骤列表\n */\n const getActiveSteps = (): GuideStep[] => {\n return props.steps.filter(step => !step.skipIf?.())\n }\n\n /**\n * * @description: 构建主题相关的 CSS 变量\n * ! @return popoverClass 字符串\n */\n const buildPopoverClass = (): string => {\n const classes = [props.popoverClass]\n if (props.theme) {\n // 通过 CSS 变量注入主题\n const root = document.documentElement\n if (props.theme.popoverBgColor)\n root.style.setProperty(\n '--c-guide-popover-bg',\n props.theme.popoverBgColor\n )\n if (props.theme.popoverTextColor)\n root.style.setProperty(\n '--c-guide-popover-text',\n props.theme.popoverTextColor\n )\n if (props.theme.primaryColor)\n root.style.setProperty(\n '--c-guide-primary',\n props.theme.primaryColor\n )\n if (props.theme.overlayOpacity !== undefined)\n root.style.setProperty(\n '--c-guide-overlay-opacity',\n String(props.theme.overlayOpacity)\n )\n if (props.theme.borderRadius)\n root.style.setProperty(\n '--c-guide-radius',\n props.theme.borderRadius\n )\n }\n return classes.filter(Boolean).join(' ')\n }\n\n /**\n * * @description: 启动引导流程\n * ? @param {boolean} force 是否强制启动(忽略持久化状态)\n */\n const startGuide = (force = false) => {\n if (!force && isCompleted()) return\n\n const activeSteps = getActiveSteps()\n if (!activeSteps.length) {\n console.warn(\n '[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。'\n )\n return\n }\n\n let currentStepIndex = 0\n\n const driverObj = driver({\n popoverClass: buildPopoverClass(),\n animate: props.animate,\n showProgress: props.showProgress,\n allowClose: props.allowClose,\n doneBtnText: props.doneBtnText,\n nextBtnText: props.nextBtnText,\n prevBtnText: props.prevBtnText,\n steps: activeSteps.map(step => ({\n element: step.element,\n popover: step.popover,\n })),\n onHighlightStarted: (_el) => {\n const step = activeSteps[currentStepIndex]\n if (step) {\n step.onHighlightStarted?.(_el as Element | undefined, step)\n emit('step-change', currentStepIndex, step)\n }\n },\n onDeselected: (_el) => {\n const step = activeSteps[currentStepIndex]\n step?.onDeselected?.(_el as Element | undefined, step)\n currentStepIndex++\n },\n onDestroyStarted: () => {\n if (currentStepIndex >= activeSteps.length) {\n markCompleted()\n emit('complete')\n } else {\n emit('close', currentStepIndex)\n }\n driverObj.destroy()\n },\n })\n\n emit('start')\n driverObj.drive()\n }\n\n defineExpose({\n startGuide,\n resetCompleted,\n isCompleted,\n })\n</script>\n"],"mappings":"stBAwCE,IAAM,EAAQ,EAeR,EAAO,EAYP,GAAA,EAAA,EAAA,cAEG,GADQ,EAAM,aAAa,WAAa,UAC9B,YAClB,CAGK,MAA6B,CACjC,GAAI,CAAC,EAAM,aAAa,QAAS,MAAO,GACxC,GAAI,CACF,OAAO,aAAa,QAAQ,EAAW,MAAM,GAAK,YAC5C,CACN,MAAO,KAKL,MAAsB,CACrB,KAAM,aAAa,QACxB,GAAI,CACF,aAAa,QAAQ,EAAW,MAAO,OAAM,MACvC,IAMJ,MAAuB,CAC3B,GAAI,CACF,aAAa,WAAW,EAAW,MAAK,MAClC,IASJ,MACG,EAAM,MAAM,OAAO,GAAQ,CAAC,EAAK,UAAU,CAAA,CAO9C,MAAkC,CACtC,IAAM,EAAU,CAAC,EAAM,aAAY,CACnC,GAAI,EAAM,MAAO,CAEf,IAAM,EAAO,SAAS,gBAClB,EAAM,MAAM,gBACd,EAAK,MAAM,YACT,uBACA,EAAM,MAAM,eACd,CACE,EAAM,MAAM,kBACd,EAAK,MAAM,YACT,yBACA,EAAM,MAAM,iBACd,CACE,EAAM,MAAM,cACd,EAAK,MAAM,YACT,oBACA,EAAM,MAAM,aACd,CACE,EAAM,MAAM,iBAAmB,IAAA,IACjC,EAAK,MAAM,YACT,4BACA,OAAO,EAAM,MAAM,eAAc,CACnC,CACE,EAAM,MAAM,cACd,EAAK,MAAM,YACT,mBACA,EAAM,MAAM,aACd,CAEJ,OAAO,EAAQ,OAAO,QAAQ,CAAC,KAAK,IAAG,EAOnC,GAAc,EAAQ,KAAU,CACpC,GAAI,CAAC,GAAS,GAAa,CAAE,OAE7B,IAAM,EAAc,GAAe,CACnC,GAAI,CAAC,EAAY,OAAQ,CACvB,QAAQ,KACN,+CACF,CACA,OAGF,IAAI,EAAmB,EAEjB,GAAA,EAAA,EAAA,QAAmB,CACvB,aAAc,GAAmB,CACjC,QAAS,EAAM,QACf,aAAc,EAAM,aACpB,WAAY,EAAM,WAClB,YAAa,EAAM,YACnB,YAAa,EAAM,YACnB,YAAa,EAAM,YACnB,MAAO,EAAY,IAAI,IAAS,CAC9B,QAAS,EAAK,QACd,QAAS,EAAK,QACf,EAAE,CACH,mBAAqB,GAAQ,CAC3B,IAAM,EAAO,EAAY,GACrB,IACF,EAAK,qBAAqB,EAA4B,EAAI,CAC1D,EAAK,cAAe,EAAkB,EAAI,GAG9C,aAAe,GAAQ,CACrB,IAAM,EAAO,EAAY,GACzB,GAAM,eAAe,EAA4B,EAAI,CACrD,KAEF,qBAAwB,CAClB,GAAoB,EAAY,QAClC,GAAc,CACd,EAAK,WAAU,EAEf,EAAK,QAAS,EAAgB,CAEhC,EAAU,SAAQ,EAErB,CAAA,CAED,EAAK,QAAO,CACZ,EAAU,OAAM,SAGlB,EAAa,CACX,aACA,iBACA,cACD,CAAA,QApMO,EAAM,cAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAgBJ,EAAA,SAAA,CAAA,OAfR,UAAU,SACV,QAAQ,UAEG,SAAA,EAAA,EAAA,aASA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,QAAA,CAAA,CAPP,KAAA,GACC,QAAO,8BAKP,EAAA,EAAA,EAAA,aAAA,EAAA,EAAA,CAFE,KAAM,EAAM,YACZ,KAAM,yDAIyB,EAAA,EAAA,EAAA,oBAAA,OAAA,MAAA,EAAA,EAAA,iBAA7B,EAAM,eAAc,CAAA,EAAA,CAAA,CAAA"}
package/dist/C_Guide2.js CHANGED
@@ -1,2 +1,2 @@
1
- import{t as e}from"./C_Icon2.js";import{NButton as t,NTooltip as n}from"naive-ui";import{computed as r,createBlock as i,createCommentVNode as a,createElementVNode as o,createVNode as s,defineComponent as c,openBlock as l,toDisplayString as u,unref as d,withCtx as f}from"vue";import{driver as p}from"driver.js";import"driver.js/dist/driver.css";var m=c({name:`C_Guide`,__name:`index`,props:{steps:{default:()=>[]},doneBtnText:{default:`完成`},nextBtnText:{default:`下一步`},prevBtnText:{default:`上一步`},showProgress:{type:Boolean,default:!0},keyboard:{type:Boolean,default:!0},animate:{type:Boolean,default:!0},allowClose:{type:Boolean,default:!0},theme:{},persistence:{},popoverClass:{default:`driverjs-theme`},showTrigger:{type:Boolean,default:!0},triggerTooltip:{default:`功能引导`},triggerIcon:{default:`mdi:sign-routes`}},emits:[`start`,`complete`,`close`,`step-change`],setup(c,{expose:m,emit:h}){let g=c,_=h,v=r(()=>`${g.persistence?.keyPrefix??`c_guide`}_completed`),y=()=>{if(!g.persistence?.enabled)return!1;try{return localStorage.getItem(v.value)===`true`}catch{return!1}},b=()=>{if(g.persistence?.enabled)try{localStorage.setItem(v.value,`true`)}catch{}},x=()=>{try{localStorage.removeItem(v.value)}catch{}},S=()=>g.steps.filter(e=>!e.skipIf?.()),C=()=>{let e=[g.popoverClass];if(g.theme){let e=document.documentElement;g.theme.popoverBgColor&&e.style.setProperty(`--c-guide-popover-bg`,g.theme.popoverBgColor),g.theme.popoverTextColor&&e.style.setProperty(`--c-guide-popover-text`,g.theme.popoverTextColor),g.theme.primaryColor&&e.style.setProperty(`--c-guide-primary`,g.theme.primaryColor),g.theme.overlayOpacity!==void 0&&e.style.setProperty(`--c-guide-overlay-opacity`,String(g.theme.overlayOpacity)),g.theme.borderRadius&&e.style.setProperty(`--c-guide-radius`,g.theme.borderRadius)}return e.filter(Boolean).join(` `)},w=(e=!1)=>{if(!e&&y())return;let t=S();if(!t.length){console.warn(`[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。`);return}let n=0,r=p({popoverClass:C(),animate:g.animate,showProgress:g.showProgress,allowClose:g.allowClose,doneBtnText:g.doneBtnText,nextBtnText:g.nextBtnText,prevBtnText:g.prevBtnText,steps:t.map(e=>({element:e.element,popover:e.popover})),onHighlightStarted:e=>{let r=t[n];r&&(r.onHighlightStarted?.(e,r),_(`step-change`,n,r))},onDeselected:e=>{let r=t[n];r?.onDeselected?.(e,r),n++},onDestroyStarted:()=>{n>=t.length?(b(),_(`complete`)):_(`close`,n),r.destroy()}});_(`start`),r.drive()};return m({startGuide:w,resetCompleted:x,isCompleted:y}),(r,c)=>g.showTrigger?(l(),i(d(n),{key:0,placement:`bottom`,trigger:`hover`},{trigger:f(()=>[s(d(t),{text:``,onClick:w},{default:f(()=>[s(e,{name:g.triggerIcon,size:18},null,8,[`name`])]),_:1})]),default:f(()=>[o(`span`,null,u(g.triggerTooltip),1)]),_:1})):a(`v-if`,!0)}});export{m as t};
1
+ import{t as e}from"./C_Icon2.js";import{computed as t,createBlock as n,createCommentVNode as r,createElementVNode as i,createVNode as a,defineComponent as o,openBlock as s,toDisplayString as c,unref as l,withCtx as u}from"vue";import{NButton as d,NTooltip as f}from"naive-ui";import{driver as p}from"driver.js";import"driver.js/dist/driver.css";const m=o({name:`C_Guide`,__name:`index`,props:{steps:{default:()=>[]},doneBtnText:{default:`完成`},nextBtnText:{default:`下一步`},prevBtnText:{default:`上一步`},showProgress:{type:Boolean,default:!0},keyboard:{type:Boolean,default:!0},animate:{type:Boolean,default:!0},allowClose:{type:Boolean,default:!0},theme:{},persistence:{},popoverClass:{default:`driverjs-theme`},showTrigger:{type:Boolean,default:!0},triggerTooltip:{default:`功能引导`},triggerIcon:{default:`mdi:sign-routes`}},emits:[`start`,`complete`,`close`,`step-change`],setup(o,{expose:m,emit:h}){let g=o,_=h,v=t(()=>`${g.persistence?.keyPrefix??`c_guide`}_completed`),y=()=>{if(!g.persistence?.enabled)return!1;try{return localStorage.getItem(v.value)===`true`}catch{return!1}},b=()=>{if(g.persistence?.enabled)try{localStorage.setItem(v.value,`true`)}catch{}},x=()=>{try{localStorage.removeItem(v.value)}catch{}},S=()=>g.steps.filter(e=>!e.skipIf?.()),C=()=>{let e=[g.popoverClass];if(g.theme){let e=document.documentElement;g.theme.popoverBgColor&&e.style.setProperty(`--c-guide-popover-bg`,g.theme.popoverBgColor),g.theme.popoverTextColor&&e.style.setProperty(`--c-guide-popover-text`,g.theme.popoverTextColor),g.theme.primaryColor&&e.style.setProperty(`--c-guide-primary`,g.theme.primaryColor),g.theme.overlayOpacity!==void 0&&e.style.setProperty(`--c-guide-overlay-opacity`,String(g.theme.overlayOpacity)),g.theme.borderRadius&&e.style.setProperty(`--c-guide-radius`,g.theme.borderRadius)}return e.filter(Boolean).join(` `)},w=(e=!1)=>{if(!e&&y())return;let t=S();if(!t.length){console.warn(`[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。`);return}let n=0,r=p({popoverClass:C(),animate:g.animate,showProgress:g.showProgress,allowClose:g.allowClose,doneBtnText:g.doneBtnText,nextBtnText:g.nextBtnText,prevBtnText:g.prevBtnText,steps:t.map(e=>({element:e.element,popover:e.popover})),onHighlightStarted:e=>{let r=t[n];r&&(r.onHighlightStarted?.(e,r),_(`step-change`,n,r))},onDeselected:e=>{let r=t[n];r?.onDeselected?.(e,r),n++},onDestroyStarted:()=>{n>=t.length?(b(),_(`complete`)):_(`close`,n),r.destroy()}});_(`start`),r.drive()};return m({startGuide:w,resetCompleted:x,isCompleted:y}),(t,o)=>g.showTrigger?(s(),n(l(f),{key:0,placement:`bottom`,trigger:`hover`},{trigger:u(()=>[a(l(d),{text:``,onClick:w},{default:u(()=>[a(e,{name:g.triggerIcon,size:18},null,8,[`name`])]),_:1})]),default:u(()=>[i(`span`,null,c(g.triggerTooltip),1)]),_:1})):r(`v-if`,!0)}});export{m as t};
2
2
  //# sourceMappingURL=C_Guide2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"C_Guide2.js","names":[],"sources":["../src/components/C_Guide/index.vue","../src/components/C_Guide/index.vue","../src/components/C_Guide/index.vue"],"sourcesContent":["/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @LastEditors: ChenYu ycyplus@gmail.com\r\n * @LastEditTime: 2026-03-06\r\n * @Description: 用户引导组件(基于 driver.js)— 增强版\r\n * 支持:步骤分组 / 键盘导航 / 步骤回调 / 主题自定义 / 完成持久化\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\r\n-->\r\n<template>\r\n <NTooltip\r\n v-if=\"props.showTrigger\"\r\n placement=\"bottom\"\r\n trigger=\"hover\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n text\r\n @click=\"startGuide\"\r\n >\r\n <C_Icon\r\n :name=\"props.triggerIcon\"\r\n :size=\"18\"\r\n />\r\n </NButton>\r\n </template>\r\n <span>{{ props.triggerTooltip }}</span>\r\n </NTooltip>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed } from 'vue'\r\n import { NTooltip, NButton } from 'naive-ui'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import { driver } from 'driver.js'\r\n import 'driver.js/dist/driver.css'\r\n import type { GuideProps, GuideStep } from './types'\r\n\r\n defineOptions({ name: 'C_Guide' })\r\n\r\n const props = withDefaults(defineProps<GuideProps>(), {\r\n steps: () => [],\r\n doneBtnText: '完成',\r\n nextBtnText: '下一步',\r\n prevBtnText: '上一步',\r\n showProgress: true,\r\n keyboard: true,\r\n animate: true,\r\n allowClose: true,\r\n popoverClass: 'driverjs-theme',\r\n showTrigger: true,\r\n triggerTooltip: '功能引导',\r\n triggerIcon: 'mdi:sign-routes',\r\n })\r\n\r\n const emit = defineEmits<{\r\n /** 引导开始 */\r\n start: []\r\n /** 引导完成(走完全部步骤) */\r\n complete: []\r\n /** 引导被用户关闭(未完成) */\r\n close: [currentStep: number]\r\n /** 步骤切换 */\r\n 'step-change': [stepIndex: number, step: GuideStep]\r\n }>()\r\n\r\n /** 持久化 key */\r\n const persistKey = computed(() => {\r\n const prefix = props.persistence?.keyPrefix ?? 'c_guide'\r\n return `${prefix}_completed`\r\n })\r\n\r\n /** 是否已完成引导 */\r\n const isCompleted = (): boolean => {\r\n if (!props.persistence?.enabled) return false\r\n try {\r\n return localStorage.getItem(persistKey.value) === 'true'\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /** 标记引导已完成 */\r\n const markCompleted = () => {\r\n if (!props.persistence?.enabled) return\r\n try {\r\n localStorage.setItem(persistKey.value, 'true')\r\n } catch {\r\n // localStorage 不可用时静默忽略\r\n }\r\n }\r\n\r\n /** 重置完成状态 */\r\n const resetCompleted = () => {\r\n try {\r\n localStorage.removeItem(persistKey.value)\r\n } catch {\r\n // 静默忽略\r\n }\r\n }\r\n\r\n /**\r\n * * @description: 过滤掉需要跳过的步骤\r\n * ! @return 有效步骤列表\r\n */\r\n const getActiveSteps = (): GuideStep[] => {\r\n return props.steps.filter(step => !step.skipIf?.())\r\n }\r\n\r\n /**\r\n * * @description: 构建主题相关的 CSS 变量\r\n * ! @return popoverClass 字符串\r\n */\r\n const buildPopoverClass = (): string => {\r\n const classes = [props.popoverClass]\r\n if (props.theme) {\r\n // 通过 CSS 变量注入主题\r\n const root = document.documentElement\r\n if (props.theme.popoverBgColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-bg',\r\n props.theme.popoverBgColor\r\n )\r\n if (props.theme.popoverTextColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-text',\r\n props.theme.popoverTextColor\r\n )\r\n if (props.theme.primaryColor)\r\n root.style.setProperty(\r\n '--c-guide-primary',\r\n props.theme.primaryColor\r\n )\r\n if (props.theme.overlayOpacity !== undefined)\r\n root.style.setProperty(\r\n '--c-guide-overlay-opacity',\r\n String(props.theme.overlayOpacity)\r\n )\r\n if (props.theme.borderRadius)\r\n root.style.setProperty(\r\n '--c-guide-radius',\r\n props.theme.borderRadius\r\n )\r\n }\r\n return classes.filter(Boolean).join(' ')\r\n }\r\n\r\n /**\r\n * * @description: 启动引导流程\r\n * ? @param {boolean} force 是否强制启动(忽略持久化状态)\r\n */\r\n const startGuide = (force = false) => {\r\n if (!force && isCompleted()) return\r\n\r\n const activeSteps = getActiveSteps()\r\n if (!activeSteps.length) {\r\n console.warn(\r\n '[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。'\r\n )\r\n return\r\n }\r\n\r\n let currentStepIndex = 0\r\n\r\n const driverObj = driver({\r\n popoverClass: buildPopoverClass(),\r\n animate: props.animate,\r\n showProgress: props.showProgress,\r\n allowClose: props.allowClose,\r\n doneBtnText: props.doneBtnText,\r\n nextBtnText: props.nextBtnText,\r\n prevBtnText: props.prevBtnText,\r\n steps: activeSteps.map(step => ({\r\n element: step.element,\r\n popover: step.popover,\r\n })),\r\n onHighlightStarted: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n if (step) {\r\n step.onHighlightStarted?.(_el as Element | undefined, step)\r\n emit('step-change', currentStepIndex, step)\r\n }\r\n },\r\n onDeselected: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n step?.onDeselected?.(_el as Element | undefined, step)\r\n currentStepIndex++\r\n },\r\n onDestroyStarted: () => {\r\n if (currentStepIndex >= activeSteps.length) {\r\n markCompleted()\r\n emit('complete')\r\n } else {\r\n emit('close', currentStepIndex)\r\n }\r\n driverObj.destroy()\r\n },\r\n })\r\n\r\n emit('start')\r\n driverObj.drive()\r\n }\r\n\r\n defineExpose({\r\n startGuide,\r\n resetCompleted,\r\n isCompleted,\r\n })\r\n</script>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @LastEditors: ChenYu ycyplus@gmail.com\r\n * @LastEditTime: 2026-03-06\r\n * @Description: 用户引导组件(基于 driver.js)— 增强版\r\n * 支持:步骤分组 / 键盘导航 / 步骤回调 / 主题自定义 / 完成持久化\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\r\n-->\r\n<template>\r\n <NTooltip\r\n v-if=\"props.showTrigger\"\r\n placement=\"bottom\"\r\n trigger=\"hover\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n text\r\n @click=\"startGuide\"\r\n >\r\n <C_Icon\r\n :name=\"props.triggerIcon\"\r\n :size=\"18\"\r\n />\r\n </NButton>\r\n </template>\r\n <span>{{ props.triggerTooltip }}</span>\r\n </NTooltip>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed } from 'vue'\r\n import { NTooltip, NButton } from 'naive-ui'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import { driver } from 'driver.js'\r\n import 'driver.js/dist/driver.css'\r\n import type { GuideProps, GuideStep } from './types'\r\n\r\n defineOptions({ name: 'C_Guide' })\r\n\r\n const props = withDefaults(defineProps<GuideProps>(), {\r\n steps: () => [],\r\n doneBtnText: '完成',\r\n nextBtnText: '下一步',\r\n prevBtnText: '上一步',\r\n showProgress: true,\r\n keyboard: true,\r\n animate: true,\r\n allowClose: true,\r\n popoverClass: 'driverjs-theme',\r\n showTrigger: true,\r\n triggerTooltip: '功能引导',\r\n triggerIcon: 'mdi:sign-routes',\r\n })\r\n\r\n const emit = defineEmits<{\r\n /** 引导开始 */\r\n start: []\r\n /** 引导完成(走完全部步骤) */\r\n complete: []\r\n /** 引导被用户关闭(未完成) */\r\n close: [currentStep: number]\r\n /** 步骤切换 */\r\n 'step-change': [stepIndex: number, step: GuideStep]\r\n }>()\r\n\r\n /** 持久化 key */\r\n const persistKey = computed(() => {\r\n const prefix = props.persistence?.keyPrefix ?? 'c_guide'\r\n return `${prefix}_completed`\r\n })\r\n\r\n /** 是否已完成引导 */\r\n const isCompleted = (): boolean => {\r\n if (!props.persistence?.enabled) return false\r\n try {\r\n return localStorage.getItem(persistKey.value) === 'true'\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /** 标记引导已完成 */\r\n const markCompleted = () => {\r\n if (!props.persistence?.enabled) return\r\n try {\r\n localStorage.setItem(persistKey.value, 'true')\r\n } catch {\r\n // localStorage 不可用时静默忽略\r\n }\r\n }\r\n\r\n /** 重置完成状态 */\r\n const resetCompleted = () => {\r\n try {\r\n localStorage.removeItem(persistKey.value)\r\n } catch {\r\n // 静默忽略\r\n }\r\n }\r\n\r\n /**\r\n * * @description: 过滤掉需要跳过的步骤\r\n * ! @return 有效步骤列表\r\n */\r\n const getActiveSteps = (): GuideStep[] => {\r\n return props.steps.filter(step => !step.skipIf?.())\r\n }\r\n\r\n /**\r\n * * @description: 构建主题相关的 CSS 变量\r\n * ! @return popoverClass 字符串\r\n */\r\n const buildPopoverClass = (): string => {\r\n const classes = [props.popoverClass]\r\n if (props.theme) {\r\n // 通过 CSS 变量注入主题\r\n const root = document.documentElement\r\n if (props.theme.popoverBgColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-bg',\r\n props.theme.popoverBgColor\r\n )\r\n if (props.theme.popoverTextColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-text',\r\n props.theme.popoverTextColor\r\n )\r\n if (props.theme.primaryColor)\r\n root.style.setProperty(\r\n '--c-guide-primary',\r\n props.theme.primaryColor\r\n )\r\n if (props.theme.overlayOpacity !== undefined)\r\n root.style.setProperty(\r\n '--c-guide-overlay-opacity',\r\n String(props.theme.overlayOpacity)\r\n )\r\n if (props.theme.borderRadius)\r\n root.style.setProperty(\r\n '--c-guide-radius',\r\n props.theme.borderRadius\r\n )\r\n }\r\n return classes.filter(Boolean).join(' ')\r\n }\r\n\r\n /**\r\n * * @description: 启动引导流程\r\n * ? @param {boolean} force 是否强制启动(忽略持久化状态)\r\n */\r\n const startGuide = (force = false) => {\r\n if (!force && isCompleted()) return\r\n\r\n const activeSteps = getActiveSteps()\r\n if (!activeSteps.length) {\r\n console.warn(\r\n '[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。'\r\n )\r\n return\r\n }\r\n\r\n let currentStepIndex = 0\r\n\r\n const driverObj = driver({\r\n popoverClass: buildPopoverClass(),\r\n animate: props.animate,\r\n showProgress: props.showProgress,\r\n allowClose: props.allowClose,\r\n doneBtnText: props.doneBtnText,\r\n nextBtnText: props.nextBtnText,\r\n prevBtnText: props.prevBtnText,\r\n steps: activeSteps.map(step => ({\r\n element: step.element,\r\n popover: step.popover,\r\n })),\r\n onHighlightStarted: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n if (step) {\r\n step.onHighlightStarted?.(_el as Element | undefined, step)\r\n emit('step-change', currentStepIndex, step)\r\n }\r\n },\r\n onDeselected: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n step?.onDeselected?.(_el as Element | undefined, step)\r\n currentStepIndex++\r\n },\r\n onDestroyStarted: () => {\r\n if (currentStepIndex >= activeSteps.length) {\r\n markCompleted()\r\n emit('complete')\r\n } else {\r\n emit('close', currentStepIndex)\r\n }\r\n driverObj.destroy()\r\n },\r\n })\r\n\r\n emit('start')\r\n driverObj.drive()\r\n }\r\n\r\n defineExpose({\r\n startGuide,\r\n resetCompleted,\r\n isCompleted,\r\n })\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @LastEditors: ChenYu ycyplus@gmail.com\r\n * @LastEditTime: 2026-03-06\r\n * @Description: 用户引导组件(基于 driver.js)— 增强版\r\n * 支持:步骤分组 / 键盘导航 / 步骤回调 / 主题自定义 / 完成持久化\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\r\n-->\r\n<template>\r\n <NTooltip\r\n v-if=\"props.showTrigger\"\r\n placement=\"bottom\"\r\n trigger=\"hover\"\r\n >\r\n <template #trigger>\r\n <NButton\r\n text\r\n @click=\"startGuide\"\r\n >\r\n <C_Icon\r\n :name=\"props.triggerIcon\"\r\n :size=\"18\"\r\n />\r\n </NButton>\r\n </template>\r\n <span>{{ props.triggerTooltip }}</span>\r\n </NTooltip>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { computed } from 'vue'\r\n import { NTooltip, NButton } from 'naive-ui'\r\n import C_Icon from '../C_Icon/index.vue'\r\n import { driver } from 'driver.js'\r\n import 'driver.js/dist/driver.css'\r\n import type { GuideProps, GuideStep } from './types'\r\n\r\n defineOptions({ name: 'C_Guide' })\r\n\r\n const props = withDefaults(defineProps<GuideProps>(), {\r\n steps: () => [],\r\n doneBtnText: '完成',\r\n nextBtnText: '下一步',\r\n prevBtnText: '上一步',\r\n showProgress: true,\r\n keyboard: true,\r\n animate: true,\r\n allowClose: true,\r\n popoverClass: 'driverjs-theme',\r\n showTrigger: true,\r\n triggerTooltip: '功能引导',\r\n triggerIcon: 'mdi:sign-routes',\r\n })\r\n\r\n const emit = defineEmits<{\r\n /** 引导开始 */\r\n start: []\r\n /** 引导完成(走完全部步骤) */\r\n complete: []\r\n /** 引导被用户关闭(未完成) */\r\n close: [currentStep: number]\r\n /** 步骤切换 */\r\n 'step-change': [stepIndex: number, step: GuideStep]\r\n }>()\r\n\r\n /** 持久化 key */\r\n const persistKey = computed(() => {\r\n const prefix = props.persistence?.keyPrefix ?? 'c_guide'\r\n return `${prefix}_completed`\r\n })\r\n\r\n /** 是否已完成引导 */\r\n const isCompleted = (): boolean => {\r\n if (!props.persistence?.enabled) return false\r\n try {\r\n return localStorage.getItem(persistKey.value) === 'true'\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /** 标记引导已完成 */\r\n const markCompleted = () => {\r\n if (!props.persistence?.enabled) return\r\n try {\r\n localStorage.setItem(persistKey.value, 'true')\r\n } catch {\r\n // localStorage 不可用时静默忽略\r\n }\r\n }\r\n\r\n /** 重置完成状态 */\r\n const resetCompleted = () => {\r\n try {\r\n localStorage.removeItem(persistKey.value)\r\n } catch {\r\n // 静默忽略\r\n }\r\n }\r\n\r\n /**\r\n * * @description: 过滤掉需要跳过的步骤\r\n * ! @return 有效步骤列表\r\n */\r\n const getActiveSteps = (): GuideStep[] => {\r\n return props.steps.filter(step => !step.skipIf?.())\r\n }\r\n\r\n /**\r\n * * @description: 构建主题相关的 CSS 变量\r\n * ! @return popoverClass 字符串\r\n */\r\n const buildPopoverClass = (): string => {\r\n const classes = [props.popoverClass]\r\n if (props.theme) {\r\n // 通过 CSS 变量注入主题\r\n const root = document.documentElement\r\n if (props.theme.popoverBgColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-bg',\r\n props.theme.popoverBgColor\r\n )\r\n if (props.theme.popoverTextColor)\r\n root.style.setProperty(\r\n '--c-guide-popover-text',\r\n props.theme.popoverTextColor\r\n )\r\n if (props.theme.primaryColor)\r\n root.style.setProperty(\r\n '--c-guide-primary',\r\n props.theme.primaryColor\r\n )\r\n if (props.theme.overlayOpacity !== undefined)\r\n root.style.setProperty(\r\n '--c-guide-overlay-opacity',\r\n String(props.theme.overlayOpacity)\r\n )\r\n if (props.theme.borderRadius)\r\n root.style.setProperty(\r\n '--c-guide-radius',\r\n props.theme.borderRadius\r\n )\r\n }\r\n return classes.filter(Boolean).join(' ')\r\n }\r\n\r\n /**\r\n * * @description: 启动引导流程\r\n * ? @param {boolean} force 是否强制启动(忽略持久化状态)\r\n */\r\n const startGuide = (force = false) => {\r\n if (!force && isCompleted()) return\r\n\r\n const activeSteps = getActiveSteps()\r\n if (!activeSteps.length) {\r\n console.warn(\r\n '[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。'\r\n )\r\n return\r\n }\r\n\r\n let currentStepIndex = 0\r\n\r\n const driverObj = driver({\r\n popoverClass: buildPopoverClass(),\r\n animate: props.animate,\r\n showProgress: props.showProgress,\r\n allowClose: props.allowClose,\r\n doneBtnText: props.doneBtnText,\r\n nextBtnText: props.nextBtnText,\r\n prevBtnText: props.prevBtnText,\r\n steps: activeSteps.map(step => ({\r\n element: step.element,\r\n popover: step.popover,\r\n })),\r\n onHighlightStarted: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n if (step) {\r\n step.onHighlightStarted?.(_el as Element | undefined, step)\r\n emit('step-change', currentStepIndex, step)\r\n }\r\n },\r\n onDeselected: (_el) => {\r\n const step = activeSteps[currentStepIndex]\r\n step?.onDeselected?.(_el as Element | undefined, step)\r\n currentStepIndex++\r\n },\r\n onDestroyStarted: () => {\r\n if (currentStepIndex >= activeSteps.length) {\r\n markCompleted()\r\n emit('complete')\r\n } else {\r\n emit('close', currentStepIndex)\r\n }\r\n driverObj.destroy()\r\n },\r\n })\r\n\r\n emit('start')\r\n driverObj.drive()\r\n }\r\n\r\n defineExpose({\r\n startGuide,\r\n resetCompleted,\r\n isCompleted,\r\n })\r\n</script>\r\n"],"mappings":"63BEwCE,IAAM,EAAQ,EAeR,EAAO,EAYP,EAAa,MAEV,GADQ,EAAM,aAAa,WAAa,UAC9B,YACjB,CAGI,MAA6B,CACjC,GAAI,CAAC,EAAM,aAAa,QAAS,MAAO,GACxC,GAAI,CACF,OAAO,aAAa,QAAQ,EAAW,MAAM,GAAK,YAC5C,CACN,MAAO,KAKL,MAAsB,CACrB,KAAM,aAAa,QACxB,GAAI,CACF,aAAa,QAAQ,EAAW,MAAO,OAAO,MACxC,IAMJ,MAAuB,CAC3B,GAAI,CACF,aAAa,WAAW,EAAW,MAAM,MACnC,IASJ,MACG,EAAM,MAAM,OAAO,GAAQ,CAAC,EAAK,UAAU,CAAC,CAO/C,MAAkC,CACtC,IAAM,EAAU,CAAC,EAAM,aAAa,CACpC,GAAI,EAAM,MAAO,CAEf,IAAM,EAAO,SAAS,gBAClB,EAAM,MAAM,gBACd,EAAK,MAAM,YACT,uBACA,EAAM,MAAM,eACb,CACC,EAAM,MAAM,kBACd,EAAK,MAAM,YACT,yBACA,EAAM,MAAM,iBACb,CACC,EAAM,MAAM,cACd,EAAK,MAAM,YACT,oBACA,EAAM,MAAM,aACb,CACC,EAAM,MAAM,iBAAmB,IAAA,IACjC,EAAK,MAAM,YACT,4BACA,OAAO,EAAM,MAAM,eAAe,CACnC,CACC,EAAM,MAAM,cACd,EAAK,MAAM,YACT,mBACA,EAAM,MAAM,aACb,CAEL,OAAO,EAAQ,OAAO,QAAQ,CAAC,KAAK,IAAI,EAOpC,GAAc,EAAQ,KAAU,CACpC,GAAI,CAAC,GAAS,GAAa,CAAE,OAE7B,IAAM,EAAc,GAAgB,CACpC,GAAI,CAAC,EAAY,OAAQ,CACvB,QAAQ,KACN,+CACD,CACD,OAGF,IAAI,EAAmB,EAEjB,EAAY,EAAO,CACvB,aAAc,GAAmB,CACjC,QAAS,EAAM,QACf,aAAc,EAAM,aACpB,WAAY,EAAM,WAClB,YAAa,EAAM,YACnB,YAAa,EAAM,YACnB,YAAa,EAAM,YACnB,MAAO,EAAY,IAAI,IAAS,CAC9B,QAAS,EAAK,QACd,QAAS,EAAK,QACf,EAAE,CACH,mBAAqB,GAAQ,CAC3B,IAAM,EAAO,EAAY,GACrB,IACF,EAAK,qBAAqB,EAA4B,EAAK,CAC3D,EAAK,cAAe,EAAkB,EAAK,GAG/C,aAAe,GAAQ,CACrB,IAAM,EAAO,EAAY,GACzB,GAAM,eAAe,EAA4B,EAAK,CACtD,KAEF,qBAAwB,CAClB,GAAoB,EAAY,QAClC,GAAe,CACf,EAAK,WAAW,EAEhB,EAAK,QAAS,EAAiB,CAEjC,EAAU,SAAS,EAEtB,CAAC,CAEF,EAAK,QAAQ,CACb,EAAU,OAAO,SAGnB,EAAa,CACX,aACA,iBACA,cACD,CAAC,QApMM,EAAM,aAAA,GAAA,CADd,EAiBW,EAAA,EAAA,CAAA,OAfT,UAAU,SACV,QAAQ,UAEG,QAAO,MASN,CARV,EAQU,EAAA,EAAA,CAAA,CAPR,KAAA,GACC,QAAO,kBAKN,CAHF,EAGE,EAAA,CAFC,KAAM,EAAM,YACZ,KAAM,6CAI0B,CAAvC,EAAuC,OAAA,KAAA,EAA9B,EAAM,eAAc,CAAA,EAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"C_Guide2.js","names":[],"sources":["../src/components/C_Guide/index.vue"],"sourcesContent":["<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-06-01\n * @LastEditors: ChenYu ycyplus@gmail.com\n * @LastEditTime: 2026-03-06\n * @Description: 用户引导组件(基于 driver.js)— 增强版\n * 支持:步骤分组 / 键盘导航 / 步骤回调 / 主题自定义 / 完成持久化\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\n-->\n<template>\n <NTooltip\n v-if=\"props.showTrigger\"\n placement=\"bottom\"\n trigger=\"hover\"\n >\n <template #trigger>\n <NButton\n text\n @click=\"startGuide\"\n >\n <C_Icon\n :name=\"props.triggerIcon\"\n :size=\"18\"\n />\n </NButton>\n </template>\n <span>{{ props.triggerTooltip }}</span>\n </NTooltip>\n</template>\n\n<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { NTooltip, NButton } from 'naive-ui'\n import C_Icon from '../C_Icon/index.vue'\n import { driver } from 'driver.js'\n import 'driver.js/dist/driver.css'\n import type { GuideProps, GuideStep } from './types'\n\n defineOptions({ name: 'C_Guide' })\n\n const props = withDefaults(defineProps<GuideProps>(), {\n steps: () => [],\n doneBtnText: '完成',\n nextBtnText: '下一步',\n prevBtnText: '上一步',\n showProgress: true,\n keyboard: true,\n animate: true,\n allowClose: true,\n popoverClass: 'driverjs-theme',\n showTrigger: true,\n triggerTooltip: '功能引导',\n triggerIcon: 'mdi:sign-routes',\n })\n\n const emit = defineEmits<{\n /** 引导开始 */\n start: []\n /** 引导完成(走完全部步骤) */\n complete: []\n /** 引导被用户关闭(未完成) */\n close: [currentStep: number]\n /** 步骤切换 */\n 'step-change': [stepIndex: number, step: GuideStep]\n }>()\n\n /** 持久化 key */\n const persistKey = computed(() => {\n const prefix = props.persistence?.keyPrefix ?? 'c_guide'\n return `${prefix}_completed`\n })\n\n /** 是否已完成引导 */\n const isCompleted = (): boolean => {\n if (!props.persistence?.enabled) return false\n try {\n return localStorage.getItem(persistKey.value) === 'true'\n } catch {\n return false\n }\n }\n\n /** 标记引导已完成 */\n const markCompleted = () => {\n if (!props.persistence?.enabled) return\n try {\n localStorage.setItem(persistKey.value, 'true')\n } catch {\n // localStorage 不可用时静默忽略\n }\n }\n\n /** 重置完成状态 */\n const resetCompleted = () => {\n try {\n localStorage.removeItem(persistKey.value)\n } catch {\n // 静默忽略\n }\n }\n\n /**\n * * @description: 过滤掉需要跳过的步骤\n * ! @return 有效步骤列表\n */\n const getActiveSteps = (): GuideStep[] => {\n return props.steps.filter(step => !step.skipIf?.())\n }\n\n /**\n * * @description: 构建主题相关的 CSS 变量\n * ! @return popoverClass 字符串\n */\n const buildPopoverClass = (): string => {\n const classes = [props.popoverClass]\n if (props.theme) {\n // 通过 CSS 变量注入主题\n const root = document.documentElement\n if (props.theme.popoverBgColor)\n root.style.setProperty(\n '--c-guide-popover-bg',\n props.theme.popoverBgColor\n )\n if (props.theme.popoverTextColor)\n root.style.setProperty(\n '--c-guide-popover-text',\n props.theme.popoverTextColor\n )\n if (props.theme.primaryColor)\n root.style.setProperty(\n '--c-guide-primary',\n props.theme.primaryColor\n )\n if (props.theme.overlayOpacity !== undefined)\n root.style.setProperty(\n '--c-guide-overlay-opacity',\n String(props.theme.overlayOpacity)\n )\n if (props.theme.borderRadius)\n root.style.setProperty(\n '--c-guide-radius',\n props.theme.borderRadius\n )\n }\n return classes.filter(Boolean).join(' ')\n }\n\n /**\n * * @description: 启动引导流程\n * ? @param {boolean} force 是否强制启动(忽略持久化状态)\n */\n const startGuide = (force = false) => {\n if (!force && isCompleted()) return\n\n const activeSteps = getActiveSteps()\n if (!activeSteps.length) {\n console.warn(\n '[C_Guide] 未提供引导步骤(steps),请通过 :steps prop 传入。'\n )\n return\n }\n\n let currentStepIndex = 0\n\n const driverObj = driver({\n popoverClass: buildPopoverClass(),\n animate: props.animate,\n showProgress: props.showProgress,\n allowClose: props.allowClose,\n doneBtnText: props.doneBtnText,\n nextBtnText: props.nextBtnText,\n prevBtnText: props.prevBtnText,\n steps: activeSteps.map(step => ({\n element: step.element,\n popover: step.popover,\n })),\n onHighlightStarted: (_el) => {\n const step = activeSteps[currentStepIndex]\n if (step) {\n step.onHighlightStarted?.(_el as Element | undefined, step)\n emit('step-change', currentStepIndex, step)\n }\n },\n onDeselected: (_el) => {\n const step = activeSteps[currentStepIndex]\n step?.onDeselected?.(_el as Element | undefined, step)\n currentStepIndex++\n },\n onDestroyStarted: () => {\n if (currentStepIndex >= activeSteps.length) {\n markCompleted()\n emit('complete')\n } else {\n emit('close', currentStepIndex)\n }\n driverObj.destroy()\n },\n })\n\n emit('start')\n driverObj.drive()\n }\n\n defineExpose({\n startGuide,\n resetCompleted,\n isCompleted,\n })\n</script>\n"],"mappings":"+3BAwCE,IAAM,EAAQ,EAeR,EAAO,EAYP,EAAa,MAEV,GADQ,EAAM,aAAa,WAAa,UAC9B,YAClB,CAGK,MAA6B,CACjC,GAAI,CAAC,EAAM,aAAa,QAAS,MAAO,GACxC,GAAI,CACF,OAAO,aAAa,QAAQ,EAAW,MAAM,GAAK,YAC5C,CACN,MAAO,KAKL,MAAsB,CACrB,KAAM,aAAa,QACxB,GAAI,CACF,aAAa,QAAQ,EAAW,MAAO,OAAM,MACvC,IAMJ,MAAuB,CAC3B,GAAI,CACF,aAAa,WAAW,EAAW,MAAK,MAClC,IASJ,MACG,EAAM,MAAM,OAAO,GAAQ,CAAC,EAAK,UAAU,CAAA,CAO9C,MAAkC,CACtC,IAAM,EAAU,CAAC,EAAM,aAAY,CACnC,GAAI,EAAM,MAAO,CAEf,IAAM,EAAO,SAAS,gBAClB,EAAM,MAAM,gBACd,EAAK,MAAM,YACT,uBACA,EAAM,MAAM,eACd,CACE,EAAM,MAAM,kBACd,EAAK,MAAM,YACT,yBACA,EAAM,MAAM,iBACd,CACE,EAAM,MAAM,cACd,EAAK,MAAM,YACT,oBACA,EAAM,MAAM,aACd,CACE,EAAM,MAAM,iBAAmB,IAAA,IACjC,EAAK,MAAM,YACT,4BACA,OAAO,EAAM,MAAM,eAAc,CACnC,CACE,EAAM,MAAM,cACd,EAAK,MAAM,YACT,mBACA,EAAM,MAAM,aACd,CAEJ,OAAO,EAAQ,OAAO,QAAQ,CAAC,KAAK,IAAG,EAOnC,GAAc,EAAQ,KAAU,CACpC,GAAI,CAAC,GAAS,GAAa,CAAE,OAE7B,IAAM,EAAc,GAAe,CACnC,GAAI,CAAC,EAAY,OAAQ,CACvB,QAAQ,KACN,+CACF,CACA,OAGF,IAAI,EAAmB,EAEjB,EAAY,EAAO,CACvB,aAAc,GAAmB,CACjC,QAAS,EAAM,QACf,aAAc,EAAM,aACpB,WAAY,EAAM,WAClB,YAAa,EAAM,YACnB,YAAa,EAAM,YACnB,YAAa,EAAM,YACnB,MAAO,EAAY,IAAI,IAAS,CAC9B,QAAS,EAAK,QACd,QAAS,EAAK,QACf,EAAE,CACH,mBAAqB,GAAQ,CAC3B,IAAM,EAAO,EAAY,GACrB,IACF,EAAK,qBAAqB,EAA4B,EAAI,CAC1D,EAAK,cAAe,EAAkB,EAAI,GAG9C,aAAe,GAAQ,CACrB,IAAM,EAAO,EAAY,GACzB,GAAM,eAAe,EAA4B,EAAI,CACrD,KAEF,qBAAwB,CAClB,GAAoB,EAAY,QAClC,GAAc,CACd,EAAK,WAAU,EAEf,EAAK,QAAS,EAAgB,CAEhC,EAAU,SAAQ,EAErB,CAAA,CAED,EAAK,QAAO,CACZ,EAAU,OAAM,SAGlB,EAAa,CACX,aACA,iBACA,cACD,CAAA,QApMO,EAAM,aAAA,GAAA,CADd,EAiBU,EAAA,EAAA,CAAA,OAfR,UAAU,SACV,QAAQ,UAEG,QAAO,MASP,CART,EAQS,EAAA,EAAA,CAAA,CAPP,KAAA,GACC,QAAO,kBAKP,CAHD,EAGC,EAAA,CAFE,KAAM,EAAM,YACZ,KAAM,6CAIyB,CAAtC,EAAsC,OAAA,KAAA,EAA7B,EAAM,eAAc,CAAA,EAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"C_Icon.js","names":["type","clickable","loading","title","ariaLabel","name","size","color","componentProps","svgPath","viewBox","alt","fallbackIcon"],"sources":["../src/hooks/useImage/index.ts","../src/components/C_Icon/index.vue","../src/components/C_Icon/index.vue","../src/components/C_Icon/index.vue"],"sourcesContent":["/**\r\n * @description: 图片路径解析函数(组件库版本)\r\n * 在组件库中,图片路径由宿主应用管理。\r\n * 如果传入的是完整 URL(http/https/data:)则直接返回;\r\n * 否则可通过 imageResolver 配置自定义解析逻辑。\r\n * @param {string} imagePath 图片路径或完整 URL\r\n * @param {Function} resolver 可选的自定义解析器\r\n * @return {Promise<string>} 处理后图片路径\r\n */\r\nexport const useImage = async (\r\n imagePath: string,\r\n resolver?: (path: string) => string | Promise<string>,\r\n): Promise<string> => {\r\n if (!imagePath) return \"\";\r\n\r\n // 完整 URL 直接返回\r\n if (\r\n imagePath.startsWith(\"http://\") ||\r\n imagePath.startsWith(\"https://\") ||\r\n imagePath.startsWith(\"data:\") ||\r\n imagePath.startsWith(\"blob:\") ||\r\n imagePath.startsWith(\"/\")\r\n ) {\r\n return imagePath;\r\n }\r\n\r\n // 如果提供了自定义解析器,使用自定义解析器\r\n if (resolver) {\r\n try {\r\n return await resolver(imagePath);\r\n } catch (e) {\r\n console.error(`[useImage] 自定义解析器处理失败: ${imagePath}`, e);\r\n return \"\";\r\n }\r\n }\r\n\r\n // 默认:直接返回路径(宿主应用应保证路径可访问)\r\n return imagePath;\r\n};\r\n\r\n/**\r\n * @description: 同步版本的图片路径处理函数\r\n * @param {string} imagePath 图片路径或完整 URL\r\n * @return {string} 图片URL\r\n */\r\nexport const useImageSync = (imagePath: string): string => {\r\n if (!imagePath) return \"\";\r\n\r\n // 完整 URL 直接返回\r\n if (\r\n imagePath.startsWith(\"http://\") ||\r\n imagePath.startsWith(\"https://\") ||\r\n imagePath.startsWith(\"data:\") ||\r\n imagePath.startsWith(\"blob:\") ||\r\n imagePath.startsWith(\"/\")\r\n ) {\r\n return imagePath;\r\n }\r\n\r\n return imagePath;\r\n};\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-05-14\r\n * @Description: 支持多种图标使用方式,默认使用Iconify图标,统一错误处理\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div\r\n ref=\"iconRef\"\r\n class=\"c-icon\"\r\n :class=\"[\r\n `c-icon--${type}`,\r\n {\r\n 'c-icon--clickable': clickable,\r\n 'c-icon--loading': loading,\r\n 'c-icon--error': hasError,\r\n },\r\n ]\"\r\n :style=\"rootStyle\"\r\n :title=\"title || undefined\"\r\n :aria-label=\"ariaLabel || iconDisplayName\"\r\n :role=\"clickable ? 'button' : 'img'\"\r\n :tabindex=\"clickable ? 0 : -1\"\r\n @click=\"handleClick\"\r\n @keydown=\"handleKeydown\"\r\n >\r\n <!-- 方式1:Iconify图标(默认方式) -->\r\n <Icon\r\n v-if=\"\r\n (type === 'iconify' || !type) && !hasError && typeof name === 'string'\r\n \"\r\n :icon=\"name\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :color=\"color\"\r\n :style=\"iconStyle\"\r\n @error=\"() => handleError('iconify', 'Iconify图标加载失败')\"\r\n />\r\n\r\n <!-- 方式2:UnoCSS图标 -->\r\n <div\r\n v-else-if=\"type === 'unocss' && !hasError && typeof name === 'string'\"\r\n :class=\"[name, iconClasses]\"\r\n :style=\"iconStyle\"\r\n @error=\"() => handleError('unocss', 'UnoCSS图标渲染失败')\"\r\n />\r\n\r\n <!-- 方式3:组件挂载方式 -->\r\n <component\r\n v-else-if=\"type === 'component' && resolvedComponent && !hasError\"\r\n :is=\"resolvedComponent\"\r\n :style=\"iconStyle\"\r\n v-bind=\"componentProps\"\r\n @error=\"() => handleError('component', '组件图标渲染失败')\"\r\n />\r\n\r\n <!-- 方式4:SVG路径方式 -->\r\n <svg\r\n v-else-if=\"type === 'svg' && svgPath && !hasError\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :viewBox=\"viewBox\"\r\n :style=\"iconStyle\"\r\n :fill=\"color\"\r\n @error=\"() => handleError('svg', 'SVG图标渲染失败')\"\r\n >\r\n <path\r\n :d=\"svgPath\"\r\n :fill=\"color\"\r\n />\r\n </svg>\r\n\r\n <!-- 方式5:图片方式 - 修复异步加载 -->\r\n <img\r\n v-else-if=\"type === 'image' && imageSrc && !hasError && !imageLoading\"\r\n :src=\"imageSrc\"\r\n :alt=\"alt || iconDisplayName\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n style=\"display: inline-block; vertical-align: middle\"\r\n @error=\"() => handleError('image', '图片图标加载失败')\"\r\n @load=\"handleImageLoad\"\r\n />\r\n\r\n <!-- 图片加载状态 -->\r\n <div\r\n v-if=\"(loading || imageLoading) && !hasError\"\r\n class=\"c-icon__loading\"\r\n :style=\"iconStyle\"\r\n >\r\n <div class=\"c-icon__spinner\" />\r\n </div>\r\n\r\n <!-- 统一错误状态显示 -->\r\n <Icon\r\n v-else-if=\"hasError && fallbackIcon\"\r\n :icon=\"fallbackIcon\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :color=\"color\"\r\n :style=\"iconStyle\"\r\n />\r\n <div\r\n v-else-if=\"hasError\"\r\n class=\"c-icon__error\"\r\n :style=\"iconStyle\"\r\n :title=\"`图标解析失败: ${iconDisplayName} (${errorMessage})`\"\r\n >\r\n ⚠️\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\n import { Icon } from '@iconify/vue'\r\n import { useImage } from '../../hooks/useImage'\r\n import { computed, nextTick, onMounted, readonly, ref, watch } from 'vue'\r\n\r\n export interface IconProps {\r\n /** 图标名称/路径/组件实例 */\r\n name: string | any\r\n /** 图标类型 */\r\n type?: 'iconify' | 'unocss' | 'component' | 'svg' | 'image'\r\n /** 图标颜色 */\r\n color?: string\r\n /** 图标大小(px) */\r\n size?: number | string\r\n /** SVG路径数据(仅type=svg时使用) */\r\n svgPath?: string\r\n /** SVG viewBox(仅type=svg时使用) */\r\n viewBox?: string\r\n /** 图片alt属性(仅type=image时使用) */\r\n alt?: string\r\n /** 是否可点击 */\r\n clickable?: boolean\r\n /** 加载状态 */\r\n loading?: boolean\r\n /** 错误时的回退图标 */\r\n fallbackIcon?: string\r\n /** 工具提示 */\r\n title?: string\r\n /** 无障碍标签 */\r\n ariaLabel?: string\r\n /** 自定义样式类 */\r\n customClass?: string\r\n /** 旋转角度 */\r\n rotate?: number\r\n /** 是否翻转 */\r\n flip?: 'horizontal' | 'vertical' | 'both'\r\n /** 传递给组件的额外属性(仅type=component时使用) */\r\n componentProps?: Record<string, any>\r\n }\r\n\r\n const props = withDefaults(defineProps<IconProps>(), {\r\n name: undefined,\r\n type: 'iconify',\r\n color: 'currentColor',\r\n size: 18,\r\n svgPath: '',\r\n viewBox: '0 0 24 24',\r\n alt: '',\r\n clickable: false,\r\n loading: false,\r\n fallbackIcon: '',\r\n title: '',\r\n ariaLabel: '',\r\n customClass: '',\r\n rotate: 0,\r\n flip: undefined,\r\n componentProps: () => ({}),\r\n })\r\n\r\n const emit = defineEmits<{\r\n click: [event: MouseEvent]\r\n error: [type: string, error?: any]\r\n load: []\r\n }>()\r\n\r\n const iconRef = ref<HTMLElement>()\r\n const hasError = ref(false)\r\n const errorMessage = ref('')\r\n const imageSrc = ref<string>('') // 存储异步加载的图片URL\r\n const imageLoading = ref(false) // 图片加载状态\r\n\r\n // 计算根元素样式\r\n const rootStyle = computed(() => ({\r\n fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n color: props.color,\r\n cursor: props.clickable ? 'pointer' : 'inherit',\r\n }))\r\n\r\n // 计算图标样式\r\n const iconStyle = computed(() => {\r\n const style: Record<string, any> = {\r\n display: 'inline-flex',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n width: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n height: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n }\r\n\r\n // 旋转\r\n if (props.rotate) {\r\n style.transform = `rotate(${props.rotate}deg)`\r\n }\r\n\r\n // 翻转\r\n if (props.flip) {\r\n const scaleX =\r\n props.flip === 'horizontal' || props.flip === 'both' ? -1 : 1\r\n const scaleY = props.flip === 'vertical' || props.flip === 'both' ? -1 : 1\r\n style.transform = `${\r\n style.transform || ''\r\n } scaleX(${scaleX}) scaleY(${scaleY})`.trim()\r\n }\r\n\r\n return style\r\n })\r\n\r\n // 获取图标名称用于显示\r\n const iconDisplayName = computed(() => {\r\n if (typeof props.name === 'string') {\r\n return props.name\r\n }\r\n if (props.name && props.name.__name) {\r\n return props.name.__name\r\n }\r\n if (props.name && props.name.name) {\r\n return props.name.name\r\n }\r\n return 'Component Icon'\r\n })\r\n\r\n // 图标类名\r\n const iconClasses = computed(() => [\r\n props.customClass,\r\n {\r\n 'c-icon__rotatable': props.rotate !== 0,\r\n 'c-icon__flippable': !!props.flip,\r\n },\r\n ])\r\n\r\n // 解析组件名称(纯计算属性,无副作用)\r\n const resolvedComponent = computed(() => {\r\n if (props.type !== 'component' || !props.name) return null\r\n return props.name\r\n })\r\n\r\n // 统一错误处理函数\r\n const handleError = (type: string, message: string, originalError?: any) => {\r\n console.warn(`[C_Icon] ${message}:`, iconDisplayName.value, originalError)\r\n errorMessage.value = message\r\n imageLoading.value = false // 错误时停止加载状态\r\n emit('error', type, originalError)\r\n\r\n // 如果有回退图标,尝试切换为 fallback Iconify 图标\r\n if (props.fallbackIcon && !hasError.value) {\r\n hasError.value = false\r\n // 将 name 重写为 fallbackIcon 并重置状态\r\n // 因为 props 不可变,通过模板中的 fallback 分支渲染\r\n return\r\n }\r\n\r\n hasError.value = true\r\n }\r\n\r\n // 处理点击事件\r\n const handleClick = (event: MouseEvent) => {\r\n if (!props.clickable || props.loading) return\r\n emit('click', event)\r\n }\r\n\r\n // 处理键盘事件\r\n const handleKeydown = (event: KeyboardEvent) => {\r\n if (!props.clickable || props.loading) return\r\n if (event.key === 'Enter' || event.key === ' ') {\r\n event.preventDefault()\r\n emit('click', event as any)\r\n }\r\n }\r\n\r\n // 处理图片加载成功\r\n const handleImageLoad = () => {\r\n imageLoading.value = false\r\n emit('load')\r\n }\r\n\r\n // 异步加载图片 - 修复关键函数\r\n const loadImageSrc = async () => {\r\n if (props.type !== 'image' || typeof props.name !== 'string') {\r\n imageSrc.value = ''\r\n return\r\n }\r\n\r\n // 如果是完整URL,直接设置\r\n if (\r\n props.name.startsWith('http://') ||\r\n props.name.startsWith('https://') ||\r\n props.name.startsWith('//') ||\r\n props.name.startsWith('/')\r\n ) {\r\n imageSrc.value = props.name\r\n return\r\n }\r\n\r\n // 其他情况都当作本地图片处理\r\n try {\r\n imageLoading.value = true\r\n const imageUrl = await useImage(props.name)\r\n if (imageUrl) {\r\n imageSrc.value = imageUrl\r\n } else {\r\n handleError('image', '图片路径解析失败')\r\n }\r\n } catch (error) {\r\n handleError('image', '图片加载失败', error)\r\n } finally {\r\n imageLoading.value = false\r\n }\r\n }\r\n\r\n // 验证规则映射\r\n const validationRules = {\r\n iconify: (): string | null => {\r\n if (!props.name.includes(':')) {\r\n console.warn(\r\n '[C_Icon] Iconify图标名称格式应为 \"prefix:name\",如 \"mdi:home\"'\r\n )\r\n }\r\n return null\r\n },\r\n unocss: (): string | null => {\r\n if (typeof props.name !== 'string' || !props.name.startsWith('i-')) {\r\n console.warn('[C_Icon] UnoCSS图标名称应以 \"i-\" 开头')\r\n }\r\n return null\r\n },\r\n component: (): string | null =>\r\n !resolvedComponent.value ? '无法解析组件' : null,\r\n svg: (): string | null => (!props.svgPath ? 'SVG路径不能为空' : null),\r\n image: (): string | null => {\r\n // 对于image类型,异步验证在loadImageSrc中处理\r\n return null\r\n },\r\n }\r\n\r\n // 验证配置\r\n const validateProps = () => {\r\n hasError.value = false\r\n errorMessage.value = ''\r\n\r\n // 基础验证\r\n if (!props.name) {\r\n return handleError('validation', '图标名称不能为空')\r\n }\r\n\r\n // 类型验证\r\n const validator = validationRules[props.type]\r\n if (!validator) {\r\n return handleError('type', '不支持的图标类型')\r\n }\r\n\r\n const errorMsg = validator()\r\n if (errorMsg) {\r\n handleError(props.type, errorMsg)\r\n }\r\n\r\n // 如果是图片类型,异步加载图片\r\n if (props.type === 'image') {\r\n loadImageSrc()\r\n }\r\n }\r\n\r\n // 监听关键属性变化\r\n watch(\r\n () => [props.name, props.type, props.svgPath],\r\n () => {\r\n nextTick(validateProps)\r\n },\r\n { immediate: true }\r\n )\r\n\r\n // 组件挂载后:仅对非 image 类型触发 load 事件(watch immediate 已完成验证)\r\n onMounted(() => {\r\n if (!hasError.value && props.type !== 'image') {\r\n emit('load')\r\n }\r\n })\r\n\r\n // 暴露方法给父组件\r\n defineExpose({\r\n /** 重新验证配置 */\r\n validate: validateProps,\r\n /** 是否有错误 */\r\n hasError: readonly(hasError),\r\n /** 错误信息 */\r\n errorMessage: readonly(errorMessage),\r\n /** DOM引用 */\r\n el: iconRef,\r\n })\r\n</script>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-05-14\r\n * @Description: 支持多种图标使用方式,默认使用Iconify图标,统一错误处理\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div\r\n ref=\"iconRef\"\r\n class=\"c-icon\"\r\n :class=\"[\r\n `c-icon--${type}`,\r\n {\r\n 'c-icon--clickable': clickable,\r\n 'c-icon--loading': loading,\r\n 'c-icon--error': hasError,\r\n },\r\n ]\"\r\n :style=\"rootStyle\"\r\n :title=\"title || undefined\"\r\n :aria-label=\"ariaLabel || iconDisplayName\"\r\n :role=\"clickable ? 'button' : 'img'\"\r\n :tabindex=\"clickable ? 0 : -1\"\r\n @click=\"handleClick\"\r\n @keydown=\"handleKeydown\"\r\n >\r\n <!-- 方式1:Iconify图标(默认方式) -->\r\n <Icon\r\n v-if=\"\r\n (type === 'iconify' || !type) && !hasError && typeof name === 'string'\r\n \"\r\n :icon=\"name\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :color=\"color\"\r\n :style=\"iconStyle\"\r\n @error=\"() => handleError('iconify', 'Iconify图标加载失败')\"\r\n />\r\n\r\n <!-- 方式2:UnoCSS图标 -->\r\n <div\r\n v-else-if=\"type === 'unocss' && !hasError && typeof name === 'string'\"\r\n :class=\"[name, iconClasses]\"\r\n :style=\"iconStyle\"\r\n @error=\"() => handleError('unocss', 'UnoCSS图标渲染失败')\"\r\n />\r\n\r\n <!-- 方式3:组件挂载方式 -->\r\n <component\r\n v-else-if=\"type === 'component' && resolvedComponent && !hasError\"\r\n :is=\"resolvedComponent\"\r\n :style=\"iconStyle\"\r\n v-bind=\"componentProps\"\r\n @error=\"() => handleError('component', '组件图标渲染失败')\"\r\n />\r\n\r\n <!-- 方式4:SVG路径方式 -->\r\n <svg\r\n v-else-if=\"type === 'svg' && svgPath && !hasError\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :viewBox=\"viewBox\"\r\n :style=\"iconStyle\"\r\n :fill=\"color\"\r\n @error=\"() => handleError('svg', 'SVG图标渲染失败')\"\r\n >\r\n <path\r\n :d=\"svgPath\"\r\n :fill=\"color\"\r\n />\r\n </svg>\r\n\r\n <!-- 方式5:图片方式 - 修复异步加载 -->\r\n <img\r\n v-else-if=\"type === 'image' && imageSrc && !hasError && !imageLoading\"\r\n :src=\"imageSrc\"\r\n :alt=\"alt || iconDisplayName\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n style=\"display: inline-block; vertical-align: middle\"\r\n @error=\"() => handleError('image', '图片图标加载失败')\"\r\n @load=\"handleImageLoad\"\r\n />\r\n\r\n <!-- 图片加载状态 -->\r\n <div\r\n v-if=\"(loading || imageLoading) && !hasError\"\r\n class=\"c-icon__loading\"\r\n :style=\"iconStyle\"\r\n >\r\n <div class=\"c-icon__spinner\" />\r\n </div>\r\n\r\n <!-- 统一错误状态显示 -->\r\n <Icon\r\n v-else-if=\"hasError && fallbackIcon\"\r\n :icon=\"fallbackIcon\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :color=\"color\"\r\n :style=\"iconStyle\"\r\n />\r\n <div\r\n v-else-if=\"hasError\"\r\n class=\"c-icon__error\"\r\n :style=\"iconStyle\"\r\n :title=\"`图标解析失败: ${iconDisplayName} (${errorMessage})`\"\r\n >\r\n ⚠️\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\n import { Icon } from '@iconify/vue'\r\n import { useImage } from '../../hooks/useImage'\r\n import { computed, nextTick, onMounted, readonly, ref, watch } from 'vue'\r\n\r\n export interface IconProps {\r\n /** 图标名称/路径/组件实例 */\r\n name: string | any\r\n /** 图标类型 */\r\n type?: 'iconify' | 'unocss' | 'component' | 'svg' | 'image'\r\n /** 图标颜色 */\r\n color?: string\r\n /** 图标大小(px) */\r\n size?: number | string\r\n /** SVG路径数据(仅type=svg时使用) */\r\n svgPath?: string\r\n /** SVG viewBox(仅type=svg时使用) */\r\n viewBox?: string\r\n /** 图片alt属性(仅type=image时使用) */\r\n alt?: string\r\n /** 是否可点击 */\r\n clickable?: boolean\r\n /** 加载状态 */\r\n loading?: boolean\r\n /** 错误时的回退图标 */\r\n fallbackIcon?: string\r\n /** 工具提示 */\r\n title?: string\r\n /** 无障碍标签 */\r\n ariaLabel?: string\r\n /** 自定义样式类 */\r\n customClass?: string\r\n /** 旋转角度 */\r\n rotate?: number\r\n /** 是否翻转 */\r\n flip?: 'horizontal' | 'vertical' | 'both'\r\n /** 传递给组件的额外属性(仅type=component时使用) */\r\n componentProps?: Record<string, any>\r\n }\r\n\r\n const props = withDefaults(defineProps<IconProps>(), {\r\n name: undefined,\r\n type: 'iconify',\r\n color: 'currentColor',\r\n size: 18,\r\n svgPath: '',\r\n viewBox: '0 0 24 24',\r\n alt: '',\r\n clickable: false,\r\n loading: false,\r\n fallbackIcon: '',\r\n title: '',\r\n ariaLabel: '',\r\n customClass: '',\r\n rotate: 0,\r\n flip: undefined,\r\n componentProps: () => ({}),\r\n })\r\n\r\n const emit = defineEmits<{\r\n click: [event: MouseEvent]\r\n error: [type: string, error?: any]\r\n load: []\r\n }>()\r\n\r\n const iconRef = ref<HTMLElement>()\r\n const hasError = ref(false)\r\n const errorMessage = ref('')\r\n const imageSrc = ref<string>('') // 存储异步加载的图片URL\r\n const imageLoading = ref(false) // 图片加载状态\r\n\r\n // 计算根元素样式\r\n const rootStyle = computed(() => ({\r\n fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n color: props.color,\r\n cursor: props.clickable ? 'pointer' : 'inherit',\r\n }))\r\n\r\n // 计算图标样式\r\n const iconStyle = computed(() => {\r\n const style: Record<string, any> = {\r\n display: 'inline-flex',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n width: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n height: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n }\r\n\r\n // 旋转\r\n if (props.rotate) {\r\n style.transform = `rotate(${props.rotate}deg)`\r\n }\r\n\r\n // 翻转\r\n if (props.flip) {\r\n const scaleX =\r\n props.flip === 'horizontal' || props.flip === 'both' ? -1 : 1\r\n const scaleY = props.flip === 'vertical' || props.flip === 'both' ? -1 : 1\r\n style.transform = `${\r\n style.transform || ''\r\n } scaleX(${scaleX}) scaleY(${scaleY})`.trim()\r\n }\r\n\r\n return style\r\n })\r\n\r\n // 获取图标名称用于显示\r\n const iconDisplayName = computed(() => {\r\n if (typeof props.name === 'string') {\r\n return props.name\r\n }\r\n if (props.name && props.name.__name) {\r\n return props.name.__name\r\n }\r\n if (props.name && props.name.name) {\r\n return props.name.name\r\n }\r\n return 'Component Icon'\r\n })\r\n\r\n // 图标类名\r\n const iconClasses = computed(() => [\r\n props.customClass,\r\n {\r\n 'c-icon__rotatable': props.rotate !== 0,\r\n 'c-icon__flippable': !!props.flip,\r\n },\r\n ])\r\n\r\n // 解析组件名称(纯计算属性,无副作用)\r\n const resolvedComponent = computed(() => {\r\n if (props.type !== 'component' || !props.name) return null\r\n return props.name\r\n })\r\n\r\n // 统一错误处理函数\r\n const handleError = (type: string, message: string, originalError?: any) => {\r\n console.warn(`[C_Icon] ${message}:`, iconDisplayName.value, originalError)\r\n errorMessage.value = message\r\n imageLoading.value = false // 错误时停止加载状态\r\n emit('error', type, originalError)\r\n\r\n // 如果有回退图标,尝试切换为 fallback Iconify 图标\r\n if (props.fallbackIcon && !hasError.value) {\r\n hasError.value = false\r\n // 将 name 重写为 fallbackIcon 并重置状态\r\n // 因为 props 不可变,通过模板中的 fallback 分支渲染\r\n return\r\n }\r\n\r\n hasError.value = true\r\n }\r\n\r\n // 处理点击事件\r\n const handleClick = (event: MouseEvent) => {\r\n if (!props.clickable || props.loading) return\r\n emit('click', event)\r\n }\r\n\r\n // 处理键盘事件\r\n const handleKeydown = (event: KeyboardEvent) => {\r\n if (!props.clickable || props.loading) return\r\n if (event.key === 'Enter' || event.key === ' ') {\r\n event.preventDefault()\r\n emit('click', event as any)\r\n }\r\n }\r\n\r\n // 处理图片加载成功\r\n const handleImageLoad = () => {\r\n imageLoading.value = false\r\n emit('load')\r\n }\r\n\r\n // 异步加载图片 - 修复关键函数\r\n const loadImageSrc = async () => {\r\n if (props.type !== 'image' || typeof props.name !== 'string') {\r\n imageSrc.value = ''\r\n return\r\n }\r\n\r\n // 如果是完整URL,直接设置\r\n if (\r\n props.name.startsWith('http://') ||\r\n props.name.startsWith('https://') ||\r\n props.name.startsWith('//') ||\r\n props.name.startsWith('/')\r\n ) {\r\n imageSrc.value = props.name\r\n return\r\n }\r\n\r\n // 其他情况都当作本地图片处理\r\n try {\r\n imageLoading.value = true\r\n const imageUrl = await useImage(props.name)\r\n if (imageUrl) {\r\n imageSrc.value = imageUrl\r\n } else {\r\n handleError('image', '图片路径解析失败')\r\n }\r\n } catch (error) {\r\n handleError('image', '图片加载失败', error)\r\n } finally {\r\n imageLoading.value = false\r\n }\r\n }\r\n\r\n // 验证规则映射\r\n const validationRules = {\r\n iconify: (): string | null => {\r\n if (!props.name.includes(':')) {\r\n console.warn(\r\n '[C_Icon] Iconify图标名称格式应为 \"prefix:name\",如 \"mdi:home\"'\r\n )\r\n }\r\n return null\r\n },\r\n unocss: (): string | null => {\r\n if (typeof props.name !== 'string' || !props.name.startsWith('i-')) {\r\n console.warn('[C_Icon] UnoCSS图标名称应以 \"i-\" 开头')\r\n }\r\n return null\r\n },\r\n component: (): string | null =>\r\n !resolvedComponent.value ? '无法解析组件' : null,\r\n svg: (): string | null => (!props.svgPath ? 'SVG路径不能为空' : null),\r\n image: (): string | null => {\r\n // 对于image类型,异步验证在loadImageSrc中处理\r\n return null\r\n },\r\n }\r\n\r\n // 验证配置\r\n const validateProps = () => {\r\n hasError.value = false\r\n errorMessage.value = ''\r\n\r\n // 基础验证\r\n if (!props.name) {\r\n return handleError('validation', '图标名称不能为空')\r\n }\r\n\r\n // 类型验证\r\n const validator = validationRules[props.type]\r\n if (!validator) {\r\n return handleError('type', '不支持的图标类型')\r\n }\r\n\r\n const errorMsg = validator()\r\n if (errorMsg) {\r\n handleError(props.type, errorMsg)\r\n }\r\n\r\n // 如果是图片类型,异步加载图片\r\n if (props.type === 'image') {\r\n loadImageSrc()\r\n }\r\n }\r\n\r\n // 监听关键属性变化\r\n watch(\r\n () => [props.name, props.type, props.svgPath],\r\n () => {\r\n nextTick(validateProps)\r\n },\r\n { immediate: true }\r\n )\r\n\r\n // 组件挂载后:仅对非 image 类型触发 load 事件(watch immediate 已完成验证)\r\n onMounted(() => {\r\n if (!hasError.value && props.type !== 'image') {\r\n emit('load')\r\n }\r\n })\r\n\r\n // 暴露方法给父组件\r\n defineExpose({\r\n /** 重新验证配置 */\r\n validate: validateProps,\r\n /** 是否有错误 */\r\n hasError: readonly(hasError),\r\n /** 错误信息 */\r\n errorMessage: readonly(errorMessage),\r\n /** DOM引用 */\r\n el: iconRef,\r\n })\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-05-14\r\n * @Description: 支持多种图标使用方式,默认使用Iconify图标,统一错误处理\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div\r\n ref=\"iconRef\"\r\n class=\"c-icon\"\r\n :class=\"[\r\n `c-icon--${type}`,\r\n {\r\n 'c-icon--clickable': clickable,\r\n 'c-icon--loading': loading,\r\n 'c-icon--error': hasError,\r\n },\r\n ]\"\r\n :style=\"rootStyle\"\r\n :title=\"title || undefined\"\r\n :aria-label=\"ariaLabel || iconDisplayName\"\r\n :role=\"clickable ? 'button' : 'img'\"\r\n :tabindex=\"clickable ? 0 : -1\"\r\n @click=\"handleClick\"\r\n @keydown=\"handleKeydown\"\r\n >\r\n <!-- 方式1:Iconify图标(默认方式) -->\r\n <Icon\r\n v-if=\"\r\n (type === 'iconify' || !type) && !hasError && typeof name === 'string'\r\n \"\r\n :icon=\"name\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :color=\"color\"\r\n :style=\"iconStyle\"\r\n @error=\"() => handleError('iconify', 'Iconify图标加载失败')\"\r\n />\r\n\r\n <!-- 方式2:UnoCSS图标 -->\r\n <div\r\n v-else-if=\"type === 'unocss' && !hasError && typeof name === 'string'\"\r\n :class=\"[name, iconClasses]\"\r\n :style=\"iconStyle\"\r\n @error=\"() => handleError('unocss', 'UnoCSS图标渲染失败')\"\r\n />\r\n\r\n <!-- 方式3:组件挂载方式 -->\r\n <component\r\n v-else-if=\"type === 'component' && resolvedComponent && !hasError\"\r\n :is=\"resolvedComponent\"\r\n :style=\"iconStyle\"\r\n v-bind=\"componentProps\"\r\n @error=\"() => handleError('component', '组件图标渲染失败')\"\r\n />\r\n\r\n <!-- 方式4:SVG路径方式 -->\r\n <svg\r\n v-else-if=\"type === 'svg' && svgPath && !hasError\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :viewBox=\"viewBox\"\r\n :style=\"iconStyle\"\r\n :fill=\"color\"\r\n @error=\"() => handleError('svg', 'SVG图标渲染失败')\"\r\n >\r\n <path\r\n :d=\"svgPath\"\r\n :fill=\"color\"\r\n />\r\n </svg>\r\n\r\n <!-- 方式5:图片方式 - 修复异步加载 -->\r\n <img\r\n v-else-if=\"type === 'image' && imageSrc && !hasError && !imageLoading\"\r\n :src=\"imageSrc\"\r\n :alt=\"alt || iconDisplayName\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n style=\"display: inline-block; vertical-align: middle\"\r\n @error=\"() => handleError('image', '图片图标加载失败')\"\r\n @load=\"handleImageLoad\"\r\n />\r\n\r\n <!-- 图片加载状态 -->\r\n <div\r\n v-if=\"(loading || imageLoading) && !hasError\"\r\n class=\"c-icon__loading\"\r\n :style=\"iconStyle\"\r\n >\r\n <div class=\"c-icon__spinner\" />\r\n </div>\r\n\r\n <!-- 统一错误状态显示 -->\r\n <Icon\r\n v-else-if=\"hasError && fallbackIcon\"\r\n :icon=\"fallbackIcon\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n :color=\"color\"\r\n :style=\"iconStyle\"\r\n />\r\n <div\r\n v-else-if=\"hasError\"\r\n class=\"c-icon__error\"\r\n :style=\"iconStyle\"\r\n :title=\"`图标解析失败: ${iconDisplayName} (${errorMessage})`\"\r\n >\r\n ⚠️\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\n import { Icon } from '@iconify/vue'\r\n import { useImage } from '../../hooks/useImage'\r\n import { computed, nextTick, onMounted, readonly, ref, watch } from 'vue'\r\n\r\n export interface IconProps {\r\n /** 图标名称/路径/组件实例 */\r\n name: string | any\r\n /** 图标类型 */\r\n type?: 'iconify' | 'unocss' | 'component' | 'svg' | 'image'\r\n /** 图标颜色 */\r\n color?: string\r\n /** 图标大小(px) */\r\n size?: number | string\r\n /** SVG路径数据(仅type=svg时使用) */\r\n svgPath?: string\r\n /** SVG viewBox(仅type=svg时使用) */\r\n viewBox?: string\r\n /** 图片alt属性(仅type=image时使用) */\r\n alt?: string\r\n /** 是否可点击 */\r\n clickable?: boolean\r\n /** 加载状态 */\r\n loading?: boolean\r\n /** 错误时的回退图标 */\r\n fallbackIcon?: string\r\n /** 工具提示 */\r\n title?: string\r\n /** 无障碍标签 */\r\n ariaLabel?: string\r\n /** 自定义样式类 */\r\n customClass?: string\r\n /** 旋转角度 */\r\n rotate?: number\r\n /** 是否翻转 */\r\n flip?: 'horizontal' | 'vertical' | 'both'\r\n /** 传递给组件的额外属性(仅type=component时使用) */\r\n componentProps?: Record<string, any>\r\n }\r\n\r\n const props = withDefaults(defineProps<IconProps>(), {\r\n name: undefined,\r\n type: 'iconify',\r\n color: 'currentColor',\r\n size: 18,\r\n svgPath: '',\r\n viewBox: '0 0 24 24',\r\n alt: '',\r\n clickable: false,\r\n loading: false,\r\n fallbackIcon: '',\r\n title: '',\r\n ariaLabel: '',\r\n customClass: '',\r\n rotate: 0,\r\n flip: undefined,\r\n componentProps: () => ({}),\r\n })\r\n\r\n const emit = defineEmits<{\r\n click: [event: MouseEvent]\r\n error: [type: string, error?: any]\r\n load: []\r\n }>()\r\n\r\n const iconRef = ref<HTMLElement>()\r\n const hasError = ref(false)\r\n const errorMessage = ref('')\r\n const imageSrc = ref<string>('') // 存储异步加载的图片URL\r\n const imageLoading = ref(false) // 图片加载状态\r\n\r\n // 计算根元素样式\r\n const rootStyle = computed(() => ({\r\n fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n color: props.color,\r\n cursor: props.clickable ? 'pointer' : 'inherit',\r\n }))\r\n\r\n // 计算图标样式\r\n const iconStyle = computed(() => {\r\n const style: Record<string, any> = {\r\n display: 'inline-flex',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n width: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n height: typeof props.size === 'number' ? `${props.size}px` : props.size,\r\n }\r\n\r\n // 旋转\r\n if (props.rotate) {\r\n style.transform = `rotate(${props.rotate}deg)`\r\n }\r\n\r\n // 翻转\r\n if (props.flip) {\r\n const scaleX =\r\n props.flip === 'horizontal' || props.flip === 'both' ? -1 : 1\r\n const scaleY = props.flip === 'vertical' || props.flip === 'both' ? -1 : 1\r\n style.transform = `${\r\n style.transform || ''\r\n } scaleX(${scaleX}) scaleY(${scaleY})`.trim()\r\n }\r\n\r\n return style\r\n })\r\n\r\n // 获取图标名称用于显示\r\n const iconDisplayName = computed(() => {\r\n if (typeof props.name === 'string') {\r\n return props.name\r\n }\r\n if (props.name && props.name.__name) {\r\n return props.name.__name\r\n }\r\n if (props.name && props.name.name) {\r\n return props.name.name\r\n }\r\n return 'Component Icon'\r\n })\r\n\r\n // 图标类名\r\n const iconClasses = computed(() => [\r\n props.customClass,\r\n {\r\n 'c-icon__rotatable': props.rotate !== 0,\r\n 'c-icon__flippable': !!props.flip,\r\n },\r\n ])\r\n\r\n // 解析组件名称(纯计算属性,无副作用)\r\n const resolvedComponent = computed(() => {\r\n if (props.type !== 'component' || !props.name) return null\r\n return props.name\r\n })\r\n\r\n // 统一错误处理函数\r\n const handleError = (type: string, message: string, originalError?: any) => {\r\n console.warn(`[C_Icon] ${message}:`, iconDisplayName.value, originalError)\r\n errorMessage.value = message\r\n imageLoading.value = false // 错误时停止加载状态\r\n emit('error', type, originalError)\r\n\r\n // 如果有回退图标,尝试切换为 fallback Iconify 图标\r\n if (props.fallbackIcon && !hasError.value) {\r\n hasError.value = false\r\n // 将 name 重写为 fallbackIcon 并重置状态\r\n // 因为 props 不可变,通过模板中的 fallback 分支渲染\r\n return\r\n }\r\n\r\n hasError.value = true\r\n }\r\n\r\n // 处理点击事件\r\n const handleClick = (event: MouseEvent) => {\r\n if (!props.clickable || props.loading) return\r\n emit('click', event)\r\n }\r\n\r\n // 处理键盘事件\r\n const handleKeydown = (event: KeyboardEvent) => {\r\n if (!props.clickable || props.loading) return\r\n if (event.key === 'Enter' || event.key === ' ') {\r\n event.preventDefault()\r\n emit('click', event as any)\r\n }\r\n }\r\n\r\n // 处理图片加载成功\r\n const handleImageLoad = () => {\r\n imageLoading.value = false\r\n emit('load')\r\n }\r\n\r\n // 异步加载图片 - 修复关键函数\r\n const loadImageSrc = async () => {\r\n if (props.type !== 'image' || typeof props.name !== 'string') {\r\n imageSrc.value = ''\r\n return\r\n }\r\n\r\n // 如果是完整URL,直接设置\r\n if (\r\n props.name.startsWith('http://') ||\r\n props.name.startsWith('https://') ||\r\n props.name.startsWith('//') ||\r\n props.name.startsWith('/')\r\n ) {\r\n imageSrc.value = props.name\r\n return\r\n }\r\n\r\n // 其他情况都当作本地图片处理\r\n try {\r\n imageLoading.value = true\r\n const imageUrl = await useImage(props.name)\r\n if (imageUrl) {\r\n imageSrc.value = imageUrl\r\n } else {\r\n handleError('image', '图片路径解析失败')\r\n }\r\n } catch (error) {\r\n handleError('image', '图片加载失败', error)\r\n } finally {\r\n imageLoading.value = false\r\n }\r\n }\r\n\r\n // 验证规则映射\r\n const validationRules = {\r\n iconify: (): string | null => {\r\n if (!props.name.includes(':')) {\r\n console.warn(\r\n '[C_Icon] Iconify图标名称格式应为 \"prefix:name\",如 \"mdi:home\"'\r\n )\r\n }\r\n return null\r\n },\r\n unocss: (): string | null => {\r\n if (typeof props.name !== 'string' || !props.name.startsWith('i-')) {\r\n console.warn('[C_Icon] UnoCSS图标名称应以 \"i-\" 开头')\r\n }\r\n return null\r\n },\r\n component: (): string | null =>\r\n !resolvedComponent.value ? '无法解析组件' : null,\r\n svg: (): string | null => (!props.svgPath ? 'SVG路径不能为空' : null),\r\n image: (): string | null => {\r\n // 对于image类型,异步验证在loadImageSrc中处理\r\n return null\r\n },\r\n }\r\n\r\n // 验证配置\r\n const validateProps = () => {\r\n hasError.value = false\r\n errorMessage.value = ''\r\n\r\n // 基础验证\r\n if (!props.name) {\r\n return handleError('validation', '图标名称不能为空')\r\n }\r\n\r\n // 类型验证\r\n const validator = validationRules[props.type]\r\n if (!validator) {\r\n return handleError('type', '不支持的图标类型')\r\n }\r\n\r\n const errorMsg = validator()\r\n if (errorMsg) {\r\n handleError(props.type, errorMsg)\r\n }\r\n\r\n // 如果是图片类型,异步加载图片\r\n if (props.type === 'image') {\r\n loadImageSrc()\r\n }\r\n }\r\n\r\n // 监听关键属性变化\r\n watch(\r\n () => [props.name, props.type, props.svgPath],\r\n () => {\r\n nextTick(validateProps)\r\n },\r\n { immediate: true }\r\n )\r\n\r\n // 组件挂载后:仅对非 image 类型触发 load 事件(watch immediate 已完成验证)\r\n onMounted(() => {\r\n if (!hasError.value && props.type !== 'image') {\r\n emit('load')\r\n }\r\n })\r\n\r\n // 暴露方法给父组件\r\n defineExpose({\r\n /** 重新验证配置 */\r\n validate: validateProps,\r\n /** 是否有错误 */\r\n hasError: readonly(hasError),\r\n /** 错误信息 */\r\n errorMessage: readonly(errorMessage),\r\n /** DOM引用 */\r\n el: iconRef,\r\n })\r\n</script>\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"C_Icon.js","names":[],"sources":["../src/hooks/useImage/index.ts","../src/components/C_Icon/index.vue"],"sourcesContent":["/**\n * @description: 图片路径解析函数(组件库版本)\n * 在组件库中,图片路径由宿主应用管理。\n * 如果传入的是完整 URL(http/https/data:)则直接返回;\n * 否则可通过 imageResolver 配置自定义解析逻辑。\n * @param {string} imagePath 图片路径或完整 URL\n * @param {Function} resolver 可选的自定义解析器\n * @return {Promise<string>} 处理后图片路径\n */\nexport const useImage = async (\n imagePath: string,\n resolver?: (path: string) => string | Promise<string>,\n): Promise<string> => {\n if (!imagePath) return \"\";\n\n // 完整 URL 直接返回\n if (\n imagePath.startsWith(\"http://\") ||\n imagePath.startsWith(\"https://\") ||\n imagePath.startsWith(\"data:\") ||\n imagePath.startsWith(\"blob:\") ||\n imagePath.startsWith(\"/\")\n ) {\n return imagePath;\n }\n\n // 如果提供了自定义解析器,使用自定义解析器\n if (resolver) {\n try {\n return await resolver(imagePath);\n } catch (e) {\n console.error(`[useImage] 自定义解析器处理失败: ${imagePath}`, e);\n return \"\";\n }\n }\n\n // 默认:直接返回路径(宿主应用应保证路径可访问)\n return imagePath;\n};\n\n/**\n * @description: 同步版本的图片路径处理函数\n * @param {string} imagePath 图片路径或完整 URL\n * @return {string} 图片URL\n */\nexport const useImageSync = (imagePath: string): string => {\n if (!imagePath) return \"\";\n\n // 完整 URL 直接返回\n if (\n imagePath.startsWith(\"http://\") ||\n imagePath.startsWith(\"https://\") ||\n imagePath.startsWith(\"data:\") ||\n imagePath.startsWith(\"blob:\") ||\n imagePath.startsWith(\"/\")\n ) {\n return imagePath;\n }\n\n return imagePath;\n};\n","<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-05-14\n * @Description: 支持多种图标使用方式,默认使用Iconify图标,统一错误处理\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div\n ref=\"iconRef\"\n class=\"c-icon\"\n :class=\"[\n `c-icon--${type}`,\n {\n 'c-icon--clickable': clickable,\n 'c-icon--loading': loading,\n 'c-icon--error': hasError,\n },\n ]\"\n :style=\"rootStyle\"\n :title=\"title || undefined\"\n :aria-label=\"ariaLabel || iconDisplayName\"\n :role=\"clickable ? 'button' : 'img'\"\n :tabindex=\"clickable ? 0 : -1\"\n @click=\"handleClick\"\n @keydown=\"handleKeydown\"\n >\n <!-- 方式1:Iconify图标(默认方式) -->\n <Icon\n v-if=\"\n (type === 'iconify' || !type) && !hasError && typeof name === 'string'\n \"\n :icon=\"name\"\n :width=\"size\"\n :height=\"size\"\n :color=\"color\"\n :style=\"iconStyle\"\n @error=\"() => handleError('iconify', 'Iconify图标加载失败')\"\n />\n\n <!-- 方式2:UnoCSS图标 -->\n <div\n v-else-if=\"type === 'unocss' && !hasError && typeof name === 'string'\"\n :class=\"[name, iconClasses]\"\n :style=\"iconStyle\"\n @error=\"() => handleError('unocss', 'UnoCSS图标渲染失败')\"\n />\n\n <!-- 方式3:组件挂载方式 -->\n <component\n v-else-if=\"type === 'component' && resolvedComponent && !hasError\"\n :is=\"resolvedComponent\"\n :style=\"iconStyle\"\n v-bind=\"componentProps\"\n @error=\"() => handleError('component', '组件图标渲染失败')\"\n />\n\n <!-- 方式4:SVG路径方式 -->\n <svg\n v-else-if=\"type === 'svg' && svgPath && !hasError\"\n :width=\"size\"\n :height=\"size\"\n :viewBox=\"viewBox\"\n :style=\"iconStyle\"\n :fill=\"color\"\n @error=\"() => handleError('svg', 'SVG图标渲染失败')\"\n >\n <path\n :d=\"svgPath\"\n :fill=\"color\"\n />\n </svg>\n\n <!-- 方式5:图片方式 - 修复异步加载 -->\n <img\n v-else-if=\"type === 'image' && imageSrc && !hasError && !imageLoading\"\n :src=\"imageSrc\"\n :alt=\"alt || iconDisplayName\"\n :width=\"size\"\n :height=\"size\"\n style=\"display: inline-block; vertical-align: middle\"\n @error=\"() => handleError('image', '图片图标加载失败')\"\n @load=\"handleImageLoad\"\n />\n\n <!-- 图片加载状态 -->\n <div\n v-if=\"(loading || imageLoading) && !hasError\"\n class=\"c-icon__loading\"\n :style=\"iconStyle\"\n >\n <div class=\"c-icon__spinner\" />\n </div>\n\n <!-- 统一错误状态显示 -->\n <Icon\n v-else-if=\"hasError && fallbackIcon\"\n :icon=\"fallbackIcon\"\n :width=\"size\"\n :height=\"size\"\n :color=\"color\"\n :style=\"iconStyle\"\n />\n <div\n v-else-if=\"hasError\"\n class=\"c-icon__error\"\n :style=\"iconStyle\"\n :title=\"`图标解析失败: ${iconDisplayName} (${errorMessage})`\"\n >\n ⚠️\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\n import { Icon } from '@iconify/vue'\n import { useImage } from '../../hooks/useImage'\n import { computed, nextTick, onMounted, readonly, ref, watch } from 'vue'\n\n export interface IconProps {\n /** 图标名称/路径/组件实例 */\n name: string | any\n /** 图标类型 */\n type?: 'iconify' | 'unocss' | 'component' | 'svg' | 'image'\n /** 图标颜色 */\n color?: string\n /** 图标大小(px) */\n size?: number | string\n /** SVG路径数据(仅type=svg时使用) */\n svgPath?: string\n /** SVG viewBox(仅type=svg时使用) */\n viewBox?: string\n /** 图片alt属性(仅type=image时使用) */\n alt?: string\n /** 是否可点击 */\n clickable?: boolean\n /** 加载状态 */\n loading?: boolean\n /** 错误时的回退图标 */\n fallbackIcon?: string\n /** 工具提示 */\n title?: string\n /** 无障碍标签 */\n ariaLabel?: string\n /** 自定义样式类 */\n customClass?: string\n /** 旋转角度 */\n rotate?: number\n /** 是否翻转 */\n flip?: 'horizontal' | 'vertical' | 'both'\n /** 传递给组件的额外属性(仅type=component时使用) */\n componentProps?: Record<string, any>\n }\n\n const props = withDefaults(defineProps<IconProps>(), {\n name: undefined,\n type: 'iconify',\n color: 'currentColor',\n size: 18,\n svgPath: '',\n viewBox: '0 0 24 24',\n alt: '',\n clickable: false,\n loading: false,\n fallbackIcon: '',\n title: '',\n ariaLabel: '',\n customClass: '',\n rotate: 0,\n flip: undefined,\n componentProps: () => ({}),\n })\n\n const emit = defineEmits<{\n click: [event: MouseEvent]\n error: [type: string, error?: any]\n load: []\n }>()\n\n const iconRef = ref<HTMLElement>()\n const hasError = ref(false)\n const errorMessage = ref('')\n const imageSrc = ref<string>('') // 存储异步加载的图片URL\n const imageLoading = ref(false) // 图片加载状态\n\n // 计算根元素样式\n const rootStyle = computed(() => ({\n fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size,\n color: props.color,\n cursor: props.clickable ? 'pointer' : 'inherit',\n }))\n\n // 计算图标样式\n const iconStyle = computed(() => {\n const style: Record<string, any> = {\n display: 'inline-flex',\n justifyContent: 'center',\n alignItems: 'center',\n width: typeof props.size === 'number' ? `${props.size}px` : props.size,\n height: typeof props.size === 'number' ? `${props.size}px` : props.size,\n }\n\n // 旋转\n if (props.rotate) {\n style.transform = `rotate(${props.rotate}deg)`\n }\n\n // 翻转\n if (props.flip) {\n const scaleX =\n props.flip === 'horizontal' || props.flip === 'both' ? -1 : 1\n const scaleY = props.flip === 'vertical' || props.flip === 'both' ? -1 : 1\n style.transform = `${\n style.transform || ''\n } scaleX(${scaleX}) scaleY(${scaleY})`.trim()\n }\n\n return style\n })\n\n // 获取图标名称用于显示\n const iconDisplayName = computed(() => {\n if (typeof props.name === 'string') {\n return props.name\n }\n if (props.name && props.name.__name) {\n return props.name.__name\n }\n if (props.name && props.name.name) {\n return props.name.name\n }\n return 'Component Icon'\n })\n\n // 图标类名\n const iconClasses = computed(() => [\n props.customClass,\n {\n 'c-icon__rotatable': props.rotate !== 0,\n 'c-icon__flippable': !!props.flip,\n },\n ])\n\n // 解析组件名称(纯计算属性,无副作用)\n const resolvedComponent = computed(() => {\n if (props.type !== 'component' || !props.name) return null\n return props.name\n })\n\n // 统一错误处理函数\n const handleError = (type: string, message: string, originalError?: any) => {\n console.warn(`[C_Icon] ${message}:`, iconDisplayName.value, originalError)\n errorMessage.value = message\n imageLoading.value = false // 错误时停止加载状态\n emit('error', type, originalError)\n\n // 如果有回退图标,尝试切换为 fallback Iconify 图标\n if (props.fallbackIcon && !hasError.value) {\n hasError.value = false\n // 将 name 重写为 fallbackIcon 并重置状态\n // 因为 props 不可变,通过模板中的 fallback 分支渲染\n return\n }\n\n hasError.value = true\n }\n\n // 处理点击事件\n const handleClick = (event: MouseEvent) => {\n if (!props.clickable || props.loading) return\n emit('click', event)\n }\n\n // 处理键盘事件\n const handleKeydown = (event: KeyboardEvent) => {\n if (!props.clickable || props.loading) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n emit('click', event as any)\n }\n }\n\n // 处理图片加载成功\n const handleImageLoad = () => {\n imageLoading.value = false\n emit('load')\n }\n\n // 异步加载图片 - 修复关键函数\n const loadImageSrc = async () => {\n if (props.type !== 'image' || typeof props.name !== 'string') {\n imageSrc.value = ''\n return\n }\n\n // 如果是完整URL,直接设置\n if (\n props.name.startsWith('http://') ||\n props.name.startsWith('https://') ||\n props.name.startsWith('//') ||\n props.name.startsWith('/')\n ) {\n imageSrc.value = props.name\n return\n }\n\n // 其他情况都当作本地图片处理\n try {\n imageLoading.value = true\n const imageUrl = await useImage(props.name)\n if (imageUrl) {\n imageSrc.value = imageUrl\n } else {\n handleError('image', '图片路径解析失败')\n }\n } catch (error) {\n handleError('image', '图片加载失败', error)\n } finally {\n imageLoading.value = false\n }\n }\n\n // 验证规则映射\n const validationRules = {\n iconify: (): string | null => {\n if (!props.name.includes(':')) {\n console.warn(\n '[C_Icon] Iconify图标名称格式应为 \"prefix:name\",如 \"mdi:home\"'\n )\n }\n return null\n },\n unocss: (): string | null => {\n if (typeof props.name !== 'string' || !props.name.startsWith('i-')) {\n console.warn('[C_Icon] UnoCSS图标名称应以 \"i-\" 开头')\n }\n return null\n },\n component: (): string | null =>\n !resolvedComponent.value ? '无法解析组件' : null,\n svg: (): string | null => (!props.svgPath ? 'SVG路径不能为空' : null),\n image: (): string | null => {\n // 对于image类型,异步验证在loadImageSrc中处理\n return null\n },\n }\n\n // 验证配置\n const validateProps = () => {\n hasError.value = false\n errorMessage.value = ''\n\n // 基础验证\n if (!props.name) {\n return handleError('validation', '图标名称不能为空')\n }\n\n // 类型验证\n const validator = validationRules[props.type]\n if (!validator) {\n return handleError('type', '不支持的图标类型')\n }\n\n const errorMsg = validator()\n if (errorMsg) {\n handleError(props.type, errorMsg)\n }\n\n // 如果是图片类型,异步加载图片\n if (props.type === 'image') {\n loadImageSrc()\n }\n }\n\n // 监听关键属性变化\n watch(\n () => [props.name, props.type, props.svgPath],\n () => {\n nextTick(validateProps)\n },\n { immediate: true }\n )\n\n // 组件挂载后:仅对非 image 类型触发 load 事件(watch immediate 已完成验证)\n onMounted(() => {\n if (!hasError.value && props.type !== 'image') {\n emit('load')\n }\n })\n\n // 暴露方法给父组件\n defineExpose({\n /** 重新验证配置 */\n validate: validateProps,\n /** 是否有错误 */\n hasError: readonly(hasError),\n /** 错误信息 */\n errorMessage: readonly(errorMessage),\n /** DOM引用 */\n el: iconRef,\n })\n</script>\n"],"mappings":"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"}
package/dist/C_Icon2.js CHANGED
@@ -1,2 +1,2 @@
1
- import{Fragment as e,computed as t,createBlock as n,createCommentVNode as r,createElementBlock as i,createElementVNode as a,createVNode as o,defineComponent as s,mergeProps as c,nextTick as l,normalizeClass as u,normalizeStyle as d,onMounted as f,openBlock as p,readonly as m,ref as h,resolveDynamicComponent as g,unref as _,watch as v}from"vue";import{Icon as y}from"@iconify/vue";const b=async(e,t)=>{if(!e)return``;if(e.startsWith(`http://`)||e.startsWith(`https://`)||e.startsWith(`data:`)||e.startsWith(`blob:`)||e.startsWith(`/`))return e;if(t)try{return await t(e)}catch(t){return console.error(`[useImage] 自定义解析器处理失败: ${e}`,t),``}return e},x=[`title`,`aria-label`,`role`,`tabindex`],S=[`width`,`height`,`viewBox`,`fill`],C=[`d`,`fill`],w=[`src`,`alt`,`width`,`height`],T=[`title`];var E=s({__name:`index`,props:{name:{default:void 0},type:{default:`iconify`},color:{default:`currentColor`},size:{default:18},svgPath:{default:``},viewBox:{default:`0 0 24 24`},alt:{default:``},clickable:{type:Boolean,default:!1},loading:{type:Boolean,default:!1},fallbackIcon:{default:``},title:{default:``},ariaLabel:{default:``},customClass:{default:``},rotate:{default:0},flip:{default:void 0},componentProps:{default:()=>({})}},emits:[`click`,`error`,`load`],setup(s,{expose:E,emit:D}){let O=s,k=D,A=h(),j=h(!1),M=h(``),N=h(``),P=h(!1),F=t(()=>({fontSize:typeof O.size==`number`?`${O.size}px`:O.size,color:O.color,cursor:O.clickable?`pointer`:`inherit`})),I=t(()=>{let e={display:`inline-flex`,justifyContent:`center`,alignItems:`center`,width:typeof O.size==`number`?`${O.size}px`:O.size,height:typeof O.size==`number`?`${O.size}px`:O.size};if(O.rotate&&(e.transform=`rotate(${O.rotate}deg)`),O.flip){let t=O.flip===`horizontal`||O.flip===`both`?-1:1,n=O.flip===`vertical`||O.flip===`both`?-1:1;e.transform=`${e.transform||``} scaleX(${t}) scaleY(${n})`.trim()}return e}),L=t(()=>typeof O.name==`string`?O.name:O.name&&O.name.__name?O.name.__name:O.name&&O.name.name?O.name.name:`Component Icon`),R=t(()=>[O.customClass,{"c-icon__rotatable":O.rotate!==0,"c-icon__flippable":!!O.flip}]),z=t(()=>O.type!==`component`||!O.name?null:O.name),B=(e,t,n)=>{if(console.warn(`[C_Icon] ${t}:`,L.value,n),M.value=t,P.value=!1,k(`error`,e,n),O.fallbackIcon&&!j.value){j.value=!1;return}j.value=!0},V=e=>{!O.clickable||O.loading||k(`click`,e)},H=e=>{!O.clickable||O.loading||(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),k(`click`,e))},U=()=>{P.value=!1,k(`load`)},W=async()=>{if(O.type!==`image`||typeof O.name!=`string`){N.value=``;return}if(O.name.startsWith(`http://`)||O.name.startsWith(`https://`)||O.name.startsWith(`//`)||O.name.startsWith(`/`)){N.value=O.name;return}try{P.value=!0;let e=await b(O.name);e?N.value=e:B(`image`,`图片路径解析失败`)}catch(e){B(`image`,`图片加载失败`,e)}finally{P.value=!1}},G={iconify:()=>(O.name.includes(`:`)||console.warn(`[C_Icon] Iconify图标名称格式应为 "prefix:name",如 "mdi:home"`),null),unocss:()=>((typeof O.name!=`string`||!O.name.startsWith(`i-`))&&console.warn(`[C_Icon] UnoCSS图标名称应以 "i-" 开头`),null),component:()=>z.value?null:`无法解析组件`,svg:()=>O.svgPath?null:`SVG路径不能为空`,image:()=>null},K=()=>{if(j.value=!1,M.value=``,!O.name)return B(`validation`,`图标名称不能为空`);let e=G[O.type];if(!e)return B(`type`,`不支持的图标类型`);let t=e();t&&B(O.type,t),O.type===`image`&&W()};return v(()=>[O.name,O.type,O.svgPath],()=>{l(K)},{immediate:!0}),f(()=>{!j.value&&O.type!==`image`&&k(`load`)}),E({validate:K,hasError:m(j),errorMessage:m(M),el:A}),(t,s)=>(p(),i(`div`,{ref_key:`iconRef`,ref:A,class:u([`c-icon`,[`c-icon--${t.type}`,{"c-icon--clickable":t.clickable,"c-icon--loading":t.loading,"c-icon--error":j.value}]]),style:d(F.value),title:t.title||void 0,"aria-label":t.ariaLabel||L.value,role:t.clickable?`button`:`img`,tabindex:t.clickable?0:-1,onClick:V,onKeydown:H},[r(` 方式1:Iconify图标(默认方式) `),(t.type===`iconify`||!t.type)&&!j.value&&typeof t.name==`string`?(p(),n(_(y),{key:0,icon:t.name,width:t.size,height:t.size,color:t.color,style:d(I.value),onError:s[0]||=()=>B(`iconify`,`Iconify图标加载失败`)},null,8,[`icon`,`width`,`height`,`color`,`style`])):t.type===`unocss`&&!j.value&&typeof t.name==`string`?(p(),i(e,{key:1},[r(` 方式2:UnoCSS图标 `),a(`div`,{class:u([t.name,R.value]),style:d(I.value),onError:s[1]||=()=>B(`unocss`,`UnoCSS图标渲染失败`)},null,38)],2112)):t.type===`component`&&z.value&&!j.value?(p(),i(e,{key:2},[r(` 方式3:组件挂载方式 `),(p(),n(g(z.value),c({style:I.value},t.componentProps,{onError:s[2]||=()=>B(`component`,`组件图标渲染失败`)}),null,16,[`style`]))],2112)):t.type===`svg`&&t.svgPath&&!j.value?(p(),i(e,{key:3},[r(` 方式4:SVG路径方式 `),(p(),i(`svg`,{width:t.size,height:t.size,viewBox:t.viewBox,style:d(I.value),fill:t.color,onError:s[3]||=()=>B(`svg`,`SVG图标渲染失败`)},[a(`path`,{d:t.svgPath,fill:t.color},null,8,C)],44,S))],2112)):t.type===`image`&&N.value&&!j.value&&!P.value?(p(),i(e,{key:4},[r(` 方式5:图片方式 - 修复异步加载 `),a(`img`,{src:N.value,alt:t.alt||L.value,width:t.size,height:t.size,style:{display:`inline-block`,"vertical-align":`middle`},onError:s[4]||=()=>B(`image`,`图片图标加载失败`),onLoad:U},null,40,w)],2112)):r(`v-if`,!0),r(` 图片加载状态 `),(t.loading||P.value)&&!j.value?(p(),i(`div`,{key:5,class:`c-icon__loading`,style:d(I.value)},s[5]||=[a(`div`,{class:`c-icon__spinner`},null,-1)],4)):j.value&&t.fallbackIcon?(p(),i(e,{key:6},[r(` 统一错误状态显示 `),o(_(y),{icon:t.fallbackIcon,width:t.size,height:t.size,color:t.color,style:d(I.value)},null,8,[`icon`,`width`,`height`,`color`,`style`])],2112)):j.value?(p(),i(`div`,{key:7,class:`c-icon__error`,style:d(I.value),title:`图标解析失败: ${L.value} (${M.value})`},` ⚠️ `,12,T)):r(`v-if`,!0)],46,x))}});export{E as t};
1
+ import{Fragment as e,computed as t,createBlock as n,createCommentVNode as r,createElementBlock as i,createElementVNode as a,createVNode as o,defineComponent as s,mergeProps as c,nextTick as l,normalizeClass as u,normalizeStyle as d,onMounted as f,openBlock as p,readonly as m,ref as h,resolveDynamicComponent as g,unref as _,watch as v}from"vue";import{Icon as y}from"@iconify/vue";const b=async(e,t)=>{if(!e)return``;if(e.startsWith(`http://`)||e.startsWith(`https://`)||e.startsWith(`data:`)||e.startsWith(`blob:`)||e.startsWith(`/`))return e;if(t)try{return await t(e)}catch(t){return console.error(`[useImage] 自定义解析器处理失败: ${e}`,t),``}return e},x=[`title`,`aria-label`,`role`,`tabindex`],S=[`width`,`height`,`viewBox`,`fill`],C=[`d`,`fill`],w=[`src`,`alt`,`width`,`height`],T=[`title`],E=s({__name:`index`,props:{name:{default:void 0},type:{default:`iconify`},color:{default:`currentColor`},size:{default:18},svgPath:{default:``},viewBox:{default:`0 0 24 24`},alt:{default:``},clickable:{type:Boolean,default:!1},loading:{type:Boolean,default:!1},fallbackIcon:{default:``},title:{default:``},ariaLabel:{default:``},customClass:{default:``},rotate:{default:0},flip:{default:void 0},componentProps:{default:()=>({})}},emits:[`click`,`error`,`load`],setup(s,{expose:E,emit:D}){let O=s,k=D,A=h(),j=h(!1),M=h(``),N=h(``),P=h(!1),F=t(()=>({fontSize:typeof O.size==`number`?`${O.size}px`:O.size,color:O.color,cursor:O.clickable?`pointer`:`inherit`})),I=t(()=>{let e={display:`inline-flex`,justifyContent:`center`,alignItems:`center`,width:typeof O.size==`number`?`${O.size}px`:O.size,height:typeof O.size==`number`?`${O.size}px`:O.size};if(O.rotate&&(e.transform=`rotate(${O.rotate}deg)`),O.flip){let t=O.flip===`horizontal`||O.flip===`both`?-1:1,n=O.flip===`vertical`||O.flip===`both`?-1:1;e.transform=`${e.transform||``} scaleX(${t}) scaleY(${n})`.trim()}return e}),L=t(()=>typeof O.name==`string`?O.name:O.name&&O.name.__name?O.name.__name:O.name&&O.name.name?O.name.name:`Component Icon`),R=t(()=>[O.customClass,{"c-icon__rotatable":O.rotate!==0,"c-icon__flippable":!!O.flip}]),z=t(()=>O.type!==`component`||!O.name?null:O.name),B=(e,t,n)=>{if(console.warn(`[C_Icon] ${t}:`,L.value,n),M.value=t,P.value=!1,k(`error`,e,n),O.fallbackIcon&&!j.value){j.value=!1;return}j.value=!0},V=e=>{!O.clickable||O.loading||k(`click`,e)},H=e=>{!O.clickable||O.loading||(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),k(`click`,e))},U=()=>{P.value=!1,k(`load`)},W=async()=>{if(O.type!==`image`||typeof O.name!=`string`){N.value=``;return}if(O.name.startsWith(`http://`)||O.name.startsWith(`https://`)||O.name.startsWith(`//`)||O.name.startsWith(`/`)){N.value=O.name;return}try{P.value=!0;let e=await b(O.name);e?N.value=e:B(`image`,`图片路径解析失败`)}catch(e){B(`image`,`图片加载失败`,e)}finally{P.value=!1}},G={iconify:()=>(O.name.includes(`:`)||console.warn(`[C_Icon] Iconify图标名称格式应为 "prefix:name",如 "mdi:home"`),null),unocss:()=>((typeof O.name!=`string`||!O.name.startsWith(`i-`))&&console.warn(`[C_Icon] UnoCSS图标名称应以 "i-" 开头`),null),component:()=>z.value?null:`无法解析组件`,svg:()=>O.svgPath?null:`SVG路径不能为空`,image:()=>null},K=()=>{if(j.value=!1,M.value=``,!O.name)return B(`validation`,`图标名称不能为空`);let e=G[O.type];if(!e)return B(`type`,`不支持的图标类型`);let t=e();t&&B(O.type,t),O.type===`image`&&W()};return v(()=>[O.name,O.type,O.svgPath],()=>{l(K)},{immediate:!0}),f(()=>{!j.value&&O.type!==`image`&&k(`load`)}),E({validate:K,hasError:m(j),errorMessage:m(M),el:A}),(t,s)=>(p(),i(`div`,{ref_key:`iconRef`,ref:A,class:u([`c-icon`,[`c-icon--${t.type}`,{"c-icon--clickable":t.clickable,"c-icon--loading":t.loading,"c-icon--error":j.value}]]),style:d(F.value),title:t.title||void 0,"aria-label":t.ariaLabel||L.value,role:t.clickable?`button`:`img`,tabindex:t.clickable?0:-1,onClick:V,onKeydown:H},[r(` 方式1:Iconify图标(默认方式) `),(t.type===`iconify`||!t.type)&&!j.value&&typeof t.name==`string`?(p(),n(_(y),{key:0,icon:t.name,width:t.size,height:t.size,color:t.color,style:d(I.value),onError:s[0]||=()=>B(`iconify`,`Iconify图标加载失败`)},null,8,[`icon`,`width`,`height`,`color`,`style`])):t.type===`unocss`&&!j.value&&typeof t.name==`string`?(p(),i(e,{key:1},[r(` 方式2:UnoCSS图标 `),a(`div`,{class:u([t.name,R.value]),style:d(I.value),onError:s[1]||=()=>B(`unocss`,`UnoCSS图标渲染失败`)},null,38)],2112)):t.type===`component`&&z.value&&!j.value?(p(),i(e,{key:2},[r(` 方式3:组件挂载方式 `),(p(),n(g(z.value),c({style:I.value},t.componentProps,{onError:s[2]||=()=>B(`component`,`组件图标渲染失败`)}),null,16,[`style`]))],2112)):t.type===`svg`&&t.svgPath&&!j.value?(p(),i(e,{key:3},[r(` 方式4:SVG路径方式 `),(p(),i(`svg`,{width:t.size,height:t.size,viewBox:t.viewBox,style:d(I.value),fill:t.color,onError:s[3]||=()=>B(`svg`,`SVG图标渲染失败`)},[a(`path`,{d:t.svgPath,fill:t.color},null,8,C)],44,S))],2112)):t.type===`image`&&N.value&&!j.value&&!P.value?(p(),i(e,{key:4},[r(` 方式5:图片方式 - 修复异步加载 `),a(`img`,{src:N.value,alt:t.alt||L.value,width:t.size,height:t.size,style:{display:`inline-block`,"vertical-align":`middle`},onError:s[4]||=()=>B(`image`,`图片图标加载失败`),onLoad:U},null,40,w)],2112)):r(`v-if`,!0),r(` 图片加载状态 `),(t.loading||P.value)&&!j.value?(p(),i(`div`,{key:5,class:`c-icon__loading`,style:d(I.value)},s[5]||=[a(`div`,{class:`c-icon__spinner`},null,-1)],4)):j.value&&t.fallbackIcon?(p(),i(e,{key:6},[r(` 统一错误状态显示 `),o(_(y),{icon:t.fallbackIcon,width:t.size,height:t.size,color:t.color,style:d(I.value)},null,8,[`icon`,`width`,`height`,`color`,`style`])],2112)):j.value?(p(),i(`div`,{key:7,class:`c-icon__error`,style:d(I.value),title:`图标解析失败: ${L.value} (${M.value})`},` ⚠️ `,12,T)):r(`v-if`,!0)],46,x))}});export{E as t};
2
2
  //# sourceMappingURL=C_Icon2.js.map