@robot-admin/naive-ui-components 0.6.2 → 0.6.4

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 (221) hide show
  1. package/CHANGELOG.md +48 -48
  2. package/README.md +18 -14
  3. package/dist/C_ActionBar-DYG0Yf3A.css.map +1 -0
  4. package/dist/C_ActionBar-MAjRhqcI.css.map +1 -0
  5. package/dist/C_ActionBar.css +47 -47
  6. package/dist/C_ActionBar.js.map +1 -1
  7. package/dist/C_ActionBar2.js +1 -1
  8. package/dist/C_ActionBar2.js.map +1 -1
  9. package/dist/{C_AntV-A3NXx_sF.css.map → C_AntV-BY8jqqmi.css.map} +1 -1
  10. package/dist/{C_AntV-D2Zp2qQF.css.map → C_AntV-BbkHc-_a.css.map} +1 -1
  11. package/dist/C_AntV.css +78 -78
  12. package/dist/C_AntV.js.map +1 -1
  13. package/dist/C_AntV2.js +2 -2
  14. package/dist/C_AntV2.js.map +1 -1
  15. package/dist/{C_Barcode-DjTmDkbQ.css.map → C_Barcode-DYDRDe2O.css.map} +1 -1
  16. package/dist/{C_Barcode-Do1Ozsmh.css.map → C_Barcode-muJdc_O4.css.map} +1 -1
  17. package/dist/C_Barcode.css +7 -7
  18. package/dist/C_Barcode.js.map +1 -1
  19. package/dist/C_Barcode2.js +1 -1
  20. package/dist/C_Barcode2.js.map +1 -1
  21. package/dist/C_Breadcrumb-D55Jd2CK.css.map +1 -0
  22. package/dist/C_Breadcrumb-mBL4CWps.css.map +1 -0
  23. package/dist/C_Breadcrumb.css +7 -0
  24. package/dist/C_Breadcrumb.js.map +1 -1
  25. package/dist/C_Breadcrumb2.js +1 -1
  26. package/dist/C_Breadcrumb2.js.map +1 -1
  27. package/dist/{C_Captcha-CZmAETly.css.map → C_Captcha-9_hk6VmY.css.map} +1 -1
  28. package/dist/{C_Captcha-Ccq3DMrR.css.map → C_Captcha-MtASlJ0f.css.map} +1 -1
  29. package/dist/C_Captcha.css +19 -19
  30. package/dist/C_Captcha.js.map +1 -1
  31. package/dist/C_Captcha2.js +1 -1
  32. package/dist/C_Captcha2.js.map +1 -1
  33. package/dist/{C_City-Cv5BESaN.css.map → C_City-Bam08Qv3.css.map} +1 -1
  34. package/dist/{C_City-DNKv4MpH.css.map → C_City-Bb3W-KaN.css.map} +1 -1
  35. package/dist/C_City.css +29 -29
  36. package/dist/C_City.js.map +1 -1
  37. package/dist/C_City2.js +1 -1
  38. package/dist/C_City2.js.map +1 -1
  39. package/dist/C_Code-BFi5xxnM.css.map +1 -0
  40. package/dist/C_Code-CZ7yfnri.css.map +1 -0
  41. package/dist/C_Code.css +3 -3
  42. package/dist/C_Code.js.map +1 -1
  43. package/dist/C_Code2.js +1 -1
  44. package/dist/C_Code2.js.map +1 -1
  45. package/dist/{C_CollapsePanel-BpofCDyl.css.map → C_CollapsePanel-BCmTFuDz.css.map} +1 -1
  46. package/dist/{C_CollapsePanel-Fap-lv_5.css.map → C_CollapsePanel-Bxnh4Zy-.css.map} +1 -1
  47. package/dist/C_CollapsePanel.css +35 -35
  48. package/dist/C_CollapsePanel.js.map +1 -1
  49. package/dist/C_CollapsePanel2.js +1 -1
  50. package/dist/C_CollapsePanel2.js.map +1 -1
  51. package/dist/{C_Cron-C0-8b5af.css.map → C_Cron-Civir5-I.css.map} +1 -1
  52. package/dist/{C_Cron-DqHQ92LN.css.map → C_Cron-iw3qpMsy.css.map} +1 -1
  53. package/dist/C_Cron.css +37 -37
  54. package/dist/C_Cron.js.map +1 -1
  55. package/dist/C_Cron2.js +1 -1
  56. package/dist/C_Cron2.js.map +1 -1
  57. package/dist/{C_Draggable-Bq6o0qXn.css.map → C_Draggable-BCjIvIwT.css.map} +1 -1
  58. package/dist/{C_Draggable-r6nhx3yM.css.map → C_Draggable-DwkSlCOS.css.map} +1 -1
  59. package/dist/C_Draggable.css +46 -46
  60. package/dist/C_Draggable.js.map +1 -1
  61. package/dist/C_Draggable2.js +1 -1
  62. package/dist/C_Draggable2.js.map +1 -1
  63. package/dist/{C_FilePreview-BVarVvnD.css.map → C_FilePreview-BhcbiXsV.css.map} +1 -1
  64. package/dist/{C_FilePreview-DHGlV4OR.css.map → C_FilePreview-DJudcIbW.css.map} +1 -1
  65. package/dist/C_FilePreview.css +91 -91
  66. package/dist/C_FilePreview.js.map +1 -1
  67. package/dist/C_FilePreview2.js +1 -1
  68. package/dist/C_FilePreview2.js.map +1 -1
  69. package/dist/{C_Form-BciQUPpS.css.map → C_Form-BtcAPIRi.css.map} +1 -1
  70. package/dist/{C_Form-CKHvXL3O.css.map → C_Form-D_gwQ7hC.css.map} +1 -1
  71. package/dist/C_Form.css +222 -222
  72. package/dist/C_Form.js.map +1 -1
  73. package/dist/C_Form2.js +1 -1
  74. package/dist/C_Form2.js.map +1 -1
  75. package/dist/{C_FormSearch-Bmd2CmG6.css.map → C_FormSearch-Bi4XYjjk.css.map} +1 -1
  76. package/dist/{C_FormSearch-DBNtnjaL.css.map → C_FormSearch-DUGOoX0u.css.map} +1 -1
  77. package/dist/C_FormSearch.css +45 -45
  78. package/dist/C_FormSearch.js.map +1 -1
  79. package/dist/C_FormSearch2.js +1 -1
  80. package/dist/C_FormSearch2.js.map +1 -1
  81. package/dist/{C_FormulaEditor-3WG2vtQl.css.map → C_FormulaEditor-CJk3SSUJ.css.map} +1 -1
  82. package/dist/{C_FormulaEditor-Cm0CokN5.css.map → C_FormulaEditor-Cy7mjfVO.css.map} +1 -1
  83. package/dist/C_FormulaEditor.css +78 -78
  84. package/dist/C_FormulaEditor.js.map +1 -1
  85. package/dist/C_FormulaEditor2.js +1 -1
  86. package/dist/C_FormulaEditor2.js.map +1 -1
  87. package/dist/C_FullCalendar-BQA0OjGB.css.map +1 -0
  88. package/dist/C_FullCalendar-ChJUosAi.css.map +1 -0
  89. package/dist/C_FullCalendar.css +3 -3
  90. package/dist/C_FullCalendar.js.map +1 -1
  91. package/dist/C_FullCalendar2.js +1 -1
  92. package/dist/C_FullCalendar2.js.map +1 -1
  93. package/dist/C_Icon.js.map +1 -1
  94. package/dist/C_Icon2.js.map +1 -1
  95. package/dist/{C_ImageCropper-D3L2-J_J.css.map → C_ImageCropper-BqZpkZfU.css.map} +1 -1
  96. package/dist/{C_ImageCropper-DrmUlaLi.css.map → C_ImageCropper-CylwGuzS.css.map} +1 -1
  97. package/dist/C_ImageCropper.css +10 -10
  98. package/dist/C_ImageCropper.js.map +1 -1
  99. package/dist/C_ImageCropper2.js +1 -1
  100. package/dist/C_ImageCropper2.js.map +1 -1
  101. package/dist/{C_Map-BCJmDnJT.css.map → C_Map-DNnBcY70.css.map} +1 -1
  102. package/dist/{C_Map-WUMXSAfy.css.map → C_Map-LEXIx6zM.css.map} +1 -1
  103. package/dist/C_Map.css +4 -4
  104. package/dist/C_Map.js.map +1 -1
  105. package/dist/C_Map2.js +1 -1
  106. package/dist/C_Map2.js.map +1 -1
  107. package/dist/{C_Markdown-7iBnLDtL.css.map → C_Markdown-Kujzx1qG.css.map} +1 -1
  108. package/dist/{C_Markdown-BbTvdFCl.css.map → C_Markdown-YCAmBbNF.css.map} +1 -1
  109. package/dist/C_Markdown.css +33 -33
  110. package/dist/C_Markdown.js.map +1 -1
  111. package/dist/C_Markdown2.js +1 -1
  112. package/dist/C_Markdown2.js.map +1 -1
  113. package/dist/C_Menu.js.map +1 -1
  114. package/dist/C_Menu2.js.map +1 -1
  115. package/dist/{C_QRCode-DMxSyogU.css.map → C_QRCode-CFNTda_1.css.map} +1 -1
  116. package/dist/{C_QRCode-G7fiAkm4.css.map → C_QRCode-CmWr9lK-.css.map} +1 -1
  117. package/dist/C_QRCode.css +9 -9
  118. package/dist/C_QRCode.js.map +1 -1
  119. package/dist/C_QRCode2.js +1 -1
  120. package/dist/C_QRCode2.js.map +1 -1
  121. package/dist/{C_Signature-B5fFz6RD.css.map → C_Signature-PN4GOLRX.css.map} +1 -1
  122. package/dist/{C_Signature-es-ZNPzr.css.map → C_Signature-fWQqGIZ-.css.map} +1 -1
  123. package/dist/C_Signature.css +14 -14
  124. package/dist/C_Signature.js.map +1 -1
  125. package/dist/C_Signature2.js +1 -1
  126. package/dist/C_Signature2.js.map +1 -1
  127. package/dist/{C_SplitPane-Br2eK8IG.css.map → C_SplitPane-BOwr1C7K.css.map} +1 -1
  128. package/dist/{C_SplitPane-DuyzUhKB.css.map → C_SplitPane-N-a_D-7F.css.map} +1 -1
  129. package/dist/C_SplitPane.css +31 -31
  130. package/dist/C_SplitPane.js.map +1 -1
  131. package/dist/C_SplitPane2.js +1 -1
  132. package/dist/C_SplitPane2.js.map +1 -1
  133. package/dist/{C_Steps-DGwXxS2Y.css.map → C_Steps-DLW2vbqC.css.map} +1 -1
  134. package/dist/{C_Steps-P9Qj9iDd.css.map → C_Steps-DwMRHbG2.css.map} +1 -1
  135. package/dist/C_Steps.css +37 -37
  136. package/dist/C_Steps.js.map +1 -1
  137. package/dist/C_Steps2.js +1 -1
  138. package/dist/C_Steps2.js.map +1 -1
  139. package/dist/{C_Table-vZlcgH6x.css.map → C_Table-BV1wUw6f.css.map} +1 -1
  140. package/dist/{C_Table-wzAO43l0.css.map → C_Table-LlJ-JEdm.css.map} +1 -1
  141. package/dist/C_Table.css +93 -93
  142. package/dist/C_Table.js.map +1 -1
  143. package/dist/C_Table2.js +1 -1
  144. package/dist/C_Table2.js.map +1 -1
  145. package/dist/{C_TagsView-BsutyC3U.css.map → C_TagsView-DX3LecK1.css.map} +1 -1
  146. package/dist/{C_TagsView-Crm7yWg9.css.map → C_TagsView-DqOTa8bL.css.map} +1 -1
  147. package/dist/C_TagsView.css +36 -36
  148. package/dist/C_TagsView.js.map +1 -1
  149. package/dist/C_TagsView2.js +1 -1
  150. package/dist/C_TagsView2.js.map +1 -1
  151. package/dist/C_Time-BzyQVvTS.css.map +1 -0
  152. package/dist/C_Time-DmpKLQHc.css.map +1 -0
  153. package/dist/C_Time.css +5 -5
  154. package/dist/C_Time.js.map +1 -1
  155. package/dist/C_Time2.js +1 -1
  156. package/dist/C_Time2.js.map +1 -1
  157. package/dist/{C_Tree-B-Le4hjQ.css.map → C_Tree-D98nX9qT.css.map} +1 -1
  158. package/dist/{C_Tree-DnGc_MPb.css.map → C_Tree-ulfm7b-P.css.map} +1 -1
  159. package/dist/C_Tree.css +33 -33
  160. package/dist/C_Tree.js.map +1 -1
  161. package/dist/C_Tree2.js +1 -1
  162. package/dist/C_Tree2.js.map +1 -1
  163. package/dist/{C_VideoPlayer-Cdgv4Zq0.css.map → C_VideoPlayer-4J96tKfK.css.map} +1 -1
  164. package/dist/{C_VideoPlayer-rm0MODUv.css.map → C_VideoPlayer-D35seHkm.css.map} +1 -1
  165. package/dist/C_VideoPlayer.css +13 -13
  166. package/dist/C_VideoPlayer.js.map +1 -1
  167. package/dist/C_VideoPlayer2.js +1 -1
  168. package/dist/C_VideoPlayer2.js.map +1 -1
  169. package/dist/{C_VtableGantt-BpY-Rng3.css.map → C_VtableGantt-C2lxFmWg.css.map} +1 -1
  170. package/dist/{C_VtableGantt-DB87-jdS.css.map → C_VtableGantt-Dwnkw_WL.css.map} +1 -1
  171. package/dist/C_VtableGantt.css +15 -15
  172. package/dist/C_VtableGantt.js.map +1 -1
  173. package/dist/C_VtableGantt2.js +1 -1
  174. package/dist/C_VtableGantt2.js.map +1 -1
  175. package/dist/{C_WorkFlow-7F6op6qN.css.map → C_WorkFlow-2hJfvvTK.css.map} +1 -1
  176. package/dist/{C_WorkFlow-Cy3HWA6A.css.map → C_WorkFlow-DHoNjl8-.css.map} +1 -1
  177. package/dist/C_WorkFlow.css +108 -108
  178. package/dist/C_WorkFlow.js.map +1 -1
  179. package/dist/C_WorkFlow2.js +1 -1
  180. package/dist/C_WorkFlow2.js.map +1 -1
  181. package/dist/constants.d.ts +4 -4
  182. package/dist/constants2.d.ts +5 -5
  183. package/dist/constants3.d.ts +1 -1
  184. package/dist/constants4.d.ts +3 -3
  185. package/dist/index.cjs.map +1 -1
  186. package/dist/index.js.map +1 -1
  187. package/dist/index11.vue.d.ts +3 -3
  188. package/dist/index12.vue.d.ts +2 -2
  189. package/dist/index13.vue.d.ts +5 -5
  190. package/dist/index15.vue.d.ts +1 -1
  191. package/dist/index17.vue.d.ts +1 -1
  192. package/dist/index19.vue.d.ts +1 -1
  193. package/dist/index4.vue.d.ts +6 -6
  194. package/dist/index5.vue.d.ts +2 -2
  195. package/dist/index6.vue.d.ts +2 -2
  196. package/dist/index7.vue.d.ts +1 -1
  197. package/dist/index8.vue.d.ts +1 -1
  198. package/dist/index9.vue.d.ts +2 -2
  199. package/dist/menuAdapter.js.map +1 -1
  200. package/dist/resolver.cjs.map +1 -1
  201. package/dist/resolver.js.map +1 -1
  202. package/dist/style.css +1218 -1209
  203. package/dist/useCollapsePanel.d.ts +1 -1
  204. package/dist/useDraggableLayout.d.ts +2 -2
  205. package/dist/useDynamicFormState.d.ts +118 -118
  206. package/dist/useInfiniteScroll.d.ts +2 -2
  207. package/dist/useQRCode.d.ts +1 -1
  208. package/dist/useSearchState.d.ts +2 -2
  209. package/dist/useSplitResize.d.ts +2 -2
  210. package/dist/useTimeSelection.d.ts +2 -2
  211. package/dist/useTreeOperations.d.ts +4 -4
  212. package/dist/useWorkflowValidation.d.ts +2 -2
  213. package/package.json +1 -1
  214. package/dist/C_ActionBar-jZPsiXtT.css.map +0 -1
  215. package/dist/C_ActionBar-nnfbZCea.css.map +0 -1
  216. package/dist/C_Code-DPZlNSxL.css.map +0 -1
  217. package/dist/C_Code-avZjAnHD.css.map +0 -1
  218. package/dist/C_FullCalendar-1U4VlG7X.css.map +0 -1
  219. package/dist/C_FullCalendar-DLUdCtqf.css.map +0 -1
  220. package/dist/C_Time-Bd_e1YDN.css.map +0 -1
  221. package/dist/C_Time-CKb_fwdm.css.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"C_Menu.js","names":["mode","collapsed","collapsedWidth","collapsedIconSize","inverted","themeOverrides","dropdownProps","indent","rootIndent"],"sources":["../src/components/C_Menu/index.vue","../src/components/C_Menu/index.vue","../src/components/C_Menu/index.vue"],"sourcesContent":["/* unplugin-vue-components disabled */<!--\r\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\r\n *\r\n * 支持两种数据输入模式:\r\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\r\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\r\n *\r\n * @example 路由模式(推荐)\r\n * ```vue\r\n * <C_Menu\r\n * :routes=\"menuData\"\r\n * :label-formatter=\"$t\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * @example 原生模式\r\n * ```vue\r\n * <C_Menu\r\n * :options=\"nMenuOptions\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NMenu\r\n ref=\"menuRef\"\r\n :options=\"mergedOptions\"\r\n :expanded-keys=\"expandedKeys\"\r\n :value=\"activeKey\"\r\n :mode=\"mode\"\r\n :collapsed=\"collapsed\"\r\n :collapsed-width=\"collapsedWidth\"\r\n :collapsed-icon-size=\"collapsedIconSize\"\r\n :inverted=\"inverted\"\r\n :theme-overrides=\"themeOverrides\"\r\n :dropdown-props=\"dropdownProps\"\r\n :indent=\"indent\"\r\n :root-indent=\"rootIndent\"\r\n @update:value=\"handleMenuClick\"\r\n @update:expanded-keys=\"onExpandedKeysChange\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\r\n import {\r\n type MenuOption,\r\n type MenuInst,\r\n type DropdownProps,\r\n } from 'naive-ui/es'\r\n import {\r\n createMenuOptions,\r\n type RouteItem,\r\n type MenuAdapterConfig,\r\n } from '../_shared'\r\n\r\n defineOptions({ name: 'C_Menu' })\r\n\r\n const props = withDefaults(\r\n defineProps<{\r\n /**\r\n * 模式一:直传 NMenu 原生 MenuOption[]\r\n * 与 `routes` 二选一,`options` 优先级更高\r\n */\r\n options?: MenuOption[]\r\n /**\r\n * 模式二:传入路由数据,自动转换为 MenuOption[]\r\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\r\n */\r\n routes?: RouteItem[]\r\n /**\r\n * 适配器配置(仅 routes 模式生效)\r\n * 可自定义 key/label/icon 取值、过滤逻辑等\r\n */\r\n adapterConfig?: MenuAdapterConfig\r\n /**\r\n * 标签文本格式化函数(routes 模式的快捷方式)\r\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\r\n */\r\n labelFormatter?: (label: string) => string\r\n /** 当前激活的菜单 key */\r\n value?: string\r\n /** 菜单模式 */\r\n mode?: 'vertical' | 'horizontal'\r\n /** 是否折叠 */\r\n collapsed?: boolean\r\n /** 折叠宽度 */\r\n collapsedWidth?: number\r\n /** 折叠图标大小 */\r\n collapsedIconSize?: number\r\n /** 是否反色 */\r\n inverted?: boolean\r\n /** 主题覆盖 */\r\n themeOverrides?: Record<string, any>\r\n /** 缩进像素 */\r\n indent?: number\r\n /** 根缩进像素 */\r\n rootIndent?: number\r\n /** 下拉菜单属性(折叠模式) */\r\n dropdownProps?: DropdownProps\r\n }>(),\r\n {\r\n mode: 'vertical',\r\n collapsed: false,\r\n collapsedWidth: 64,\r\n collapsedIconSize: 22,\r\n inverted: false,\r\n indent: 24,\r\n rootIndent: 16,\r\n dropdownProps: () => ({\r\n placement: 'right-start' as const,\r\n trigger: 'hover' as const,\r\n arrowStyle: { color: 'var(--n-color)' },\r\n }),\r\n }\r\n )\r\n\r\n const emit = defineEmits<{\r\n /** 菜单项被选中 */\r\n select: [key: string]\r\n /** 展开项变化 */\r\n 'update:expandedKeys': [keys: string[]]\r\n }>()\r\n\r\n const menuRef = ref<MenuInst | null>(null)\r\n const expandedKeys = ref<string[]>([])\r\n\r\n const activeKey = computed(() => props.value ?? '')\r\n\r\n // ====== 数据合成 ======\r\n\r\n /** 合并后的最终 MenuOption[] */\r\n const mergedOptions = computed<MenuOption[]>(() => {\r\n // 优先使用 options 直传模式\r\n if (props.options) return props.options\r\n\r\n // routes 模式 → 适配器转换\r\n if (props.routes) {\r\n const config: MenuAdapterConfig = {\r\n ...props.adapterConfig,\r\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\r\n }\r\n return createMenuOptions(props.routes, config)\r\n }\r\n\r\n return []\r\n })\r\n\r\n // ====== 展开逻辑 ======\r\n\r\n /** 从 MenuOption[] 中查找所有祖先 key */\r\n const findParentKeys = (\r\n items: MenuOption[],\r\n targetKey: string,\r\n parentKeys: string[] = []\r\n ): string[] => {\r\n for (const item of items) {\r\n const children = item.children as MenuOption[] | undefined\r\n if (children?.length) {\r\n const currentKeys = [...parentKeys, item.key as string]\r\n if (children.some(child => child.key === targetKey)) {\r\n return currentKeys\r\n }\r\n const result = findParentKeys(children, targetKey, currentKeys)\r\n if (result.length > 0) return result\r\n }\r\n }\r\n return []\r\n }\r\n\r\n /** 通过路径段推导展开 key + 查找祖先 key */\r\n const computeExpandedKeys = (path: string): Set<string> => {\r\n const segments = path.split('/').filter(Boolean)\r\n const keys = new Set<string>()\r\n let currentPath = ''\r\n segments.forEach(segment => {\r\n currentPath += `/${segment}`\r\n keys.add(currentPath)\r\n })\r\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\r\n return keys\r\n }\r\n\r\n // ====== 事件处理 ======\r\n\r\n const handleMenuClick = (key: string) => {\r\n if (key && key !== activeKey.value) {\r\n emit('select', key)\r\n }\r\n }\r\n\r\n const onExpandedKeysChange = (keys: string[]) => {\r\n expandedKeys.value = keys\r\n emit('update:expandedKeys', keys)\r\n }\r\n\r\n // ====== 暴露方法 ======\r\n const showOption = (key: string) => menuRef.value?.showOption(key)\r\n defineExpose({ showOption, menuRef })\r\n\r\n // ====== 路由跟踪 ======\r\n\r\n watch(\r\n () => activeKey.value,\r\n newKey => {\r\n if (!newKey) return\r\n const newKeys = computeExpandedKeys(newKey)\r\n expandedKeys.value = Array.from(\r\n new Set([...expandedKeys.value, ...newKeys])\r\n )\r\n nextTick(() => menuRef.value?.showOption(newKey))\r\n },\r\n { immediate: true }\r\n )\r\n\r\n onMounted(() => {\r\n nextTick(() => {\r\n if (activeKey.value) {\r\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\r\n menuRef.value?.showOption(activeKey.value)\r\n }\r\n })\r\n })\r\n</script>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\r\n *\r\n * 支持两种数据输入模式:\r\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\r\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\r\n *\r\n * @example 路由模式(推荐)\r\n * ```vue\r\n * <C_Menu\r\n * :routes=\"menuData\"\r\n * :label-formatter=\"$t\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * @example 原生模式\r\n * ```vue\r\n * <C_Menu\r\n * :options=\"nMenuOptions\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NMenu\r\n ref=\"menuRef\"\r\n :options=\"mergedOptions\"\r\n :expanded-keys=\"expandedKeys\"\r\n :value=\"activeKey\"\r\n :mode=\"mode\"\r\n :collapsed=\"collapsed\"\r\n :collapsed-width=\"collapsedWidth\"\r\n :collapsed-icon-size=\"collapsedIconSize\"\r\n :inverted=\"inverted\"\r\n :theme-overrides=\"themeOverrides\"\r\n :dropdown-props=\"dropdownProps\"\r\n :indent=\"indent\"\r\n :root-indent=\"rootIndent\"\r\n @update:value=\"handleMenuClick\"\r\n @update:expanded-keys=\"onExpandedKeysChange\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\r\n import {\r\n type MenuOption,\r\n type MenuInst,\r\n type DropdownProps,\r\n } from 'naive-ui/es'\r\n import {\r\n createMenuOptions,\r\n type RouteItem,\r\n type MenuAdapterConfig,\r\n } from '../_shared'\r\n\r\n defineOptions({ name: 'C_Menu' })\r\n\r\n const props = withDefaults(\r\n defineProps<{\r\n /**\r\n * 模式一:直传 NMenu 原生 MenuOption[]\r\n * 与 `routes` 二选一,`options` 优先级更高\r\n */\r\n options?: MenuOption[]\r\n /**\r\n * 模式二:传入路由数据,自动转换为 MenuOption[]\r\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\r\n */\r\n routes?: RouteItem[]\r\n /**\r\n * 适配器配置(仅 routes 模式生效)\r\n * 可自定义 key/label/icon 取值、过滤逻辑等\r\n */\r\n adapterConfig?: MenuAdapterConfig\r\n /**\r\n * 标签文本格式化函数(routes 模式的快捷方式)\r\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\r\n */\r\n labelFormatter?: (label: string) => string\r\n /** 当前激活的菜单 key */\r\n value?: string\r\n /** 菜单模式 */\r\n mode?: 'vertical' | 'horizontal'\r\n /** 是否折叠 */\r\n collapsed?: boolean\r\n /** 折叠宽度 */\r\n collapsedWidth?: number\r\n /** 折叠图标大小 */\r\n collapsedIconSize?: number\r\n /** 是否反色 */\r\n inverted?: boolean\r\n /** 主题覆盖 */\r\n themeOverrides?: Record<string, any>\r\n /** 缩进像素 */\r\n indent?: number\r\n /** 根缩进像素 */\r\n rootIndent?: number\r\n /** 下拉菜单属性(折叠模式) */\r\n dropdownProps?: DropdownProps\r\n }>(),\r\n {\r\n mode: 'vertical',\r\n collapsed: false,\r\n collapsedWidth: 64,\r\n collapsedIconSize: 22,\r\n inverted: false,\r\n indent: 24,\r\n rootIndent: 16,\r\n dropdownProps: () => ({\r\n placement: 'right-start' as const,\r\n trigger: 'hover' as const,\r\n arrowStyle: { color: 'var(--n-color)' },\r\n }),\r\n }\r\n )\r\n\r\n const emit = defineEmits<{\r\n /** 菜单项被选中 */\r\n select: [key: string]\r\n /** 展开项变化 */\r\n 'update:expandedKeys': [keys: string[]]\r\n }>()\r\n\r\n const menuRef = ref<MenuInst | null>(null)\r\n const expandedKeys = ref<string[]>([])\r\n\r\n const activeKey = computed(() => props.value ?? '')\r\n\r\n // ====== 数据合成 ======\r\n\r\n /** 合并后的最终 MenuOption[] */\r\n const mergedOptions = computed<MenuOption[]>(() => {\r\n // 优先使用 options 直传模式\r\n if (props.options) return props.options\r\n\r\n // routes 模式 → 适配器转换\r\n if (props.routes) {\r\n const config: MenuAdapterConfig = {\r\n ...props.adapterConfig,\r\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\r\n }\r\n return createMenuOptions(props.routes, config)\r\n }\r\n\r\n return []\r\n })\r\n\r\n // ====== 展开逻辑 ======\r\n\r\n /** 从 MenuOption[] 中查找所有祖先 key */\r\n const findParentKeys = (\r\n items: MenuOption[],\r\n targetKey: string,\r\n parentKeys: string[] = []\r\n ): string[] => {\r\n for (const item of items) {\r\n const children = item.children as MenuOption[] | undefined\r\n if (children?.length) {\r\n const currentKeys = [...parentKeys, item.key as string]\r\n if (children.some(child => child.key === targetKey)) {\r\n return currentKeys\r\n }\r\n const result = findParentKeys(children, targetKey, currentKeys)\r\n if (result.length > 0) return result\r\n }\r\n }\r\n return []\r\n }\r\n\r\n /** 通过路径段推导展开 key + 查找祖先 key */\r\n const computeExpandedKeys = (path: string): Set<string> => {\r\n const segments = path.split('/').filter(Boolean)\r\n const keys = new Set<string>()\r\n let currentPath = ''\r\n segments.forEach(segment => {\r\n currentPath += `/${segment}`\r\n keys.add(currentPath)\r\n })\r\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\r\n return keys\r\n }\r\n\r\n // ====== 事件处理 ======\r\n\r\n const handleMenuClick = (key: string) => {\r\n if (key && key !== activeKey.value) {\r\n emit('select', key)\r\n }\r\n }\r\n\r\n const onExpandedKeysChange = (keys: string[]) => {\r\n expandedKeys.value = keys\r\n emit('update:expandedKeys', keys)\r\n }\r\n\r\n // ====== 暴露方法 ======\r\n const showOption = (key: string) => menuRef.value?.showOption(key)\r\n defineExpose({ showOption, menuRef })\r\n\r\n // ====== 路由跟踪 ======\r\n\r\n watch(\r\n () => activeKey.value,\r\n newKey => {\r\n if (!newKey) return\r\n const newKeys = computeExpandedKeys(newKey)\r\n expandedKeys.value = Array.from(\r\n new Set([...expandedKeys.value, ...newKeys])\r\n )\r\n nextTick(() => menuRef.value?.showOption(newKey))\r\n },\r\n { immediate: true }\r\n )\r\n\r\n onMounted(() => {\r\n nextTick(() => {\r\n if (activeKey.value) {\r\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\r\n menuRef.value?.showOption(activeKey.value)\r\n }\r\n })\r\n })\r\n</script>\r\n","<!--\r\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\r\n *\r\n * 支持两种数据输入模式:\r\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\r\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\r\n *\r\n * @example 路由模式(推荐)\r\n * ```vue\r\n * <C_Menu\r\n * :routes=\"menuData\"\r\n * :label-formatter=\"$t\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * @example 原生模式\r\n * ```vue\r\n * <C_Menu\r\n * :options=\"nMenuOptions\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NMenu\r\n ref=\"menuRef\"\r\n :options=\"mergedOptions\"\r\n :expanded-keys=\"expandedKeys\"\r\n :value=\"activeKey\"\r\n :mode=\"mode\"\r\n :collapsed=\"collapsed\"\r\n :collapsed-width=\"collapsedWidth\"\r\n :collapsed-icon-size=\"collapsedIconSize\"\r\n :inverted=\"inverted\"\r\n :theme-overrides=\"themeOverrides\"\r\n :dropdown-props=\"dropdownProps\"\r\n :indent=\"indent\"\r\n :root-indent=\"rootIndent\"\r\n @update:value=\"handleMenuClick\"\r\n @update:expanded-keys=\"onExpandedKeysChange\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\r\n import {\r\n type MenuOption,\r\n type MenuInst,\r\n type DropdownProps,\r\n } from 'naive-ui/es'\r\n import {\r\n createMenuOptions,\r\n type RouteItem,\r\n type MenuAdapterConfig,\r\n } from '../_shared'\r\n\r\n defineOptions({ name: 'C_Menu' })\r\n\r\n const props = withDefaults(\r\n defineProps<{\r\n /**\r\n * 模式一:直传 NMenu 原生 MenuOption[]\r\n * 与 `routes` 二选一,`options` 优先级更高\r\n */\r\n options?: MenuOption[]\r\n /**\r\n * 模式二:传入路由数据,自动转换为 MenuOption[]\r\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\r\n */\r\n routes?: RouteItem[]\r\n /**\r\n * 适配器配置(仅 routes 模式生效)\r\n * 可自定义 key/label/icon 取值、过滤逻辑等\r\n */\r\n adapterConfig?: MenuAdapterConfig\r\n /**\r\n * 标签文本格式化函数(routes 模式的快捷方式)\r\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\r\n */\r\n labelFormatter?: (label: string) => string\r\n /** 当前激活的菜单 key */\r\n value?: string\r\n /** 菜单模式 */\r\n mode?: 'vertical' | 'horizontal'\r\n /** 是否折叠 */\r\n collapsed?: boolean\r\n /** 折叠宽度 */\r\n collapsedWidth?: number\r\n /** 折叠图标大小 */\r\n collapsedIconSize?: number\r\n /** 是否反色 */\r\n inverted?: boolean\r\n /** 主题覆盖 */\r\n themeOverrides?: Record<string, any>\r\n /** 缩进像素 */\r\n indent?: number\r\n /** 根缩进像素 */\r\n rootIndent?: number\r\n /** 下拉菜单属性(折叠模式) */\r\n dropdownProps?: DropdownProps\r\n }>(),\r\n {\r\n mode: 'vertical',\r\n collapsed: false,\r\n collapsedWidth: 64,\r\n collapsedIconSize: 22,\r\n inverted: false,\r\n indent: 24,\r\n rootIndent: 16,\r\n dropdownProps: () => ({\r\n placement: 'right-start' as const,\r\n trigger: 'hover' as const,\r\n arrowStyle: { color: 'var(--n-color)' },\r\n }),\r\n }\r\n )\r\n\r\n const emit = defineEmits<{\r\n /** 菜单项被选中 */\r\n select: [key: string]\r\n /** 展开项变化 */\r\n 'update:expandedKeys': [keys: string[]]\r\n }>()\r\n\r\n const menuRef = ref<MenuInst | null>(null)\r\n const expandedKeys = ref<string[]>([])\r\n\r\n const activeKey = computed(() => props.value ?? '')\r\n\r\n // ====== 数据合成 ======\r\n\r\n /** 合并后的最终 MenuOption[] */\r\n const mergedOptions = computed<MenuOption[]>(() => {\r\n // 优先使用 options 直传模式\r\n if (props.options) return props.options\r\n\r\n // routes 模式 → 适配器转换\r\n if (props.routes) {\r\n const config: MenuAdapterConfig = {\r\n ...props.adapterConfig,\r\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\r\n }\r\n return createMenuOptions(props.routes, config)\r\n }\r\n\r\n return []\r\n })\r\n\r\n // ====== 展开逻辑 ======\r\n\r\n /** 从 MenuOption[] 中查找所有祖先 key */\r\n const findParentKeys = (\r\n items: MenuOption[],\r\n targetKey: string,\r\n parentKeys: string[] = []\r\n ): string[] => {\r\n for (const item of items) {\r\n const children = item.children as MenuOption[] | undefined\r\n if (children?.length) {\r\n const currentKeys = [...parentKeys, item.key as string]\r\n if (children.some(child => child.key === targetKey)) {\r\n return currentKeys\r\n }\r\n const result = findParentKeys(children, targetKey, currentKeys)\r\n if (result.length > 0) return result\r\n }\r\n }\r\n return []\r\n }\r\n\r\n /** 通过路径段推导展开 key + 查找祖先 key */\r\n const computeExpandedKeys = (path: string): Set<string> => {\r\n const segments = path.split('/').filter(Boolean)\r\n const keys = new Set<string>()\r\n let currentPath = ''\r\n segments.forEach(segment => {\r\n currentPath += `/${segment}`\r\n keys.add(currentPath)\r\n })\r\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\r\n return keys\r\n }\r\n\r\n // ====== 事件处理 ======\r\n\r\n const handleMenuClick = (key: string) => {\r\n if (key && key !== activeKey.value) {\r\n emit('select', key)\r\n }\r\n }\r\n\r\n const onExpandedKeysChange = (keys: string[]) => {\r\n expandedKeys.value = keys\r\n emit('update:expandedKeys', keys)\r\n }\r\n\r\n // ====== 暴露方法 ======\r\n const showOption = (key: string) => menuRef.value?.showOption(key)\r\n defineExpose({ showOption, menuRef })\r\n\r\n // ====== 路由跟踪 ======\r\n\r\n watch(\r\n () => activeKey.value,\r\n newKey => {\r\n if (!newKey) return\r\n const newKeys = computeExpandedKeys(newKey)\r\n expandedKeys.value = Array.from(\r\n new Set([...expandedKeys.value, ...newKeys])\r\n )\r\n nextTick(() => menuRef.value?.showOption(newKey))\r\n },\r\n { immediate: true }\r\n )\r\n\r\n onMounted(() => {\r\n nextTick(() => {\r\n if (activeKey.value) {\r\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\r\n menuRef.value?.showOption(activeKey.value)\r\n }\r\n })\r\n })\r\n</script>\r\n"],"mappings":"gnBE6DE,IAAM,EAAQ,EA2DR,EAAO,EAOP,GAAA,EAAA,EAAA,KAA+B,KAAK,CACpC,GAAA,EAAA,EAAA,KAA6B,EAAE,CAAC,CAEhC,GAAA,EAAA,EAAA,cAA2B,EAAM,OAAS,GAAG,CAK7C,GAAA,EAAA,EAAA,cAA6C,CAEjD,GAAI,EAAM,QAAS,OAAO,EAAM,QAGhC,GAAI,EAAM,OAAQ,CAChB,IAAM,EAA4B,CAChC,GAAG,EAAM,cACT,GAAI,EAAM,gBAAkB,CAAE,eAAgB,EAAM,eAAgB,CACrE,CACD,OAAO,EAAA,EAAkB,EAAM,OAAQ,EAAO,CAGhD,MAAO,EAAE,EACT,CAKI,GACJ,EACA,EACA,EAAuB,EAAE,GACZ,CACb,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAW,EAAK,SACtB,GAAI,GAAU,OAAQ,CACpB,IAAM,EAAc,CAAC,GAAG,EAAY,EAAK,IAAc,CACvD,GAAI,EAAS,KAAK,GAAS,EAAM,MAAQ,EAAU,CACjD,OAAO,EAET,IAAM,EAAS,EAAe,EAAU,EAAW,EAAY,CAC/D,GAAI,EAAO,OAAS,EAAG,OAAO,GAGlC,MAAO,EAAE,EAIL,EAAuB,GAA8B,CACzD,IAAM,EAAW,EAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CAC1C,EAAO,IAAI,IACb,EAAc,GAMlB,OALA,EAAS,QAAQ,GAAW,CAC1B,GAAe,IAAI,IACnB,EAAK,IAAI,EAAY,EACrB,CACF,EAAe,EAAc,MAAO,EAAK,CAAC,QAAQ,GAAO,EAAK,IAAI,EAAI,CAAC,CAChE,GAKH,EAAmB,GAAgB,CACnC,GAAO,IAAQ,EAAU,OAC3B,EAAK,SAAU,EAAI,EAIjB,EAAwB,GAAmB,CAC/C,EAAa,MAAQ,EACrB,EAAK,sBAAuB,EAAK,SAKnC,EAAa,CAAE,WADK,GAAgB,EAAQ,OAAO,WAAW,EAAI,CACvC,UAAS,CAAC,EAIrC,EAAA,EAAA,WACQ,EAAU,MAChB,GAAU,CACR,GAAI,CAAC,EAAQ,OACb,IAAM,EAAU,EAAoB,EAAO,CAC3C,EAAa,MAAQ,MAAM,KACzB,IAAI,IAAI,CAAC,GAAG,EAAa,MAAO,GAAG,EAAQ,CAAC,CAC7C,EACD,EAAA,EAAA,cAAe,EAAQ,OAAO,WAAW,EAAO,CAAC,EAEnD,CAAE,UAAW,GAAM,CACpB,EAED,EAAA,EAAA,eAAgB,EACd,EAAA,EAAA,cAAe,CACT,EAAU,QACZ,EAAa,MAAQ,MAAM,KAAK,EAAoB,EAAU,MAAM,CAAC,CACrE,EAAQ,OAAO,WAAW,EAAU,MAAM,GAE5C,EACF,iEAtLA,EAAA,SAfI,UAAJ,IAAI,EACH,QAAS,EAAA,MACT,gBAAe,EAAA,MACf,MAAO,EAAA,MACP,KAAMA,EAAAA,KACN,UAAWC,EAAAA,UACX,kBAAiBC,EAAAA,eACjB,sBAAqBC,EAAAA,kBACrB,SAAUC,EAAAA,SACV,kBAAiBC,EAAAA,eACjB,iBAAgBC,EAAAA,cAChB,OAAQC,EAAAA,OACR,cAAaC,EAAAA,WACb,iBAAc,EACd,wBAAsB"}
1
+ {"version":3,"file":"C_Menu.js","names":["mode","collapsed","collapsedWidth","collapsedIconSize","inverted","themeOverrides","dropdownProps","indent","rootIndent"],"sources":["../src/components/C_Menu/index.vue","../src/components/C_Menu/index.vue","../src/components/C_Menu/index.vue"],"sourcesContent":["/* unplugin-vue-components disabled */<!--\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\n *\n * 支持两种数据输入模式:\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\n *\n * @example 路由模式(推荐)\n * ```vue\n * <C_Menu\n * :routes=\"menuData\"\n * :label-formatter=\"$t\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * @example 原生模式\n * ```vue\n * <C_Menu\n * :options=\"nMenuOptions\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <NMenu\n ref=\"menuRef\"\n :options=\"mergedOptions\"\n :expanded-keys=\"expandedKeys\"\n :value=\"activeKey\"\n :mode=\"mode\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"collapsedWidth\"\n :collapsed-icon-size=\"collapsedIconSize\"\n :inverted=\"inverted\"\n :theme-overrides=\"themeOverrides\"\n :dropdown-props=\"dropdownProps\"\n :indent=\"indent\"\n :root-indent=\"rootIndent\"\n @update:value=\"handleMenuClick\"\n @update:expanded-keys=\"onExpandedKeysChange\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\n import {\n type MenuOption,\n type MenuInst,\n type DropdownProps,\n } from 'naive-ui/es'\n import {\n createMenuOptions,\n type RouteItem,\n type MenuAdapterConfig,\n } from '../_shared'\n\n defineOptions({ name: 'C_Menu' })\n\n const props = withDefaults(\n defineProps<{\n /**\n * 模式一:直传 NMenu 原生 MenuOption[]\n * 与 `routes` 二选一,`options` 优先级更高\n */\n options?: MenuOption[]\n /**\n * 模式二:传入路由数据,自动转换为 MenuOption[]\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\n */\n routes?: RouteItem[]\n /**\n * 适配器配置(仅 routes 模式生效)\n * 可自定义 key/label/icon 取值、过滤逻辑等\n */\n adapterConfig?: MenuAdapterConfig\n /**\n * 标签文本格式化函数(routes 模式的快捷方式)\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\n */\n labelFormatter?: (label: string) => string\n /** 当前激活的菜单 key */\n value?: string\n /** 菜单模式 */\n mode?: 'vertical' | 'horizontal'\n /** 是否折叠 */\n collapsed?: boolean\n /** 折叠宽度 */\n collapsedWidth?: number\n /** 折叠图标大小 */\n collapsedIconSize?: number\n /** 是否反色 */\n inverted?: boolean\n /** 主题覆盖 */\n themeOverrides?: Record<string, any>\n /** 缩进像素 */\n indent?: number\n /** 根缩进像素 */\n rootIndent?: number\n /** 下拉菜单属性(折叠模式) */\n dropdownProps?: DropdownProps\n }>(),\n {\n mode: 'vertical',\n collapsed: false,\n collapsedWidth: 64,\n collapsedIconSize: 22,\n inverted: false,\n indent: 24,\n rootIndent: 16,\n dropdownProps: () => ({\n placement: 'right-start' as const,\n trigger: 'hover' as const,\n arrowStyle: { color: 'var(--n-color)' },\n }),\n }\n )\n\n const emit = defineEmits<{\n /** 菜单项被选中 */\n select: [key: string]\n /** 展开项变化 */\n 'update:expandedKeys': [keys: string[]]\n }>()\n\n const menuRef = ref<MenuInst | null>(null)\n const expandedKeys = ref<string[]>([])\n\n const activeKey = computed(() => props.value ?? '')\n\n // ====== 数据合成 ======\n\n /** 合并后的最终 MenuOption[] */\n const mergedOptions = computed<MenuOption[]>(() => {\n // 优先使用 options 直传模式\n if (props.options) return props.options\n\n // routes 模式 → 适配器转换\n if (props.routes) {\n const config: MenuAdapterConfig = {\n ...props.adapterConfig,\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\n }\n return createMenuOptions(props.routes, config)\n }\n\n return []\n })\n\n // ====== 展开逻辑 ======\n\n /** 从 MenuOption[] 中查找所有祖先 key */\n const findParentKeys = (\n items: MenuOption[],\n targetKey: string,\n parentKeys: string[] = []\n ): string[] => {\n for (const item of items) {\n const children = item.children as MenuOption[] | undefined\n if (children?.length) {\n const currentKeys = [...parentKeys, item.key as string]\n if (children.some(child => child.key === targetKey)) {\n return currentKeys\n }\n const result = findParentKeys(children, targetKey, currentKeys)\n if (result.length > 0) return result\n }\n }\n return []\n }\n\n /** 通过路径段推导展开 key + 查找祖先 key */\n const computeExpandedKeys = (path: string): Set<string> => {\n const segments = path.split('/').filter(Boolean)\n const keys = new Set<string>()\n let currentPath = ''\n segments.forEach(segment => {\n currentPath += `/${segment}`\n keys.add(currentPath)\n })\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\n return keys\n }\n\n // ====== 事件处理 ======\n\n const handleMenuClick = (key: string) => {\n if (key && key !== activeKey.value) {\n emit('select', key)\n }\n }\n\n const onExpandedKeysChange = (keys: string[]) => {\n expandedKeys.value = keys\n emit('update:expandedKeys', keys)\n }\n\n // ====== 暴露方法 ======\n const showOption = (key: string) => menuRef.value?.showOption(key)\n defineExpose({ showOption, menuRef })\n\n // ====== 路由跟踪 ======\n\n watch(\n () => activeKey.value,\n newKey => {\n if (!newKey) return\n const newKeys = computeExpandedKeys(newKey)\n expandedKeys.value = Array.from(\n new Set([...expandedKeys.value, ...newKeys])\n )\n nextTick(() => menuRef.value?.showOption(newKey))\n },\n { immediate: true }\n )\n\n onMounted(() => {\n nextTick(() => {\n if (activeKey.value) {\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\n menuRef.value?.showOption(activeKey.value)\n }\n })\n })\n</script>\n","/* unplugin-vue-components disabled */<!--\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\n *\n * 支持两种数据输入模式:\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\n *\n * @example 路由模式(推荐)\n * ```vue\n * <C_Menu\n * :routes=\"menuData\"\n * :label-formatter=\"$t\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * @example 原生模式\n * ```vue\n * <C_Menu\n * :options=\"nMenuOptions\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <NMenu\n ref=\"menuRef\"\n :options=\"mergedOptions\"\n :expanded-keys=\"expandedKeys\"\n :value=\"activeKey\"\n :mode=\"mode\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"collapsedWidth\"\n :collapsed-icon-size=\"collapsedIconSize\"\n :inverted=\"inverted\"\n :theme-overrides=\"themeOverrides\"\n :dropdown-props=\"dropdownProps\"\n :indent=\"indent\"\n :root-indent=\"rootIndent\"\n @update:value=\"handleMenuClick\"\n @update:expanded-keys=\"onExpandedKeysChange\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\n import {\n type MenuOption,\n type MenuInst,\n type DropdownProps,\n } from 'naive-ui/es'\n import {\n createMenuOptions,\n type RouteItem,\n type MenuAdapterConfig,\n } from '../_shared'\n\n defineOptions({ name: 'C_Menu' })\n\n const props = withDefaults(\n defineProps<{\n /**\n * 模式一:直传 NMenu 原生 MenuOption[]\n * 与 `routes` 二选一,`options` 优先级更高\n */\n options?: MenuOption[]\n /**\n * 模式二:传入路由数据,自动转换为 MenuOption[]\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\n */\n routes?: RouteItem[]\n /**\n * 适配器配置(仅 routes 模式生效)\n * 可自定义 key/label/icon 取值、过滤逻辑等\n */\n adapterConfig?: MenuAdapterConfig\n /**\n * 标签文本格式化函数(routes 模式的快捷方式)\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\n */\n labelFormatter?: (label: string) => string\n /** 当前激活的菜单 key */\n value?: string\n /** 菜单模式 */\n mode?: 'vertical' | 'horizontal'\n /** 是否折叠 */\n collapsed?: boolean\n /** 折叠宽度 */\n collapsedWidth?: number\n /** 折叠图标大小 */\n collapsedIconSize?: number\n /** 是否反色 */\n inverted?: boolean\n /** 主题覆盖 */\n themeOverrides?: Record<string, any>\n /** 缩进像素 */\n indent?: number\n /** 根缩进像素 */\n rootIndent?: number\n /** 下拉菜单属性(折叠模式) */\n dropdownProps?: DropdownProps\n }>(),\n {\n mode: 'vertical',\n collapsed: false,\n collapsedWidth: 64,\n collapsedIconSize: 22,\n inverted: false,\n indent: 24,\n rootIndent: 16,\n dropdownProps: () => ({\n placement: 'right-start' as const,\n trigger: 'hover' as const,\n arrowStyle: { color: 'var(--n-color)' },\n }),\n }\n )\n\n const emit = defineEmits<{\n /** 菜单项被选中 */\n select: [key: string]\n /** 展开项变化 */\n 'update:expandedKeys': [keys: string[]]\n }>()\n\n const menuRef = ref<MenuInst | null>(null)\n const expandedKeys = ref<string[]>([])\n\n const activeKey = computed(() => props.value ?? '')\n\n // ====== 数据合成 ======\n\n /** 合并后的最终 MenuOption[] */\n const mergedOptions = computed<MenuOption[]>(() => {\n // 优先使用 options 直传模式\n if (props.options) return props.options\n\n // routes 模式 → 适配器转换\n if (props.routes) {\n const config: MenuAdapterConfig = {\n ...props.adapterConfig,\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\n }\n return createMenuOptions(props.routes, config)\n }\n\n return []\n })\n\n // ====== 展开逻辑 ======\n\n /** 从 MenuOption[] 中查找所有祖先 key */\n const findParentKeys = (\n items: MenuOption[],\n targetKey: string,\n parentKeys: string[] = []\n ): string[] => {\n for (const item of items) {\n const children = item.children as MenuOption[] | undefined\n if (children?.length) {\n const currentKeys = [...parentKeys, item.key as string]\n if (children.some(child => child.key === targetKey)) {\n return currentKeys\n }\n const result = findParentKeys(children, targetKey, currentKeys)\n if (result.length > 0) return result\n }\n }\n return []\n }\n\n /** 通过路径段推导展开 key + 查找祖先 key */\n const computeExpandedKeys = (path: string): Set<string> => {\n const segments = path.split('/').filter(Boolean)\n const keys = new Set<string>()\n let currentPath = ''\n segments.forEach(segment => {\n currentPath += `/${segment}`\n keys.add(currentPath)\n })\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\n return keys\n }\n\n // ====== 事件处理 ======\n\n const handleMenuClick = (key: string) => {\n if (key && key !== activeKey.value) {\n emit('select', key)\n }\n }\n\n const onExpandedKeysChange = (keys: string[]) => {\n expandedKeys.value = keys\n emit('update:expandedKeys', keys)\n }\n\n // ====== 暴露方法 ======\n const showOption = (key: string) => menuRef.value?.showOption(key)\n defineExpose({ showOption, menuRef })\n\n // ====== 路由跟踪 ======\n\n watch(\n () => activeKey.value,\n newKey => {\n if (!newKey) return\n const newKeys = computeExpandedKeys(newKey)\n expandedKeys.value = Array.from(\n new Set([...expandedKeys.value, ...newKeys])\n )\n nextTick(() => menuRef.value?.showOption(newKey))\n },\n { immediate: true }\n )\n\n onMounted(() => {\n nextTick(() => {\n if (activeKey.value) {\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\n menuRef.value?.showOption(activeKey.value)\n }\n })\n })\n</script>\n","<!--\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\n *\n * 支持两种数据输入模式:\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\n *\n * @example 路由模式(推荐)\n * ```vue\n * <C_Menu\n * :routes=\"menuData\"\n * :label-formatter=\"$t\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * @example 原生模式\n * ```vue\n * <C_Menu\n * :options=\"nMenuOptions\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <NMenu\n ref=\"menuRef\"\n :options=\"mergedOptions\"\n :expanded-keys=\"expandedKeys\"\n :value=\"activeKey\"\n :mode=\"mode\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"collapsedWidth\"\n :collapsed-icon-size=\"collapsedIconSize\"\n :inverted=\"inverted\"\n :theme-overrides=\"themeOverrides\"\n :dropdown-props=\"dropdownProps\"\n :indent=\"indent\"\n :root-indent=\"rootIndent\"\n @update:value=\"handleMenuClick\"\n @update:expanded-keys=\"onExpandedKeysChange\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\n import {\n type MenuOption,\n type MenuInst,\n type DropdownProps,\n } from 'naive-ui/es'\n import {\n createMenuOptions,\n type RouteItem,\n type MenuAdapterConfig,\n } from '../_shared'\n\n defineOptions({ name: 'C_Menu' })\n\n const props = withDefaults(\n defineProps<{\n /**\n * 模式一:直传 NMenu 原生 MenuOption[]\n * 与 `routes` 二选一,`options` 优先级更高\n */\n options?: MenuOption[]\n /**\n * 模式二:传入路由数据,自动转换为 MenuOption[]\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\n */\n routes?: RouteItem[]\n /**\n * 适配器配置(仅 routes 模式生效)\n * 可自定义 key/label/icon 取值、过滤逻辑等\n */\n adapterConfig?: MenuAdapterConfig\n /**\n * 标签文本格式化函数(routes 模式的快捷方式)\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\n */\n labelFormatter?: (label: string) => string\n /** 当前激活的菜单 key */\n value?: string\n /** 菜单模式 */\n mode?: 'vertical' | 'horizontal'\n /** 是否折叠 */\n collapsed?: boolean\n /** 折叠宽度 */\n collapsedWidth?: number\n /** 折叠图标大小 */\n collapsedIconSize?: number\n /** 是否反色 */\n inverted?: boolean\n /** 主题覆盖 */\n themeOverrides?: Record<string, any>\n /** 缩进像素 */\n indent?: number\n /** 根缩进像素 */\n rootIndent?: number\n /** 下拉菜单属性(折叠模式) */\n dropdownProps?: DropdownProps\n }>(),\n {\n mode: 'vertical',\n collapsed: false,\n collapsedWidth: 64,\n collapsedIconSize: 22,\n inverted: false,\n indent: 24,\n rootIndent: 16,\n dropdownProps: () => ({\n placement: 'right-start' as const,\n trigger: 'hover' as const,\n arrowStyle: { color: 'var(--n-color)' },\n }),\n }\n )\n\n const emit = defineEmits<{\n /** 菜单项被选中 */\n select: [key: string]\n /** 展开项变化 */\n 'update:expandedKeys': [keys: string[]]\n }>()\n\n const menuRef = ref<MenuInst | null>(null)\n const expandedKeys = ref<string[]>([])\n\n const activeKey = computed(() => props.value ?? '')\n\n // ====== 数据合成 ======\n\n /** 合并后的最终 MenuOption[] */\n const mergedOptions = computed<MenuOption[]>(() => {\n // 优先使用 options 直传模式\n if (props.options) return props.options\n\n // routes 模式 → 适配器转换\n if (props.routes) {\n const config: MenuAdapterConfig = {\n ...props.adapterConfig,\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\n }\n return createMenuOptions(props.routes, config)\n }\n\n return []\n })\n\n // ====== 展开逻辑 ======\n\n /** 从 MenuOption[] 中查找所有祖先 key */\n const findParentKeys = (\n items: MenuOption[],\n targetKey: string,\n parentKeys: string[] = []\n ): string[] => {\n for (const item of items) {\n const children = item.children as MenuOption[] | undefined\n if (children?.length) {\n const currentKeys = [...parentKeys, item.key as string]\n if (children.some(child => child.key === targetKey)) {\n return currentKeys\n }\n const result = findParentKeys(children, targetKey, currentKeys)\n if (result.length > 0) return result\n }\n }\n return []\n }\n\n /** 通过路径段推导展开 key + 查找祖先 key */\n const computeExpandedKeys = (path: string): Set<string> => {\n const segments = path.split('/').filter(Boolean)\n const keys = new Set<string>()\n let currentPath = ''\n segments.forEach(segment => {\n currentPath += `/${segment}`\n keys.add(currentPath)\n })\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\n return keys\n }\n\n // ====== 事件处理 ======\n\n const handleMenuClick = (key: string) => {\n if (key && key !== activeKey.value) {\n emit('select', key)\n }\n }\n\n const onExpandedKeysChange = (keys: string[]) => {\n expandedKeys.value = keys\n emit('update:expandedKeys', keys)\n }\n\n // ====== 暴露方法 ======\n const showOption = (key: string) => menuRef.value?.showOption(key)\n defineExpose({ showOption, menuRef })\n\n // ====== 路由跟踪 ======\n\n watch(\n () => activeKey.value,\n newKey => {\n if (!newKey) return\n const newKeys = computeExpandedKeys(newKey)\n expandedKeys.value = Array.from(\n new Set([...expandedKeys.value, ...newKeys])\n )\n nextTick(() => menuRef.value?.showOption(newKey))\n },\n { immediate: true }\n )\n\n onMounted(() => {\n nextTick(() => {\n if (activeKey.value) {\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\n menuRef.value?.showOption(activeKey.value)\n }\n })\n })\n</script>\n"],"mappings":"gnBE6DE,IAAM,EAAQ,EA2DR,EAAO,EAOP,GAAA,EAAA,EAAA,KAA+B,KAAI,CACnC,GAAA,EAAA,EAAA,KAA6B,EAAE,CAAA,CAE/B,GAAA,EAAA,EAAA,cAA2B,EAAM,OAAS,GAAE,CAK5C,GAAA,EAAA,EAAA,cAA6C,CAEjD,GAAI,EAAM,QAAS,OAAO,EAAM,QAGhC,GAAI,EAAM,OAAQ,CAChB,IAAM,EAA4B,CAChC,GAAG,EAAM,cACT,GAAI,EAAM,gBAAkB,CAAE,eAAgB,EAAM,eAAgB,CACtE,CACA,OAAO,EAAA,EAAkB,EAAM,OAAQ,EAAM,CAG/C,MAAO,EAAC,EACT,CAKK,GACJ,EACA,EACA,EAAuB,EAAC,GACX,CACb,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAW,EAAK,SACtB,GAAI,GAAU,OAAQ,CACpB,IAAM,EAAc,CAAC,GAAG,EAAY,EAAK,IAAa,CACtD,GAAI,EAAS,KAAK,GAAS,EAAM,MAAQ,EAAU,CACjD,OAAO,EAET,IAAM,EAAS,EAAe,EAAU,EAAW,EAAW,CAC9D,GAAI,EAAO,OAAS,EAAG,OAAO,GAGlC,MAAO,EAAC,EAIJ,EAAuB,GAA8B,CACzD,IAAM,EAAW,EAAK,MAAM,IAAI,CAAC,OAAO,QAAO,CACzC,EAAO,IAAI,IACb,EAAc,GAMlB,OALA,EAAS,QAAQ,GAAW,CAC1B,GAAe,IAAI,IACnB,EAAK,IAAI,EAAW,EACrB,CACD,EAAe,EAAc,MAAO,EAAK,CAAC,QAAQ,GAAO,EAAK,IAAI,EAAI,CAAA,CAC/D,GAKH,EAAmB,GAAgB,CACnC,GAAO,IAAQ,EAAU,OAC3B,EAAK,SAAU,EAAG,EAIhB,EAAwB,GAAmB,CAC/C,EAAa,MAAQ,EACrB,EAAK,sBAAuB,EAAI,SAKlC,EAAa,CAAE,WADK,GAAgB,EAAQ,OAAO,WAAW,EAAG,CACtC,UAAS,CAAA,EAIpC,EAAA,EAAA,WACQ,EAAU,MAChB,GAAU,CACR,GAAI,CAAC,EAAQ,OACb,IAAM,EAAU,EAAoB,EAAM,CAC1C,EAAa,MAAQ,MAAM,KACzB,IAAI,IAAI,CAAC,GAAG,EAAa,MAAO,GAAG,EAAQ,CAAA,CAC7C,EACA,EAAA,EAAA,cAAe,EAAQ,OAAO,WAAW,EAAO,CAAA,EAElD,CAAE,UAAW,GAAK,CACpB,EAEA,EAAA,EAAA,eAAgB,EACd,EAAA,EAAA,cAAe,CACT,EAAU,QACZ,EAAa,MAAQ,MAAM,KAAK,EAAoB,EAAU,MAAM,CAAA,CACpE,EAAQ,OAAO,WAAW,EAAU,MAAK,GAE5C,EACF,iEAtLC,EAAA,SAfI,UAAJ,IAAI,EACH,QAAS,EAAA,MACT,gBAAe,EAAA,MACf,MAAO,EAAA,MACP,KAAMA,EAAAA,KACN,UAAWC,EAAAA,UACX,kBAAiBC,EAAAA,eACjB,sBAAqBC,EAAAA,kBACrB,SAAUC,EAAAA,SACV,kBAAiBC,EAAAA,eACjB,iBAAgBC,EAAAA,cAChB,OAAQC,EAAAA,OACR,cAAaC,EAAAA,WACb,iBAAc,EACd,wBAAsB"}
@@ -1 +1 @@
1
- {"version":3,"file":"C_Menu2.js","names":["mode","collapsed","collapsedWidth","collapsedIconSize","inverted","themeOverrides","dropdownProps","indent","rootIndent"],"sources":["../src/components/C_Menu/index.vue","../src/components/C_Menu/index.vue","../src/components/C_Menu/index.vue"],"sourcesContent":["/* unplugin-vue-components disabled */<!--\r\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\r\n *\r\n * 支持两种数据输入模式:\r\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\r\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\r\n *\r\n * @example 路由模式(推荐)\r\n * ```vue\r\n * <C_Menu\r\n * :routes=\"menuData\"\r\n * :label-formatter=\"$t\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * @example 原生模式\r\n * ```vue\r\n * <C_Menu\r\n * :options=\"nMenuOptions\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NMenu\r\n ref=\"menuRef\"\r\n :options=\"mergedOptions\"\r\n :expanded-keys=\"expandedKeys\"\r\n :value=\"activeKey\"\r\n :mode=\"mode\"\r\n :collapsed=\"collapsed\"\r\n :collapsed-width=\"collapsedWidth\"\r\n :collapsed-icon-size=\"collapsedIconSize\"\r\n :inverted=\"inverted\"\r\n :theme-overrides=\"themeOverrides\"\r\n :dropdown-props=\"dropdownProps\"\r\n :indent=\"indent\"\r\n :root-indent=\"rootIndent\"\r\n @update:value=\"handleMenuClick\"\r\n @update:expanded-keys=\"onExpandedKeysChange\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\r\n import {\r\n type MenuOption,\r\n type MenuInst,\r\n type DropdownProps,\r\n } from 'naive-ui/es'\r\n import {\r\n createMenuOptions,\r\n type RouteItem,\r\n type MenuAdapterConfig,\r\n } from '../_shared'\r\n\r\n defineOptions({ name: 'C_Menu' })\r\n\r\n const props = withDefaults(\r\n defineProps<{\r\n /**\r\n * 模式一:直传 NMenu 原生 MenuOption[]\r\n * 与 `routes` 二选一,`options` 优先级更高\r\n */\r\n options?: MenuOption[]\r\n /**\r\n * 模式二:传入路由数据,自动转换为 MenuOption[]\r\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\r\n */\r\n routes?: RouteItem[]\r\n /**\r\n * 适配器配置(仅 routes 模式生效)\r\n * 可自定义 key/label/icon 取值、过滤逻辑等\r\n */\r\n adapterConfig?: MenuAdapterConfig\r\n /**\r\n * 标签文本格式化函数(routes 模式的快捷方式)\r\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\r\n */\r\n labelFormatter?: (label: string) => string\r\n /** 当前激活的菜单 key */\r\n value?: string\r\n /** 菜单模式 */\r\n mode?: 'vertical' | 'horizontal'\r\n /** 是否折叠 */\r\n collapsed?: boolean\r\n /** 折叠宽度 */\r\n collapsedWidth?: number\r\n /** 折叠图标大小 */\r\n collapsedIconSize?: number\r\n /** 是否反色 */\r\n inverted?: boolean\r\n /** 主题覆盖 */\r\n themeOverrides?: Record<string, any>\r\n /** 缩进像素 */\r\n indent?: number\r\n /** 根缩进像素 */\r\n rootIndent?: number\r\n /** 下拉菜单属性(折叠模式) */\r\n dropdownProps?: DropdownProps\r\n }>(),\r\n {\r\n mode: 'vertical',\r\n collapsed: false,\r\n collapsedWidth: 64,\r\n collapsedIconSize: 22,\r\n inverted: false,\r\n indent: 24,\r\n rootIndent: 16,\r\n dropdownProps: () => ({\r\n placement: 'right-start' as const,\r\n trigger: 'hover' as const,\r\n arrowStyle: { color: 'var(--n-color)' },\r\n }),\r\n }\r\n )\r\n\r\n const emit = defineEmits<{\r\n /** 菜单项被选中 */\r\n select: [key: string]\r\n /** 展开项变化 */\r\n 'update:expandedKeys': [keys: string[]]\r\n }>()\r\n\r\n const menuRef = ref<MenuInst | null>(null)\r\n const expandedKeys = ref<string[]>([])\r\n\r\n const activeKey = computed(() => props.value ?? '')\r\n\r\n // ====== 数据合成 ======\r\n\r\n /** 合并后的最终 MenuOption[] */\r\n const mergedOptions = computed<MenuOption[]>(() => {\r\n // 优先使用 options 直传模式\r\n if (props.options) return props.options\r\n\r\n // routes 模式 → 适配器转换\r\n if (props.routes) {\r\n const config: MenuAdapterConfig = {\r\n ...props.adapterConfig,\r\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\r\n }\r\n return createMenuOptions(props.routes, config)\r\n }\r\n\r\n return []\r\n })\r\n\r\n // ====== 展开逻辑 ======\r\n\r\n /** 从 MenuOption[] 中查找所有祖先 key */\r\n const findParentKeys = (\r\n items: MenuOption[],\r\n targetKey: string,\r\n parentKeys: string[] = []\r\n ): string[] => {\r\n for (const item of items) {\r\n const children = item.children as MenuOption[] | undefined\r\n if (children?.length) {\r\n const currentKeys = [...parentKeys, item.key as string]\r\n if (children.some(child => child.key === targetKey)) {\r\n return currentKeys\r\n }\r\n const result = findParentKeys(children, targetKey, currentKeys)\r\n if (result.length > 0) return result\r\n }\r\n }\r\n return []\r\n }\r\n\r\n /** 通过路径段推导展开 key + 查找祖先 key */\r\n const computeExpandedKeys = (path: string): Set<string> => {\r\n const segments = path.split('/').filter(Boolean)\r\n const keys = new Set<string>()\r\n let currentPath = ''\r\n segments.forEach(segment => {\r\n currentPath += `/${segment}`\r\n keys.add(currentPath)\r\n })\r\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\r\n return keys\r\n }\r\n\r\n // ====== 事件处理 ======\r\n\r\n const handleMenuClick = (key: string) => {\r\n if (key && key !== activeKey.value) {\r\n emit('select', key)\r\n }\r\n }\r\n\r\n const onExpandedKeysChange = (keys: string[]) => {\r\n expandedKeys.value = keys\r\n emit('update:expandedKeys', keys)\r\n }\r\n\r\n // ====== 暴露方法 ======\r\n const showOption = (key: string) => menuRef.value?.showOption(key)\r\n defineExpose({ showOption, menuRef })\r\n\r\n // ====== 路由跟踪 ======\r\n\r\n watch(\r\n () => activeKey.value,\r\n newKey => {\r\n if (!newKey) return\r\n const newKeys = computeExpandedKeys(newKey)\r\n expandedKeys.value = Array.from(\r\n new Set([...expandedKeys.value, ...newKeys])\r\n )\r\n nextTick(() => menuRef.value?.showOption(newKey))\r\n },\r\n { immediate: true }\r\n )\r\n\r\n onMounted(() => {\r\n nextTick(() => {\r\n if (activeKey.value) {\r\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\r\n menuRef.value?.showOption(activeKey.value)\r\n }\r\n })\r\n })\r\n</script>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\r\n *\r\n * 支持两种数据输入模式:\r\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\r\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\r\n *\r\n * @example 路由模式(推荐)\r\n * ```vue\r\n * <C_Menu\r\n * :routes=\"menuData\"\r\n * :label-formatter=\"$t\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * @example 原生模式\r\n * ```vue\r\n * <C_Menu\r\n * :options=\"nMenuOptions\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NMenu\r\n ref=\"menuRef\"\r\n :options=\"mergedOptions\"\r\n :expanded-keys=\"expandedKeys\"\r\n :value=\"activeKey\"\r\n :mode=\"mode\"\r\n :collapsed=\"collapsed\"\r\n :collapsed-width=\"collapsedWidth\"\r\n :collapsed-icon-size=\"collapsedIconSize\"\r\n :inverted=\"inverted\"\r\n :theme-overrides=\"themeOverrides\"\r\n :dropdown-props=\"dropdownProps\"\r\n :indent=\"indent\"\r\n :root-indent=\"rootIndent\"\r\n @update:value=\"handleMenuClick\"\r\n @update:expanded-keys=\"onExpandedKeysChange\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\r\n import {\r\n type MenuOption,\r\n type MenuInst,\r\n type DropdownProps,\r\n } from 'naive-ui/es'\r\n import {\r\n createMenuOptions,\r\n type RouteItem,\r\n type MenuAdapterConfig,\r\n } from '../_shared'\r\n\r\n defineOptions({ name: 'C_Menu' })\r\n\r\n const props = withDefaults(\r\n defineProps<{\r\n /**\r\n * 模式一:直传 NMenu 原生 MenuOption[]\r\n * 与 `routes` 二选一,`options` 优先级更高\r\n */\r\n options?: MenuOption[]\r\n /**\r\n * 模式二:传入路由数据,自动转换为 MenuOption[]\r\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\r\n */\r\n routes?: RouteItem[]\r\n /**\r\n * 适配器配置(仅 routes 模式生效)\r\n * 可自定义 key/label/icon 取值、过滤逻辑等\r\n */\r\n adapterConfig?: MenuAdapterConfig\r\n /**\r\n * 标签文本格式化函数(routes 模式的快捷方式)\r\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\r\n */\r\n labelFormatter?: (label: string) => string\r\n /** 当前激活的菜单 key */\r\n value?: string\r\n /** 菜单模式 */\r\n mode?: 'vertical' | 'horizontal'\r\n /** 是否折叠 */\r\n collapsed?: boolean\r\n /** 折叠宽度 */\r\n collapsedWidth?: number\r\n /** 折叠图标大小 */\r\n collapsedIconSize?: number\r\n /** 是否反色 */\r\n inverted?: boolean\r\n /** 主题覆盖 */\r\n themeOverrides?: Record<string, any>\r\n /** 缩进像素 */\r\n indent?: number\r\n /** 根缩进像素 */\r\n rootIndent?: number\r\n /** 下拉菜单属性(折叠模式) */\r\n dropdownProps?: DropdownProps\r\n }>(),\r\n {\r\n mode: 'vertical',\r\n collapsed: false,\r\n collapsedWidth: 64,\r\n collapsedIconSize: 22,\r\n inverted: false,\r\n indent: 24,\r\n rootIndent: 16,\r\n dropdownProps: () => ({\r\n placement: 'right-start' as const,\r\n trigger: 'hover' as const,\r\n arrowStyle: { color: 'var(--n-color)' },\r\n }),\r\n }\r\n )\r\n\r\n const emit = defineEmits<{\r\n /** 菜单项被选中 */\r\n select: [key: string]\r\n /** 展开项变化 */\r\n 'update:expandedKeys': [keys: string[]]\r\n }>()\r\n\r\n const menuRef = ref<MenuInst | null>(null)\r\n const expandedKeys = ref<string[]>([])\r\n\r\n const activeKey = computed(() => props.value ?? '')\r\n\r\n // ====== 数据合成 ======\r\n\r\n /** 合并后的最终 MenuOption[] */\r\n const mergedOptions = computed<MenuOption[]>(() => {\r\n // 优先使用 options 直传模式\r\n if (props.options) return props.options\r\n\r\n // routes 模式 → 适配器转换\r\n if (props.routes) {\r\n const config: MenuAdapterConfig = {\r\n ...props.adapterConfig,\r\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\r\n }\r\n return createMenuOptions(props.routes, config)\r\n }\r\n\r\n return []\r\n })\r\n\r\n // ====== 展开逻辑 ======\r\n\r\n /** 从 MenuOption[] 中查找所有祖先 key */\r\n const findParentKeys = (\r\n items: MenuOption[],\r\n targetKey: string,\r\n parentKeys: string[] = []\r\n ): string[] => {\r\n for (const item of items) {\r\n const children = item.children as MenuOption[] | undefined\r\n if (children?.length) {\r\n const currentKeys = [...parentKeys, item.key as string]\r\n if (children.some(child => child.key === targetKey)) {\r\n return currentKeys\r\n }\r\n const result = findParentKeys(children, targetKey, currentKeys)\r\n if (result.length > 0) return result\r\n }\r\n }\r\n return []\r\n }\r\n\r\n /** 通过路径段推导展开 key + 查找祖先 key */\r\n const computeExpandedKeys = (path: string): Set<string> => {\r\n const segments = path.split('/').filter(Boolean)\r\n const keys = new Set<string>()\r\n let currentPath = ''\r\n segments.forEach(segment => {\r\n currentPath += `/${segment}`\r\n keys.add(currentPath)\r\n })\r\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\r\n return keys\r\n }\r\n\r\n // ====== 事件处理 ======\r\n\r\n const handleMenuClick = (key: string) => {\r\n if (key && key !== activeKey.value) {\r\n emit('select', key)\r\n }\r\n }\r\n\r\n const onExpandedKeysChange = (keys: string[]) => {\r\n expandedKeys.value = keys\r\n emit('update:expandedKeys', keys)\r\n }\r\n\r\n // ====== 暴露方法 ======\r\n const showOption = (key: string) => menuRef.value?.showOption(key)\r\n defineExpose({ showOption, menuRef })\r\n\r\n // ====== 路由跟踪 ======\r\n\r\n watch(\r\n () => activeKey.value,\r\n newKey => {\r\n if (!newKey) return\r\n const newKeys = computeExpandedKeys(newKey)\r\n expandedKeys.value = Array.from(\r\n new Set([...expandedKeys.value, ...newKeys])\r\n )\r\n nextTick(() => menuRef.value?.showOption(newKey))\r\n },\r\n { immediate: true }\r\n )\r\n\r\n onMounted(() => {\r\n nextTick(() => {\r\n if (activeKey.value) {\r\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\r\n menuRef.value?.showOption(activeKey.value)\r\n }\r\n })\r\n })\r\n</script>\r\n","<!--\r\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\r\n *\r\n * 支持两种数据输入模式:\r\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\r\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\r\n *\r\n * @example 路由模式(推荐)\r\n * ```vue\r\n * <C_Menu\r\n * :routes=\"menuData\"\r\n * :label-formatter=\"$t\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * @example 原生模式\r\n * ```vue\r\n * <C_Menu\r\n * :options=\"nMenuOptions\"\r\n * @select=\"router.push\"\r\n * />\r\n * ```\r\n *\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NMenu\r\n ref=\"menuRef\"\r\n :options=\"mergedOptions\"\r\n :expanded-keys=\"expandedKeys\"\r\n :value=\"activeKey\"\r\n :mode=\"mode\"\r\n :collapsed=\"collapsed\"\r\n :collapsed-width=\"collapsedWidth\"\r\n :collapsed-icon-size=\"collapsedIconSize\"\r\n :inverted=\"inverted\"\r\n :theme-overrides=\"themeOverrides\"\r\n :dropdown-props=\"dropdownProps\"\r\n :indent=\"indent\"\r\n :root-indent=\"rootIndent\"\r\n @update:value=\"handleMenuClick\"\r\n @update:expanded-keys=\"onExpandedKeysChange\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\r\n import {\r\n type MenuOption,\r\n type MenuInst,\r\n type DropdownProps,\r\n } from 'naive-ui/es'\r\n import {\r\n createMenuOptions,\r\n type RouteItem,\r\n type MenuAdapterConfig,\r\n } from '../_shared'\r\n\r\n defineOptions({ name: 'C_Menu' })\r\n\r\n const props = withDefaults(\r\n defineProps<{\r\n /**\r\n * 模式一:直传 NMenu 原生 MenuOption[]\r\n * 与 `routes` 二选一,`options` 优先级更高\r\n */\r\n options?: MenuOption[]\r\n /**\r\n * 模式二:传入路由数据,自动转换为 MenuOption[]\r\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\r\n */\r\n routes?: RouteItem[]\r\n /**\r\n * 适配器配置(仅 routes 模式生效)\r\n * 可自定义 key/label/icon 取值、过滤逻辑等\r\n */\r\n adapterConfig?: MenuAdapterConfig\r\n /**\r\n * 标签文本格式化函数(routes 模式的快捷方式)\r\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\r\n */\r\n labelFormatter?: (label: string) => string\r\n /** 当前激活的菜单 key */\r\n value?: string\r\n /** 菜单模式 */\r\n mode?: 'vertical' | 'horizontal'\r\n /** 是否折叠 */\r\n collapsed?: boolean\r\n /** 折叠宽度 */\r\n collapsedWidth?: number\r\n /** 折叠图标大小 */\r\n collapsedIconSize?: number\r\n /** 是否反色 */\r\n inverted?: boolean\r\n /** 主题覆盖 */\r\n themeOverrides?: Record<string, any>\r\n /** 缩进像素 */\r\n indent?: number\r\n /** 根缩进像素 */\r\n rootIndent?: number\r\n /** 下拉菜单属性(折叠模式) */\r\n dropdownProps?: DropdownProps\r\n }>(),\r\n {\r\n mode: 'vertical',\r\n collapsed: false,\r\n collapsedWidth: 64,\r\n collapsedIconSize: 22,\r\n inverted: false,\r\n indent: 24,\r\n rootIndent: 16,\r\n dropdownProps: () => ({\r\n placement: 'right-start' as const,\r\n trigger: 'hover' as const,\r\n arrowStyle: { color: 'var(--n-color)' },\r\n }),\r\n }\r\n )\r\n\r\n const emit = defineEmits<{\r\n /** 菜单项被选中 */\r\n select: [key: string]\r\n /** 展开项变化 */\r\n 'update:expandedKeys': [keys: string[]]\r\n }>()\r\n\r\n const menuRef = ref<MenuInst | null>(null)\r\n const expandedKeys = ref<string[]>([])\r\n\r\n const activeKey = computed(() => props.value ?? '')\r\n\r\n // ====== 数据合成 ======\r\n\r\n /** 合并后的最终 MenuOption[] */\r\n const mergedOptions = computed<MenuOption[]>(() => {\r\n // 优先使用 options 直传模式\r\n if (props.options) return props.options\r\n\r\n // routes 模式 → 适配器转换\r\n if (props.routes) {\r\n const config: MenuAdapterConfig = {\r\n ...props.adapterConfig,\r\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\r\n }\r\n return createMenuOptions(props.routes, config)\r\n }\r\n\r\n return []\r\n })\r\n\r\n // ====== 展开逻辑 ======\r\n\r\n /** 从 MenuOption[] 中查找所有祖先 key */\r\n const findParentKeys = (\r\n items: MenuOption[],\r\n targetKey: string,\r\n parentKeys: string[] = []\r\n ): string[] => {\r\n for (const item of items) {\r\n const children = item.children as MenuOption[] | undefined\r\n if (children?.length) {\r\n const currentKeys = [...parentKeys, item.key as string]\r\n if (children.some(child => child.key === targetKey)) {\r\n return currentKeys\r\n }\r\n const result = findParentKeys(children, targetKey, currentKeys)\r\n if (result.length > 0) return result\r\n }\r\n }\r\n return []\r\n }\r\n\r\n /** 通过路径段推导展开 key + 查找祖先 key */\r\n const computeExpandedKeys = (path: string): Set<string> => {\r\n const segments = path.split('/').filter(Boolean)\r\n const keys = new Set<string>()\r\n let currentPath = ''\r\n segments.forEach(segment => {\r\n currentPath += `/${segment}`\r\n keys.add(currentPath)\r\n })\r\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\r\n return keys\r\n }\r\n\r\n // ====== 事件处理 ======\r\n\r\n const handleMenuClick = (key: string) => {\r\n if (key && key !== activeKey.value) {\r\n emit('select', key)\r\n }\r\n }\r\n\r\n const onExpandedKeysChange = (keys: string[]) => {\r\n expandedKeys.value = keys\r\n emit('update:expandedKeys', keys)\r\n }\r\n\r\n // ====== 暴露方法 ======\r\n const showOption = (key: string) => menuRef.value?.showOption(key)\r\n defineExpose({ showOption, menuRef })\r\n\r\n // ====== 路由跟踪 ======\r\n\r\n watch(\r\n () => activeKey.value,\r\n newKey => {\r\n if (!newKey) return\r\n const newKeys = computeExpandedKeys(newKey)\r\n expandedKeys.value = Array.from(\r\n new Set([...expandedKeys.value, ...newKeys])\r\n )\r\n nextTick(() => menuRef.value?.showOption(newKey))\r\n },\r\n { immediate: true }\r\n )\r\n\r\n onMounted(() => {\r\n nextTick(() => {\r\n if (activeKey.value) {\r\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\r\n menuRef.value?.showOption(activeKey.value)\r\n }\r\n })\r\n })\r\n</script>\r\n"],"mappings":"ksBE6DE,IAAM,EAAQ,EA2DR,EAAO,EAOP,EAAU,EAAqB,KAAK,CACpC,EAAe,EAAc,EAAE,CAAC,CAEhC,EAAY,MAAe,EAAM,OAAS,GAAG,CAK7C,EAAgB,MAA6B,CAEjD,GAAI,EAAM,QAAS,OAAO,EAAM,QAGhC,GAAI,EAAM,OAAQ,CAChB,IAAM,EAA4B,CAChC,GAAG,EAAM,cACT,GAAI,EAAM,gBAAkB,CAAE,eAAgB,EAAM,eAAgB,CACrE,CACD,OAAO,EAAkB,EAAM,OAAQ,EAAO,CAGhD,MAAO,EAAE,EACT,CAKI,GACJ,EACA,EACA,EAAuB,EAAE,GACZ,CACb,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAW,EAAK,SACtB,GAAI,GAAU,OAAQ,CACpB,IAAM,EAAc,CAAC,GAAG,EAAY,EAAK,IAAc,CACvD,GAAI,EAAS,KAAK,GAAS,EAAM,MAAQ,EAAU,CACjD,OAAO,EAET,IAAM,EAAS,EAAe,EAAU,EAAW,EAAY,CAC/D,GAAI,EAAO,OAAS,EAAG,OAAO,GAGlC,MAAO,EAAE,EAIL,EAAuB,GAA8B,CACzD,IAAM,EAAW,EAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CAC1C,EAAO,IAAI,IACb,EAAc,GAMlB,OALA,EAAS,QAAQ,GAAW,CAC1B,GAAe,IAAI,IACnB,EAAK,IAAI,EAAY,EACrB,CACF,EAAe,EAAc,MAAO,EAAK,CAAC,QAAQ,GAAO,EAAK,IAAI,EAAI,CAAC,CAChE,GAKH,EAAmB,GAAgB,CACnC,GAAO,IAAQ,EAAU,OAC3B,EAAK,SAAU,EAAI,EAIjB,EAAwB,GAAmB,CAC/C,EAAa,MAAQ,EACrB,EAAK,sBAAuB,EAAK,SAKnC,EAAa,CAAE,WADK,GAAgB,EAAQ,OAAO,WAAW,EAAI,CACvC,UAAS,CAAC,CAIrC,MACQ,EAAU,MAChB,GAAU,CACR,GAAI,CAAC,EAAQ,OACb,IAAM,EAAU,EAAoB,EAAO,CAC3C,EAAa,MAAQ,MAAM,KACzB,IAAI,IAAI,CAAC,GAAG,EAAa,MAAO,GAAG,EAAQ,CAAC,CAC7C,CACD,MAAe,EAAQ,OAAO,WAAW,EAAO,CAAC,EAEnD,CAAE,UAAW,GAAM,CACpB,CAED,MAAgB,CACd,MAAe,CACT,EAAU,QACZ,EAAa,MAAQ,MAAM,KAAK,EAAoB,EAAU,MAAM,CAAC,CACrE,EAAQ,OAAO,WAAW,EAAU,MAAM,GAE5C,EACF,4BAtMF,EAgBE,EAAA,SAfI,UAAJ,IAAI,EACH,QAAS,EAAA,MACT,gBAAe,EAAA,MACf,MAAO,EAAA,MACP,KAAMA,EAAAA,KACN,UAAWC,EAAAA,UACX,kBAAiBC,EAAAA,eACjB,sBAAqBC,EAAAA,kBACrB,SAAUC,EAAAA,SACV,kBAAiBC,EAAAA,eACjB,iBAAgBC,EAAAA,cAChB,OAAQC,EAAAA,OACR,cAAaC,EAAAA,WACb,iBAAc,EACd,wBAAsB"}
1
+ {"version":3,"file":"C_Menu2.js","names":["mode","collapsed","collapsedWidth","collapsedIconSize","inverted","themeOverrides","dropdownProps","indent","rootIndent"],"sources":["../src/components/C_Menu/index.vue","../src/components/C_Menu/index.vue","../src/components/C_Menu/index.vue"],"sourcesContent":["/* unplugin-vue-components disabled */<!--\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\n *\n * 支持两种数据输入模式:\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\n *\n * @example 路由模式(推荐)\n * ```vue\n * <C_Menu\n * :routes=\"menuData\"\n * :label-formatter=\"$t\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * @example 原生模式\n * ```vue\n * <C_Menu\n * :options=\"nMenuOptions\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <NMenu\n ref=\"menuRef\"\n :options=\"mergedOptions\"\n :expanded-keys=\"expandedKeys\"\n :value=\"activeKey\"\n :mode=\"mode\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"collapsedWidth\"\n :collapsed-icon-size=\"collapsedIconSize\"\n :inverted=\"inverted\"\n :theme-overrides=\"themeOverrides\"\n :dropdown-props=\"dropdownProps\"\n :indent=\"indent\"\n :root-indent=\"rootIndent\"\n @update:value=\"handleMenuClick\"\n @update:expanded-keys=\"onExpandedKeysChange\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\n import {\n type MenuOption,\n type MenuInst,\n type DropdownProps,\n } from 'naive-ui/es'\n import {\n createMenuOptions,\n type RouteItem,\n type MenuAdapterConfig,\n } from '../_shared'\n\n defineOptions({ name: 'C_Menu' })\n\n const props = withDefaults(\n defineProps<{\n /**\n * 模式一:直传 NMenu 原生 MenuOption[]\n * 与 `routes` 二选一,`options` 优先级更高\n */\n options?: MenuOption[]\n /**\n * 模式二:传入路由数据,自动转换为 MenuOption[]\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\n */\n routes?: RouteItem[]\n /**\n * 适配器配置(仅 routes 模式生效)\n * 可自定义 key/label/icon 取值、过滤逻辑等\n */\n adapterConfig?: MenuAdapterConfig\n /**\n * 标签文本格式化函数(routes 模式的快捷方式)\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\n */\n labelFormatter?: (label: string) => string\n /** 当前激活的菜单 key */\n value?: string\n /** 菜单模式 */\n mode?: 'vertical' | 'horizontal'\n /** 是否折叠 */\n collapsed?: boolean\n /** 折叠宽度 */\n collapsedWidth?: number\n /** 折叠图标大小 */\n collapsedIconSize?: number\n /** 是否反色 */\n inverted?: boolean\n /** 主题覆盖 */\n themeOverrides?: Record<string, any>\n /** 缩进像素 */\n indent?: number\n /** 根缩进像素 */\n rootIndent?: number\n /** 下拉菜单属性(折叠模式) */\n dropdownProps?: DropdownProps\n }>(),\n {\n mode: 'vertical',\n collapsed: false,\n collapsedWidth: 64,\n collapsedIconSize: 22,\n inverted: false,\n indent: 24,\n rootIndent: 16,\n dropdownProps: () => ({\n placement: 'right-start' as const,\n trigger: 'hover' as const,\n arrowStyle: { color: 'var(--n-color)' },\n }),\n }\n )\n\n const emit = defineEmits<{\n /** 菜单项被选中 */\n select: [key: string]\n /** 展开项变化 */\n 'update:expandedKeys': [keys: string[]]\n }>()\n\n const menuRef = ref<MenuInst | null>(null)\n const expandedKeys = ref<string[]>([])\n\n const activeKey = computed(() => props.value ?? '')\n\n // ====== 数据合成 ======\n\n /** 合并后的最终 MenuOption[] */\n const mergedOptions = computed<MenuOption[]>(() => {\n // 优先使用 options 直传模式\n if (props.options) return props.options\n\n // routes 模式 → 适配器转换\n if (props.routes) {\n const config: MenuAdapterConfig = {\n ...props.adapterConfig,\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\n }\n return createMenuOptions(props.routes, config)\n }\n\n return []\n })\n\n // ====== 展开逻辑 ======\n\n /** 从 MenuOption[] 中查找所有祖先 key */\n const findParentKeys = (\n items: MenuOption[],\n targetKey: string,\n parentKeys: string[] = []\n ): string[] => {\n for (const item of items) {\n const children = item.children as MenuOption[] | undefined\n if (children?.length) {\n const currentKeys = [...parentKeys, item.key as string]\n if (children.some(child => child.key === targetKey)) {\n return currentKeys\n }\n const result = findParentKeys(children, targetKey, currentKeys)\n if (result.length > 0) return result\n }\n }\n return []\n }\n\n /** 通过路径段推导展开 key + 查找祖先 key */\n const computeExpandedKeys = (path: string): Set<string> => {\n const segments = path.split('/').filter(Boolean)\n const keys = new Set<string>()\n let currentPath = ''\n segments.forEach(segment => {\n currentPath += `/${segment}`\n keys.add(currentPath)\n })\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\n return keys\n }\n\n // ====== 事件处理 ======\n\n const handleMenuClick = (key: string) => {\n if (key && key !== activeKey.value) {\n emit('select', key)\n }\n }\n\n const onExpandedKeysChange = (keys: string[]) => {\n expandedKeys.value = keys\n emit('update:expandedKeys', keys)\n }\n\n // ====== 暴露方法 ======\n const showOption = (key: string) => menuRef.value?.showOption(key)\n defineExpose({ showOption, menuRef })\n\n // ====== 路由跟踪 ======\n\n watch(\n () => activeKey.value,\n newKey => {\n if (!newKey) return\n const newKeys = computeExpandedKeys(newKey)\n expandedKeys.value = Array.from(\n new Set([...expandedKeys.value, ...newKeys])\n )\n nextTick(() => menuRef.value?.showOption(newKey))\n },\n { immediate: true }\n )\n\n onMounted(() => {\n nextTick(() => {\n if (activeKey.value) {\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\n menuRef.value?.showOption(activeKey.value)\n }\n })\n })\n</script>\n","/* unplugin-vue-components disabled */<!--\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\n *\n * 支持两种数据输入模式:\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\n *\n * @example 路由模式(推荐)\n * ```vue\n * <C_Menu\n * :routes=\"menuData\"\n * :label-formatter=\"$t\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * @example 原生模式\n * ```vue\n * <C_Menu\n * :options=\"nMenuOptions\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <NMenu\n ref=\"menuRef\"\n :options=\"mergedOptions\"\n :expanded-keys=\"expandedKeys\"\n :value=\"activeKey\"\n :mode=\"mode\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"collapsedWidth\"\n :collapsed-icon-size=\"collapsedIconSize\"\n :inverted=\"inverted\"\n :theme-overrides=\"themeOverrides\"\n :dropdown-props=\"dropdownProps\"\n :indent=\"indent\"\n :root-indent=\"rootIndent\"\n @update:value=\"handleMenuClick\"\n @update:expanded-keys=\"onExpandedKeysChange\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\n import {\n type MenuOption,\n type MenuInst,\n type DropdownProps,\n } from 'naive-ui/es'\n import {\n createMenuOptions,\n type RouteItem,\n type MenuAdapterConfig,\n } from '../_shared'\n\n defineOptions({ name: 'C_Menu' })\n\n const props = withDefaults(\n defineProps<{\n /**\n * 模式一:直传 NMenu 原生 MenuOption[]\n * 与 `routes` 二选一,`options` 优先级更高\n */\n options?: MenuOption[]\n /**\n * 模式二:传入路由数据,自动转换为 MenuOption[]\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\n */\n routes?: RouteItem[]\n /**\n * 适配器配置(仅 routes 模式生效)\n * 可自定义 key/label/icon 取值、过滤逻辑等\n */\n adapterConfig?: MenuAdapterConfig\n /**\n * 标签文本格式化函数(routes 模式的快捷方式)\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\n */\n labelFormatter?: (label: string) => string\n /** 当前激活的菜单 key */\n value?: string\n /** 菜单模式 */\n mode?: 'vertical' | 'horizontal'\n /** 是否折叠 */\n collapsed?: boolean\n /** 折叠宽度 */\n collapsedWidth?: number\n /** 折叠图标大小 */\n collapsedIconSize?: number\n /** 是否反色 */\n inverted?: boolean\n /** 主题覆盖 */\n themeOverrides?: Record<string, any>\n /** 缩进像素 */\n indent?: number\n /** 根缩进像素 */\n rootIndent?: number\n /** 下拉菜单属性(折叠模式) */\n dropdownProps?: DropdownProps\n }>(),\n {\n mode: 'vertical',\n collapsed: false,\n collapsedWidth: 64,\n collapsedIconSize: 22,\n inverted: false,\n indent: 24,\n rootIndent: 16,\n dropdownProps: () => ({\n placement: 'right-start' as const,\n trigger: 'hover' as const,\n arrowStyle: { color: 'var(--n-color)' },\n }),\n }\n )\n\n const emit = defineEmits<{\n /** 菜单项被选中 */\n select: [key: string]\n /** 展开项变化 */\n 'update:expandedKeys': [keys: string[]]\n }>()\n\n const menuRef = ref<MenuInst | null>(null)\n const expandedKeys = ref<string[]>([])\n\n const activeKey = computed(() => props.value ?? '')\n\n // ====== 数据合成 ======\n\n /** 合并后的最终 MenuOption[] */\n const mergedOptions = computed<MenuOption[]>(() => {\n // 优先使用 options 直传模式\n if (props.options) return props.options\n\n // routes 模式 → 适配器转换\n if (props.routes) {\n const config: MenuAdapterConfig = {\n ...props.adapterConfig,\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\n }\n return createMenuOptions(props.routes, config)\n }\n\n return []\n })\n\n // ====== 展开逻辑 ======\n\n /** 从 MenuOption[] 中查找所有祖先 key */\n const findParentKeys = (\n items: MenuOption[],\n targetKey: string,\n parentKeys: string[] = []\n ): string[] => {\n for (const item of items) {\n const children = item.children as MenuOption[] | undefined\n if (children?.length) {\n const currentKeys = [...parentKeys, item.key as string]\n if (children.some(child => child.key === targetKey)) {\n return currentKeys\n }\n const result = findParentKeys(children, targetKey, currentKeys)\n if (result.length > 0) return result\n }\n }\n return []\n }\n\n /** 通过路径段推导展开 key + 查找祖先 key */\n const computeExpandedKeys = (path: string): Set<string> => {\n const segments = path.split('/').filter(Boolean)\n const keys = new Set<string>()\n let currentPath = ''\n segments.forEach(segment => {\n currentPath += `/${segment}`\n keys.add(currentPath)\n })\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\n return keys\n }\n\n // ====== 事件处理 ======\n\n const handleMenuClick = (key: string) => {\n if (key && key !== activeKey.value) {\n emit('select', key)\n }\n }\n\n const onExpandedKeysChange = (keys: string[]) => {\n expandedKeys.value = keys\n emit('update:expandedKeys', keys)\n }\n\n // ====== 暴露方法 ======\n const showOption = (key: string) => menuRef.value?.showOption(key)\n defineExpose({ showOption, menuRef })\n\n // ====== 路由跟踪 ======\n\n watch(\n () => activeKey.value,\n newKey => {\n if (!newKey) return\n const newKeys = computeExpandedKeys(newKey)\n expandedKeys.value = Array.from(\n new Set([...expandedKeys.value, ...newKeys])\n )\n nextTick(() => menuRef.value?.showOption(newKey))\n },\n { immediate: true }\n )\n\n onMounted(() => {\n nextTick(() => {\n if (activeKey.value) {\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\n menuRef.value?.showOption(activeKey.value)\n }\n })\n })\n</script>\n","<!--\n * @Description: 通用菜单组件 — 基于 NMenu 的树形导航菜单\n *\n * 支持两种数据输入模式:\n * 1. `options` — 直传 NMenu 原生 MenuOption[](零转换,高级用户)\n * 2. `routes` — 传入路由数据,自动通过内置适配器转换为 MenuOption[]\n *\n * @example 路由模式(推荐)\n * ```vue\n * <C_Menu\n * :routes=\"menuData\"\n * :label-formatter=\"$t\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * @example 原生模式\n * ```vue\n * <C_Menu\n * :options=\"nMenuOptions\"\n * @select=\"router.push\"\n * />\n * ```\n *\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <NMenu\n ref=\"menuRef\"\n :options=\"mergedOptions\"\n :expanded-keys=\"expandedKeys\"\n :value=\"activeKey\"\n :mode=\"mode\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"collapsedWidth\"\n :collapsed-icon-size=\"collapsedIconSize\"\n :inverted=\"inverted\"\n :theme-overrides=\"themeOverrides\"\n :dropdown-props=\"dropdownProps\"\n :indent=\"indent\"\n :root-indent=\"rootIndent\"\n @update:value=\"handleMenuClick\"\n @update:expanded-keys=\"onExpandedKeysChange\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import { ref, computed, watch, nextTick, onMounted } from 'vue'\n import {\n type MenuOption,\n type MenuInst,\n type DropdownProps,\n } from 'naive-ui/es'\n import {\n createMenuOptions,\n type RouteItem,\n type MenuAdapterConfig,\n } from '../_shared'\n\n defineOptions({ name: 'C_Menu' })\n\n const props = withDefaults(\n defineProps<{\n /**\n * 模式一:直传 NMenu 原生 MenuOption[]\n * 与 `routes` 二选一,`options` 优先级更高\n */\n options?: MenuOption[]\n /**\n * 模式二:传入路由数据,自动转换为 MenuOption[]\n * 兼容 Vue Router RouteRecordRaw / 后端 JSON 路由表\n */\n routes?: RouteItem[]\n /**\n * 适配器配置(仅 routes 模式生效)\n * 可自定义 key/label/icon 取值、过滤逻辑等\n */\n adapterConfig?: MenuAdapterConfig\n /**\n * 标签文本格式化函数(routes 模式的快捷方式)\n * 等同于 `adapterConfig.labelFormatter`,但优先级更高\n */\n labelFormatter?: (label: string) => string\n /** 当前激活的菜单 key */\n value?: string\n /** 菜单模式 */\n mode?: 'vertical' | 'horizontal'\n /** 是否折叠 */\n collapsed?: boolean\n /** 折叠宽度 */\n collapsedWidth?: number\n /** 折叠图标大小 */\n collapsedIconSize?: number\n /** 是否反色 */\n inverted?: boolean\n /** 主题覆盖 */\n themeOverrides?: Record<string, any>\n /** 缩进像素 */\n indent?: number\n /** 根缩进像素 */\n rootIndent?: number\n /** 下拉菜单属性(折叠模式) */\n dropdownProps?: DropdownProps\n }>(),\n {\n mode: 'vertical',\n collapsed: false,\n collapsedWidth: 64,\n collapsedIconSize: 22,\n inverted: false,\n indent: 24,\n rootIndent: 16,\n dropdownProps: () => ({\n placement: 'right-start' as const,\n trigger: 'hover' as const,\n arrowStyle: { color: 'var(--n-color)' },\n }),\n }\n )\n\n const emit = defineEmits<{\n /** 菜单项被选中 */\n select: [key: string]\n /** 展开项变化 */\n 'update:expandedKeys': [keys: string[]]\n }>()\n\n const menuRef = ref<MenuInst | null>(null)\n const expandedKeys = ref<string[]>([])\n\n const activeKey = computed(() => props.value ?? '')\n\n // ====== 数据合成 ======\n\n /** 合并后的最终 MenuOption[] */\n const mergedOptions = computed<MenuOption[]>(() => {\n // 优先使用 options 直传模式\n if (props.options) return props.options\n\n // routes 模式 → 适配器转换\n if (props.routes) {\n const config: MenuAdapterConfig = {\n ...props.adapterConfig,\n ...(props.labelFormatter && { labelFormatter: props.labelFormatter }),\n }\n return createMenuOptions(props.routes, config)\n }\n\n return []\n })\n\n // ====== 展开逻辑 ======\n\n /** 从 MenuOption[] 中查找所有祖先 key */\n const findParentKeys = (\n items: MenuOption[],\n targetKey: string,\n parentKeys: string[] = []\n ): string[] => {\n for (const item of items) {\n const children = item.children as MenuOption[] | undefined\n if (children?.length) {\n const currentKeys = [...parentKeys, item.key as string]\n if (children.some(child => child.key === targetKey)) {\n return currentKeys\n }\n const result = findParentKeys(children, targetKey, currentKeys)\n if (result.length > 0) return result\n }\n }\n return []\n }\n\n /** 通过路径段推导展开 key + 查找祖先 key */\n const computeExpandedKeys = (path: string): Set<string> => {\n const segments = path.split('/').filter(Boolean)\n const keys = new Set<string>()\n let currentPath = ''\n segments.forEach(segment => {\n currentPath += `/${segment}`\n keys.add(currentPath)\n })\n findParentKeys(mergedOptions.value, path).forEach(key => keys.add(key))\n return keys\n }\n\n // ====== 事件处理 ======\n\n const handleMenuClick = (key: string) => {\n if (key && key !== activeKey.value) {\n emit('select', key)\n }\n }\n\n const onExpandedKeysChange = (keys: string[]) => {\n expandedKeys.value = keys\n emit('update:expandedKeys', keys)\n }\n\n // ====== 暴露方法 ======\n const showOption = (key: string) => menuRef.value?.showOption(key)\n defineExpose({ showOption, menuRef })\n\n // ====== 路由跟踪 ======\n\n watch(\n () => activeKey.value,\n newKey => {\n if (!newKey) return\n const newKeys = computeExpandedKeys(newKey)\n expandedKeys.value = Array.from(\n new Set([...expandedKeys.value, ...newKeys])\n )\n nextTick(() => menuRef.value?.showOption(newKey))\n },\n { immediate: true }\n )\n\n onMounted(() => {\n nextTick(() => {\n if (activeKey.value) {\n expandedKeys.value = Array.from(computeExpandedKeys(activeKey.value))\n menuRef.value?.showOption(activeKey.value)\n }\n })\n })\n</script>\n"],"mappings":"ksBE6DE,IAAM,EAAQ,EA2DR,EAAO,EAOP,EAAU,EAAqB,KAAI,CACnC,EAAe,EAAc,EAAE,CAAA,CAE/B,EAAY,MAAe,EAAM,OAAS,GAAE,CAK5C,EAAgB,MAA6B,CAEjD,GAAI,EAAM,QAAS,OAAO,EAAM,QAGhC,GAAI,EAAM,OAAQ,CAChB,IAAM,EAA4B,CAChC,GAAG,EAAM,cACT,GAAI,EAAM,gBAAkB,CAAE,eAAgB,EAAM,eAAgB,CACtE,CACA,OAAO,EAAkB,EAAM,OAAQ,EAAM,CAG/C,MAAO,EAAC,EACT,CAKK,GACJ,EACA,EACA,EAAuB,EAAC,GACX,CACb,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAW,EAAK,SACtB,GAAI,GAAU,OAAQ,CACpB,IAAM,EAAc,CAAC,GAAG,EAAY,EAAK,IAAa,CACtD,GAAI,EAAS,KAAK,GAAS,EAAM,MAAQ,EAAU,CACjD,OAAO,EAET,IAAM,EAAS,EAAe,EAAU,EAAW,EAAW,CAC9D,GAAI,EAAO,OAAS,EAAG,OAAO,GAGlC,MAAO,EAAC,EAIJ,EAAuB,GAA8B,CACzD,IAAM,EAAW,EAAK,MAAM,IAAI,CAAC,OAAO,QAAO,CACzC,EAAO,IAAI,IACb,EAAc,GAMlB,OALA,EAAS,QAAQ,GAAW,CAC1B,GAAe,IAAI,IACnB,EAAK,IAAI,EAAW,EACrB,CACD,EAAe,EAAc,MAAO,EAAK,CAAC,QAAQ,GAAO,EAAK,IAAI,EAAI,CAAA,CAC/D,GAKH,EAAmB,GAAgB,CACnC,GAAO,IAAQ,EAAU,OAC3B,EAAK,SAAU,EAAG,EAIhB,EAAwB,GAAmB,CAC/C,EAAa,MAAQ,EACrB,EAAK,sBAAuB,EAAI,SAKlC,EAAa,CAAE,WADK,GAAgB,EAAQ,OAAO,WAAW,EAAG,CACtC,UAAS,CAAA,CAIpC,MACQ,EAAU,MAChB,GAAU,CACR,GAAI,CAAC,EAAQ,OACb,IAAM,EAAU,EAAoB,EAAM,CAC1C,EAAa,MAAQ,MAAM,KACzB,IAAI,IAAI,CAAC,GAAG,EAAa,MAAO,GAAG,EAAQ,CAAA,CAC7C,CACA,MAAe,EAAQ,OAAO,WAAW,EAAO,CAAA,EAElD,CAAE,UAAW,GAAK,CACpB,CAEA,MAAgB,CACd,MAAe,CACT,EAAU,QACZ,EAAa,MAAQ,MAAM,KAAK,EAAoB,EAAU,MAAM,CAAA,CACpE,EAAQ,OAAO,WAAW,EAAU,MAAK,GAE5C,EACF,4BAtMD,EAgBE,EAAA,SAfI,UAAJ,IAAI,EACH,QAAS,EAAA,MACT,gBAAe,EAAA,MACf,MAAO,EAAA,MACP,KAAMA,EAAAA,KACN,UAAWC,EAAAA,UACX,kBAAiBC,EAAAA,eACjB,sBAAqBC,EAAAA,kBACrB,SAAUC,EAAAA,SACV,kBAAiBC,EAAAA,eACjB,iBAAgBC,EAAAA,cAChB,OAAQC,EAAAA,OACR,cAAaC,EAAAA,WACb,iBAAc,EACd,wBAAsB"}
@@ -1 +1 @@
1
- {"version":3,"file":"C_QRCode-DMxSyogU.css","names":[],"sources":["../src/components/C_QRCode/index.vue?vue&type=style&index=0&scoped=7a4201b5&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* C_QRCode styles */\n.c-qrcode[data-v-7a4201b5] {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper[data-v-7a4201b5] {\n display: inline-block;\n padding: 8px;\n background: var(--c-bg-content, #fff);\n border-radius: 6px;\n transition: all 0.3s ease;\n overflow: hidden;\n max-width: 100%;\n line-height: 0;\n}\n.c-qrcode .qrcode-wrapper.with-border[data-v-7a4201b5] {\n border: 1px dashed var(--c-border-default, #e0e0e6);\n}\n.c-qrcode .qrcode-wrapper[data-v-7a4201b5]:hover {\n border-color: var(--primary-color, #409eff);\n box-shadow: var(--c-shadow-sm, 0 1px 4px rgba(0, 0, 0, 0.08));\n}\n.c-qrcode .qrcode-wrapper canvas[data-v-7a4201b5] {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-7a4201b5] {\n display: flex;\n align-items: center;\n justify-content: center;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-7a4201b5] svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n.c-qrcode .qrcode-label[data-v-7a4201b5] {\n font-size: 14px;\n color: var(--c-text-secondary, #999);\n text-align: center;\n font-weight: 500;\n word-break: break-all;\n max-width: 100%;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"C_QRCode-CFNTda_1.css","names":[],"sources":["../src/components/C_QRCode/index.vue?vue&type=style&index=0&scoped=fe18e0fe&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* C_QRCode styles */\n.c-qrcode[data-v-fe18e0fe] {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper[data-v-fe18e0fe] {\n display: inline-block;\n padding: 8px;\n background: var(--c-bg-content, #fff);\n border-radius: 6px;\n transition: all 0.3s ease;\n overflow: hidden;\n max-width: 100%;\n line-height: 0;\n}\n.c-qrcode .qrcode-wrapper.with-border[data-v-fe18e0fe] {\n border: 1px dashed var(--c-border-default, #e0e0e6);\n}\n.c-qrcode .qrcode-wrapper[data-v-fe18e0fe]:hover {\n border-color: var(--primary-color, #409eff);\n box-shadow: var(--c-shadow-sm, 0 1px 4px rgba(0, 0, 0, 0.08));\n}\n.c-qrcode .qrcode-wrapper canvas[data-v-fe18e0fe] {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-fe18e0fe] {\n display: flex;\n align-items: center;\n justify-content: center;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-fe18e0fe] svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n.c-qrcode .qrcode-label[data-v-fe18e0fe] {\n font-size: 14px;\n color: var(--c-text-secondary, #999);\n text-align: center;\n font-weight: 500;\n word-break: break-all;\n max-width: 100%;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
@@ -1 +1 @@
1
- {"version":3,"file":"C_QRCode-G7fiAkm4.css","names":[],"sources":["../src/components/C_QRCode/index.vue?vue&type=style&index=0&scoped=7a4201b5&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* C_QRCode styles */\n.c-qrcode[data-v-7a4201b5] {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper[data-v-7a4201b5] {\n display: inline-block;\n padding: 8px;\n background: var(--c-bg-content, #fff);\n border-radius: 6px;\n transition: all 0.3s ease;\n overflow: hidden;\n max-width: 100%;\n line-height: 0;\n}\n.c-qrcode .qrcode-wrapper.with-border[data-v-7a4201b5] {\n border: 1px dashed var(--c-border-default, #e0e0e6);\n}\n.c-qrcode .qrcode-wrapper[data-v-7a4201b5]:hover {\n border-color: var(--primary-color, #409eff);\n box-shadow: var(--c-shadow-sm, 0 1px 4px rgba(0, 0, 0, 0.08));\n}\n.c-qrcode .qrcode-wrapper canvas[data-v-7a4201b5] {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-7a4201b5] {\n display: flex;\n align-items: center;\n justify-content: center;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-7a4201b5] svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n.c-qrcode .qrcode-label[data-v-7a4201b5] {\n font-size: 14px;\n color: var(--c-text-secondary, #999);\n text-align: center;\n font-weight: 500;\n word-break: break-all;\n max-width: 100%;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"C_QRCode-CmWr9lK-.css","names":[],"sources":["../src/components/C_QRCode/index.vue?vue&type=style&index=0&scoped=fe18e0fe&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* C_QRCode styles */\n.c-qrcode[data-v-fe18e0fe] {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper[data-v-fe18e0fe] {\n display: inline-block;\n padding: 8px;\n background: var(--c-bg-content, #fff);\n border-radius: 6px;\n transition: all 0.3s ease;\n overflow: hidden;\n max-width: 100%;\n line-height: 0;\n}\n.c-qrcode .qrcode-wrapper.with-border[data-v-fe18e0fe] {\n border: 1px dashed var(--c-border-default, #e0e0e6);\n}\n.c-qrcode .qrcode-wrapper[data-v-fe18e0fe]:hover {\n border-color: var(--primary-color, #409eff);\n box-shadow: var(--c-shadow-sm, 0 1px 4px rgba(0, 0, 0, 0.08));\n}\n.c-qrcode .qrcode-wrapper canvas[data-v-fe18e0fe] {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-fe18e0fe] {\n display: flex;\n align-items: center;\n justify-content: center;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-fe18e0fe] svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n.c-qrcode .qrcode-label[data-v-fe18e0fe] {\n font-size: 14px;\n color: var(--c-text-secondary, #999);\n text-align: center;\n font-weight: 500;\n word-break: break-all;\n max-width: 100%;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
package/dist/C_QRCode.css CHANGED
@@ -1,12 +1,12 @@
1
1
  /* unplugin-vue-components disabled *//* C_QRCode styles */
2
- .c-qrcode[data-v-7a4201b5] {
2
+ .c-qrcode[data-v-fe18e0fe] {
3
3
  display: inline-flex;
4
4
  flex-direction: column;
5
5
  align-items: center;
6
6
  gap: 8px;
7
7
  max-width: 100%;
8
8
  }
9
- .c-qrcode .qrcode-wrapper[data-v-7a4201b5] {
9
+ .c-qrcode .qrcode-wrapper[data-v-fe18e0fe] {
10
10
  display: inline-block;
11
11
  padding: 8px;
12
12
  background: var(--c-bg-content, #fff);
@@ -16,30 +16,30 @@
16
16
  max-width: 100%;
17
17
  line-height: 0;
18
18
  }
19
- .c-qrcode .qrcode-wrapper.with-border[data-v-7a4201b5] {
19
+ .c-qrcode .qrcode-wrapper.with-border[data-v-fe18e0fe] {
20
20
  border: 1px dashed var(--c-border-default, #e0e0e6);
21
21
  }
22
- .c-qrcode .qrcode-wrapper[data-v-7a4201b5]:hover {
22
+ .c-qrcode .qrcode-wrapper[data-v-fe18e0fe]:hover {
23
23
  border-color: var(--primary-color, #409eff);
24
24
  box-shadow: var(--c-shadow-sm, 0 1px 4px rgba(0, 0, 0, 0.08));
25
25
  }
26
- .c-qrcode .qrcode-wrapper canvas[data-v-7a4201b5] {
26
+ .c-qrcode .qrcode-wrapper canvas[data-v-fe18e0fe] {
27
27
  display: block;
28
28
  max-width: 100%;
29
29
  height: auto;
30
30
  }
31
- .c-qrcode .qrcode-wrapper .qrcode-svg[data-v-7a4201b5] {
31
+ .c-qrcode .qrcode-wrapper .qrcode-svg[data-v-fe18e0fe] {
32
32
  display: flex;
33
33
  align-items: center;
34
34
  justify-content: center;
35
35
  max-width: 100%;
36
36
  }
37
- .c-qrcode .qrcode-wrapper .qrcode-svg[data-v-7a4201b5] svg {
37
+ .c-qrcode .qrcode-wrapper .qrcode-svg[data-v-fe18e0fe] svg {
38
38
  width: 100%;
39
39
  height: 100%;
40
40
  display: block;
41
41
  }
42
- .c-qrcode .qrcode-label[data-v-7a4201b5] {
42
+ .c-qrcode .qrcode-label[data-v-fe18e0fe] {
43
43
  font-size: 14px;
44
44
  color: var(--c-text-secondary, #999);
45
45
  text-align: center;
@@ -48,4 +48,4 @@
48
48
  max-width: 100%;
49
49
  }
50
50
 
51
- /*# sourceMappingURL=C_QRCode-DMxSyogU.css.map*/
51
+ /*# sourceMappingURL=C_QRCode-CFNTda_1.css.map*/
@@ -1 +1 @@
1
- {"version":3,"file":"C_QRCode.js","names":["QRCode","showBorder","mode","size","showLabel","label"],"sources":["../src/components/C_QRCode/composables/useQRCode.ts","../src/components/C_QRCode/index.vue","../src/components/C_QRCode/index.vue","../src/components/C_QRCode/index.vue"],"sourcesContent":["import { ref, computed, watch } from \"vue\";\r\nimport QRCode from \"qrcode\";\r\nimport type { Ref } from \"vue\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n RenderMode,\r\n} from \"../types\";\r\n\r\ninterface UseQRCodeOptions {\r\n value: Ref<string>;\r\n size: Ref<number>;\r\n color: Ref<string>;\r\n bgColor: Ref<string>;\r\n errorCorrectionLevel: Ref<ErrorCorrectionLevel>;\r\n margin: Ref<number>;\r\n mode: Ref<RenderMode>;\r\n logo: Ref<LogoOptions | undefined>;\r\n}\r\n\r\nfunction drawLogo(\r\n canvas: HTMLCanvasElement,\r\n logo: LogoOptions,\r\n qrSize: number,\r\n): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return reject(new Error(\"Canvas context 不可用\"));\r\n\r\n const img = new Image();\r\n img.crossOrigin = \"anonymous\";\r\n img.onload = () => {\r\n const ratio = logo.size ?? 0.2;\r\n const logoSize = Math.floor(qrSize * ratio);\r\n const padding = logo.padding ?? 4;\r\n const borderRadius = logo.borderRadius ?? 4;\r\n\r\n const x = (canvas.width - logoSize) / 2;\r\n const y = (canvas.height - logoSize) / 2;\r\n\r\n ctx.save();\r\n const bgX = x - padding;\r\n const bgY = y - padding;\r\n const bgSize = logoSize + padding * 2;\r\n const r = borderRadius + padding;\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(bgX + r, bgY);\r\n ctx.arcTo(bgX + bgSize, bgY, bgX + bgSize, bgY + bgSize, r);\r\n ctx.arcTo(bgX + bgSize, bgY + bgSize, bgX, bgY + bgSize, r);\r\n ctx.arcTo(bgX, bgY + bgSize, bgX, bgY, r);\r\n ctx.arcTo(bgX, bgY, bgX + bgSize, bgY, r);\r\n ctx.closePath();\r\n ctx.fillStyle = logo.bgColor ?? \"#ffffff\";\r\n ctx.fill();\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(x + borderRadius, y);\r\n ctx.arcTo(x + logoSize, y, x + logoSize, y + logoSize, borderRadius);\r\n ctx.arcTo(x + logoSize, y + logoSize, x, y + logoSize, borderRadius);\r\n ctx.arcTo(x, y + logoSize, x, y, borderRadius);\r\n ctx.arcTo(x, y, x + logoSize, y, borderRadius);\r\n ctx.closePath();\r\n ctx.clip();\r\n ctx.drawImage(img, x, y, logoSize, logoSize);\r\n ctx.restore();\r\n\r\n resolve();\r\n };\r\n img.onerror = () => reject(new Error(`Logo 加载失败: ${logo.src}`));\r\n img.src = logo.src;\r\n });\r\n}\r\n\r\nexport function useQRCode(\r\n canvasRef: Ref<HTMLCanvasElement | null>,\r\n options: UseQRCodeOptions,\r\n) {\r\n const svgHtml = ref(\"\");\r\n const error = ref<Error | null>(null);\r\n const loading = ref(false);\r\n\r\n const effectiveLevel = computed<ErrorCorrectionLevel>(() => {\r\n if (options.logo.value) {\r\n const level = options.errorCorrectionLevel.value;\r\n if (level === \"L\" || level === \"M\") return \"Q\";\r\n return level;\r\n }\r\n return options.errorCorrectionLevel.value;\r\n });\r\n\r\n const qrOptions = computed(() => ({\r\n width: options.size.value,\r\n margin: options.margin.value,\r\n errorCorrectionLevel: effectiveLevel.value,\r\n color: {\r\n dark: options.color.value,\r\n light: options.bgColor.value,\r\n },\r\n }));\r\n\r\n async function renderCanvas() {\r\n const canvas = canvasRef.value;\r\n if (!canvas || !options.value.value) return;\r\n await QRCode.toCanvas(canvas, options.value.value, qrOptions.value);\r\n if (options.logo.value) {\r\n await drawLogo(canvas, options.logo.value, options.size.value);\r\n }\r\n }\r\n\r\n async function renderSvg() {\r\n if (!options.value.value) {\r\n svgHtml.value = \"\";\r\n return;\r\n }\r\n const svgString = await QRCode.toString(options.value.value, {\r\n ...qrOptions.value,\r\n type: \"svg\",\r\n });\r\n svgHtml.value = svgString;\r\n }\r\n\r\n async function render() {\r\n if (!options.value.value) return;\r\n loading.value = true;\r\n error.value = null;\r\n try {\r\n if (options.mode.value === \"canvas\") {\r\n await renderCanvas();\r\n } else {\r\n await renderSvg();\r\n }\r\n } catch (e) {\r\n error.value = e instanceof Error ? e : new Error(String(e));\r\n console.error(\"二维码渲染失败:\", e);\r\n } finally {\r\n loading.value = false;\r\n }\r\n }\r\n\r\n async function toDataURL(\r\n type: ExportType = \"png\",\r\n quality = 0.92,\r\n ): Promise<string> {\r\n if (type === \"svg\") {\r\n const svgStr = await QRCode.toString(options.value.value, {\r\n ...qrOptions.value,\r\n type: \"svg\",\r\n });\r\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;\r\n }\r\n const tempCanvas = document.createElement(\"canvas\");\r\n await QRCode.toCanvas(tempCanvas, options.value.value, qrOptions.value);\r\n if (options.logo.value) {\r\n await drawLogo(tempCanvas, options.logo.value, options.size.value);\r\n }\r\n const mimeType = type === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\r\n return tempCanvas.toDataURL(mimeType, quality);\r\n }\r\n\r\n async function download(filename = \"qrcode\", type: ExportType = \"png\") {\r\n const dataUrl = await toDataURL(type);\r\n const link = document.createElement(\"a\");\r\n link.download = `${filename}.${type}`;\r\n link.href = dataUrl;\r\n link.click();\r\n }\r\n\r\n watch(\r\n [\r\n options.value,\r\n options.size,\r\n options.color,\r\n options.bgColor,\r\n options.errorCorrectionLevel,\r\n options.margin,\r\n options.mode,\r\n options.logo,\r\n ],\r\n () => render(),\r\n { deep: true },\r\n );\r\n\r\n return { svgHtml, error, loading, render, toDataURL, download };\r\n}\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-16\r\n * @Description: 二维码组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-qrcode\">\r\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\r\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\r\n <div\r\n v-if=\"mode === 'svg'\"\r\n class=\"qrcode-svg\"\r\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\r\n v-html=\"svgHtml\"\r\n />\r\n </div>\r\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\r\n {{ label }}\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRef, watch, onMounted } from \"vue\";\r\nimport { useQRCode } from \"./composables/useQRCode\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n QRCodeExpose,\r\n RenderMode,\r\n} from \"./types\";\r\n\r\ndefineOptions({ name: \"C_QRCode\" });\r\n\r\ninterface Props {\r\n value: string;\r\n size?: number;\r\n color?: string;\r\n bgColor?: string;\r\n errorCorrectionLevel?: ErrorCorrectionLevel;\r\n margin?: number;\r\n mode?: RenderMode;\r\n logo?: LogoOptions;\r\n showBorder?: boolean;\r\n label?: string;\r\n showLabel?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n size: 200,\r\n color: \"#000000\",\r\n bgColor: \"#FFFFFF\",\r\n errorCorrectionLevel: \"M\",\r\n margin: 2,\r\n mode: \"canvas\",\r\n logo: undefined,\r\n showBorder: true,\r\n label: \"\",\r\n showLabel: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n error: [error: Error];\r\n}>();\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\n\r\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\r\n value: toRef(props, \"value\"),\r\n size: toRef(props, \"size\"),\r\n color: toRef(props, \"color\"),\r\n bgColor: toRef(props, \"bgColor\"),\r\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\r\n margin: toRef(props, \"margin\"),\r\n mode: toRef(props, \"mode\"),\r\n logo: toRef(props, \"logo\"),\r\n});\r\n\r\nwatch(error, (e) => {\r\n if (e) emit(\"error\", e);\r\n});\r\n\r\nonMounted(() => render());\r\n\r\ndefineExpose<QRCodeExpose>({\r\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\r\n download: (filename?: string, type?: ExportType) => download(filename, type),\r\n refresh: () => render(),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-16\r\n * @Description: 二维码组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-qrcode\">\r\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\r\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\r\n <div\r\n v-if=\"mode === 'svg'\"\r\n class=\"qrcode-svg\"\r\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\r\n v-html=\"svgHtml\"\r\n />\r\n </div>\r\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\r\n {{ label }}\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRef, watch, onMounted } from \"vue\";\r\nimport { useQRCode } from \"./composables/useQRCode\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n QRCodeExpose,\r\n RenderMode,\r\n} from \"./types\";\r\n\r\ndefineOptions({ name: \"C_QRCode\" });\r\n\r\ninterface Props {\r\n value: string;\r\n size?: number;\r\n color?: string;\r\n bgColor?: string;\r\n errorCorrectionLevel?: ErrorCorrectionLevel;\r\n margin?: number;\r\n mode?: RenderMode;\r\n logo?: LogoOptions;\r\n showBorder?: boolean;\r\n label?: string;\r\n showLabel?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n size: 200,\r\n color: \"#000000\",\r\n bgColor: \"#FFFFFF\",\r\n errorCorrectionLevel: \"M\",\r\n margin: 2,\r\n mode: \"canvas\",\r\n logo: undefined,\r\n showBorder: true,\r\n label: \"\",\r\n showLabel: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n error: [error: Error];\r\n}>();\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\n\r\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\r\n value: toRef(props, \"value\"),\r\n size: toRef(props, \"size\"),\r\n color: toRef(props, \"color\"),\r\n bgColor: toRef(props, \"bgColor\"),\r\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\r\n margin: toRef(props, \"margin\"),\r\n mode: toRef(props, \"mode\"),\r\n logo: toRef(props, \"logo\"),\r\n});\r\n\r\nwatch(error, (e) => {\r\n if (e) emit(\"error\", e);\r\n});\r\n\r\nonMounted(() => render());\r\n\r\ndefineExpose<QRCodeExpose>({\r\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\r\n download: (filename?: string, type?: ExportType) => download(filename, type),\r\n refresh: () => render(),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-16\r\n * @Description: 二维码组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-qrcode\">\r\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\r\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\r\n <div\r\n v-if=\"mode === 'svg'\"\r\n class=\"qrcode-svg\"\r\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\r\n v-html=\"svgHtml\"\r\n />\r\n </div>\r\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\r\n {{ label }}\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRef, watch, onMounted } from \"vue\";\r\nimport { useQRCode } from \"./composables/useQRCode\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n QRCodeExpose,\r\n RenderMode,\r\n} from \"./types\";\r\n\r\ndefineOptions({ name: \"C_QRCode\" });\r\n\r\ninterface Props {\r\n value: string;\r\n size?: number;\r\n color?: string;\r\n bgColor?: string;\r\n errorCorrectionLevel?: ErrorCorrectionLevel;\r\n margin?: number;\r\n mode?: RenderMode;\r\n logo?: LogoOptions;\r\n showBorder?: boolean;\r\n label?: string;\r\n showLabel?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n size: 200,\r\n color: \"#000000\",\r\n bgColor: \"#FFFFFF\",\r\n errorCorrectionLevel: \"M\",\r\n margin: 2,\r\n mode: \"canvas\",\r\n logo: undefined,\r\n showBorder: true,\r\n label: \"\",\r\n showLabel: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n error: [error: Error];\r\n}>();\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\n\r\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\r\n value: toRef(props, \"value\"),\r\n size: toRef(props, \"size\"),\r\n color: toRef(props, \"color\"),\r\n bgColor: toRef(props, \"bgColor\"),\r\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\r\n margin: toRef(props, \"margin\"),\r\n mode: toRef(props, \"mode\"),\r\n logo: toRef(props, \"logo\"),\r\n});\r\n\r\nwatch(error, (e) => {\r\n if (e) emit(\"error\", e);\r\n});\r\n\r\nonMounted(() => render());\r\n\r\ndefineExpose<QRCodeExpose>({\r\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\r\n download: (filename?: string, type?: ExportType) => download(filename, type),\r\n refresh: () => render(),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"C_QRCode.js","names":["QRCode","showBorder","mode","size","showLabel","label"],"sources":["../src/components/C_QRCode/composables/useQRCode.ts","../src/components/C_QRCode/index.vue","../src/components/C_QRCode/index.vue","../src/components/C_QRCode/index.vue"],"sourcesContent":["import { ref, computed, watch } from \"vue\";\r\nimport QRCode from \"qrcode\";\r\nimport type { Ref } from \"vue\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n RenderMode,\r\n} from \"../types\";\r\n\r\ninterface UseQRCodeOptions {\r\n value: Ref<string>;\r\n size: Ref<number>;\r\n color: Ref<string>;\r\n bgColor: Ref<string>;\r\n errorCorrectionLevel: Ref<ErrorCorrectionLevel>;\r\n margin: Ref<number>;\r\n mode: Ref<RenderMode>;\r\n logo: Ref<LogoOptions | undefined>;\r\n}\r\n\r\nfunction drawLogo(\r\n canvas: HTMLCanvasElement,\r\n logo: LogoOptions,\r\n qrSize: number,\r\n): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return reject(new Error(\"Canvas context 不可用\"));\r\n\r\n const img = new Image();\r\n img.crossOrigin = \"anonymous\";\r\n img.onload = () => {\r\n const ratio = logo.size ?? 0.2;\r\n const logoSize = Math.floor(qrSize * ratio);\r\n const padding = logo.padding ?? 4;\r\n const borderRadius = logo.borderRadius ?? 4;\r\n\r\n const x = (canvas.width - logoSize) / 2;\r\n const y = (canvas.height - logoSize) / 2;\r\n\r\n ctx.save();\r\n const bgX = x - padding;\r\n const bgY = y - padding;\r\n const bgSize = logoSize + padding * 2;\r\n const r = borderRadius + padding;\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(bgX + r, bgY);\r\n ctx.arcTo(bgX + bgSize, bgY, bgX + bgSize, bgY + bgSize, r);\r\n ctx.arcTo(bgX + bgSize, bgY + bgSize, bgX, bgY + bgSize, r);\r\n ctx.arcTo(bgX, bgY + bgSize, bgX, bgY, r);\r\n ctx.arcTo(bgX, bgY, bgX + bgSize, bgY, r);\r\n ctx.closePath();\r\n ctx.fillStyle = logo.bgColor ?? \"#ffffff\";\r\n ctx.fill();\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(x + borderRadius, y);\r\n ctx.arcTo(x + logoSize, y, x + logoSize, y + logoSize, borderRadius);\r\n ctx.arcTo(x + logoSize, y + logoSize, x, y + logoSize, borderRadius);\r\n ctx.arcTo(x, y + logoSize, x, y, borderRadius);\r\n ctx.arcTo(x, y, x + logoSize, y, borderRadius);\r\n ctx.closePath();\r\n ctx.clip();\r\n ctx.drawImage(img, x, y, logoSize, logoSize);\r\n ctx.restore();\r\n\r\n resolve();\r\n };\r\n img.onerror = () => reject(new Error(`Logo 加载失败: ${logo.src}`));\r\n img.src = logo.src;\r\n });\r\n}\r\n\r\nexport function useQRCode(\r\n canvasRef: Ref<HTMLCanvasElement | null>,\r\n options: UseQRCodeOptions,\r\n) {\r\n const svgHtml = ref(\"\");\r\n const error = ref<Error | null>(null);\r\n const loading = ref(false);\r\n\r\n const effectiveLevel = computed<ErrorCorrectionLevel>(() => {\r\n if (options.logo.value) {\r\n const level = options.errorCorrectionLevel.value;\r\n if (level === \"L\" || level === \"M\") return \"Q\";\r\n return level;\r\n }\r\n return options.errorCorrectionLevel.value;\r\n });\r\n\r\n const qrOptions = computed(() => ({\r\n width: options.size.value,\r\n margin: options.margin.value,\r\n errorCorrectionLevel: effectiveLevel.value,\r\n color: {\r\n dark: options.color.value,\r\n light: options.bgColor.value,\r\n },\r\n }));\r\n\r\n async function renderCanvas() {\r\n const canvas = canvasRef.value;\r\n if (!canvas || !options.value.value) return;\r\n await QRCode.toCanvas(canvas, options.value.value, qrOptions.value);\r\n if (options.logo.value) {\r\n await drawLogo(canvas, options.logo.value, options.size.value);\r\n }\r\n }\r\n\r\n async function renderSvg() {\r\n if (!options.value.value) {\r\n svgHtml.value = \"\";\r\n return;\r\n }\r\n const svgString = await QRCode.toString(options.value.value, {\r\n ...qrOptions.value,\r\n type: \"svg\",\r\n });\r\n svgHtml.value = svgString;\r\n }\r\n\r\n async function render() {\r\n if (!options.value.value) return;\r\n loading.value = true;\r\n error.value = null;\r\n try {\r\n if (options.mode.value === \"canvas\") {\r\n await renderCanvas();\r\n } else {\r\n await renderSvg();\r\n }\r\n } catch (e) {\r\n error.value = e instanceof Error ? e : new Error(String(e));\r\n console.error(\"二维码渲染失败:\", e);\r\n } finally {\r\n loading.value = false;\r\n }\r\n }\r\n\r\n async function toDataURL(\r\n type: ExportType = \"png\",\r\n quality = 0.92,\r\n ): Promise<string> {\r\n if (type === \"svg\") {\r\n const svgStr = await QRCode.toString(options.value.value, {\r\n ...qrOptions.value,\r\n type: \"svg\",\r\n });\r\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;\r\n }\r\n const tempCanvas = document.createElement(\"canvas\");\r\n await QRCode.toCanvas(tempCanvas, options.value.value, qrOptions.value);\r\n if (options.logo.value) {\r\n await drawLogo(tempCanvas, options.logo.value, options.size.value);\r\n }\r\n const mimeType = type === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\r\n return tempCanvas.toDataURL(mimeType, quality);\r\n }\r\n\r\n async function download(filename = \"qrcode\", type: ExportType = \"png\") {\r\n const dataUrl = await toDataURL(type);\r\n const link = document.createElement(\"a\");\r\n link.download = `${filename}.${type}`;\r\n link.href = dataUrl;\r\n link.click();\r\n }\r\n\r\n watch(\r\n [\r\n options.value,\r\n options.size,\r\n options.color,\r\n options.bgColor,\r\n options.errorCorrectionLevel,\r\n options.margin,\r\n options.mode,\r\n options.logo,\r\n ],\r\n () => render(),\r\n { deep: true },\r\n );\r\n\r\n return { svgHtml, error, loading, render, toDataURL, download };\r\n}\r\n","/* unplugin-vue-components disabled */<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-06-16\n * @Description: 二维码组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-qrcode\">\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\n <div\n v-if=\"mode === 'svg'\"\n class=\"qrcode-svg\"\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\n v-html=\"svgHtml\"\n />\n </div>\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\n {{ label }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, toRef, watch, onMounted } from \"vue\";\nimport { useQRCode } from \"./composables/useQRCode\";\nimport type {\n ErrorCorrectionLevel,\n ExportType,\n LogoOptions,\n QRCodeExpose,\n RenderMode,\n} from \"./types\";\n\ndefineOptions({ name: \"C_QRCode\" });\n\ninterface Props {\n value: string;\n size?: number;\n color?: string;\n bgColor?: string;\n errorCorrectionLevel?: ErrorCorrectionLevel;\n margin?: number;\n mode?: RenderMode;\n logo?: LogoOptions;\n showBorder?: boolean;\n label?: string;\n showLabel?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 200,\n color: \"#000000\",\n bgColor: \"#FFFFFF\",\n errorCorrectionLevel: \"M\",\n margin: 2,\n mode: \"canvas\",\n logo: undefined,\n showBorder: true,\n label: \"\",\n showLabel: false,\n});\n\nconst emit = defineEmits<{\n error: [error: Error];\n}>();\n\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\n\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\n value: toRef(props, \"value\"),\n size: toRef(props, \"size\"),\n color: toRef(props, \"color\"),\n bgColor: toRef(props, \"bgColor\"),\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\n margin: toRef(props, \"margin\"),\n mode: toRef(props, \"mode\"),\n logo: toRef(props, \"logo\"),\n});\n\nwatch(error, (e) => {\n if (e) emit(\"error\", e);\n});\n\nonMounted(() => render());\n\ndefineExpose<QRCodeExpose>({\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\n download: (filename?: string, type?: ExportType) => download(filename, type),\n refresh: () => render(),\n});\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"./index.scss\";\n</style>\n","/* unplugin-vue-components disabled */<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-06-16\n * @Description: 二维码组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-qrcode\">\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\n <div\n v-if=\"mode === 'svg'\"\n class=\"qrcode-svg\"\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\n v-html=\"svgHtml\"\n />\n </div>\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\n {{ label }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, toRef, watch, onMounted } from \"vue\";\nimport { useQRCode } from \"./composables/useQRCode\";\nimport type {\n ErrorCorrectionLevel,\n ExportType,\n LogoOptions,\n QRCodeExpose,\n RenderMode,\n} from \"./types\";\n\ndefineOptions({ name: \"C_QRCode\" });\n\ninterface Props {\n value: string;\n size?: number;\n color?: string;\n bgColor?: string;\n errorCorrectionLevel?: ErrorCorrectionLevel;\n margin?: number;\n mode?: RenderMode;\n logo?: LogoOptions;\n showBorder?: boolean;\n label?: string;\n showLabel?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 200,\n color: \"#000000\",\n bgColor: \"#FFFFFF\",\n errorCorrectionLevel: \"M\",\n margin: 2,\n mode: \"canvas\",\n logo: undefined,\n showBorder: true,\n label: \"\",\n showLabel: false,\n});\n\nconst emit = defineEmits<{\n error: [error: Error];\n}>();\n\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\n\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\n value: toRef(props, \"value\"),\n size: toRef(props, \"size\"),\n color: toRef(props, \"color\"),\n bgColor: toRef(props, \"bgColor\"),\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\n margin: toRef(props, \"margin\"),\n mode: toRef(props, \"mode\"),\n logo: toRef(props, \"logo\"),\n});\n\nwatch(error, (e) => {\n if (e) emit(\"error\", e);\n});\n\nonMounted(() => render());\n\ndefineExpose<QRCodeExpose>({\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\n download: (filename?: string, type?: ExportType) => download(filename, type),\n refresh: () => render(),\n});\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"./index.scss\";\n</style>\n","<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-06-16\n * @Description: 二维码组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-qrcode\">\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\n <div\n v-if=\"mode === 'svg'\"\n class=\"qrcode-svg\"\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\n v-html=\"svgHtml\"\n />\n </div>\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\n {{ label }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, toRef, watch, onMounted } from \"vue\";\nimport { useQRCode } from \"./composables/useQRCode\";\nimport type {\n ErrorCorrectionLevel,\n ExportType,\n LogoOptions,\n QRCodeExpose,\n RenderMode,\n} from \"./types\";\n\ndefineOptions({ name: \"C_QRCode\" });\n\ninterface Props {\n value: string;\n size?: number;\n color?: string;\n bgColor?: string;\n errorCorrectionLevel?: ErrorCorrectionLevel;\n margin?: number;\n mode?: RenderMode;\n logo?: LogoOptions;\n showBorder?: boolean;\n label?: string;\n showLabel?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 200,\n color: \"#000000\",\n bgColor: \"#FFFFFF\",\n errorCorrectionLevel: \"M\",\n margin: 2,\n mode: \"canvas\",\n logo: undefined,\n showBorder: true,\n label: \"\",\n showLabel: false,\n});\n\nconst emit = defineEmits<{\n error: [error: Error];\n}>();\n\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\n\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\n value: toRef(props, \"value\"),\n size: toRef(props, \"size\"),\n color: toRef(props, \"color\"),\n bgColor: toRef(props, \"bgColor\"),\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\n margin: toRef(props, \"margin\"),\n mode: toRef(props, \"mode\"),\n logo: toRef(props, \"logo\"),\n});\n\nwatch(error, (e) => {\n if (e) emit(\"error\", e);\n});\n\nonMounted(() => render());\n\ndefineExpose<QRCodeExpose>({\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\n download: (filename?: string, type?: ExportType) => download(filename, type),\n refresh: () => render(),\n});\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"./index.scss\";\n</style>\n"],"mappings":"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"}
package/dist/C_QRCode2.js CHANGED
@@ -1,2 +1,2 @@
1
- import{t as e}from"./export-helper.js";import{computed as t,createCommentVNode as n,createElementBlock as r,createElementVNode as i,defineComponent as a,normalizeClass as o,normalizeStyle as s,onMounted as c,openBlock as l,ref as u,toDisplayString as d,toRef as f,unref as p,vShow as m,watch as h,withDirectives as g}from"vue";import _ from"qrcode";function v(e,t,n){return new Promise((r,i)=>{let a=e.getContext(`2d`);if(!a)return i(Error(`Canvas context 不可用`));let o=new Image;o.crossOrigin=`anonymous`,o.onload=()=>{let i=t.size??.2,s=Math.floor(n*i),c=t.padding??4,l=t.borderRadius??4,u=(e.width-s)/2,d=(e.height-s)/2;a.save();let f=u-c,p=d-c,m=s+c*2,h=l+c;a.beginPath(),a.moveTo(f+h,p),a.arcTo(f+m,p,f+m,p+m,h),a.arcTo(f+m,p+m,f,p+m,h),a.arcTo(f,p+m,f,p,h),a.arcTo(f,p,f+m,p,h),a.closePath(),a.fillStyle=t.bgColor??`#ffffff`,a.fill(),a.beginPath(),a.moveTo(u+l,d),a.arcTo(u+s,d,u+s,d+s,l),a.arcTo(u+s,d+s,u,d+s,l),a.arcTo(u,d+s,u,d,l),a.arcTo(u,d,u+s,d,l),a.closePath(),a.clip(),a.drawImage(o,u,d,s,s),a.restore(),r()},o.onerror=()=>i(Error(`Logo 加载失败: ${t.src}`)),o.src=t.src})}function y(e,n){let r=u(``),i=u(null),a=u(!1),o=t(()=>{if(n.logo.value){let e=n.errorCorrectionLevel.value;return e===`L`||e===`M`?`Q`:e}return n.errorCorrectionLevel.value}),s=t(()=>({width:n.size.value,margin:n.margin.value,errorCorrectionLevel:o.value,color:{dark:n.color.value,light:n.bgColor.value}}));async function c(){let t=e.value;!t||!n.value.value||(await _.toCanvas(t,n.value.value,s.value),n.logo.value&&await v(t,n.logo.value,n.size.value))}async function l(){if(!n.value.value){r.value=``;return}r.value=await _.toString(n.value.value,{...s.value,type:`svg`})}async function d(){if(n.value.value){a.value=!0,i.value=null;try{n.mode.value===`canvas`?await c():await l()}catch(e){i.value=e instanceof Error?e:Error(String(e)),console.error(`二维码渲染失败:`,e)}finally{a.value=!1}}}async function f(e=`png`,t=.92){if(e===`svg`){let e=await _.toString(n.value.value,{...s.value,type:`svg`});return`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`}let r=document.createElement(`canvas`);await _.toCanvas(r,n.value.value,s.value),n.logo.value&&await v(r,n.logo.value,n.size.value);let i=e===`jpeg`?`image/jpeg`:`image/png`;return r.toDataURL(i,t)}async function p(e=`qrcode`,t=`png`){let n=await f(t),r=document.createElement(`a`);r.download=`${e}.${t}`,r.href=n,r.click()}return h([n.value,n.size,n.color,n.bgColor,n.errorCorrectionLevel,n.margin,n.mode,n.logo],()=>d(),{deep:!0}),{svgHtml:r,error:i,loading:a,render:d,toDataURL:f,download:p}}const b={class:`c-qrcode`},x=[`innerHTML`],S={key:0,class:`qrcode-label`};var C=e(a({name:`C_QRCode`,__name:`index`,props:{value:{},size:{default:200},color:{default:`#000000`},bgColor:{default:`#FFFFFF`},errorCorrectionLevel:{default:`M`},margin:{default:2},mode:{default:`canvas`},logo:{default:void 0},showBorder:{type:Boolean,default:!0},label:{default:``},showLabel:{type:Boolean,default:!1}},emits:[`error`],setup(e,{expose:t,emit:a}){let _=e,v=a,C=u(null),{svgHtml:w,error:T,render:E,toDataURL:D,download:O}=y(C,{value:f(_,`value`),size:f(_,`size`),color:f(_,`color`),bgColor:f(_,`bgColor`),errorCorrectionLevel:f(_,`errorCorrectionLevel`),margin:f(_,`margin`),mode:f(_,`mode`),logo:f(_,`logo`)});return h(T,e=>{e&&v(`error`,e)}),c(()=>E()),t({toDataURL:(e,t)=>D(e,t),download:(e,t)=>O(e,t),refresh:()=>E()}),(e,t)=>(l(),r(`div`,b,[i(`div`,{class:o([`qrcode-wrapper`,{"with-border":e.showBorder}])},[g(i(`canvas`,{ref_key:`canvasRef`,ref:C},null,512),[[m,e.mode===`canvas`]]),e.mode===`svg`?(l(),r(`div`,{key:0,class:`qrcode-svg`,style:s({width:`${e.size}px`,height:`${e.size}px`}),innerHTML:p(w)},null,12,x)):n(`v-if`,!0)],2),e.showLabel&&e.label?(l(),r(`div`,S,d(e.label),1)):n(`v-if`,!0)]))}}),[[`__scopeId`,`data-v-7a4201b5`]]);export{y as n,C as t};
1
+ import{t as e}from"./export-helper.js";import{computed as t,createCommentVNode as n,createElementBlock as r,createElementVNode as i,defineComponent as a,normalizeClass as o,normalizeStyle as s,onMounted as c,openBlock as l,ref as u,toDisplayString as d,toRef as f,unref as p,vShow as m,watch as h,withDirectives as g}from"vue";import _ from"qrcode";function v(e,t,n){return new Promise((r,i)=>{let a=e.getContext(`2d`);if(!a)return i(Error(`Canvas context 不可用`));let o=new Image;o.crossOrigin=`anonymous`,o.onload=()=>{let i=t.size??.2,s=Math.floor(n*i),c=t.padding??4,l=t.borderRadius??4,u=(e.width-s)/2,d=(e.height-s)/2;a.save();let f=u-c,p=d-c,m=s+c*2,h=l+c;a.beginPath(),a.moveTo(f+h,p),a.arcTo(f+m,p,f+m,p+m,h),a.arcTo(f+m,p+m,f,p+m,h),a.arcTo(f,p+m,f,p,h),a.arcTo(f,p,f+m,p,h),a.closePath(),a.fillStyle=t.bgColor??`#ffffff`,a.fill(),a.beginPath(),a.moveTo(u+l,d),a.arcTo(u+s,d,u+s,d+s,l),a.arcTo(u+s,d+s,u,d+s,l),a.arcTo(u,d+s,u,d,l),a.arcTo(u,d,u+s,d,l),a.closePath(),a.clip(),a.drawImage(o,u,d,s,s),a.restore(),r()},o.onerror=()=>i(Error(`Logo 加载失败: ${t.src}`)),o.src=t.src})}function y(e,n){let r=u(``),i=u(null),a=u(!1),o=t(()=>{if(n.logo.value){let e=n.errorCorrectionLevel.value;return e===`L`||e===`M`?`Q`:e}return n.errorCorrectionLevel.value}),s=t(()=>({width:n.size.value,margin:n.margin.value,errorCorrectionLevel:o.value,color:{dark:n.color.value,light:n.bgColor.value}}));async function c(){let t=e.value;!t||!n.value.value||(await _.toCanvas(t,n.value.value,s.value),n.logo.value&&await v(t,n.logo.value,n.size.value))}async function l(){if(!n.value.value){r.value=``;return}r.value=await _.toString(n.value.value,{...s.value,type:`svg`})}async function d(){if(n.value.value){a.value=!0,i.value=null;try{n.mode.value===`canvas`?await c():await l()}catch(e){i.value=e instanceof Error?e:Error(String(e)),console.error(`二维码渲染失败:`,e)}finally{a.value=!1}}}async function f(e=`png`,t=.92){if(e===`svg`){let e=await _.toString(n.value.value,{...s.value,type:`svg`});return`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`}let r=document.createElement(`canvas`);await _.toCanvas(r,n.value.value,s.value),n.logo.value&&await v(r,n.logo.value,n.size.value);let i=e===`jpeg`?`image/jpeg`:`image/png`;return r.toDataURL(i,t)}async function p(e=`qrcode`,t=`png`){let n=await f(t),r=document.createElement(`a`);r.download=`${e}.${t}`,r.href=n,r.click()}return h([n.value,n.size,n.color,n.bgColor,n.errorCorrectionLevel,n.margin,n.mode,n.logo],()=>d(),{deep:!0}),{svgHtml:r,error:i,loading:a,render:d,toDataURL:f,download:p}}const b={class:`c-qrcode`},x=[`innerHTML`],S={key:0,class:`qrcode-label`};var C=e(a({name:`C_QRCode`,__name:`index`,props:{value:{},size:{default:200},color:{default:`#000000`},bgColor:{default:`#FFFFFF`},errorCorrectionLevel:{default:`M`},margin:{default:2},mode:{default:`canvas`},logo:{default:void 0},showBorder:{type:Boolean,default:!0},label:{default:``},showLabel:{type:Boolean,default:!1}},emits:[`error`],setup(e,{expose:t,emit:a}){let _=e,v=a,C=u(null),{svgHtml:w,error:T,render:E,toDataURL:D,download:O}=y(C,{value:f(_,`value`),size:f(_,`size`),color:f(_,`color`),bgColor:f(_,`bgColor`),errorCorrectionLevel:f(_,`errorCorrectionLevel`),margin:f(_,`margin`),mode:f(_,`mode`),logo:f(_,`logo`)});return h(T,e=>{e&&v(`error`,e)}),c(()=>E()),t({toDataURL:(e,t)=>D(e,t),download:(e,t)=>O(e,t),refresh:()=>E()}),(e,t)=>(l(),r(`div`,b,[i(`div`,{class:o([`qrcode-wrapper`,{"with-border":e.showBorder}])},[g(i(`canvas`,{ref_key:`canvasRef`,ref:C},null,512),[[m,e.mode===`canvas`]]),e.mode===`svg`?(l(),r(`div`,{key:0,class:`qrcode-svg`,style:s({width:`${e.size}px`,height:`${e.size}px`}),innerHTML:p(w)},null,12,x)):n(`v-if`,!0)],2),e.showLabel&&e.label?(l(),r(`div`,S,d(e.label),1)):n(`v-if`,!0)]))}}),[[`__scopeId`,`data-v-fe18e0fe`]]);export{y as n,C as t};
2
2
  //# sourceMappingURL=C_QRCode2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"C_QRCode2.js","names":["showBorder","mode","size","showLabel","label"],"sources":["../src/components/C_QRCode/composables/useQRCode.ts","../src/components/C_QRCode/index.vue","../src/components/C_QRCode/index.vue","../src/components/C_QRCode/index.vue"],"sourcesContent":["import { ref, computed, watch } from \"vue\";\r\nimport QRCode from \"qrcode\";\r\nimport type { Ref } from \"vue\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n RenderMode,\r\n} from \"../types\";\r\n\r\ninterface UseQRCodeOptions {\r\n value: Ref<string>;\r\n size: Ref<number>;\r\n color: Ref<string>;\r\n bgColor: Ref<string>;\r\n errorCorrectionLevel: Ref<ErrorCorrectionLevel>;\r\n margin: Ref<number>;\r\n mode: Ref<RenderMode>;\r\n logo: Ref<LogoOptions | undefined>;\r\n}\r\n\r\nfunction drawLogo(\r\n canvas: HTMLCanvasElement,\r\n logo: LogoOptions,\r\n qrSize: number,\r\n): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return reject(new Error(\"Canvas context 不可用\"));\r\n\r\n const img = new Image();\r\n img.crossOrigin = \"anonymous\";\r\n img.onload = () => {\r\n const ratio = logo.size ?? 0.2;\r\n const logoSize = Math.floor(qrSize * ratio);\r\n const padding = logo.padding ?? 4;\r\n const borderRadius = logo.borderRadius ?? 4;\r\n\r\n const x = (canvas.width - logoSize) / 2;\r\n const y = (canvas.height - logoSize) / 2;\r\n\r\n ctx.save();\r\n const bgX = x - padding;\r\n const bgY = y - padding;\r\n const bgSize = logoSize + padding * 2;\r\n const r = borderRadius + padding;\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(bgX + r, bgY);\r\n ctx.arcTo(bgX + bgSize, bgY, bgX + bgSize, bgY + bgSize, r);\r\n ctx.arcTo(bgX + bgSize, bgY + bgSize, bgX, bgY + bgSize, r);\r\n ctx.arcTo(bgX, bgY + bgSize, bgX, bgY, r);\r\n ctx.arcTo(bgX, bgY, bgX + bgSize, bgY, r);\r\n ctx.closePath();\r\n ctx.fillStyle = logo.bgColor ?? \"#ffffff\";\r\n ctx.fill();\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(x + borderRadius, y);\r\n ctx.arcTo(x + logoSize, y, x + logoSize, y + logoSize, borderRadius);\r\n ctx.arcTo(x + logoSize, y + logoSize, x, y + logoSize, borderRadius);\r\n ctx.arcTo(x, y + logoSize, x, y, borderRadius);\r\n ctx.arcTo(x, y, x + logoSize, y, borderRadius);\r\n ctx.closePath();\r\n ctx.clip();\r\n ctx.drawImage(img, x, y, logoSize, logoSize);\r\n ctx.restore();\r\n\r\n resolve();\r\n };\r\n img.onerror = () => reject(new Error(`Logo 加载失败: ${logo.src}`));\r\n img.src = logo.src;\r\n });\r\n}\r\n\r\nexport function useQRCode(\r\n canvasRef: Ref<HTMLCanvasElement | null>,\r\n options: UseQRCodeOptions,\r\n) {\r\n const svgHtml = ref(\"\");\r\n const error = ref<Error | null>(null);\r\n const loading = ref(false);\r\n\r\n const effectiveLevel = computed<ErrorCorrectionLevel>(() => {\r\n if (options.logo.value) {\r\n const level = options.errorCorrectionLevel.value;\r\n if (level === \"L\" || level === \"M\") return \"Q\";\r\n return level;\r\n }\r\n return options.errorCorrectionLevel.value;\r\n });\r\n\r\n const qrOptions = computed(() => ({\r\n width: options.size.value,\r\n margin: options.margin.value,\r\n errorCorrectionLevel: effectiveLevel.value,\r\n color: {\r\n dark: options.color.value,\r\n light: options.bgColor.value,\r\n },\r\n }));\r\n\r\n async function renderCanvas() {\r\n const canvas = canvasRef.value;\r\n if (!canvas || !options.value.value) return;\r\n await QRCode.toCanvas(canvas, options.value.value, qrOptions.value);\r\n if (options.logo.value) {\r\n await drawLogo(canvas, options.logo.value, options.size.value);\r\n }\r\n }\r\n\r\n async function renderSvg() {\r\n if (!options.value.value) {\r\n svgHtml.value = \"\";\r\n return;\r\n }\r\n const svgString = await QRCode.toString(options.value.value, {\r\n ...qrOptions.value,\r\n type: \"svg\",\r\n });\r\n svgHtml.value = svgString;\r\n }\r\n\r\n async function render() {\r\n if (!options.value.value) return;\r\n loading.value = true;\r\n error.value = null;\r\n try {\r\n if (options.mode.value === \"canvas\") {\r\n await renderCanvas();\r\n } else {\r\n await renderSvg();\r\n }\r\n } catch (e) {\r\n error.value = e instanceof Error ? e : new Error(String(e));\r\n console.error(\"二维码渲染失败:\", e);\r\n } finally {\r\n loading.value = false;\r\n }\r\n }\r\n\r\n async function toDataURL(\r\n type: ExportType = \"png\",\r\n quality = 0.92,\r\n ): Promise<string> {\r\n if (type === \"svg\") {\r\n const svgStr = await QRCode.toString(options.value.value, {\r\n ...qrOptions.value,\r\n type: \"svg\",\r\n });\r\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;\r\n }\r\n const tempCanvas = document.createElement(\"canvas\");\r\n await QRCode.toCanvas(tempCanvas, options.value.value, qrOptions.value);\r\n if (options.logo.value) {\r\n await drawLogo(tempCanvas, options.logo.value, options.size.value);\r\n }\r\n const mimeType = type === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\r\n return tempCanvas.toDataURL(mimeType, quality);\r\n }\r\n\r\n async function download(filename = \"qrcode\", type: ExportType = \"png\") {\r\n const dataUrl = await toDataURL(type);\r\n const link = document.createElement(\"a\");\r\n link.download = `${filename}.${type}`;\r\n link.href = dataUrl;\r\n link.click();\r\n }\r\n\r\n watch(\r\n [\r\n options.value,\r\n options.size,\r\n options.color,\r\n options.bgColor,\r\n options.errorCorrectionLevel,\r\n options.margin,\r\n options.mode,\r\n options.logo,\r\n ],\r\n () => render(),\r\n { deep: true },\r\n );\r\n\r\n return { svgHtml, error, loading, render, toDataURL, download };\r\n}\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-16\r\n * @Description: 二维码组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-qrcode\">\r\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\r\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\r\n <div\r\n v-if=\"mode === 'svg'\"\r\n class=\"qrcode-svg\"\r\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\r\n v-html=\"svgHtml\"\r\n />\r\n </div>\r\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\r\n {{ label }}\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRef, watch, onMounted } from \"vue\";\r\nimport { useQRCode } from \"./composables/useQRCode\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n QRCodeExpose,\r\n RenderMode,\r\n} from \"./types\";\r\n\r\ndefineOptions({ name: \"C_QRCode\" });\r\n\r\ninterface Props {\r\n value: string;\r\n size?: number;\r\n color?: string;\r\n bgColor?: string;\r\n errorCorrectionLevel?: ErrorCorrectionLevel;\r\n margin?: number;\r\n mode?: RenderMode;\r\n logo?: LogoOptions;\r\n showBorder?: boolean;\r\n label?: string;\r\n showLabel?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n size: 200,\r\n color: \"#000000\",\r\n bgColor: \"#FFFFFF\",\r\n errorCorrectionLevel: \"M\",\r\n margin: 2,\r\n mode: \"canvas\",\r\n logo: undefined,\r\n showBorder: true,\r\n label: \"\",\r\n showLabel: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n error: [error: Error];\r\n}>();\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\n\r\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\r\n value: toRef(props, \"value\"),\r\n size: toRef(props, \"size\"),\r\n color: toRef(props, \"color\"),\r\n bgColor: toRef(props, \"bgColor\"),\r\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\r\n margin: toRef(props, \"margin\"),\r\n mode: toRef(props, \"mode\"),\r\n logo: toRef(props, \"logo\"),\r\n});\r\n\r\nwatch(error, (e) => {\r\n if (e) emit(\"error\", e);\r\n});\r\n\r\nonMounted(() => render());\r\n\r\ndefineExpose<QRCodeExpose>({\r\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\r\n download: (filename?: string, type?: ExportType) => download(filename, type),\r\n refresh: () => render(),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-16\r\n * @Description: 二维码组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-qrcode\">\r\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\r\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\r\n <div\r\n v-if=\"mode === 'svg'\"\r\n class=\"qrcode-svg\"\r\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\r\n v-html=\"svgHtml\"\r\n />\r\n </div>\r\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\r\n {{ label }}\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRef, watch, onMounted } from \"vue\";\r\nimport { useQRCode } from \"./composables/useQRCode\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n QRCodeExpose,\r\n RenderMode,\r\n} from \"./types\";\r\n\r\ndefineOptions({ name: \"C_QRCode\" });\r\n\r\ninterface Props {\r\n value: string;\r\n size?: number;\r\n color?: string;\r\n bgColor?: string;\r\n errorCorrectionLevel?: ErrorCorrectionLevel;\r\n margin?: number;\r\n mode?: RenderMode;\r\n logo?: LogoOptions;\r\n showBorder?: boolean;\r\n label?: string;\r\n showLabel?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n size: 200,\r\n color: \"#000000\",\r\n bgColor: \"#FFFFFF\",\r\n errorCorrectionLevel: \"M\",\r\n margin: 2,\r\n mode: \"canvas\",\r\n logo: undefined,\r\n showBorder: true,\r\n label: \"\",\r\n showLabel: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n error: [error: Error];\r\n}>();\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\n\r\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\r\n value: toRef(props, \"value\"),\r\n size: toRef(props, \"size\"),\r\n color: toRef(props, \"color\"),\r\n bgColor: toRef(props, \"bgColor\"),\r\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\r\n margin: toRef(props, \"margin\"),\r\n mode: toRef(props, \"mode\"),\r\n logo: toRef(props, \"logo\"),\r\n});\r\n\r\nwatch(error, (e) => {\r\n if (e) emit(\"error\", e);\r\n});\r\n\r\nonMounted(() => render());\r\n\r\ndefineExpose<QRCodeExpose>({\r\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\r\n download: (filename?: string, type?: ExportType) => download(filename, type),\r\n refresh: () => render(),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-16\r\n * @Description: 二维码组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-qrcode\">\r\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\r\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\r\n <div\r\n v-if=\"mode === 'svg'\"\r\n class=\"qrcode-svg\"\r\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\r\n v-html=\"svgHtml\"\r\n />\r\n </div>\r\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\r\n {{ label }}\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRef, watch, onMounted } from \"vue\";\r\nimport { useQRCode } from \"./composables/useQRCode\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n QRCodeExpose,\r\n RenderMode,\r\n} from \"./types\";\r\n\r\ndefineOptions({ name: \"C_QRCode\" });\r\n\r\ninterface Props {\r\n value: string;\r\n size?: number;\r\n color?: string;\r\n bgColor?: string;\r\n errorCorrectionLevel?: ErrorCorrectionLevel;\r\n margin?: number;\r\n mode?: RenderMode;\r\n logo?: LogoOptions;\r\n showBorder?: boolean;\r\n label?: string;\r\n showLabel?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n size: 200,\r\n color: \"#000000\",\r\n bgColor: \"#FFFFFF\",\r\n errorCorrectionLevel: \"M\",\r\n margin: 2,\r\n mode: \"canvas\",\r\n logo: undefined,\r\n showBorder: true,\r\n label: \"\",\r\n showLabel: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n error: [error: Error];\r\n}>();\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\n\r\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\r\n value: toRef(props, \"value\"),\r\n size: toRef(props, \"size\"),\r\n color: toRef(props, \"color\"),\r\n bgColor: toRef(props, \"bgColor\"),\r\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\r\n margin: toRef(props, \"margin\"),\r\n mode: toRef(props, \"mode\"),\r\n logo: toRef(props, \"logo\"),\r\n});\r\n\r\nwatch(error, (e) => {\r\n if (e) emit(\"error\", e);\r\n});\r\n\r\nonMounted(() => render());\r\n\r\ndefineExpose<QRCodeExpose>({\r\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\r\n download: (filename?: string, type?: ExportType) => download(filename, type),\r\n refresh: () => render(),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n"],"mappings":"6VAqBA,SAAS,EACP,EACA,EACA,EACe,CACf,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,EAAM,EAAO,WAAW,KAAK,CACnC,GAAI,CAAC,EAAK,OAAO,EAAW,MAAM,qBAAqB,CAAC,CAExD,IAAM,EAAM,IAAI,MAChB,EAAI,YAAc,YAClB,EAAI,WAAe,CACjB,IAAM,EAAQ,EAAK,MAAQ,GACrB,EAAW,KAAK,MAAM,EAAS,EAAM,CACrC,EAAU,EAAK,SAAW,EAC1B,EAAe,EAAK,cAAgB,EAEpC,GAAK,EAAO,MAAQ,GAAY,EAChC,GAAK,EAAO,OAAS,GAAY,EAEvC,EAAI,MAAM,CACV,IAAM,EAAM,EAAI,EACV,EAAM,EAAI,EACV,EAAS,EAAW,EAAU,EAC9B,EAAI,EAAe,EAEzB,EAAI,WAAW,CACf,EAAI,OAAO,EAAM,EAAG,EAAI,CACxB,EAAI,MAAM,EAAM,EAAQ,EAAK,EAAM,EAAQ,EAAM,EAAQ,EAAE,CAC3D,EAAI,MAAM,EAAM,EAAQ,EAAM,EAAQ,EAAK,EAAM,EAAQ,EAAE,CAC3D,EAAI,MAAM,EAAK,EAAM,EAAQ,EAAK,EAAK,EAAE,CACzC,EAAI,MAAM,EAAK,EAAK,EAAM,EAAQ,EAAK,EAAE,CACzC,EAAI,WAAW,CACf,EAAI,UAAY,EAAK,SAAW,UAChC,EAAI,MAAM,CAEV,EAAI,WAAW,CACf,EAAI,OAAO,EAAI,EAAc,EAAE,CAC/B,EAAI,MAAM,EAAI,EAAU,EAAG,EAAI,EAAU,EAAI,EAAU,EAAa,CACpE,EAAI,MAAM,EAAI,EAAU,EAAI,EAAU,EAAG,EAAI,EAAU,EAAa,CACpE,EAAI,MAAM,EAAG,EAAI,EAAU,EAAG,EAAG,EAAa,CAC9C,EAAI,MAAM,EAAG,EAAG,EAAI,EAAU,EAAG,EAAa,CAC9C,EAAI,WAAW,CACf,EAAI,MAAM,CACV,EAAI,UAAU,EAAK,EAAG,EAAG,EAAU,EAAS,CAC5C,EAAI,SAAS,CAEb,GAAS,EAEX,EAAI,YAAgB,EAAW,MAAM,cAAc,EAAK,MAAM,CAAC,CAC/D,EAAI,IAAM,EAAK,KACf,CAGJ,SAAgB,EACd,EACA,EACA,CACA,IAAM,EAAU,EAAI,GAAG,CACjB,EAAQ,EAAkB,KAAK,CAC/B,EAAU,EAAI,GAAM,CAEpB,EAAiB,MAAqC,CAC1D,GAAI,EAAQ,KAAK,MAAO,CACtB,IAAM,EAAQ,EAAQ,qBAAqB,MAE3C,OADI,IAAU,KAAO,IAAU,IAAY,IACpC,EAET,OAAO,EAAQ,qBAAqB,OACpC,CAEI,EAAY,OAAgB,CAChC,MAAO,EAAQ,KAAK,MACpB,OAAQ,EAAQ,OAAO,MACvB,qBAAsB,EAAe,MACrC,MAAO,CACL,KAAM,EAAQ,MAAM,MACpB,MAAO,EAAQ,QAAQ,MACxB,CACF,EAAE,CAEH,eAAe,GAAe,CAC5B,IAAM,EAAS,EAAU,MACrB,CAAC,GAAU,CAAC,EAAQ,MAAM,QAC9B,MAAM,EAAO,SAAS,EAAQ,EAAQ,MAAM,MAAO,EAAU,MAAM,CAC/D,EAAQ,KAAK,OACf,MAAM,EAAS,EAAQ,EAAQ,KAAK,MAAO,EAAQ,KAAK,MAAM,EAIlE,eAAe,GAAY,CACzB,GAAI,CAAC,EAAQ,MAAM,MAAO,CACxB,EAAQ,MAAQ,GAChB,OAMF,EAAQ,MAJU,MAAM,EAAO,SAAS,EAAQ,MAAM,MAAO,CAC3D,GAAG,EAAU,MACb,KAAM,MACP,CAAC,CAIJ,eAAe,GAAS,CACjB,KAAQ,MAAM,MAEnB,CADA,EAAQ,MAAQ,GAChB,EAAM,MAAQ,KACd,GAAI,CACE,EAAQ,KAAK,QAAU,SACzB,MAAM,GAAc,CAEpB,MAAM,GAAW,OAEZ,EAAG,CACV,EAAM,MAAQ,aAAa,MAAQ,EAAQ,MAAM,OAAO,EAAE,CAAC,CAC3D,QAAQ,MAAM,WAAY,EAAE,QACpB,CACR,EAAQ,MAAQ,KAIpB,eAAe,EACb,EAAmB,MACnB,EAAU,IACO,CACjB,GAAI,IAAS,MAAO,CAClB,IAAM,EAAS,MAAM,EAAO,SAAS,EAAQ,MAAM,MAAO,CACxD,GAAG,EAAU,MACb,KAAM,MACP,CAAC,CACF,MAAO,oCAAoC,mBAAmB,EAAO,GAEvE,IAAM,EAAa,SAAS,cAAc,SAAS,CACnD,MAAM,EAAO,SAAS,EAAY,EAAQ,MAAM,MAAO,EAAU,MAAM,CACnE,EAAQ,KAAK,OACf,MAAM,EAAS,EAAY,EAAQ,KAAK,MAAO,EAAQ,KAAK,MAAM,CAEpE,IAAM,EAAW,IAAS,OAAS,aAAe,YAClD,OAAO,EAAW,UAAU,EAAU,EAAQ,CAGhD,eAAe,EAAS,EAAW,SAAU,EAAmB,MAAO,CACrE,IAAM,EAAU,MAAM,EAAU,EAAK,CAC/B,EAAO,SAAS,cAAc,IAAI,CACxC,EAAK,SAAW,GAAG,EAAS,GAAG,IAC/B,EAAK,KAAO,EACZ,EAAK,OAAO,CAkBd,OAfA,EACE,CACE,EAAQ,MACR,EAAQ,KACR,EAAQ,MACR,EAAQ,QACR,EAAQ,qBACR,EAAQ,OACR,EAAQ,KACR,EAAQ,KACT,KACK,GAAQ,CACd,CAAE,KAAM,GAAM,CACf,CAEM,CAAE,UAAS,QAAO,UAAS,SAAQ,YAAW,WAAU,0bGrIjE,IAAM,EAAQ,EAaR,EAAO,EAIP,EAAY,EAA8B,KAAK,CAE/C,CAAE,UAAS,QAAO,SAAQ,YAAW,YAAa,EAAU,EAAW,CAC3E,MAAO,EAAM,EAAO,QAAQ,CAC5B,KAAM,EAAM,EAAO,OAAO,CAC1B,MAAO,EAAM,EAAO,QAAQ,CAC5B,QAAS,EAAM,EAAO,UAAU,CAChC,qBAAsB,EAAM,EAAO,uBAAuB,CAC1D,OAAQ,EAAM,EAAO,SAAS,CAC9B,KAAM,EAAM,EAAO,OAAO,CAC1B,KAAM,EAAM,EAAO,OAAO,CAC3B,CAAC,QAEF,EAAM,EAAQ,GAAM,CACd,GAAG,EAAK,QAAS,EAAE,EACvB,CAEF,MAAgB,GAAQ,CAAC,CAEzB,EAA2B,CACzB,WAAY,EAAmB,IAAqB,EAAU,EAAM,EAAQ,CAC5E,UAAW,EAAmB,IAAsB,EAAS,EAAU,EAAK,CAC5E,YAAe,GAAQ,CACxB,CAAC,aAnFA,EAaM,MAbN,EAaM,CAZJ,EAQM,MAAA,CARD,MAAK,EAAA,CAAC,iBAAgB,CAAA,cAA0BA,EAAAA,WAAU,CAAA,CAAA,CAAA,CAAA,CAAA,EAC7D,EAAqD,SAAA,SAAd,YAAJ,IAAI,iBAAvBC,EAAAA,OAAI,SAAA,CAAA,CAAA,CAEZA,EAAAA,OAAI,OAAA,GAAA,CADZ,EAKE,MAAA,OAHA,MAAM,aACL,MAAK,EAAA,CAAA,MAAA,GAAcC,EAAAA,KAAI,IAAA,OAAA,GAAiBA,EAAAA,KAAI,IAAA,CAAA,CAC7C,UAAQ,EAAA,EAAA,+BAGDC,EAAAA,WAAaC,EAAAA,OAAAA,GAAAA,CAAxB,EAEM,MAFN,EAEM,EADDA,EAAAA,MAAK,CAAA,EAAA,EAAA,EAAA,OAAA,GAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"C_QRCode2.js","names":["showBorder","mode","size","showLabel","label"],"sources":["../src/components/C_QRCode/composables/useQRCode.ts","../src/components/C_QRCode/index.vue","../src/components/C_QRCode/index.vue","../src/components/C_QRCode/index.vue"],"sourcesContent":["import { ref, computed, watch } from \"vue\";\r\nimport QRCode from \"qrcode\";\r\nimport type { Ref } from \"vue\";\r\nimport type {\r\n ErrorCorrectionLevel,\r\n ExportType,\r\n LogoOptions,\r\n RenderMode,\r\n} from \"../types\";\r\n\r\ninterface UseQRCodeOptions {\r\n value: Ref<string>;\r\n size: Ref<number>;\r\n color: Ref<string>;\r\n bgColor: Ref<string>;\r\n errorCorrectionLevel: Ref<ErrorCorrectionLevel>;\r\n margin: Ref<number>;\r\n mode: Ref<RenderMode>;\r\n logo: Ref<LogoOptions | undefined>;\r\n}\r\n\r\nfunction drawLogo(\r\n canvas: HTMLCanvasElement,\r\n logo: LogoOptions,\r\n qrSize: number,\r\n): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return reject(new Error(\"Canvas context 不可用\"));\r\n\r\n const img = new Image();\r\n img.crossOrigin = \"anonymous\";\r\n img.onload = () => {\r\n const ratio = logo.size ?? 0.2;\r\n const logoSize = Math.floor(qrSize * ratio);\r\n const padding = logo.padding ?? 4;\r\n const borderRadius = logo.borderRadius ?? 4;\r\n\r\n const x = (canvas.width - logoSize) / 2;\r\n const y = (canvas.height - logoSize) / 2;\r\n\r\n ctx.save();\r\n const bgX = x - padding;\r\n const bgY = y - padding;\r\n const bgSize = logoSize + padding * 2;\r\n const r = borderRadius + padding;\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(bgX + r, bgY);\r\n ctx.arcTo(bgX + bgSize, bgY, bgX + bgSize, bgY + bgSize, r);\r\n ctx.arcTo(bgX + bgSize, bgY + bgSize, bgX, bgY + bgSize, r);\r\n ctx.arcTo(bgX, bgY + bgSize, bgX, bgY, r);\r\n ctx.arcTo(bgX, bgY, bgX + bgSize, bgY, r);\r\n ctx.closePath();\r\n ctx.fillStyle = logo.bgColor ?? \"#ffffff\";\r\n ctx.fill();\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(x + borderRadius, y);\r\n ctx.arcTo(x + logoSize, y, x + logoSize, y + logoSize, borderRadius);\r\n ctx.arcTo(x + logoSize, y + logoSize, x, y + logoSize, borderRadius);\r\n ctx.arcTo(x, y + logoSize, x, y, borderRadius);\r\n ctx.arcTo(x, y, x + logoSize, y, borderRadius);\r\n ctx.closePath();\r\n ctx.clip();\r\n ctx.drawImage(img, x, y, logoSize, logoSize);\r\n ctx.restore();\r\n\r\n resolve();\r\n };\r\n img.onerror = () => reject(new Error(`Logo 加载失败: ${logo.src}`));\r\n img.src = logo.src;\r\n });\r\n}\r\n\r\nexport function useQRCode(\r\n canvasRef: Ref<HTMLCanvasElement | null>,\r\n options: UseQRCodeOptions,\r\n) {\r\n const svgHtml = ref(\"\");\r\n const error = ref<Error | null>(null);\r\n const loading = ref(false);\r\n\r\n const effectiveLevel = computed<ErrorCorrectionLevel>(() => {\r\n if (options.logo.value) {\r\n const level = options.errorCorrectionLevel.value;\r\n if (level === \"L\" || level === \"M\") return \"Q\";\r\n return level;\r\n }\r\n return options.errorCorrectionLevel.value;\r\n });\r\n\r\n const qrOptions = computed(() => ({\r\n width: options.size.value,\r\n margin: options.margin.value,\r\n errorCorrectionLevel: effectiveLevel.value,\r\n color: {\r\n dark: options.color.value,\r\n light: options.bgColor.value,\r\n },\r\n }));\r\n\r\n async function renderCanvas() {\r\n const canvas = canvasRef.value;\r\n if (!canvas || !options.value.value) return;\r\n await QRCode.toCanvas(canvas, options.value.value, qrOptions.value);\r\n if (options.logo.value) {\r\n await drawLogo(canvas, options.logo.value, options.size.value);\r\n }\r\n }\r\n\r\n async function renderSvg() {\r\n if (!options.value.value) {\r\n svgHtml.value = \"\";\r\n return;\r\n }\r\n const svgString = await QRCode.toString(options.value.value, {\r\n ...qrOptions.value,\r\n type: \"svg\",\r\n });\r\n svgHtml.value = svgString;\r\n }\r\n\r\n async function render() {\r\n if (!options.value.value) return;\r\n loading.value = true;\r\n error.value = null;\r\n try {\r\n if (options.mode.value === \"canvas\") {\r\n await renderCanvas();\r\n } else {\r\n await renderSvg();\r\n }\r\n } catch (e) {\r\n error.value = e instanceof Error ? e : new Error(String(e));\r\n console.error(\"二维码渲染失败:\", e);\r\n } finally {\r\n loading.value = false;\r\n }\r\n }\r\n\r\n async function toDataURL(\r\n type: ExportType = \"png\",\r\n quality = 0.92,\r\n ): Promise<string> {\r\n if (type === \"svg\") {\r\n const svgStr = await QRCode.toString(options.value.value, {\r\n ...qrOptions.value,\r\n type: \"svg\",\r\n });\r\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;\r\n }\r\n const tempCanvas = document.createElement(\"canvas\");\r\n await QRCode.toCanvas(tempCanvas, options.value.value, qrOptions.value);\r\n if (options.logo.value) {\r\n await drawLogo(tempCanvas, options.logo.value, options.size.value);\r\n }\r\n const mimeType = type === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\r\n return tempCanvas.toDataURL(mimeType, quality);\r\n }\r\n\r\n async function download(filename = \"qrcode\", type: ExportType = \"png\") {\r\n const dataUrl = await toDataURL(type);\r\n const link = document.createElement(\"a\");\r\n link.download = `${filename}.${type}`;\r\n link.href = dataUrl;\r\n link.click();\r\n }\r\n\r\n watch(\r\n [\r\n options.value,\r\n options.size,\r\n options.color,\r\n options.bgColor,\r\n options.errorCorrectionLevel,\r\n options.margin,\r\n options.mode,\r\n options.logo,\r\n ],\r\n () => render(),\r\n { deep: true },\r\n );\r\n\r\n return { svgHtml, error, loading, render, toDataURL, download };\r\n}\r\n","/* unplugin-vue-components disabled */<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-06-16\n * @Description: 二维码组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-qrcode\">\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\n <div\n v-if=\"mode === 'svg'\"\n class=\"qrcode-svg\"\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\n v-html=\"svgHtml\"\n />\n </div>\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\n {{ label }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, toRef, watch, onMounted } from \"vue\";\nimport { useQRCode } from \"./composables/useQRCode\";\nimport type {\n ErrorCorrectionLevel,\n ExportType,\n LogoOptions,\n QRCodeExpose,\n RenderMode,\n} from \"./types\";\n\ndefineOptions({ name: \"C_QRCode\" });\n\ninterface Props {\n value: string;\n size?: number;\n color?: string;\n bgColor?: string;\n errorCorrectionLevel?: ErrorCorrectionLevel;\n margin?: number;\n mode?: RenderMode;\n logo?: LogoOptions;\n showBorder?: boolean;\n label?: string;\n showLabel?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 200,\n color: \"#000000\",\n bgColor: \"#FFFFFF\",\n errorCorrectionLevel: \"M\",\n margin: 2,\n mode: \"canvas\",\n logo: undefined,\n showBorder: true,\n label: \"\",\n showLabel: false,\n});\n\nconst emit = defineEmits<{\n error: [error: Error];\n}>();\n\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\n\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\n value: toRef(props, \"value\"),\n size: toRef(props, \"size\"),\n color: toRef(props, \"color\"),\n bgColor: toRef(props, \"bgColor\"),\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\n margin: toRef(props, \"margin\"),\n mode: toRef(props, \"mode\"),\n logo: toRef(props, \"logo\"),\n});\n\nwatch(error, (e) => {\n if (e) emit(\"error\", e);\n});\n\nonMounted(() => render());\n\ndefineExpose<QRCodeExpose>({\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\n download: (filename?: string, type?: ExportType) => download(filename, type),\n refresh: () => render(),\n});\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"./index.scss\";\n</style>\n","/* unplugin-vue-components disabled */<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-06-16\n * @Description: 二维码组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-qrcode\">\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\n <div\n v-if=\"mode === 'svg'\"\n class=\"qrcode-svg\"\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\n v-html=\"svgHtml\"\n />\n </div>\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\n {{ label }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, toRef, watch, onMounted } from \"vue\";\nimport { useQRCode } from \"./composables/useQRCode\";\nimport type {\n ErrorCorrectionLevel,\n ExportType,\n LogoOptions,\n QRCodeExpose,\n RenderMode,\n} from \"./types\";\n\ndefineOptions({ name: \"C_QRCode\" });\n\ninterface Props {\n value: string;\n size?: number;\n color?: string;\n bgColor?: string;\n errorCorrectionLevel?: ErrorCorrectionLevel;\n margin?: number;\n mode?: RenderMode;\n logo?: LogoOptions;\n showBorder?: boolean;\n label?: string;\n showLabel?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 200,\n color: \"#000000\",\n bgColor: \"#FFFFFF\",\n errorCorrectionLevel: \"M\",\n margin: 2,\n mode: \"canvas\",\n logo: undefined,\n showBorder: true,\n label: \"\",\n showLabel: false,\n});\n\nconst emit = defineEmits<{\n error: [error: Error];\n}>();\n\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\n\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\n value: toRef(props, \"value\"),\n size: toRef(props, \"size\"),\n color: toRef(props, \"color\"),\n bgColor: toRef(props, \"bgColor\"),\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\n margin: toRef(props, \"margin\"),\n mode: toRef(props, \"mode\"),\n logo: toRef(props, \"logo\"),\n});\n\nwatch(error, (e) => {\n if (e) emit(\"error\", e);\n});\n\nonMounted(() => render());\n\ndefineExpose<QRCodeExpose>({\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\n download: (filename?: string, type?: ExportType) => download(filename, type),\n refresh: () => render(),\n});\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"./index.scss\";\n</style>\n","<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-06-16\n * @Description: 二维码组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-qrcode\">\n <div class=\"qrcode-wrapper\" :class=\"{ 'with-border': showBorder }\">\n <canvas v-show=\"mode === 'canvas'\" ref=\"canvasRef\" />\n <div\n v-if=\"mode === 'svg'\"\n class=\"qrcode-svg\"\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\n v-html=\"svgHtml\"\n />\n </div>\n <div v-if=\"showLabel && label\" class=\"qrcode-label\">\n {{ label }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, toRef, watch, onMounted } from \"vue\";\nimport { useQRCode } from \"./composables/useQRCode\";\nimport type {\n ErrorCorrectionLevel,\n ExportType,\n LogoOptions,\n QRCodeExpose,\n RenderMode,\n} from \"./types\";\n\ndefineOptions({ name: \"C_QRCode\" });\n\ninterface Props {\n value: string;\n size?: number;\n color?: string;\n bgColor?: string;\n errorCorrectionLevel?: ErrorCorrectionLevel;\n margin?: number;\n mode?: RenderMode;\n logo?: LogoOptions;\n showBorder?: boolean;\n label?: string;\n showLabel?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 200,\n color: \"#000000\",\n bgColor: \"#FFFFFF\",\n errorCorrectionLevel: \"M\",\n margin: 2,\n mode: \"canvas\",\n logo: undefined,\n showBorder: true,\n label: \"\",\n showLabel: false,\n});\n\nconst emit = defineEmits<{\n error: [error: Error];\n}>();\n\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\n\nconst { svgHtml, error, render, toDataURL, download } = useQRCode(canvasRef, {\n value: toRef(props, \"value\"),\n size: toRef(props, \"size\"),\n color: toRef(props, \"color\"),\n bgColor: toRef(props, \"bgColor\"),\n errorCorrectionLevel: toRef(props, \"errorCorrectionLevel\"),\n margin: toRef(props, \"margin\"),\n mode: toRef(props, \"mode\"),\n logo: toRef(props, \"logo\"),\n});\n\nwatch(error, (e) => {\n if (e) emit(\"error\", e);\n});\n\nonMounted(() => render());\n\ndefineExpose<QRCodeExpose>({\n toDataURL: (type?: ExportType, quality?: number) => toDataURL(type, quality),\n download: (filename?: string, type?: ExportType) => download(filename, type),\n refresh: () => render(),\n});\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"./index.scss\";\n</style>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"C_Signature-B5fFz6RD.css","names":[],"sources":["../src/components/C_Signature/index.vue?vue&type=style&index=0&scoped=1c733bfe&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* 电子签名组件样式 */\n.c-signature[data-v-1c733bfe] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 100%;\n}\n.c-signature .signature-toolbar[data-v-1c733bfe] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n background: var(--c-bg-card, #fff);\n border: 1px solid var(--c-border, #e1e4e8);\n border-radius: var(--c-radius, 6px);\n flex-wrap: wrap;\n}\n.c-signature .signature-toolbar .toolbar-section[data-v-1c733bfe] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.c-signature .signature-toolbar .toolbar-section.divider[data-v-1c733bfe] {\n width: 1px;\n height: 24px;\n background: var(--c-border, #e1e4e8);\n margin: 0 4px;\n}\n.c-signature .signature-toolbar .toolbar-section .section-label[data-v-1c733bfe] {\n font-size: 13px;\n color: var(--c-text-3, #999);\n font-weight: 500;\n}\n.c-signature .signature-canvas-wrapper[data-v-1c733bfe] {\n position: relative;\n border: 1px solid var(--c-border, #e1e4e8);\n border-radius: var(--c-radius, 6px);\n overflow: hidden;\n background: var(--c-bg, #fff);\n transition: border-color 0.3s;\n}\n.c-signature .signature-canvas-wrapper[data-v-1c733bfe]:hover {\n border-color: var(--c-primary, #409eff);\n}\n.c-signature .signature-canvas-wrapper.disabled[data-v-1c733bfe] {\n opacity: 0.6;\n cursor: not-allowed;\n border-color: var(--c-border, #e1e4e8);\n}\n.c-signature .signature-canvas-wrapper.readonly[data-v-1c733bfe] {\n cursor: default;\n}\n.c-signature .signature-canvas-wrapper .signature-canvas[data-v-1c733bfe] {\n display: block;\n width: 100%;\n height: 100%;\n cursor: crosshair;\n touch-action: none;\n}\n.c-signature .signature-canvas-wrapper .signature-canvas.disabled[data-v-1c733bfe], .c-signature .signature-canvas-wrapper .signature-canvas.readonly[data-v-1c733bfe] {\n cursor: not-allowed;\n}\n.c-signature .signature-canvas-wrapper .canvas-placeholder[data-v-1c733bfe] {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: var(--c-text-4, #ccc);\n font-size: 14px;\n pointer-events: none;\n user-select: none;\n}\n.c-signature .signature-actions[data-v-1c733bfe] {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"C_Signature-PN4GOLRX.css","names":[],"sources":["../src/components/C_Signature/index.vue?vue&type=style&index=0&scoped=fbc647a9&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* 电子签名组件样式 */\n.c-signature[data-v-fbc647a9] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 100%;\n}\n.c-signature .signature-toolbar[data-v-fbc647a9] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n background: var(--c-bg-card, #fff);\n border: 1px solid var(--c-border, #e1e4e8);\n border-radius: var(--c-radius, 6px);\n flex-wrap: wrap;\n}\n.c-signature .signature-toolbar .toolbar-section[data-v-fbc647a9] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.c-signature .signature-toolbar .toolbar-section.divider[data-v-fbc647a9] {\n width: 1px;\n height: 24px;\n background: var(--c-border, #e1e4e8);\n margin: 0 4px;\n}\n.c-signature .signature-toolbar .toolbar-section .section-label[data-v-fbc647a9] {\n font-size: 13px;\n color: var(--c-text-3, #999);\n font-weight: 500;\n}\n.c-signature .signature-canvas-wrapper[data-v-fbc647a9] {\n position: relative;\n border: 1px solid var(--c-border, #e1e4e8);\n border-radius: var(--c-radius, 6px);\n overflow: hidden;\n background: var(--c-bg, #fff);\n transition: border-color 0.3s;\n}\n.c-signature .signature-canvas-wrapper[data-v-fbc647a9]:hover {\n border-color: var(--c-primary, #409eff);\n}\n.c-signature .signature-canvas-wrapper.disabled[data-v-fbc647a9] {\n opacity: 0.6;\n cursor: not-allowed;\n border-color: var(--c-border, #e1e4e8);\n}\n.c-signature .signature-canvas-wrapper.readonly[data-v-fbc647a9] {\n cursor: default;\n}\n.c-signature .signature-canvas-wrapper .signature-canvas[data-v-fbc647a9] {\n display: block;\n width: 100%;\n height: 100%;\n cursor: crosshair;\n touch-action: none;\n}\n.c-signature .signature-canvas-wrapper .signature-canvas.disabled[data-v-fbc647a9], .c-signature .signature-canvas-wrapper .signature-canvas.readonly[data-v-fbc647a9] {\n cursor: not-allowed;\n}\n.c-signature .signature-canvas-wrapper .canvas-placeholder[data-v-fbc647a9] {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: var(--c-text-4, #ccc);\n font-size: 14px;\n pointer-events: none;\n user-select: none;\n}\n.c-signature .signature-actions[data-v-fbc647a9] {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
@@ -1 +1 @@
1
- {"version":3,"file":"C_Signature-es-ZNPzr.css","names":[],"sources":["../src/components/C_Signature/index.vue?vue&type=style&index=0&scoped=1c733bfe&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* 电子签名组件样式 */\n.c-signature[data-v-1c733bfe] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 100%;\n}\n.c-signature .signature-toolbar[data-v-1c733bfe] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n background: var(--c-bg-card, #fff);\n border: 1px solid var(--c-border, #e1e4e8);\n border-radius: var(--c-radius, 6px);\n flex-wrap: wrap;\n}\n.c-signature .signature-toolbar .toolbar-section[data-v-1c733bfe] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.c-signature .signature-toolbar .toolbar-section.divider[data-v-1c733bfe] {\n width: 1px;\n height: 24px;\n background: var(--c-border, #e1e4e8);\n margin: 0 4px;\n}\n.c-signature .signature-toolbar .toolbar-section .section-label[data-v-1c733bfe] {\n font-size: 13px;\n color: var(--c-text-3, #999);\n font-weight: 500;\n}\n.c-signature .signature-canvas-wrapper[data-v-1c733bfe] {\n position: relative;\n border: 1px solid var(--c-border, #e1e4e8);\n border-radius: var(--c-radius, 6px);\n overflow: hidden;\n background: var(--c-bg, #fff);\n transition: border-color 0.3s;\n}\n.c-signature .signature-canvas-wrapper[data-v-1c733bfe]:hover {\n border-color: var(--c-primary, #409eff);\n}\n.c-signature .signature-canvas-wrapper.disabled[data-v-1c733bfe] {\n opacity: 0.6;\n cursor: not-allowed;\n border-color: var(--c-border, #e1e4e8);\n}\n.c-signature .signature-canvas-wrapper.readonly[data-v-1c733bfe] {\n cursor: default;\n}\n.c-signature .signature-canvas-wrapper .signature-canvas[data-v-1c733bfe] {\n display: block;\n width: 100%;\n height: 100%;\n cursor: crosshair;\n touch-action: none;\n}\n.c-signature .signature-canvas-wrapper .signature-canvas.disabled[data-v-1c733bfe], .c-signature .signature-canvas-wrapper .signature-canvas.readonly[data-v-1c733bfe] {\n cursor: not-allowed;\n}\n.c-signature .signature-canvas-wrapper .canvas-placeholder[data-v-1c733bfe] {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: var(--c-text-4, #ccc);\n font-size: 14px;\n pointer-events: none;\n user-select: none;\n}\n.c-signature .signature-actions[data-v-1c733bfe] {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"C_Signature-fWQqGIZ-.css","names":[],"sources":["../src/components/C_Signature/index.vue?vue&type=style&index=0&scoped=fbc647a9&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* 电子签名组件样式 */\n.c-signature[data-v-fbc647a9] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 100%;\n}\n.c-signature .signature-toolbar[data-v-fbc647a9] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n background: var(--c-bg-card, #fff);\n border: 1px solid var(--c-border, #e1e4e8);\n border-radius: var(--c-radius, 6px);\n flex-wrap: wrap;\n}\n.c-signature .signature-toolbar .toolbar-section[data-v-fbc647a9] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.c-signature .signature-toolbar .toolbar-section.divider[data-v-fbc647a9] {\n width: 1px;\n height: 24px;\n background: var(--c-border, #e1e4e8);\n margin: 0 4px;\n}\n.c-signature .signature-toolbar .toolbar-section .section-label[data-v-fbc647a9] {\n font-size: 13px;\n color: var(--c-text-3, #999);\n font-weight: 500;\n}\n.c-signature .signature-canvas-wrapper[data-v-fbc647a9] {\n position: relative;\n border: 1px solid var(--c-border, #e1e4e8);\n border-radius: var(--c-radius, 6px);\n overflow: hidden;\n background: var(--c-bg, #fff);\n transition: border-color 0.3s;\n}\n.c-signature .signature-canvas-wrapper[data-v-fbc647a9]:hover {\n border-color: var(--c-primary, #409eff);\n}\n.c-signature .signature-canvas-wrapper.disabled[data-v-fbc647a9] {\n opacity: 0.6;\n cursor: not-allowed;\n border-color: var(--c-border, #e1e4e8);\n}\n.c-signature .signature-canvas-wrapper.readonly[data-v-fbc647a9] {\n cursor: default;\n}\n.c-signature .signature-canvas-wrapper .signature-canvas[data-v-fbc647a9] {\n display: block;\n width: 100%;\n height: 100%;\n cursor: crosshair;\n touch-action: none;\n}\n.c-signature .signature-canvas-wrapper .signature-canvas.disabled[data-v-fbc647a9], .c-signature .signature-canvas-wrapper .signature-canvas.readonly[data-v-fbc647a9] {\n cursor: not-allowed;\n}\n.c-signature .signature-canvas-wrapper .canvas-placeholder[data-v-fbc647a9] {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: var(--c-text-4, #ccc);\n font-size: 14px;\n pointer-events: none;\n user-select: none;\n}\n.c-signature .signature-actions[data-v-fbc647a9] {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}