@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_ImageCropper2.js","names":["ratioPresets","currentRatio","$emit","circular","previewData"],"sources":["../src/components/C_ImageCropper/composables/useCropperCore.ts","../src/components/C_ImageCropper/components/CropperToolbar.vue","../src/components/C_ImageCropper/components/CropperToolbar.vue","../src/components/C_ImageCropper/components/CropperToolbar.vue","../src/components/C_ImageCropper/components/CropperPreview.vue","../src/components/C_ImageCropper/components/CropperPreview.vue","../src/components/C_ImageCropper/components/CropperPreview.vue","../src/components/C_ImageCropper/index.vue","../src/components/C_ImageCropper/index.vue","../src/components/C_ImageCropper/index.vue"],"sourcesContent":["import { ref } from \"vue\";\r\nimport type { Ref } from \"vue\";\r\nimport type { CropOutputFormat, CropResult } from \"../types\";\r\n\r\ninterface UseCropperCoreOptions {\r\n format?: Ref<CropOutputFormat>;\r\n quality?: Ref<number>;\r\n maxWidth?: Ref<number>;\r\n maxHeight?: Ref<number>;\r\n}\r\n\r\nexport function useCropperCore(options: UseCropperCoreOptions = {}) {\r\n const cropperRef = ref<any>(null);\r\n\r\n function rotateLeft() {\r\n cropperRef.value?.rotateLeft();\r\n }\r\n\r\n function rotateRight() {\r\n cropperRef.value?.rotateRight();\r\n }\r\n\r\n function rotate(angle: number) {\r\n const steps = Math.round(angle / 90);\r\n const fn = steps > 0 ? rotateRight : rotateLeft;\r\n for (let i = 0; i < Math.abs(steps); i++) fn();\r\n }\r\n\r\n function zoom(scale: number) {\r\n cropperRef.value?.changeScale(scale > 0 ? 1 : -1);\r\n }\r\n\r\n function flipX() {\r\n const el = cropperRef.value?.$refs?.img;\r\n if (!el) return;\r\n const current = el.style.transform || \"\";\r\n if (current.includes(\"scaleX(-1)\")) {\r\n el.style.transform = current.replace(\"scaleX(-1)\", \"scaleX(1)\");\r\n } else {\r\n el.style.transform =\r\n current.replace(/scaleX\\([^)]*\\)/, \"\") + \" scaleX(-1)\";\r\n }\r\n }\r\n\r\n function flipY() {\r\n const el = cropperRef.value?.$refs?.img;\r\n if (!el) return;\r\n const current = el.style.transform || \"\";\r\n if (current.includes(\"scaleY(-1)\")) {\r\n el.style.transform = current.replace(\"scaleY(-1)\", \"scaleY(1)\");\r\n } else {\r\n el.style.transform =\r\n current.replace(/scaleY\\([^)]*\\)/, \"\") + \" scaleY(-1)\";\r\n }\r\n }\r\n\r\n function reset() {\r\n cropperRef.value?.refresh();\r\n }\r\n\r\n function constrainSize(w: number, h: number) {\r\n let ow = w;\r\n let oh = h;\r\n const maxW = options.maxWidth?.value ?? 0;\r\n const maxH = options.maxHeight?.value ?? 0;\r\n\r\n if (maxW > 0 && ow > maxW) {\r\n const ratio = maxW / ow;\r\n ow = maxW;\r\n oh = Math.round(oh * ratio);\r\n }\r\n if (maxH > 0 && oh > maxH) {\r\n const ratio = maxH / oh;\r\n oh = maxH;\r\n ow = Math.round(ow * ratio);\r\n }\r\n return { width: ow, height: oh };\r\n }\r\n\r\n function getCropResult(): Promise<CropResult> {\r\n return new Promise((resolve, reject) => {\r\n const cropper = cropperRef.value;\r\n if (!cropper) return reject(new Error(\"Cropper not initialized\"));\r\n\r\n const format = options.format?.value ?? \"png\";\r\n const quality = options.quality?.value ?? 0.92;\r\n const mime =\r\n format === \"jpeg\"\r\n ? \"image/jpeg\"\r\n : format === \"webp\"\r\n ? \"image/webp\"\r\n : \"image/png\";\r\n\r\n cropper.getCropData((base64: string) => {\r\n cropper.getCropBlob((blob: Blob) => {\r\n const img = new Image();\r\n img.onload = () => {\r\n const { width, height } = constrainSize(img.width, img.height);\r\n\r\n if (width !== img.width || height !== img.height) {\r\n const canvas = document.createElement(\"canvas\");\r\n canvas.width = width;\r\n canvas.height = height;\r\n const ctx = canvas.getContext(\"2d\")!;\r\n ctx.drawImage(img, 0, 0, width, height);\r\n const constrainedBase64 = canvas.toDataURL(mime, quality);\r\n canvas.toBlob(\r\n (constrainedBlob) => {\r\n resolve({\r\n base64: constrainedBase64,\r\n blob: constrainedBlob!,\r\n width,\r\n height,\r\n format,\r\n });\r\n },\r\n mime,\r\n format === \"png\" ? undefined : quality,\r\n );\r\n } else {\r\n resolve({\r\n base64,\r\n blob,\r\n width: img.width,\r\n height: img.height,\r\n format,\r\n });\r\n }\r\n };\r\n img.onerror = () => reject(new Error(\"Failed to load crop result\"));\r\n img.src = base64;\r\n });\r\n });\r\n });\r\n }\r\n\r\n return {\r\n cropperRef,\r\n rotate,\r\n rotateLeft,\r\n rotateRight,\r\n zoom,\r\n flipX,\r\n flipY,\r\n reset,\r\n getCropResult,\r\n };\r\n}\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"cropper-toolbar\">\r\n <NButtonGroup size=\"small\">\r\n <NButton\r\n v-for=\"item in ratioPresets\"\r\n :key=\"item.value\"\r\n :type=\"currentRatio === item.value ? 'primary' : 'default'\"\r\n @click=\"$emit('ratio', item.value)\"\r\n >\r\n {{ item.label }}\r\n </NButton>\r\n </NButtonGroup>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', -90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-left\" /></template>\r\n </NButton>\r\n </template>\r\n 逆时针旋转 90°\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', 90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-right\" /></template>\r\n </NButton>\r\n </template>\r\n 顺时针旋转 90°\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipX')\">\r\n <template #icon><C_Icon name=\"mdi:flip-horizontal\" /></template>\r\n </NButton>\r\n </template>\r\n 水平翻转\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipY')\">\r\n <template #icon><C_Icon name=\"mdi:flip-vertical\" /></template>\r\n </NButton>\r\n </template>\r\n 垂直翻转\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', 0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-plus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 放大\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', -0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-minus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 缩小\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('reset')\">\r\n <template #icon><C_Icon name=\"mdi:refresh\" /></template>\r\n </NButton>\r\n </template>\r\n 重置\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NButton, NButtonGroup, NDivider, NTooltip } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { AspectRatioPreset } from \"../types\";\r\n\r\ndefineProps<{\r\n currentRatio: number;\r\n ratioPresets: AspectRatioPreset[];\r\n}>();\r\n\r\ndefineEmits<{\r\n ratio: [value: number];\r\n rotate: [angle: number];\r\n flipX: [];\r\n flipY: [];\r\n zoom: [scale: number];\r\n reset: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-toolbar {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n align-items: center;\r\n padding: 8px 0;\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"cropper-toolbar\">\r\n <NButtonGroup size=\"small\">\r\n <NButton\r\n v-for=\"item in ratioPresets\"\r\n :key=\"item.value\"\r\n :type=\"currentRatio === item.value ? 'primary' : 'default'\"\r\n @click=\"$emit('ratio', item.value)\"\r\n >\r\n {{ item.label }}\r\n </NButton>\r\n </NButtonGroup>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', -90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-left\" /></template>\r\n </NButton>\r\n </template>\r\n 逆时针旋转 90°\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', 90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-right\" /></template>\r\n </NButton>\r\n </template>\r\n 顺时针旋转 90°\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipX')\">\r\n <template #icon><C_Icon name=\"mdi:flip-horizontal\" /></template>\r\n </NButton>\r\n </template>\r\n 水平翻转\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipY')\">\r\n <template #icon><C_Icon name=\"mdi:flip-vertical\" /></template>\r\n </NButton>\r\n </template>\r\n 垂直翻转\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', 0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-plus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 放大\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', -0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-minus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 缩小\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('reset')\">\r\n <template #icon><C_Icon name=\"mdi:refresh\" /></template>\r\n </NButton>\r\n </template>\r\n 重置\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NButton, NButtonGroup, NDivider, NTooltip } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { AspectRatioPreset } from \"../types\";\r\n\r\ndefineProps<{\r\n currentRatio: number;\r\n ratioPresets: AspectRatioPreset[];\r\n}>();\r\n\r\ndefineEmits<{\r\n ratio: [value: number];\r\n rotate: [angle: number];\r\n flipX: [];\r\n flipY: [];\r\n zoom: [scale: number];\r\n reset: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-toolbar {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n align-items: center;\r\n padding: 8px 0;\r\n}\r\n</style>\r\n","<template>\r\n <div class=\"cropper-toolbar\">\r\n <NButtonGroup size=\"small\">\r\n <NButton\r\n v-for=\"item in ratioPresets\"\r\n :key=\"item.value\"\r\n :type=\"currentRatio === item.value ? 'primary' : 'default'\"\r\n @click=\"$emit('ratio', item.value)\"\r\n >\r\n {{ item.label }}\r\n </NButton>\r\n </NButtonGroup>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', -90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-left\" /></template>\r\n </NButton>\r\n </template>\r\n 逆时针旋转 90°\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', 90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-right\" /></template>\r\n </NButton>\r\n </template>\r\n 顺时针旋转 90°\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipX')\">\r\n <template #icon><C_Icon name=\"mdi:flip-horizontal\" /></template>\r\n </NButton>\r\n </template>\r\n 水平翻转\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipY')\">\r\n <template #icon><C_Icon name=\"mdi:flip-vertical\" /></template>\r\n </NButton>\r\n </template>\r\n 垂直翻转\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', 0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-plus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 放大\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', -0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-minus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 缩小\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('reset')\">\r\n <template #icon><C_Icon name=\"mdi:refresh\" /></template>\r\n </NButton>\r\n </template>\r\n 重置\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NButton, NButtonGroup, NDivider, NTooltip } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { AspectRatioPreset } from \"../types\";\r\n\r\ndefineProps<{\r\n currentRatio: number;\r\n ratioPresets: AspectRatioPreset[];\r\n}>();\r\n\r\ndefineEmits<{\r\n ratio: [value: number];\r\n rotate: [angle: number];\r\n flipX: [];\r\n flipY: [];\r\n zoom: [scale: number];\r\n reset: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-toolbar {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n align-items: center;\r\n padding: 8px 0;\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"cropper-preview\">\r\n <div class=\"preview-label\">\r\n <C_Icon name=\"mdi:eye-outline\" />\r\n <span>裁剪预览</span>\r\n </div>\r\n\r\n <div\r\n ref=\"mainBoxRef\"\r\n class=\"preview-main\"\r\n :class=\"{ 'preview-main--circular': circular }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div class=\"preview-main__viewport\" :style=\"mainViewportStyle\">\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"preview\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n <div v-else class=\"preview-empty\">\r\n <C_Icon\r\n name=\"mdi:image-outline\"\r\n style=\"font-size: 32px; opacity: 0.2\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"preview-thumbs\">\r\n <div v-for=\"item in thumbSizes\" :key=\"item.label\" class=\"preview-thumb\">\r\n <div\r\n class=\"preview-thumb__box\"\r\n :class=\"{ 'preview-thumb__box--circular': circular }\"\r\n :style=\"{ width: `${item.size}px`, height: `${item.size}px` }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div\r\n class=\"preview-thumb__viewport\"\r\n :style=\"getThumbViewportStyle(item.size)\"\r\n >\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"thumb\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n <span class=\"preview-thumb__label\">{{ item.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onBeforeUnmount } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\nconst props = defineProps<{\r\n previewData: any;\r\n circular?: boolean;\r\n}>();\r\n\r\nconst mainBoxRef = ref<HTMLElement | null>(null);\r\nconst mainBoxWidth = ref(200);\r\n\r\nconst thumbSizes = [\r\n { label: \"80px\", size: 80 },\r\n { label: \"48px\", size: 48 },\r\n { label: \"32px\", size: 32 },\r\n];\r\n\r\nconst hasValidPreview = computed(() => {\r\n const d = props.previewData;\r\n return d && d.url && d.w > 0 && d.h > 0;\r\n});\r\n\r\nconst mainViewportStyle = computed(() => {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n const zoom = mainBoxWidth.value / w;\r\n return { zoom, overflow: \"hidden\" };\r\n});\r\n\r\nfunction getThumbViewportStyle(size: number) {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n return { zoom: size / w, overflow: \"hidden\" };\r\n}\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n if (mainBoxRef.value) {\r\n mainBoxWidth.value = mainBoxRef.value.clientWidth;\r\n resizeObserver = new ResizeObserver((entries) => {\r\n requestAnimationFrame(() => {\r\n for (const entry of entries) {\r\n mainBoxWidth.value = entry.contentRect.width;\r\n }\r\n });\r\n });\r\n resizeObserver.observe(mainBoxRef.value);\r\n }\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n resizeObserver?.disconnect();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-preview {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 16px;\r\n\r\n .preview-label {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-2);\r\n }\r\n\r\n .preview-main {\r\n width: 100%;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 6px;\r\n background:\r\n linear-gradient(45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(45deg, transparent 75%, #f0f0f0 75%),\r\n linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);\r\n background-position:\r\n 0 0,\r\n 0 8px,\r\n 8px -8px,\r\n -8px 0;\r\n background-size: 16px 16px;\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n }\r\n\r\n .preview-empty {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 100%;\r\n height: 100%;\r\n min-height: 120px;\r\n }\r\n\r\n .preview-thumbs {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-end;\r\n justify-content: center;\r\n }\r\n\r\n .preview-thumb {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n align-items: center;\r\n\r\n &__box {\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n background: var(--body-color);\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n\r\n &__label {\r\n font-size: 11px;\r\n color: var(--text-color-3);\r\n }\r\n }\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"cropper-preview\">\r\n <div class=\"preview-label\">\r\n <C_Icon name=\"mdi:eye-outline\" />\r\n <span>裁剪预览</span>\r\n </div>\r\n\r\n <div\r\n ref=\"mainBoxRef\"\r\n class=\"preview-main\"\r\n :class=\"{ 'preview-main--circular': circular }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div class=\"preview-main__viewport\" :style=\"mainViewportStyle\">\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"preview\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n <div v-else class=\"preview-empty\">\r\n <C_Icon\r\n name=\"mdi:image-outline\"\r\n style=\"font-size: 32px; opacity: 0.2\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"preview-thumbs\">\r\n <div v-for=\"item in thumbSizes\" :key=\"item.label\" class=\"preview-thumb\">\r\n <div\r\n class=\"preview-thumb__box\"\r\n :class=\"{ 'preview-thumb__box--circular': circular }\"\r\n :style=\"{ width: `${item.size}px`, height: `${item.size}px` }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div\r\n class=\"preview-thumb__viewport\"\r\n :style=\"getThumbViewportStyle(item.size)\"\r\n >\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"thumb\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n <span class=\"preview-thumb__label\">{{ item.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onBeforeUnmount } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\nconst props = defineProps<{\r\n previewData: any;\r\n circular?: boolean;\r\n}>();\r\n\r\nconst mainBoxRef = ref<HTMLElement | null>(null);\r\nconst mainBoxWidth = ref(200);\r\n\r\nconst thumbSizes = [\r\n { label: \"80px\", size: 80 },\r\n { label: \"48px\", size: 48 },\r\n { label: \"32px\", size: 32 },\r\n];\r\n\r\nconst hasValidPreview = computed(() => {\r\n const d = props.previewData;\r\n return d && d.url && d.w > 0 && d.h > 0;\r\n});\r\n\r\nconst mainViewportStyle = computed(() => {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n const zoom = mainBoxWidth.value / w;\r\n return { zoom, overflow: \"hidden\" };\r\n});\r\n\r\nfunction getThumbViewportStyle(size: number) {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n return { zoom: size / w, overflow: \"hidden\" };\r\n}\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n if (mainBoxRef.value) {\r\n mainBoxWidth.value = mainBoxRef.value.clientWidth;\r\n resizeObserver = new ResizeObserver((entries) => {\r\n requestAnimationFrame(() => {\r\n for (const entry of entries) {\r\n mainBoxWidth.value = entry.contentRect.width;\r\n }\r\n });\r\n });\r\n resizeObserver.observe(mainBoxRef.value);\r\n }\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n resizeObserver?.disconnect();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-preview {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 16px;\r\n\r\n .preview-label {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-2);\r\n }\r\n\r\n .preview-main {\r\n width: 100%;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 6px;\r\n background:\r\n linear-gradient(45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(45deg, transparent 75%, #f0f0f0 75%),\r\n linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);\r\n background-position:\r\n 0 0,\r\n 0 8px,\r\n 8px -8px,\r\n -8px 0;\r\n background-size: 16px 16px;\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n }\r\n\r\n .preview-empty {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 100%;\r\n height: 100%;\r\n min-height: 120px;\r\n }\r\n\r\n .preview-thumbs {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-end;\r\n justify-content: center;\r\n }\r\n\r\n .preview-thumb {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n align-items: center;\r\n\r\n &__box {\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n background: var(--body-color);\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n\r\n &__label {\r\n font-size: 11px;\r\n color: var(--text-color-3);\r\n }\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <div class=\"cropper-preview\">\r\n <div class=\"preview-label\">\r\n <C_Icon name=\"mdi:eye-outline\" />\r\n <span>裁剪预览</span>\r\n </div>\r\n\r\n <div\r\n ref=\"mainBoxRef\"\r\n class=\"preview-main\"\r\n :class=\"{ 'preview-main--circular': circular }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div class=\"preview-main__viewport\" :style=\"mainViewportStyle\">\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"preview\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n <div v-else class=\"preview-empty\">\r\n <C_Icon\r\n name=\"mdi:image-outline\"\r\n style=\"font-size: 32px; opacity: 0.2\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"preview-thumbs\">\r\n <div v-for=\"item in thumbSizes\" :key=\"item.label\" class=\"preview-thumb\">\r\n <div\r\n class=\"preview-thumb__box\"\r\n :class=\"{ 'preview-thumb__box--circular': circular }\"\r\n :style=\"{ width: `${item.size}px`, height: `${item.size}px` }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div\r\n class=\"preview-thumb__viewport\"\r\n :style=\"getThumbViewportStyle(item.size)\"\r\n >\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"thumb\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n <span class=\"preview-thumb__label\">{{ item.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onBeforeUnmount } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\nconst props = defineProps<{\r\n previewData: any;\r\n circular?: boolean;\r\n}>();\r\n\r\nconst mainBoxRef = ref<HTMLElement | null>(null);\r\nconst mainBoxWidth = ref(200);\r\n\r\nconst thumbSizes = [\r\n { label: \"80px\", size: 80 },\r\n { label: \"48px\", size: 48 },\r\n { label: \"32px\", size: 32 },\r\n];\r\n\r\nconst hasValidPreview = computed(() => {\r\n const d = props.previewData;\r\n return d && d.url && d.w > 0 && d.h > 0;\r\n});\r\n\r\nconst mainViewportStyle = computed(() => {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n const zoom = mainBoxWidth.value / w;\r\n return { zoom, overflow: \"hidden\" };\r\n});\r\n\r\nfunction getThumbViewportStyle(size: number) {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n return { zoom: size / w, overflow: \"hidden\" };\r\n}\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n if (mainBoxRef.value) {\r\n mainBoxWidth.value = mainBoxRef.value.clientWidth;\r\n resizeObserver = new ResizeObserver((entries) => {\r\n requestAnimationFrame(() => {\r\n for (const entry of entries) {\r\n mainBoxWidth.value = entry.contentRect.width;\r\n }\r\n });\r\n });\r\n resizeObserver.observe(mainBoxRef.value);\r\n }\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n resizeObserver?.disconnect();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-preview {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 16px;\r\n\r\n .preview-label {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-2);\r\n }\r\n\r\n .preview-main {\r\n width: 100%;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 6px;\r\n background:\r\n linear-gradient(45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(45deg, transparent 75%, #f0f0f0 75%),\r\n linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);\r\n background-position:\r\n 0 0,\r\n 0 8px,\r\n 8px -8px,\r\n -8px 0;\r\n background-size: 16px 16px;\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n }\r\n\r\n .preview-empty {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 100%;\r\n height: 100%;\r\n min-height: 120px;\r\n }\r\n\r\n .preview-thumbs {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-end;\r\n justify-content: center;\r\n }\r\n\r\n .preview-thumb {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n align-items: center;\r\n\r\n &__box {\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n background: var(--body-color);\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n\r\n &__label {\r\n font-size: 11px;\r\n color: var(--text-color-3);\r\n }\r\n }\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-25\r\n * @Description: 图片裁剪组件(基于 vue-cropper)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NModal\r\n v-if=\"props.modal\"\r\n v-model:show=\"modalVisible\"\r\n preset=\"card\"\r\n :title=\"props.modalTitle || '图片裁剪'\"\r\n :style=\"{ width: '860px' }\"\r\n :mask-closable=\"false\"\r\n :closable=\"true\"\r\n >\r\n <div class=\"c-image-cropper__body\">\r\n <CropperToolbar\r\n v-if=\"props.showToolbar\"\r\n :current-ratio=\"currentRatio\"\r\n :ratio-presets=\"ratioPresets\"\r\n @ratio=\"handleRatio\"\r\n @rotate=\"rotate\"\r\n @flip-x=\"core.flipX\"\r\n @flip-y=\"core.flipY\"\r\n @zoom=\"core.zoom\"\r\n @reset=\"handleReset\"\r\n />\r\n <div class=\"c-image-cropper__workspace\">\r\n <div\r\n class=\"c-image-cropper__canvas\"\r\n :style=\"{ height: containerHeight }\"\r\n >\r\n <VueCropper\r\n v-if=\"imgSrc\"\r\n ref=\"modalCropperRef\"\r\n :img=\"imgSrc\"\r\n :output-size=\"props.outputQuality\"\r\n :output-type=\"vueCropperOutputType\"\r\n :can-scale=\"false\"\r\n :auto-crop=\"true\"\r\n :auto-crop-width=\"autoCropSize.width\"\r\n :auto-crop-height=\"autoCropSize.height\"\r\n :fixed=\"isFixed\"\r\n :fixed-number=\"fixedNumber\"\r\n :center-box=\"true\"\r\n :info=\"true\"\r\n :info-true=\"true\"\r\n :can-move=\"true\"\r\n :can-move-box=\"true\"\r\n :original=\"false\"\r\n :high=\"true\"\r\n :full=\"false\"\r\n :mode=\"'contain'\"\r\n @real-time=\"handleRealTimePreview\"\r\n @img-load=\"onImgLoad\"\r\n />\r\n <div v-else class=\"c-image-cropper__placeholder\">\r\n <C_Icon\r\n name=\"mdi:image-plus-outline\"\r\n style=\"font-size: 48px; opacity: 0.3\"\r\n />\r\n <span>请选择图片</span>\r\n </div>\r\n </div>\r\n <div\r\n v-if=\"props.showPreview && imgSrc\"\r\n class=\"c-image-cropper__preview-panel\"\r\n >\r\n <CropperPreview\r\n :preview-data=\"previewData\"\r\n :circular=\"props.circular\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n <template #footer>\r\n <NSpace justify=\"end\">\r\n <NButton @click=\"handleCancel\">取消</NButton>\r\n <NButton type=\"primary\" :loading=\"exporting\" @click=\"handleConfirm\"\r\n >确认裁剪</NButton\r\n >\r\n </NSpace>\r\n </template>\r\n </NModal>\r\n\r\n <div v-else class=\"c-image-cropper\">\r\n <div class=\"c-image-cropper__body\">\r\n <CropperToolbar\r\n v-if=\"props.showToolbar\"\r\n :current-ratio=\"currentRatio\"\r\n :ratio-presets=\"ratioPresets\"\r\n @ratio=\"handleRatio\"\r\n @rotate=\"rotate\"\r\n @flip-x=\"core.flipX\"\r\n @flip-y=\"core.flipY\"\r\n @zoom=\"core.zoom\"\r\n @reset=\"handleReset\"\r\n />\r\n <div class=\"c-image-cropper__workspace\">\r\n <div\r\n class=\"c-image-cropper__canvas\"\r\n :style=\"{ height: containerHeight }\"\r\n >\r\n <VueCropper\r\n v-if=\"imgSrc\"\r\n ref=\"inlineCropperRef\"\r\n :img=\"imgSrc\"\r\n :output-size=\"props.outputQuality\"\r\n :output-type=\"vueCropperOutputType\"\r\n :can-scale=\"false\"\r\n :auto-crop=\"true\"\r\n :auto-crop-width=\"autoCropSize.width\"\r\n :auto-crop-height=\"autoCropSize.height\"\r\n :fixed=\"isFixed\"\r\n :fixed-number=\"fixedNumber\"\r\n :center-box=\"true\"\r\n :info=\"true\"\r\n :info-true=\"true\"\r\n :can-move=\"true\"\r\n :can-move-box=\"true\"\r\n :original=\"false\"\r\n :high=\"true\"\r\n :full=\"false\"\r\n :mode=\"'contain'\"\r\n @real-time=\"handleRealTimePreview\"\r\n @img-load=\"onImgLoad\"\r\n />\r\n <div v-else class=\"c-image-cropper__placeholder\">\r\n <C_Icon\r\n name=\"mdi:image-plus-outline\"\r\n style=\"font-size: 48px; opacity: 0.3\"\r\n />\r\n <span>请选择图片</span>\r\n </div>\r\n </div>\r\n <div\r\n v-if=\"props.showPreview && imgSrc\"\r\n class=\"c-image-cropper__preview-panel\"\r\n >\r\n <CropperPreview\r\n :preview-data=\"previewData\"\r\n :circular=\"props.circular\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NModal, NButton, NSpace } from \"naive-ui\";\r\nimport { VueCropper } from \"vue-cropper\";\r\nimport C_Icon from \"../C_Icon/index.vue\";\r\nimport type {\r\n AspectRatioPreset,\r\n CropOutputFormat,\r\n ImageCropperExpose,\r\n ImageCropperProps,\r\n} from \"./types\";\r\nimport { useCropperCore } from \"./composables/useCropperCore\";\r\nimport CropperToolbar from \"./components/CropperToolbar.vue\";\r\nimport CropperPreview from \"./components/CropperPreview.vue\";\r\n\r\ndefineOptions({ name: \"C_ImageCropper\" });\r\n\r\nconst props = withDefaults(defineProps<ImageCropperProps>(), {\r\n src: \"\",\r\n aspectRatio: 0,\r\n outputFormat: \"png\",\r\n outputQuality: 0.92,\r\n maxOutputWidth: 0,\r\n maxOutputHeight: 0,\r\n showPreview: true,\r\n showToolbar: true,\r\n circular: false,\r\n disabled: false,\r\n height: \"400px\",\r\n modal: false,\r\n modalTitle: \"图片裁剪\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n crop: [result: any];\r\n ready: [];\r\n error: [error: Event];\r\n confirm: [result: any];\r\n cancel: [];\r\n}>();\r\n\r\nconst imgSrc = ref(props.src);\r\nconst currentRatio = ref(props.aspectRatio);\r\nconst modalVisible = ref(false);\r\nconst exporting = ref(false);\r\nconst previewData = ref<any>(null);\r\n\r\nconst inlineCropperRef = ref<any>(null);\r\nconst modalCropperRef = ref<any>(null);\r\n\r\nconst activeCropperRef = computed(() =>\r\n props.modal ? modalCropperRef.value : inlineCropperRef.value,\r\n);\r\n\r\nconst containerHeight = computed(() =>\r\n typeof props.height === \"number\" ? `${props.height}px` : props.height,\r\n);\r\n\r\nconst vueCropperOutputType = computed(() => {\r\n const map: Record<CropOutputFormat, string> = {\r\n png: \"png\",\r\n jpeg: \"jpeg\",\r\n webp: \"webp\",\r\n };\r\n return map[props.outputFormat as CropOutputFormat] || \"png\";\r\n});\r\n\r\nconst isFixed = computed(() => currentRatio.value > 0);\r\n\r\nconst fixedNumber = computed(() => {\r\n if (currentRatio.value <= 0) return [1, 1];\r\n if (currentRatio.value === 1) return [1, 1];\r\n if (Math.abs(currentRatio.value - 16 / 9) < 0.01) return [16, 9];\r\n if (Math.abs(currentRatio.value - 4 / 3) < 0.01) return [4, 3];\r\n if (Math.abs(currentRatio.value - 3 / 2) < 0.01) return [3, 2];\r\n return [Math.round(currentRatio.value * 100), 100];\r\n});\r\n\r\nconst autoCropSize = computed(() => ({\r\n width: 300,\r\n height: currentRatio.value > 0 ? 300 / currentRatio.value : 200,\r\n}));\r\n\r\nconst ratioPresets: AspectRatioPreset[] = [\r\n { label: \"自由\", value: 0 },\r\n { label: \"1:1\", value: 1 },\r\n { label: \"16:9\", value: 16 / 9 },\r\n { label: \"4:3\", value: 4 / 3 },\r\n { label: \"3:2\", value: 3 / 2 },\r\n];\r\n\r\nconst formatRef = computed(() => props.outputFormat as CropOutputFormat);\r\nconst qualityRef = computed(() => props.outputQuality);\r\nconst maxWRef = computed(() => props.maxOutputWidth);\r\nconst maxHRef = computed(() => props.maxOutputHeight);\r\n\r\nconst core = useCropperCore({\r\n format: formatRef,\r\n quality: qualityRef,\r\n maxWidth: maxWRef,\r\n maxHeight: maxHRef,\r\n});\r\n\r\nwatch(activeCropperRef, (v) => {\r\n core.cropperRef.value = v;\r\n});\r\n\r\nfunction handleRealTimePreview(data: any) {\r\n previewData.value = data;\r\n}\r\nfunction onImgLoad(status: string) {\r\n if (status === \"success\") emit(\"ready\");\r\n}\r\nfunction rotate(angle: number) {\r\n core.rotate(angle);\r\n}\r\nfunction handleRatio(v: number) {\r\n currentRatio.value = v;\r\n}\r\nfunction handleReset() {\r\n core.reset();\r\n}\r\n\r\nasync function handleConfirm() {\r\n exporting.value = true;\r\n try {\r\n const result = await core.getCropResult();\r\n emit(\"confirm\", result);\r\n emit(\"crop\", result);\r\n modalVisible.value = false;\r\n } finally {\r\n exporting.value = false;\r\n }\r\n}\r\n\r\nfunction handleCancel() {\r\n modalVisible.value = false;\r\n emit(\"cancel\");\r\n}\r\n\r\nfunction loadFile(file: File) {\r\n const reader = new FileReader();\r\n reader.onload = (e) => {\r\n imgSrc.value = e.target?.result as string;\r\n };\r\n reader.readAsDataURL(file);\r\n}\r\n\r\nwatch(\r\n () => props.src,\r\n (v) => {\r\n imgSrc.value = v;\r\n },\r\n);\r\nwatch(\r\n () => props.aspectRatio,\r\n (v) => {\r\n currentRatio.value = v;\r\n },\r\n);\r\n\r\ndefineExpose<ImageCropperExpose>({\r\n getCropResult: core.getCropResult,\r\n rotate: core.rotate,\r\n zoom: core.zoom,\r\n flipX: core.flipX,\r\n flipY: core.flipY,\r\n reset: core.reset,\r\n setAspectRatio: (r: number) => {\r\n currentRatio.value = r;\r\n },\r\n loadFile,\r\n open: (src?: string) => {\r\n if (src) imgSrc.value = src;\r\n modalVisible.value = true;\r\n },\r\n close: () => {\r\n modalVisible.value = false;\r\n },\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: 2026-02-25\r\n * @Description: 图片裁剪组件(基于 vue-cropper)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NModal\r\n v-if=\"props.modal\"\r\n v-model:show=\"modalVisible\"\r\n preset=\"card\"\r\n :title=\"props.modalTitle || '图片裁剪'\"\r\n :style=\"{ width: '860px' }\"\r\n :mask-closable=\"false\"\r\n :closable=\"true\"\r\n >\r\n <div class=\"c-image-cropper__body\">\r\n <CropperToolbar\r\n v-if=\"props.showToolbar\"\r\n :current-ratio=\"currentRatio\"\r\n :ratio-presets=\"ratioPresets\"\r\n @ratio=\"handleRatio\"\r\n @rotate=\"rotate\"\r\n @flip-x=\"core.flipX\"\r\n @flip-y=\"core.flipY\"\r\n @zoom=\"core.zoom\"\r\n @reset=\"handleReset\"\r\n />\r\n <div class=\"c-image-cropper__workspace\">\r\n <div\r\n class=\"c-image-cropper__canvas\"\r\n :style=\"{ height: containerHeight }\"\r\n >\r\n <VueCropper\r\n v-if=\"imgSrc\"\r\n ref=\"modalCropperRef\"\r\n :img=\"imgSrc\"\r\n :output-size=\"props.outputQuality\"\r\n :output-type=\"vueCropperOutputType\"\r\n :can-scale=\"false\"\r\n :auto-crop=\"true\"\r\n :auto-crop-width=\"autoCropSize.width\"\r\n :auto-crop-height=\"autoCropSize.height\"\r\n :fixed=\"isFixed\"\r\n :fixed-number=\"fixedNumber\"\r\n :center-box=\"true\"\r\n :info=\"true\"\r\n :info-true=\"true\"\r\n :can-move=\"true\"\r\n :can-move-box=\"true\"\r\n :original=\"false\"\r\n :high=\"true\"\r\n :full=\"false\"\r\n :mode=\"'contain'\"\r\n @real-time=\"handleRealTimePreview\"\r\n @img-load=\"onImgLoad\"\r\n />\r\n <div v-else class=\"c-image-cropper__placeholder\">\r\n <C_Icon\r\n name=\"mdi:image-plus-outline\"\r\n style=\"font-size: 48px; opacity: 0.3\"\r\n />\r\n <span>请选择图片</span>\r\n </div>\r\n </div>\r\n <div\r\n v-if=\"props.showPreview && imgSrc\"\r\n class=\"c-image-cropper__preview-panel\"\r\n >\r\n <CropperPreview\r\n :preview-data=\"previewData\"\r\n :circular=\"props.circular\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n <template #footer>\r\n <NSpace justify=\"end\">\r\n <NButton @click=\"handleCancel\">取消</NButton>\r\n <NButton type=\"primary\" :loading=\"exporting\" @click=\"handleConfirm\"\r\n >确认裁剪</NButton\r\n >\r\n </NSpace>\r\n </template>\r\n </NModal>\r\n\r\n <div v-else class=\"c-image-cropper\">\r\n <div class=\"c-image-cropper__body\">\r\n <CropperToolbar\r\n v-if=\"props.showToolbar\"\r\n :current-ratio=\"currentRatio\"\r\n :ratio-presets=\"ratioPresets\"\r\n @ratio=\"handleRatio\"\r\n @rotate=\"rotate\"\r\n @flip-x=\"core.flipX\"\r\n @flip-y=\"core.flipY\"\r\n @zoom=\"core.zoom\"\r\n @reset=\"handleReset\"\r\n />\r\n <div class=\"c-image-cropper__workspace\">\r\n <div\r\n class=\"c-image-cropper__canvas\"\r\n :style=\"{ height: containerHeight }\"\r\n >\r\n <VueCropper\r\n v-if=\"imgSrc\"\r\n ref=\"inlineCropperRef\"\r\n :img=\"imgSrc\"\r\n :output-size=\"props.outputQuality\"\r\n :output-type=\"vueCropperOutputType\"\r\n :can-scale=\"false\"\r\n :auto-crop=\"true\"\r\n :auto-crop-width=\"autoCropSize.width\"\r\n :auto-crop-height=\"autoCropSize.height\"\r\n :fixed=\"isFixed\"\r\n :fixed-number=\"fixedNumber\"\r\n :center-box=\"true\"\r\n :info=\"true\"\r\n :info-true=\"true\"\r\n :can-move=\"true\"\r\n :can-move-box=\"true\"\r\n :original=\"false\"\r\n :high=\"true\"\r\n :full=\"false\"\r\n :mode=\"'contain'\"\r\n @real-time=\"handleRealTimePreview\"\r\n @img-load=\"onImgLoad\"\r\n />\r\n <div v-else class=\"c-image-cropper__placeholder\">\r\n <C_Icon\r\n name=\"mdi:image-plus-outline\"\r\n style=\"font-size: 48px; opacity: 0.3\"\r\n />\r\n <span>请选择图片</span>\r\n </div>\r\n </div>\r\n <div\r\n v-if=\"props.showPreview && imgSrc\"\r\n class=\"c-image-cropper__preview-panel\"\r\n >\r\n <CropperPreview\r\n :preview-data=\"previewData\"\r\n :circular=\"props.circular\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NModal, NButton, NSpace } from \"naive-ui\";\r\nimport { VueCropper } from \"vue-cropper\";\r\nimport C_Icon from \"../C_Icon/index.vue\";\r\nimport type {\r\n AspectRatioPreset,\r\n CropOutputFormat,\r\n ImageCropperExpose,\r\n ImageCropperProps,\r\n} from \"./types\";\r\nimport { useCropperCore } from \"./composables/useCropperCore\";\r\nimport CropperToolbar from \"./components/CropperToolbar.vue\";\r\nimport CropperPreview from \"./components/CropperPreview.vue\";\r\n\r\ndefineOptions({ name: \"C_ImageCropper\" });\r\n\r\nconst props = withDefaults(defineProps<ImageCropperProps>(), {\r\n src: \"\",\r\n aspectRatio: 0,\r\n outputFormat: \"png\",\r\n outputQuality: 0.92,\r\n maxOutputWidth: 0,\r\n maxOutputHeight: 0,\r\n showPreview: true,\r\n showToolbar: true,\r\n circular: false,\r\n disabled: false,\r\n height: \"400px\",\r\n modal: false,\r\n modalTitle: \"图片裁剪\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n crop: [result: any];\r\n ready: [];\r\n error: [error: Event];\r\n confirm: [result: any];\r\n cancel: [];\r\n}>();\r\n\r\nconst imgSrc = ref(props.src);\r\nconst currentRatio = ref(props.aspectRatio);\r\nconst modalVisible = ref(false);\r\nconst exporting = ref(false);\r\nconst previewData = ref<any>(null);\r\n\r\nconst inlineCropperRef = ref<any>(null);\r\nconst modalCropperRef = ref<any>(null);\r\n\r\nconst activeCropperRef = computed(() =>\r\n props.modal ? modalCropperRef.value : inlineCropperRef.value,\r\n);\r\n\r\nconst containerHeight = computed(() =>\r\n typeof props.height === \"number\" ? `${props.height}px` : props.height,\r\n);\r\n\r\nconst vueCropperOutputType = computed(() => {\r\n const map: Record<CropOutputFormat, string> = {\r\n png: \"png\",\r\n jpeg: \"jpeg\",\r\n webp: \"webp\",\r\n };\r\n return map[props.outputFormat as CropOutputFormat] || \"png\";\r\n});\r\n\r\nconst isFixed = computed(() => currentRatio.value > 0);\r\n\r\nconst fixedNumber = computed(() => {\r\n if (currentRatio.value <= 0) return [1, 1];\r\n if (currentRatio.value === 1) return [1, 1];\r\n if (Math.abs(currentRatio.value - 16 / 9) < 0.01) return [16, 9];\r\n if (Math.abs(currentRatio.value - 4 / 3) < 0.01) return [4, 3];\r\n if (Math.abs(currentRatio.value - 3 / 2) < 0.01) return [3, 2];\r\n return [Math.round(currentRatio.value * 100), 100];\r\n});\r\n\r\nconst autoCropSize = computed(() => ({\r\n width: 300,\r\n height: currentRatio.value > 0 ? 300 / currentRatio.value : 200,\r\n}));\r\n\r\nconst ratioPresets: AspectRatioPreset[] = [\r\n { label: \"自由\", value: 0 },\r\n { label: \"1:1\", value: 1 },\r\n { label: \"16:9\", value: 16 / 9 },\r\n { label: \"4:3\", value: 4 / 3 },\r\n { label: \"3:2\", value: 3 / 2 },\r\n];\r\n\r\nconst formatRef = computed(() => props.outputFormat as CropOutputFormat);\r\nconst qualityRef = computed(() => props.outputQuality);\r\nconst maxWRef = computed(() => props.maxOutputWidth);\r\nconst maxHRef = computed(() => props.maxOutputHeight);\r\n\r\nconst core = useCropperCore({\r\n format: formatRef,\r\n quality: qualityRef,\r\n maxWidth: maxWRef,\r\n maxHeight: maxHRef,\r\n});\r\n\r\nwatch(activeCropperRef, (v) => {\r\n core.cropperRef.value = v;\r\n});\r\n\r\nfunction handleRealTimePreview(data: any) {\r\n previewData.value = data;\r\n}\r\nfunction onImgLoad(status: string) {\r\n if (status === \"success\") emit(\"ready\");\r\n}\r\nfunction rotate(angle: number) {\r\n core.rotate(angle);\r\n}\r\nfunction handleRatio(v: number) {\r\n currentRatio.value = v;\r\n}\r\nfunction handleReset() {\r\n core.reset();\r\n}\r\n\r\nasync function handleConfirm() {\r\n exporting.value = true;\r\n try {\r\n const result = await core.getCropResult();\r\n emit(\"confirm\", result);\r\n emit(\"crop\", result);\r\n modalVisible.value = false;\r\n } finally {\r\n exporting.value = false;\r\n }\r\n}\r\n\r\nfunction handleCancel() {\r\n modalVisible.value = false;\r\n emit(\"cancel\");\r\n}\r\n\r\nfunction loadFile(file: File) {\r\n const reader = new FileReader();\r\n reader.onload = (e) => {\r\n imgSrc.value = e.target?.result as string;\r\n };\r\n reader.readAsDataURL(file);\r\n}\r\n\r\nwatch(\r\n () => props.src,\r\n (v) => {\r\n imgSrc.value = v;\r\n },\r\n);\r\nwatch(\r\n () => props.aspectRatio,\r\n (v) => {\r\n currentRatio.value = v;\r\n },\r\n);\r\n\r\ndefineExpose<ImageCropperExpose>({\r\n getCropResult: core.getCropResult,\r\n rotate: core.rotate,\r\n zoom: core.zoom,\r\n flipX: core.flipX,\r\n flipY: core.flipY,\r\n reset: core.reset,\r\n setAspectRatio: (r: number) => {\r\n currentRatio.value = r;\r\n },\r\n loadFile,\r\n open: (src?: string) => {\r\n if (src) imgSrc.value = src;\r\n modalVisible.value = true;\r\n },\r\n close: () => {\r\n modalVisible.value = false;\r\n },\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: 2026-02-25\r\n * @Description: 图片裁剪组件(基于 vue-cropper)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NModal\r\n v-if=\"props.modal\"\r\n v-model:show=\"modalVisible\"\r\n preset=\"card\"\r\n :title=\"props.modalTitle || '图片裁剪'\"\r\n :style=\"{ width: '860px' }\"\r\n :mask-closable=\"false\"\r\n :closable=\"true\"\r\n >\r\n <div class=\"c-image-cropper__body\">\r\n <CropperToolbar\r\n v-if=\"props.showToolbar\"\r\n :current-ratio=\"currentRatio\"\r\n :ratio-presets=\"ratioPresets\"\r\n @ratio=\"handleRatio\"\r\n @rotate=\"rotate\"\r\n @flip-x=\"core.flipX\"\r\n @flip-y=\"core.flipY\"\r\n @zoom=\"core.zoom\"\r\n @reset=\"handleReset\"\r\n />\r\n <div class=\"c-image-cropper__workspace\">\r\n <div\r\n class=\"c-image-cropper__canvas\"\r\n :style=\"{ height: containerHeight }\"\r\n >\r\n <VueCropper\r\n v-if=\"imgSrc\"\r\n ref=\"modalCropperRef\"\r\n :img=\"imgSrc\"\r\n :output-size=\"props.outputQuality\"\r\n :output-type=\"vueCropperOutputType\"\r\n :can-scale=\"false\"\r\n :auto-crop=\"true\"\r\n :auto-crop-width=\"autoCropSize.width\"\r\n :auto-crop-height=\"autoCropSize.height\"\r\n :fixed=\"isFixed\"\r\n :fixed-number=\"fixedNumber\"\r\n :center-box=\"true\"\r\n :info=\"true\"\r\n :info-true=\"true\"\r\n :can-move=\"true\"\r\n :can-move-box=\"true\"\r\n :original=\"false\"\r\n :high=\"true\"\r\n :full=\"false\"\r\n :mode=\"'contain'\"\r\n @real-time=\"handleRealTimePreview\"\r\n @img-load=\"onImgLoad\"\r\n />\r\n <div v-else class=\"c-image-cropper__placeholder\">\r\n <C_Icon\r\n name=\"mdi:image-plus-outline\"\r\n style=\"font-size: 48px; opacity: 0.3\"\r\n />\r\n <span>请选择图片</span>\r\n </div>\r\n </div>\r\n <div\r\n v-if=\"props.showPreview && imgSrc\"\r\n class=\"c-image-cropper__preview-panel\"\r\n >\r\n <CropperPreview\r\n :preview-data=\"previewData\"\r\n :circular=\"props.circular\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n <template #footer>\r\n <NSpace justify=\"end\">\r\n <NButton @click=\"handleCancel\">取消</NButton>\r\n <NButton type=\"primary\" :loading=\"exporting\" @click=\"handleConfirm\"\r\n >确认裁剪</NButton\r\n >\r\n </NSpace>\r\n </template>\r\n </NModal>\r\n\r\n <div v-else class=\"c-image-cropper\">\r\n <div class=\"c-image-cropper__body\">\r\n <CropperToolbar\r\n v-if=\"props.showToolbar\"\r\n :current-ratio=\"currentRatio\"\r\n :ratio-presets=\"ratioPresets\"\r\n @ratio=\"handleRatio\"\r\n @rotate=\"rotate\"\r\n @flip-x=\"core.flipX\"\r\n @flip-y=\"core.flipY\"\r\n @zoom=\"core.zoom\"\r\n @reset=\"handleReset\"\r\n />\r\n <div class=\"c-image-cropper__workspace\">\r\n <div\r\n class=\"c-image-cropper__canvas\"\r\n :style=\"{ height: containerHeight }\"\r\n >\r\n <VueCropper\r\n v-if=\"imgSrc\"\r\n ref=\"inlineCropperRef\"\r\n :img=\"imgSrc\"\r\n :output-size=\"props.outputQuality\"\r\n :output-type=\"vueCropperOutputType\"\r\n :can-scale=\"false\"\r\n :auto-crop=\"true\"\r\n :auto-crop-width=\"autoCropSize.width\"\r\n :auto-crop-height=\"autoCropSize.height\"\r\n :fixed=\"isFixed\"\r\n :fixed-number=\"fixedNumber\"\r\n :center-box=\"true\"\r\n :info=\"true\"\r\n :info-true=\"true\"\r\n :can-move=\"true\"\r\n :can-move-box=\"true\"\r\n :original=\"false\"\r\n :high=\"true\"\r\n :full=\"false\"\r\n :mode=\"'contain'\"\r\n @real-time=\"handleRealTimePreview\"\r\n @img-load=\"onImgLoad\"\r\n />\r\n <div v-else class=\"c-image-cropper__placeholder\">\r\n <C_Icon\r\n name=\"mdi:image-plus-outline\"\r\n style=\"font-size: 48px; opacity: 0.3\"\r\n />\r\n <span>请选择图片</span>\r\n </div>\r\n </div>\r\n <div\r\n v-if=\"props.showPreview && imgSrc\"\r\n class=\"c-image-cropper__preview-panel\"\r\n >\r\n <CropperPreview\r\n :preview-data=\"previewData\"\r\n :circular=\"props.circular\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NModal, NButton, NSpace } from \"naive-ui\";\r\nimport { VueCropper } from \"vue-cropper\";\r\nimport C_Icon from \"../C_Icon/index.vue\";\r\nimport type {\r\n AspectRatioPreset,\r\n CropOutputFormat,\r\n ImageCropperExpose,\r\n ImageCropperProps,\r\n} from \"./types\";\r\nimport { useCropperCore } from \"./composables/useCropperCore\";\r\nimport CropperToolbar from \"./components/CropperToolbar.vue\";\r\nimport CropperPreview from \"./components/CropperPreview.vue\";\r\n\r\ndefineOptions({ name: \"C_ImageCropper\" });\r\n\r\nconst props = withDefaults(defineProps<ImageCropperProps>(), {\r\n src: \"\",\r\n aspectRatio: 0,\r\n outputFormat: \"png\",\r\n outputQuality: 0.92,\r\n maxOutputWidth: 0,\r\n maxOutputHeight: 0,\r\n showPreview: true,\r\n showToolbar: true,\r\n circular: false,\r\n disabled: false,\r\n height: \"400px\",\r\n modal: false,\r\n modalTitle: \"图片裁剪\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n crop: [result: any];\r\n ready: [];\r\n error: [error: Event];\r\n confirm: [result: any];\r\n cancel: [];\r\n}>();\r\n\r\nconst imgSrc = ref(props.src);\r\nconst currentRatio = ref(props.aspectRatio);\r\nconst modalVisible = ref(false);\r\nconst exporting = ref(false);\r\nconst previewData = ref<any>(null);\r\n\r\nconst inlineCropperRef = ref<any>(null);\r\nconst modalCropperRef = ref<any>(null);\r\n\r\nconst activeCropperRef = computed(() =>\r\n props.modal ? modalCropperRef.value : inlineCropperRef.value,\r\n);\r\n\r\nconst containerHeight = computed(() =>\r\n typeof props.height === \"number\" ? `${props.height}px` : props.height,\r\n);\r\n\r\nconst vueCropperOutputType = computed(() => {\r\n const map: Record<CropOutputFormat, string> = {\r\n png: \"png\",\r\n jpeg: \"jpeg\",\r\n webp: \"webp\",\r\n };\r\n return map[props.outputFormat as CropOutputFormat] || \"png\";\r\n});\r\n\r\nconst isFixed = computed(() => currentRatio.value > 0);\r\n\r\nconst fixedNumber = computed(() => {\r\n if (currentRatio.value <= 0) return [1, 1];\r\n if (currentRatio.value === 1) return [1, 1];\r\n if (Math.abs(currentRatio.value - 16 / 9) < 0.01) return [16, 9];\r\n if (Math.abs(currentRatio.value - 4 / 3) < 0.01) return [4, 3];\r\n if (Math.abs(currentRatio.value - 3 / 2) < 0.01) return [3, 2];\r\n return [Math.round(currentRatio.value * 100), 100];\r\n});\r\n\r\nconst autoCropSize = computed(() => ({\r\n width: 300,\r\n height: currentRatio.value > 0 ? 300 / currentRatio.value : 200,\r\n}));\r\n\r\nconst ratioPresets: AspectRatioPreset[] = [\r\n { label: \"自由\", value: 0 },\r\n { label: \"1:1\", value: 1 },\r\n { label: \"16:9\", value: 16 / 9 },\r\n { label: \"4:3\", value: 4 / 3 },\r\n { label: \"3:2\", value: 3 / 2 },\r\n];\r\n\r\nconst formatRef = computed(() => props.outputFormat as CropOutputFormat);\r\nconst qualityRef = computed(() => props.outputQuality);\r\nconst maxWRef = computed(() => props.maxOutputWidth);\r\nconst maxHRef = computed(() => props.maxOutputHeight);\r\n\r\nconst core = useCropperCore({\r\n format: formatRef,\r\n quality: qualityRef,\r\n maxWidth: maxWRef,\r\n maxHeight: maxHRef,\r\n});\r\n\r\nwatch(activeCropperRef, (v) => {\r\n core.cropperRef.value = v;\r\n});\r\n\r\nfunction handleRealTimePreview(data: any) {\r\n previewData.value = data;\r\n}\r\nfunction onImgLoad(status: string) {\r\n if (status === \"success\") emit(\"ready\");\r\n}\r\nfunction rotate(angle: number) {\r\n core.rotate(angle);\r\n}\r\nfunction handleRatio(v: number) {\r\n currentRatio.value = v;\r\n}\r\nfunction handleReset() {\r\n core.reset();\r\n}\r\n\r\nasync function handleConfirm() {\r\n exporting.value = true;\r\n try {\r\n const result = await core.getCropResult();\r\n emit(\"confirm\", result);\r\n emit(\"crop\", result);\r\n modalVisible.value = false;\r\n } finally {\r\n exporting.value = false;\r\n }\r\n}\r\n\r\nfunction handleCancel() {\r\n modalVisible.value = false;\r\n emit(\"cancel\");\r\n}\r\n\r\nfunction loadFile(file: File) {\r\n const reader = new FileReader();\r\n reader.onload = (e) => {\r\n imgSrc.value = e.target?.result as string;\r\n };\r\n reader.readAsDataURL(file);\r\n}\r\n\r\nwatch(\r\n () => props.src,\r\n (v) => {\r\n imgSrc.value = v;\r\n },\r\n);\r\nwatch(\r\n () => props.aspectRatio,\r\n (v) => {\r\n currentRatio.value = v;\r\n },\r\n);\r\n\r\ndefineExpose<ImageCropperExpose>({\r\n getCropResult: core.getCropResult,\r\n rotate: core.rotate,\r\n zoom: core.zoom,\r\n flipX: core.flipX,\r\n flipY: core.flipY,\r\n reset: core.reset,\r\n setAspectRatio: (r: number) => {\r\n currentRatio.value = r;\r\n },\r\n loadFile,\r\n open: (src?: string) => {\r\n if (src) imgSrc.value = src;\r\n modalVisible.value = true;\r\n },\r\n close: () => {\r\n modalVisible.value = false;\r\n },\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_ImageCropper2.js","names":["ratioPresets","currentRatio","$emit","circular","previewData"],"sources":["../src/components/C_ImageCropper/composables/useCropperCore.ts","../src/components/C_ImageCropper/components/CropperToolbar.vue","../src/components/C_ImageCropper/components/CropperToolbar.vue","../src/components/C_ImageCropper/components/CropperToolbar.vue","../src/components/C_ImageCropper/components/CropperPreview.vue","../src/components/C_ImageCropper/components/CropperPreview.vue","../src/components/C_ImageCropper/components/CropperPreview.vue","../src/components/C_ImageCropper/index.vue","../src/components/C_ImageCropper/index.vue","../src/components/C_ImageCropper/index.vue"],"sourcesContent":["import { ref } from \"vue\";\r\nimport type { Ref } from \"vue\";\r\nimport type { CropOutputFormat, CropResult } from \"../types\";\r\n\r\ninterface UseCropperCoreOptions {\r\n format?: Ref<CropOutputFormat>;\r\n quality?: Ref<number>;\r\n maxWidth?: Ref<number>;\r\n maxHeight?: Ref<number>;\r\n}\r\n\r\nexport function useCropperCore(options: UseCropperCoreOptions = {}) {\r\n const cropperRef = ref<any>(null);\r\n\r\n function rotateLeft() {\r\n cropperRef.value?.rotateLeft();\r\n }\r\n\r\n function rotateRight() {\r\n cropperRef.value?.rotateRight();\r\n }\r\n\r\n function rotate(angle: number) {\r\n const steps = Math.round(angle / 90);\r\n const fn = steps > 0 ? rotateRight : rotateLeft;\r\n for (let i = 0; i < Math.abs(steps); i++) fn();\r\n }\r\n\r\n function zoom(scale: number) {\r\n cropperRef.value?.changeScale(scale > 0 ? 1 : -1);\r\n }\r\n\r\n function flipX() {\r\n const el = cropperRef.value?.$refs?.img;\r\n if (!el) return;\r\n const current = el.style.transform || \"\";\r\n if (current.includes(\"scaleX(-1)\")) {\r\n el.style.transform = current.replace(\"scaleX(-1)\", \"scaleX(1)\");\r\n } else {\r\n el.style.transform =\r\n current.replace(/scaleX\\([^)]*\\)/, \"\") + \" scaleX(-1)\";\r\n }\r\n }\r\n\r\n function flipY() {\r\n const el = cropperRef.value?.$refs?.img;\r\n if (!el) return;\r\n const current = el.style.transform || \"\";\r\n if (current.includes(\"scaleY(-1)\")) {\r\n el.style.transform = current.replace(\"scaleY(-1)\", \"scaleY(1)\");\r\n } else {\r\n el.style.transform =\r\n current.replace(/scaleY\\([^)]*\\)/, \"\") + \" scaleY(-1)\";\r\n }\r\n }\r\n\r\n function reset() {\r\n cropperRef.value?.refresh();\r\n }\r\n\r\n function constrainSize(w: number, h: number) {\r\n let ow = w;\r\n let oh = h;\r\n const maxW = options.maxWidth?.value ?? 0;\r\n const maxH = options.maxHeight?.value ?? 0;\r\n\r\n if (maxW > 0 && ow > maxW) {\r\n const ratio = maxW / ow;\r\n ow = maxW;\r\n oh = Math.round(oh * ratio);\r\n }\r\n if (maxH > 0 && oh > maxH) {\r\n const ratio = maxH / oh;\r\n oh = maxH;\r\n ow = Math.round(ow * ratio);\r\n }\r\n return { width: ow, height: oh };\r\n }\r\n\r\n function getCropResult(): Promise<CropResult> {\r\n return new Promise((resolve, reject) => {\r\n const cropper = cropperRef.value;\r\n if (!cropper) return reject(new Error(\"Cropper not initialized\"));\r\n\r\n const format = options.format?.value ?? \"png\";\r\n const quality = options.quality?.value ?? 0.92;\r\n const mime =\r\n format === \"jpeg\"\r\n ? \"image/jpeg\"\r\n : format === \"webp\"\r\n ? \"image/webp\"\r\n : \"image/png\";\r\n\r\n cropper.getCropData((base64: string) => {\r\n cropper.getCropBlob((blob: Blob) => {\r\n const img = new Image();\r\n img.onload = () => {\r\n const { width, height } = constrainSize(img.width, img.height);\r\n\r\n if (width !== img.width || height !== img.height) {\r\n const canvas = document.createElement(\"canvas\");\r\n canvas.width = width;\r\n canvas.height = height;\r\n const ctx = canvas.getContext(\"2d\")!;\r\n ctx.drawImage(img, 0, 0, width, height);\r\n const constrainedBase64 = canvas.toDataURL(mime, quality);\r\n canvas.toBlob(\r\n (constrainedBlob) => {\r\n resolve({\r\n base64: constrainedBase64,\r\n blob: constrainedBlob!,\r\n width,\r\n height,\r\n format,\r\n });\r\n },\r\n mime,\r\n format === \"png\" ? undefined : quality,\r\n );\r\n } else {\r\n resolve({\r\n base64,\r\n blob,\r\n width: img.width,\r\n height: img.height,\r\n format,\r\n });\r\n }\r\n };\r\n img.onerror = () => reject(new Error(\"Failed to load crop result\"));\r\n img.src = base64;\r\n });\r\n });\r\n });\r\n }\r\n\r\n return {\r\n cropperRef,\r\n rotate,\r\n rotateLeft,\r\n rotateRight,\r\n zoom,\r\n flipX,\r\n flipY,\r\n reset,\r\n getCropResult,\r\n };\r\n}\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"cropper-toolbar\">\r\n <NButtonGroup size=\"small\">\r\n <NButton\r\n v-for=\"item in ratioPresets\"\r\n :key=\"item.value\"\r\n :type=\"currentRatio === item.value ? 'primary' : 'default'\"\r\n @click=\"$emit('ratio', item.value)\"\r\n >\r\n {{ item.label }}\r\n </NButton>\r\n </NButtonGroup>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', -90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-left\" /></template>\r\n </NButton>\r\n </template>\r\n 逆时针旋转 90°\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', 90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-right\" /></template>\r\n </NButton>\r\n </template>\r\n 顺时针旋转 90°\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipX')\">\r\n <template #icon><C_Icon name=\"mdi:flip-horizontal\" /></template>\r\n </NButton>\r\n </template>\r\n 水平翻转\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipY')\">\r\n <template #icon><C_Icon name=\"mdi:flip-vertical\" /></template>\r\n </NButton>\r\n </template>\r\n 垂直翻转\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', 0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-plus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 放大\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', -0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-minus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 缩小\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('reset')\">\r\n <template #icon><C_Icon name=\"mdi:refresh\" /></template>\r\n </NButton>\r\n </template>\r\n 重置\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NButton, NButtonGroup, NDivider, NTooltip } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { AspectRatioPreset } from \"../types\";\r\n\r\ndefineProps<{\r\n currentRatio: number;\r\n ratioPresets: AspectRatioPreset[];\r\n}>();\r\n\r\ndefineEmits<{\r\n ratio: [value: number];\r\n rotate: [angle: number];\r\n flipX: [];\r\n flipY: [];\r\n zoom: [scale: number];\r\n reset: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-toolbar {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n align-items: center;\r\n padding: 8px 0;\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"cropper-toolbar\">\r\n <NButtonGroup size=\"small\">\r\n <NButton\r\n v-for=\"item in ratioPresets\"\r\n :key=\"item.value\"\r\n :type=\"currentRatio === item.value ? 'primary' : 'default'\"\r\n @click=\"$emit('ratio', item.value)\"\r\n >\r\n {{ item.label }}\r\n </NButton>\r\n </NButtonGroup>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', -90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-left\" /></template>\r\n </NButton>\r\n </template>\r\n 逆时针旋转 90°\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', 90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-right\" /></template>\r\n </NButton>\r\n </template>\r\n 顺时针旋转 90°\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipX')\">\r\n <template #icon><C_Icon name=\"mdi:flip-horizontal\" /></template>\r\n </NButton>\r\n </template>\r\n 水平翻转\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipY')\">\r\n <template #icon><C_Icon name=\"mdi:flip-vertical\" /></template>\r\n </NButton>\r\n </template>\r\n 垂直翻转\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', 0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-plus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 放大\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', -0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-minus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 缩小\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('reset')\">\r\n <template #icon><C_Icon name=\"mdi:refresh\" /></template>\r\n </NButton>\r\n </template>\r\n 重置\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NButton, NButtonGroup, NDivider, NTooltip } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { AspectRatioPreset } from \"../types\";\r\n\r\ndefineProps<{\r\n currentRatio: number;\r\n ratioPresets: AspectRatioPreset[];\r\n}>();\r\n\r\ndefineEmits<{\r\n ratio: [value: number];\r\n rotate: [angle: number];\r\n flipX: [];\r\n flipY: [];\r\n zoom: [scale: number];\r\n reset: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-toolbar {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n align-items: center;\r\n padding: 8px 0;\r\n}\r\n</style>\r\n","<template>\r\n <div class=\"cropper-toolbar\">\r\n <NButtonGroup size=\"small\">\r\n <NButton\r\n v-for=\"item in ratioPresets\"\r\n :key=\"item.value\"\r\n :type=\"currentRatio === item.value ? 'primary' : 'default'\"\r\n @click=\"$emit('ratio', item.value)\"\r\n >\r\n {{ item.label }}\r\n </NButton>\r\n </NButtonGroup>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', -90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-left\" /></template>\r\n </NButton>\r\n </template>\r\n 逆时针旋转 90°\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('rotate', 90)\">\r\n <template #icon><C_Icon name=\"mdi:rotate-right\" /></template>\r\n </NButton>\r\n </template>\r\n 顺时针旋转 90°\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipX')\">\r\n <template #icon><C_Icon name=\"mdi:flip-horizontal\" /></template>\r\n </NButton>\r\n </template>\r\n 水平翻转\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('flipY')\">\r\n <template #icon><C_Icon name=\"mdi:flip-vertical\" /></template>\r\n </NButton>\r\n </template>\r\n 垂直翻转\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', 0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-plus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 放大\r\n </NTooltip>\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('zoom', -0.1)\">\r\n <template #icon><C_Icon name=\"mdi:magnify-minus-outline\" /></template>\r\n </NButton>\r\n </template>\r\n 缩小\r\n </NTooltip>\r\n\r\n <NDivider vertical />\r\n\r\n <NTooltip>\r\n <template #trigger>\r\n <NButton size=\"small\" quaternary @click=\"$emit('reset')\">\r\n <template #icon><C_Icon name=\"mdi:refresh\" /></template>\r\n </NButton>\r\n </template>\r\n 重置\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NButton, NButtonGroup, NDivider, NTooltip } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { AspectRatioPreset } from \"../types\";\r\n\r\ndefineProps<{\r\n currentRatio: number;\r\n ratioPresets: AspectRatioPreset[];\r\n}>();\r\n\r\ndefineEmits<{\r\n ratio: [value: number];\r\n rotate: [angle: number];\r\n flipX: [];\r\n flipY: [];\r\n zoom: [scale: number];\r\n reset: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-toolbar {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n align-items: center;\r\n padding: 8px 0;\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"cropper-preview\">\r\n <div class=\"preview-label\">\r\n <C_Icon name=\"mdi:eye-outline\" />\r\n <span>裁剪预览</span>\r\n </div>\r\n\r\n <div\r\n ref=\"mainBoxRef\"\r\n class=\"preview-main\"\r\n :class=\"{ 'preview-main--circular': circular }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div class=\"preview-main__viewport\" :style=\"mainViewportStyle\">\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"preview\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n <div v-else class=\"preview-empty\">\r\n <C_Icon\r\n name=\"mdi:image-outline\"\r\n style=\"font-size: 32px; opacity: 0.2\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"preview-thumbs\">\r\n <div v-for=\"item in thumbSizes\" :key=\"item.label\" class=\"preview-thumb\">\r\n <div\r\n class=\"preview-thumb__box\"\r\n :class=\"{ 'preview-thumb__box--circular': circular }\"\r\n :style=\"{ width: `${item.size}px`, height: `${item.size}px` }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div\r\n class=\"preview-thumb__viewport\"\r\n :style=\"getThumbViewportStyle(item.size)\"\r\n >\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"thumb\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n <span class=\"preview-thumb__label\">{{ item.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onBeforeUnmount } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\nconst props = defineProps<{\r\n previewData: any;\r\n circular?: boolean;\r\n}>();\r\n\r\nconst mainBoxRef = ref<HTMLElement | null>(null);\r\nconst mainBoxWidth = ref(200);\r\n\r\nconst thumbSizes = [\r\n { label: \"80px\", size: 80 },\r\n { label: \"48px\", size: 48 },\r\n { label: \"32px\", size: 32 },\r\n];\r\n\r\nconst hasValidPreview = computed(() => {\r\n const d = props.previewData;\r\n return d && d.url && d.w > 0 && d.h > 0;\r\n});\r\n\r\nconst mainViewportStyle = computed(() => {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n const zoom = mainBoxWidth.value / w;\r\n return { zoom, overflow: \"hidden\" };\r\n});\r\n\r\nfunction getThumbViewportStyle(size: number) {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n return { zoom: size / w, overflow: \"hidden\" };\r\n}\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n if (mainBoxRef.value) {\r\n mainBoxWidth.value = mainBoxRef.value.clientWidth;\r\n resizeObserver = new ResizeObserver((entries) => {\r\n requestAnimationFrame(() => {\r\n for (const entry of entries) {\r\n mainBoxWidth.value = entry.contentRect.width;\r\n }\r\n });\r\n });\r\n resizeObserver.observe(mainBoxRef.value);\r\n }\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n resizeObserver?.disconnect();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-preview {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 16px;\r\n\r\n .preview-label {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-2);\r\n }\r\n\r\n .preview-main {\r\n width: 100%;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 6px;\r\n background:\r\n linear-gradient(45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(45deg, transparent 75%, #f0f0f0 75%),\r\n linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);\r\n background-position:\r\n 0 0,\r\n 0 8px,\r\n 8px -8px,\r\n -8px 0;\r\n background-size: 16px 16px;\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n }\r\n\r\n .preview-empty {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 100%;\r\n height: 100%;\r\n min-height: 120px;\r\n }\r\n\r\n .preview-thumbs {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-end;\r\n justify-content: center;\r\n }\r\n\r\n .preview-thumb {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n align-items: center;\r\n\r\n &__box {\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n background: var(--body-color);\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n\r\n &__label {\r\n font-size: 11px;\r\n color: var(--text-color-3);\r\n }\r\n }\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"cropper-preview\">\r\n <div class=\"preview-label\">\r\n <C_Icon name=\"mdi:eye-outline\" />\r\n <span>裁剪预览</span>\r\n </div>\r\n\r\n <div\r\n ref=\"mainBoxRef\"\r\n class=\"preview-main\"\r\n :class=\"{ 'preview-main--circular': circular }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div class=\"preview-main__viewport\" :style=\"mainViewportStyle\">\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"preview\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n <div v-else class=\"preview-empty\">\r\n <C_Icon\r\n name=\"mdi:image-outline\"\r\n style=\"font-size: 32px; opacity: 0.2\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"preview-thumbs\">\r\n <div v-for=\"item in thumbSizes\" :key=\"item.label\" class=\"preview-thumb\">\r\n <div\r\n class=\"preview-thumb__box\"\r\n :class=\"{ 'preview-thumb__box--circular': circular }\"\r\n :style=\"{ width: `${item.size}px`, height: `${item.size}px` }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div\r\n class=\"preview-thumb__viewport\"\r\n :style=\"getThumbViewportStyle(item.size)\"\r\n >\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"thumb\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n <span class=\"preview-thumb__label\">{{ item.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onBeforeUnmount } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\nconst props = defineProps<{\r\n previewData: any;\r\n circular?: boolean;\r\n}>();\r\n\r\nconst mainBoxRef = ref<HTMLElement | null>(null);\r\nconst mainBoxWidth = ref(200);\r\n\r\nconst thumbSizes = [\r\n { label: \"80px\", size: 80 },\r\n { label: \"48px\", size: 48 },\r\n { label: \"32px\", size: 32 },\r\n];\r\n\r\nconst hasValidPreview = computed(() => {\r\n const d = props.previewData;\r\n return d && d.url && d.w > 0 && d.h > 0;\r\n});\r\n\r\nconst mainViewportStyle = computed(() => {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n const zoom = mainBoxWidth.value / w;\r\n return { zoom, overflow: \"hidden\" };\r\n});\r\n\r\nfunction getThumbViewportStyle(size: number) {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n return { zoom: size / w, overflow: \"hidden\" };\r\n}\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n if (mainBoxRef.value) {\r\n mainBoxWidth.value = mainBoxRef.value.clientWidth;\r\n resizeObserver = new ResizeObserver((entries) => {\r\n requestAnimationFrame(() => {\r\n for (const entry of entries) {\r\n mainBoxWidth.value = entry.contentRect.width;\r\n }\r\n });\r\n });\r\n resizeObserver.observe(mainBoxRef.value);\r\n }\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n resizeObserver?.disconnect();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-preview {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 16px;\r\n\r\n .preview-label {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-2);\r\n }\r\n\r\n .preview-main {\r\n width: 100%;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 6px;\r\n background:\r\n linear-gradient(45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(45deg, transparent 75%, #f0f0f0 75%),\r\n linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);\r\n background-position:\r\n 0 0,\r\n 0 8px,\r\n 8px -8px,\r\n -8px 0;\r\n background-size: 16px 16px;\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n }\r\n\r\n .preview-empty {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 100%;\r\n height: 100%;\r\n min-height: 120px;\r\n }\r\n\r\n .preview-thumbs {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-end;\r\n justify-content: center;\r\n }\r\n\r\n .preview-thumb {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n align-items: center;\r\n\r\n &__box {\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n background: var(--body-color);\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n\r\n &__label {\r\n font-size: 11px;\r\n color: var(--text-color-3);\r\n }\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <div class=\"cropper-preview\">\r\n <div class=\"preview-label\">\r\n <C_Icon name=\"mdi:eye-outline\" />\r\n <span>裁剪预览</span>\r\n </div>\r\n\r\n <div\r\n ref=\"mainBoxRef\"\r\n class=\"preview-main\"\r\n :class=\"{ 'preview-main--circular': circular }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div class=\"preview-main__viewport\" :style=\"mainViewportStyle\">\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"preview\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n <div v-else class=\"preview-empty\">\r\n <C_Icon\r\n name=\"mdi:image-outline\"\r\n style=\"font-size: 32px; opacity: 0.2\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"preview-thumbs\">\r\n <div v-for=\"item in thumbSizes\" :key=\"item.label\" class=\"preview-thumb\">\r\n <div\r\n class=\"preview-thumb__box\"\r\n :class=\"{ 'preview-thumb__box--circular': circular }\"\r\n :style=\"{ width: `${item.size}px`, height: `${item.size}px` }\"\r\n >\r\n <template v-if=\"hasValidPreview\">\r\n <div\r\n class=\"preview-thumb__viewport\"\r\n :style=\"getThumbViewportStyle(item.size)\"\r\n >\r\n <div :style=\"previewData.div\">\r\n <img\r\n :src=\"previewData.url\"\r\n :style=\"previewData.img\"\r\n alt=\"thumb\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n <span class=\"preview-thumb__label\">{{ item.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onBeforeUnmount } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\n\r\nconst props = defineProps<{\r\n previewData: any;\r\n circular?: boolean;\r\n}>();\r\n\r\nconst mainBoxRef = ref<HTMLElement | null>(null);\r\nconst mainBoxWidth = ref(200);\r\n\r\nconst thumbSizes = [\r\n { label: \"80px\", size: 80 },\r\n { label: \"48px\", size: 48 },\r\n { label: \"32px\", size: 32 },\r\n];\r\n\r\nconst hasValidPreview = computed(() => {\r\n const d = props.previewData;\r\n return d && d.url && d.w > 0 && d.h > 0;\r\n});\r\n\r\nconst mainViewportStyle = computed(() => {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n const zoom = mainBoxWidth.value / w;\r\n return { zoom, overflow: \"hidden\" };\r\n});\r\n\r\nfunction getThumbViewportStyle(size: number) {\r\n if (!hasValidPreview.value) return {};\r\n const { w } = props.previewData;\r\n return { zoom: size / w, overflow: \"hidden\" };\r\n}\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n if (mainBoxRef.value) {\r\n mainBoxWidth.value = mainBoxRef.value.clientWidth;\r\n resizeObserver = new ResizeObserver((entries) => {\r\n requestAnimationFrame(() => {\r\n for (const entry of entries) {\r\n mainBoxWidth.value = entry.contentRect.width;\r\n }\r\n });\r\n });\r\n resizeObserver.observe(mainBoxRef.value);\r\n }\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n resizeObserver?.disconnect();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.cropper-preview {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 16px;\r\n\r\n .preview-label {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-2);\r\n }\r\n\r\n .preview-main {\r\n width: 100%;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 6px;\r\n background:\r\n linear-gradient(45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),\r\n linear-gradient(45deg, transparent 75%, #f0f0f0 75%),\r\n linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);\r\n background-position:\r\n 0 0,\r\n 0 8px,\r\n 8px -8px,\r\n -8px 0;\r\n background-size: 16px 16px;\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n }\r\n\r\n .preview-empty {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 100%;\r\n height: 100%;\r\n min-height: 120px;\r\n }\r\n\r\n .preview-thumbs {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-end;\r\n justify-content: center;\r\n }\r\n\r\n .preview-thumb {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n align-items: center;\r\n\r\n &__box {\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n background: var(--body-color);\r\n\r\n &--circular {\r\n border-radius: 50%;\r\n }\r\n }\r\n\r\n &__viewport img {\r\n display: block;\r\n max-width: none !important;\r\n }\r\n\r\n &__label {\r\n font-size: 11px;\r\n color: var(--text-color-3);\r\n }\r\n }\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2026-02-25\n * @Description: 图片裁剪组件(基于 vue-cropper)\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\n-->\n<template>\n <NModal\n v-if=\"props.modal\"\n v-model:show=\"modalVisible\"\n preset=\"card\"\n :title=\"props.modalTitle || '图片裁剪'\"\n :style=\"{ width: '860px' }\"\n :mask-closable=\"false\"\n :closable=\"true\"\n >\n <div class=\"c-image-cropper__body\">\n <CropperToolbar\n v-if=\"props.showToolbar\"\n :current-ratio=\"currentRatio\"\n :ratio-presets=\"ratioPresets\"\n @ratio=\"handleRatio\"\n @rotate=\"rotate\"\n @flip-x=\"core.flipX\"\n @flip-y=\"core.flipY\"\n @zoom=\"core.zoom\"\n @reset=\"handleReset\"\n />\n <div class=\"c-image-cropper__workspace\">\n <div\n class=\"c-image-cropper__canvas\"\n :style=\"{ height: containerHeight }\"\n >\n <VueCropper\n v-if=\"imgSrc\"\n ref=\"modalCropperRef\"\n :img=\"imgSrc\"\n :output-size=\"props.outputQuality\"\n :output-type=\"vueCropperOutputType\"\n :can-scale=\"false\"\n :auto-crop=\"true\"\n :auto-crop-width=\"autoCropSize.width\"\n :auto-crop-height=\"autoCropSize.height\"\n :fixed=\"isFixed\"\n :fixed-number=\"fixedNumber\"\n :center-box=\"true\"\n :info=\"true\"\n :info-true=\"true\"\n :can-move=\"true\"\n :can-move-box=\"true\"\n :original=\"false\"\n :high=\"true\"\n :full=\"false\"\n :mode=\"'contain'\"\n @real-time=\"handleRealTimePreview\"\n @img-load=\"onImgLoad\"\n />\n <div v-else class=\"c-image-cropper__placeholder\">\n <C_Icon\n name=\"mdi:image-plus-outline\"\n style=\"font-size: 48px; opacity: 0.3\"\n />\n <span>请选择图片</span>\n </div>\n </div>\n <div\n v-if=\"props.showPreview && imgSrc\"\n class=\"c-image-cropper__preview-panel\"\n >\n <CropperPreview\n :preview-data=\"previewData\"\n :circular=\"props.circular\"\n />\n </div>\n </div>\n </div>\n <template #footer>\n <NSpace justify=\"end\">\n <NButton @click=\"handleCancel\">取消</NButton>\n <NButton type=\"primary\" :loading=\"exporting\" @click=\"handleConfirm\"\n >确认裁剪</NButton\n >\n </NSpace>\n </template>\n </NModal>\n\n <div v-else class=\"c-image-cropper\">\n <div class=\"c-image-cropper__body\">\n <CropperToolbar\n v-if=\"props.showToolbar\"\n :current-ratio=\"currentRatio\"\n :ratio-presets=\"ratioPresets\"\n @ratio=\"handleRatio\"\n @rotate=\"rotate\"\n @flip-x=\"core.flipX\"\n @flip-y=\"core.flipY\"\n @zoom=\"core.zoom\"\n @reset=\"handleReset\"\n />\n <div class=\"c-image-cropper__workspace\">\n <div\n class=\"c-image-cropper__canvas\"\n :style=\"{ height: containerHeight }\"\n >\n <VueCropper\n v-if=\"imgSrc\"\n ref=\"inlineCropperRef\"\n :img=\"imgSrc\"\n :output-size=\"props.outputQuality\"\n :output-type=\"vueCropperOutputType\"\n :can-scale=\"false\"\n :auto-crop=\"true\"\n :auto-crop-width=\"autoCropSize.width\"\n :auto-crop-height=\"autoCropSize.height\"\n :fixed=\"isFixed\"\n :fixed-number=\"fixedNumber\"\n :center-box=\"true\"\n :info=\"true\"\n :info-true=\"true\"\n :can-move=\"true\"\n :can-move-box=\"true\"\n :original=\"false\"\n :high=\"true\"\n :full=\"false\"\n :mode=\"'contain'\"\n @real-time=\"handleRealTimePreview\"\n @img-load=\"onImgLoad\"\n />\n <div v-else class=\"c-image-cropper__placeholder\">\n <C_Icon\n name=\"mdi:image-plus-outline\"\n style=\"font-size: 48px; opacity: 0.3\"\n />\n <span>请选择图片</span>\n </div>\n </div>\n <div\n v-if=\"props.showPreview && imgSrc\"\n class=\"c-image-cropper__preview-panel\"\n >\n <CropperPreview\n :preview-data=\"previewData\"\n :circular=\"props.circular\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from \"vue\";\nimport { NModal, NButton, NSpace } from \"naive-ui\";\nimport { VueCropper } from \"vue-cropper\";\nimport C_Icon from \"../C_Icon/index.vue\";\nimport type {\n AspectRatioPreset,\n CropOutputFormat,\n ImageCropperExpose,\n ImageCropperProps,\n} from \"./types\";\nimport { useCropperCore } from \"./composables/useCropperCore\";\nimport CropperToolbar from \"./components/CropperToolbar.vue\";\nimport CropperPreview from \"./components/CropperPreview.vue\";\n\ndefineOptions({ name: \"C_ImageCropper\" });\n\nconst props = withDefaults(defineProps<ImageCropperProps>(), {\n src: \"\",\n aspectRatio: 0,\n outputFormat: \"png\",\n outputQuality: 0.92,\n maxOutputWidth: 0,\n maxOutputHeight: 0,\n showPreview: true,\n showToolbar: true,\n circular: false,\n disabled: false,\n height: \"400px\",\n modal: false,\n modalTitle: \"图片裁剪\",\n});\n\nconst emit = defineEmits<{\n crop: [result: any];\n ready: [];\n error: [error: Event];\n confirm: [result: any];\n cancel: [];\n}>();\n\nconst imgSrc = ref(props.src);\nconst currentRatio = ref(props.aspectRatio);\nconst modalVisible = ref(false);\nconst exporting = ref(false);\nconst previewData = ref<any>(null);\n\nconst inlineCropperRef = ref<any>(null);\nconst modalCropperRef = ref<any>(null);\n\nconst activeCropperRef = computed(() =>\n props.modal ? modalCropperRef.value : inlineCropperRef.value,\n);\n\nconst containerHeight = computed(() =>\n typeof props.height === \"number\" ? `${props.height}px` : props.height,\n);\n\nconst vueCropperOutputType = computed(() => {\n const map: Record<CropOutputFormat, string> = {\n png: \"png\",\n jpeg: \"jpeg\",\n webp: \"webp\",\n };\n return map[props.outputFormat as CropOutputFormat] || \"png\";\n});\n\nconst isFixed = computed(() => currentRatio.value > 0);\n\nconst fixedNumber = computed(() => {\n if (currentRatio.value <= 0) return [1, 1];\n if (currentRatio.value === 1) return [1, 1];\n if (Math.abs(currentRatio.value - 16 / 9) < 0.01) return [16, 9];\n if (Math.abs(currentRatio.value - 4 / 3) < 0.01) return [4, 3];\n if (Math.abs(currentRatio.value - 3 / 2) < 0.01) return [3, 2];\n return [Math.round(currentRatio.value * 100), 100];\n});\n\nconst autoCropSize = computed(() => ({\n width: 300,\n height: currentRatio.value > 0 ? 300 / currentRatio.value : 200,\n}));\n\nconst ratioPresets: AspectRatioPreset[] = [\n { label: \"自由\", value: 0 },\n { label: \"1:1\", value: 1 },\n { label: \"16:9\", value: 16 / 9 },\n { label: \"4:3\", value: 4 / 3 },\n { label: \"3:2\", value: 3 / 2 },\n];\n\nconst formatRef = computed(() => props.outputFormat as CropOutputFormat);\nconst qualityRef = computed(() => props.outputQuality);\nconst maxWRef = computed(() => props.maxOutputWidth);\nconst maxHRef = computed(() => props.maxOutputHeight);\n\nconst core = useCropperCore({\n format: formatRef,\n quality: qualityRef,\n maxWidth: maxWRef,\n maxHeight: maxHRef,\n});\n\nwatch(activeCropperRef, (v) => {\n core.cropperRef.value = v;\n});\n\nfunction handleRealTimePreview(data: any) {\n previewData.value = data;\n}\nfunction onImgLoad(status: string) {\n if (status === \"success\") emit(\"ready\");\n}\nfunction rotate(angle: number) {\n core.rotate(angle);\n}\nfunction handleRatio(v: number) {\n currentRatio.value = v;\n}\nfunction handleReset() {\n core.reset();\n}\n\nasync function handleConfirm() {\n exporting.value = true;\n try {\n const result = await core.getCropResult();\n emit(\"confirm\", result);\n emit(\"crop\", result);\n modalVisible.value = false;\n } finally {\n exporting.value = false;\n }\n}\n\nfunction handleCancel() {\n modalVisible.value = false;\n emit(\"cancel\");\n}\n\nfunction loadFile(file: File) {\n const reader = new FileReader();\n reader.onload = (e) => {\n imgSrc.value = e.target?.result as string;\n };\n reader.readAsDataURL(file);\n}\n\nwatch(\n () => props.src,\n (v) => {\n imgSrc.value = v;\n },\n);\nwatch(\n () => props.aspectRatio,\n (v) => {\n currentRatio.value = v;\n },\n);\n\ndefineExpose<ImageCropperExpose>({\n getCropResult: core.getCropResult,\n rotate: core.rotate,\n zoom: core.zoom,\n flipX: core.flipX,\n flipY: core.flipY,\n reset: core.reset,\n setAspectRatio: (r: number) => {\n currentRatio.value = r;\n },\n loadFile,\n open: (src?: string) => {\n if (src) imgSrc.value = src;\n modalVisible.value = true;\n },\n close: () => {\n modalVisible.value = false;\n },\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: 2026-02-25\n * @Description: 图片裁剪组件(基于 vue-cropper)\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\n-->\n<template>\n <NModal\n v-if=\"props.modal\"\n v-model:show=\"modalVisible\"\n preset=\"card\"\n :title=\"props.modalTitle || '图片裁剪'\"\n :style=\"{ width: '860px' }\"\n :mask-closable=\"false\"\n :closable=\"true\"\n >\n <div class=\"c-image-cropper__body\">\n <CropperToolbar\n v-if=\"props.showToolbar\"\n :current-ratio=\"currentRatio\"\n :ratio-presets=\"ratioPresets\"\n @ratio=\"handleRatio\"\n @rotate=\"rotate\"\n @flip-x=\"core.flipX\"\n @flip-y=\"core.flipY\"\n @zoom=\"core.zoom\"\n @reset=\"handleReset\"\n />\n <div class=\"c-image-cropper__workspace\">\n <div\n class=\"c-image-cropper__canvas\"\n :style=\"{ height: containerHeight }\"\n >\n <VueCropper\n v-if=\"imgSrc\"\n ref=\"modalCropperRef\"\n :img=\"imgSrc\"\n :output-size=\"props.outputQuality\"\n :output-type=\"vueCropperOutputType\"\n :can-scale=\"false\"\n :auto-crop=\"true\"\n :auto-crop-width=\"autoCropSize.width\"\n :auto-crop-height=\"autoCropSize.height\"\n :fixed=\"isFixed\"\n :fixed-number=\"fixedNumber\"\n :center-box=\"true\"\n :info=\"true\"\n :info-true=\"true\"\n :can-move=\"true\"\n :can-move-box=\"true\"\n :original=\"false\"\n :high=\"true\"\n :full=\"false\"\n :mode=\"'contain'\"\n @real-time=\"handleRealTimePreview\"\n @img-load=\"onImgLoad\"\n />\n <div v-else class=\"c-image-cropper__placeholder\">\n <C_Icon\n name=\"mdi:image-plus-outline\"\n style=\"font-size: 48px; opacity: 0.3\"\n />\n <span>请选择图片</span>\n </div>\n </div>\n <div\n v-if=\"props.showPreview && imgSrc\"\n class=\"c-image-cropper__preview-panel\"\n >\n <CropperPreview\n :preview-data=\"previewData\"\n :circular=\"props.circular\"\n />\n </div>\n </div>\n </div>\n <template #footer>\n <NSpace justify=\"end\">\n <NButton @click=\"handleCancel\">取消</NButton>\n <NButton type=\"primary\" :loading=\"exporting\" @click=\"handleConfirm\"\n >确认裁剪</NButton\n >\n </NSpace>\n </template>\n </NModal>\n\n <div v-else class=\"c-image-cropper\">\n <div class=\"c-image-cropper__body\">\n <CropperToolbar\n v-if=\"props.showToolbar\"\n :current-ratio=\"currentRatio\"\n :ratio-presets=\"ratioPresets\"\n @ratio=\"handleRatio\"\n @rotate=\"rotate\"\n @flip-x=\"core.flipX\"\n @flip-y=\"core.flipY\"\n @zoom=\"core.zoom\"\n @reset=\"handleReset\"\n />\n <div class=\"c-image-cropper__workspace\">\n <div\n class=\"c-image-cropper__canvas\"\n :style=\"{ height: containerHeight }\"\n >\n <VueCropper\n v-if=\"imgSrc\"\n ref=\"inlineCropperRef\"\n :img=\"imgSrc\"\n :output-size=\"props.outputQuality\"\n :output-type=\"vueCropperOutputType\"\n :can-scale=\"false\"\n :auto-crop=\"true\"\n :auto-crop-width=\"autoCropSize.width\"\n :auto-crop-height=\"autoCropSize.height\"\n :fixed=\"isFixed\"\n :fixed-number=\"fixedNumber\"\n :center-box=\"true\"\n :info=\"true\"\n :info-true=\"true\"\n :can-move=\"true\"\n :can-move-box=\"true\"\n :original=\"false\"\n :high=\"true\"\n :full=\"false\"\n :mode=\"'contain'\"\n @real-time=\"handleRealTimePreview\"\n @img-load=\"onImgLoad\"\n />\n <div v-else class=\"c-image-cropper__placeholder\">\n <C_Icon\n name=\"mdi:image-plus-outline\"\n style=\"font-size: 48px; opacity: 0.3\"\n />\n <span>请选择图片</span>\n </div>\n </div>\n <div\n v-if=\"props.showPreview && imgSrc\"\n class=\"c-image-cropper__preview-panel\"\n >\n <CropperPreview\n :preview-data=\"previewData\"\n :circular=\"props.circular\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from \"vue\";\nimport { NModal, NButton, NSpace } from \"naive-ui\";\nimport { VueCropper } from \"vue-cropper\";\nimport C_Icon from \"../C_Icon/index.vue\";\nimport type {\n AspectRatioPreset,\n CropOutputFormat,\n ImageCropperExpose,\n ImageCropperProps,\n} from \"./types\";\nimport { useCropperCore } from \"./composables/useCropperCore\";\nimport CropperToolbar from \"./components/CropperToolbar.vue\";\nimport CropperPreview from \"./components/CropperPreview.vue\";\n\ndefineOptions({ name: \"C_ImageCropper\" });\n\nconst props = withDefaults(defineProps<ImageCropperProps>(), {\n src: \"\",\n aspectRatio: 0,\n outputFormat: \"png\",\n outputQuality: 0.92,\n maxOutputWidth: 0,\n maxOutputHeight: 0,\n showPreview: true,\n showToolbar: true,\n circular: false,\n disabled: false,\n height: \"400px\",\n modal: false,\n modalTitle: \"图片裁剪\",\n});\n\nconst emit = defineEmits<{\n crop: [result: any];\n ready: [];\n error: [error: Event];\n confirm: [result: any];\n cancel: [];\n}>();\n\nconst imgSrc = ref(props.src);\nconst currentRatio = ref(props.aspectRatio);\nconst modalVisible = ref(false);\nconst exporting = ref(false);\nconst previewData = ref<any>(null);\n\nconst inlineCropperRef = ref<any>(null);\nconst modalCropperRef = ref<any>(null);\n\nconst activeCropperRef = computed(() =>\n props.modal ? modalCropperRef.value : inlineCropperRef.value,\n);\n\nconst containerHeight = computed(() =>\n typeof props.height === \"number\" ? `${props.height}px` : props.height,\n);\n\nconst vueCropperOutputType = computed(() => {\n const map: Record<CropOutputFormat, string> = {\n png: \"png\",\n jpeg: \"jpeg\",\n webp: \"webp\",\n };\n return map[props.outputFormat as CropOutputFormat] || \"png\";\n});\n\nconst isFixed = computed(() => currentRatio.value > 0);\n\nconst fixedNumber = computed(() => {\n if (currentRatio.value <= 0) return [1, 1];\n if (currentRatio.value === 1) return [1, 1];\n if (Math.abs(currentRatio.value - 16 / 9) < 0.01) return [16, 9];\n if (Math.abs(currentRatio.value - 4 / 3) < 0.01) return [4, 3];\n if (Math.abs(currentRatio.value - 3 / 2) < 0.01) return [3, 2];\n return [Math.round(currentRatio.value * 100), 100];\n});\n\nconst autoCropSize = computed(() => ({\n width: 300,\n height: currentRatio.value > 0 ? 300 / currentRatio.value : 200,\n}));\n\nconst ratioPresets: AspectRatioPreset[] = [\n { label: \"自由\", value: 0 },\n { label: \"1:1\", value: 1 },\n { label: \"16:9\", value: 16 / 9 },\n { label: \"4:3\", value: 4 / 3 },\n { label: \"3:2\", value: 3 / 2 },\n];\n\nconst formatRef = computed(() => props.outputFormat as CropOutputFormat);\nconst qualityRef = computed(() => props.outputQuality);\nconst maxWRef = computed(() => props.maxOutputWidth);\nconst maxHRef = computed(() => props.maxOutputHeight);\n\nconst core = useCropperCore({\n format: formatRef,\n quality: qualityRef,\n maxWidth: maxWRef,\n maxHeight: maxHRef,\n});\n\nwatch(activeCropperRef, (v) => {\n core.cropperRef.value = v;\n});\n\nfunction handleRealTimePreview(data: any) {\n previewData.value = data;\n}\nfunction onImgLoad(status: string) {\n if (status === \"success\") emit(\"ready\");\n}\nfunction rotate(angle: number) {\n core.rotate(angle);\n}\nfunction handleRatio(v: number) {\n currentRatio.value = v;\n}\nfunction handleReset() {\n core.reset();\n}\n\nasync function handleConfirm() {\n exporting.value = true;\n try {\n const result = await core.getCropResult();\n emit(\"confirm\", result);\n emit(\"crop\", result);\n modalVisible.value = false;\n } finally {\n exporting.value = false;\n }\n}\n\nfunction handleCancel() {\n modalVisible.value = false;\n emit(\"cancel\");\n}\n\nfunction loadFile(file: File) {\n const reader = new FileReader();\n reader.onload = (e) => {\n imgSrc.value = e.target?.result as string;\n };\n reader.readAsDataURL(file);\n}\n\nwatch(\n () => props.src,\n (v) => {\n imgSrc.value = v;\n },\n);\nwatch(\n () => props.aspectRatio,\n (v) => {\n currentRatio.value = v;\n },\n);\n\ndefineExpose<ImageCropperExpose>({\n getCropResult: core.getCropResult,\n rotate: core.rotate,\n zoom: core.zoom,\n flipX: core.flipX,\n flipY: core.flipY,\n reset: core.reset,\n setAspectRatio: (r: number) => {\n currentRatio.value = r;\n },\n loadFile,\n open: (src?: string) => {\n if (src) imgSrc.value = src;\n modalVisible.value = true;\n },\n close: () => {\n modalVisible.value = false;\n },\n});\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"./index.scss\";\n</style>\n","<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2026-02-25\n * @Description: 图片裁剪组件(基于 vue-cropper)\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\n-->\n<template>\n <NModal\n v-if=\"props.modal\"\n v-model:show=\"modalVisible\"\n preset=\"card\"\n :title=\"props.modalTitle || '图片裁剪'\"\n :style=\"{ width: '860px' }\"\n :mask-closable=\"false\"\n :closable=\"true\"\n >\n <div class=\"c-image-cropper__body\">\n <CropperToolbar\n v-if=\"props.showToolbar\"\n :current-ratio=\"currentRatio\"\n :ratio-presets=\"ratioPresets\"\n @ratio=\"handleRatio\"\n @rotate=\"rotate\"\n @flip-x=\"core.flipX\"\n @flip-y=\"core.flipY\"\n @zoom=\"core.zoom\"\n @reset=\"handleReset\"\n />\n <div class=\"c-image-cropper__workspace\">\n <div\n class=\"c-image-cropper__canvas\"\n :style=\"{ height: containerHeight }\"\n >\n <VueCropper\n v-if=\"imgSrc\"\n ref=\"modalCropperRef\"\n :img=\"imgSrc\"\n :output-size=\"props.outputQuality\"\n :output-type=\"vueCropperOutputType\"\n :can-scale=\"false\"\n :auto-crop=\"true\"\n :auto-crop-width=\"autoCropSize.width\"\n :auto-crop-height=\"autoCropSize.height\"\n :fixed=\"isFixed\"\n :fixed-number=\"fixedNumber\"\n :center-box=\"true\"\n :info=\"true\"\n :info-true=\"true\"\n :can-move=\"true\"\n :can-move-box=\"true\"\n :original=\"false\"\n :high=\"true\"\n :full=\"false\"\n :mode=\"'contain'\"\n @real-time=\"handleRealTimePreview\"\n @img-load=\"onImgLoad\"\n />\n <div v-else class=\"c-image-cropper__placeholder\">\n <C_Icon\n name=\"mdi:image-plus-outline\"\n style=\"font-size: 48px; opacity: 0.3\"\n />\n <span>请选择图片</span>\n </div>\n </div>\n <div\n v-if=\"props.showPreview && imgSrc\"\n class=\"c-image-cropper__preview-panel\"\n >\n <CropperPreview\n :preview-data=\"previewData\"\n :circular=\"props.circular\"\n />\n </div>\n </div>\n </div>\n <template #footer>\n <NSpace justify=\"end\">\n <NButton @click=\"handleCancel\">取消</NButton>\n <NButton type=\"primary\" :loading=\"exporting\" @click=\"handleConfirm\"\n >确认裁剪</NButton\n >\n </NSpace>\n </template>\n </NModal>\n\n <div v-else class=\"c-image-cropper\">\n <div class=\"c-image-cropper__body\">\n <CropperToolbar\n v-if=\"props.showToolbar\"\n :current-ratio=\"currentRatio\"\n :ratio-presets=\"ratioPresets\"\n @ratio=\"handleRatio\"\n @rotate=\"rotate\"\n @flip-x=\"core.flipX\"\n @flip-y=\"core.flipY\"\n @zoom=\"core.zoom\"\n @reset=\"handleReset\"\n />\n <div class=\"c-image-cropper__workspace\">\n <div\n class=\"c-image-cropper__canvas\"\n :style=\"{ height: containerHeight }\"\n >\n <VueCropper\n v-if=\"imgSrc\"\n ref=\"inlineCropperRef\"\n :img=\"imgSrc\"\n :output-size=\"props.outputQuality\"\n :output-type=\"vueCropperOutputType\"\n :can-scale=\"false\"\n :auto-crop=\"true\"\n :auto-crop-width=\"autoCropSize.width\"\n :auto-crop-height=\"autoCropSize.height\"\n :fixed=\"isFixed\"\n :fixed-number=\"fixedNumber\"\n :center-box=\"true\"\n :info=\"true\"\n :info-true=\"true\"\n :can-move=\"true\"\n :can-move-box=\"true\"\n :original=\"false\"\n :high=\"true\"\n :full=\"false\"\n :mode=\"'contain'\"\n @real-time=\"handleRealTimePreview\"\n @img-load=\"onImgLoad\"\n />\n <div v-else class=\"c-image-cropper__placeholder\">\n <C_Icon\n name=\"mdi:image-plus-outline\"\n style=\"font-size: 48px; opacity: 0.3\"\n />\n <span>请选择图片</span>\n </div>\n </div>\n <div\n v-if=\"props.showPreview && imgSrc\"\n class=\"c-image-cropper__preview-panel\"\n >\n <CropperPreview\n :preview-data=\"previewData\"\n :circular=\"props.circular\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from \"vue\";\nimport { NModal, NButton, NSpace } from \"naive-ui\";\nimport { VueCropper } from \"vue-cropper\";\nimport C_Icon from \"../C_Icon/index.vue\";\nimport type {\n AspectRatioPreset,\n CropOutputFormat,\n ImageCropperExpose,\n ImageCropperProps,\n} from \"./types\";\nimport { useCropperCore } from \"./composables/useCropperCore\";\nimport CropperToolbar from \"./components/CropperToolbar.vue\";\nimport CropperPreview from \"./components/CropperPreview.vue\";\n\ndefineOptions({ name: \"C_ImageCropper\" });\n\nconst props = withDefaults(defineProps<ImageCropperProps>(), {\n src: \"\",\n aspectRatio: 0,\n outputFormat: \"png\",\n outputQuality: 0.92,\n maxOutputWidth: 0,\n maxOutputHeight: 0,\n showPreview: true,\n showToolbar: true,\n circular: false,\n disabled: false,\n height: \"400px\",\n modal: false,\n modalTitle: \"图片裁剪\",\n});\n\nconst emit = defineEmits<{\n crop: [result: any];\n ready: [];\n error: [error: Event];\n confirm: [result: any];\n cancel: [];\n}>();\n\nconst imgSrc = ref(props.src);\nconst currentRatio = ref(props.aspectRatio);\nconst modalVisible = ref(false);\nconst exporting = ref(false);\nconst previewData = ref<any>(null);\n\nconst inlineCropperRef = ref<any>(null);\nconst modalCropperRef = ref<any>(null);\n\nconst activeCropperRef = computed(() =>\n props.modal ? modalCropperRef.value : inlineCropperRef.value,\n);\n\nconst containerHeight = computed(() =>\n typeof props.height === \"number\" ? `${props.height}px` : props.height,\n);\n\nconst vueCropperOutputType = computed(() => {\n const map: Record<CropOutputFormat, string> = {\n png: \"png\",\n jpeg: \"jpeg\",\n webp: \"webp\",\n };\n return map[props.outputFormat as CropOutputFormat] || \"png\";\n});\n\nconst isFixed = computed(() => currentRatio.value > 0);\n\nconst fixedNumber = computed(() => {\n if (currentRatio.value <= 0) return [1, 1];\n if (currentRatio.value === 1) return [1, 1];\n if (Math.abs(currentRatio.value - 16 / 9) < 0.01) return [16, 9];\n if (Math.abs(currentRatio.value - 4 / 3) < 0.01) return [4, 3];\n if (Math.abs(currentRatio.value - 3 / 2) < 0.01) return [3, 2];\n return [Math.round(currentRatio.value * 100), 100];\n});\n\nconst autoCropSize = computed(() => ({\n width: 300,\n height: currentRatio.value > 0 ? 300 / currentRatio.value : 200,\n}));\n\nconst ratioPresets: AspectRatioPreset[] = [\n { label: \"自由\", value: 0 },\n { label: \"1:1\", value: 1 },\n { label: \"16:9\", value: 16 / 9 },\n { label: \"4:3\", value: 4 / 3 },\n { label: \"3:2\", value: 3 / 2 },\n];\n\nconst formatRef = computed(() => props.outputFormat as CropOutputFormat);\nconst qualityRef = computed(() => props.outputQuality);\nconst maxWRef = computed(() => props.maxOutputWidth);\nconst maxHRef = computed(() => props.maxOutputHeight);\n\nconst core = useCropperCore({\n format: formatRef,\n quality: qualityRef,\n maxWidth: maxWRef,\n maxHeight: maxHRef,\n});\n\nwatch(activeCropperRef, (v) => {\n core.cropperRef.value = v;\n});\n\nfunction handleRealTimePreview(data: any) {\n previewData.value = data;\n}\nfunction onImgLoad(status: string) {\n if (status === \"success\") emit(\"ready\");\n}\nfunction rotate(angle: number) {\n core.rotate(angle);\n}\nfunction handleRatio(v: number) {\n currentRatio.value = v;\n}\nfunction handleReset() {\n core.reset();\n}\n\nasync function handleConfirm() {\n exporting.value = true;\n try {\n const result = await core.getCropResult();\n emit(\"confirm\", result);\n emit(\"crop\", result);\n modalVisible.value = false;\n } finally {\n exporting.value = false;\n }\n}\n\nfunction handleCancel() {\n modalVisible.value = false;\n emit(\"cancel\");\n}\n\nfunction loadFile(file: File) {\n const reader = new FileReader();\n reader.onload = (e) => {\n imgSrc.value = e.target?.result as string;\n };\n reader.readAsDataURL(file);\n}\n\nwatch(\n () => props.src,\n (v) => {\n imgSrc.value = v;\n },\n);\nwatch(\n () => props.aspectRatio,\n (v) => {\n currentRatio.value = v;\n },\n);\n\ndefineExpose<ImageCropperExpose>({\n getCropResult: core.getCropResult,\n rotate: core.rotate,\n zoom: core.zoom,\n flipX: core.flipX,\n flipY: core.flipY,\n reset: core.reset,\n setAspectRatio: (r: number) => {\n currentRatio.value = r;\n },\n loadFile,\n open: (src?: string) => {\n if (src) imgSrc.value = src;\n modalVisible.value = true;\n },\n close: () => {\n modalVisible.value = false;\n },\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_Map-BCJmDnJT.css","names":[],"sources":["../src/components/C_Map/index.vue?vue&type=style&index=0&scoped=a3602506&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled */.c-map[data-v-a3602506] {\n position: relative;\n width: 100%;\n min-height: 300px;\n background: #f5f5f5;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-container[data-v-a3602506] {\n width: 100%;\n height: 100%;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-loading[data-v-a3602506] {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n border-radius: 4px;\n z-index: 1000;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"C_Map-DNnBcY70.css","names":[],"sources":["../src/components/C_Map/index.vue?vue&type=style&index=0&scoped=340816cd&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled */.c-map[data-v-340816cd] {\n position: relative;\n width: 100%;\n min-height: 300px;\n background: #f5f5f5;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-container[data-v-340816cd] {\n width: 100%;\n height: 100%;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-loading[data-v-340816cd] {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n border-radius: 4px;\n z-index: 1000;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
@@ -1 +1 @@
1
- {"version":3,"file":"C_Map-WUMXSAfy.css","names":[],"sources":["../src/components/C_Map/index.vue?vue&type=style&index=0&scoped=a3602506&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled */.c-map[data-v-a3602506] {\n position: relative;\n width: 100%;\n min-height: 300px;\n background: #f5f5f5;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-container[data-v-a3602506] {\n width: 100%;\n height: 100%;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-loading[data-v-a3602506] {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n border-radius: 4px;\n z-index: 1000;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"C_Map-LEXIx6zM.css","names":[],"sources":["../src/components/C_Map/index.vue?vue&type=style&index=0&scoped=340816cd&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled */.c-map[data-v-340816cd] {\n position: relative;\n width: 100%;\n min-height: 300px;\n background: #f5f5f5;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-container[data-v-340816cd] {\n width: 100%;\n height: 100%;\n border-radius: 4px;\n overflow: hidden;\n}\n.c-map .map-loading[data-v-340816cd] {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n border-radius: 4px;\n z-index: 1000;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
package/dist/C_Map.css CHANGED
@@ -1,4 +1,4 @@
1
- /* unplugin-vue-components disabled */.c-map[data-v-a3602506] {
1
+ /* unplugin-vue-components disabled */.c-map[data-v-340816cd] {
2
2
  position: relative;
3
3
  width: 100%;
4
4
  min-height: 300px;
@@ -6,13 +6,13 @@
6
6
  border-radius: 4px;
7
7
  overflow: hidden;
8
8
  }
9
- .c-map .map-container[data-v-a3602506] {
9
+ .c-map .map-container[data-v-340816cd] {
10
10
  width: 100%;
11
11
  height: 100%;
12
12
  border-radius: 4px;
13
13
  overflow: hidden;
14
14
  }
15
- .c-map .map-loading[data-v-a3602506] {
15
+ .c-map .map-loading[data-v-340816cd] {
16
16
  position: absolute;
17
17
  top: 0;
18
18
  left: 0;
@@ -26,4 +26,4 @@
26
26
  z-index: 1000;
27
27
  }
28
28
 
29
- /*# sourceMappingURL=C_Map-BCJmDnJT.css.map*/
29
+ /*# sourceMappingURL=C_Map-DNnBcY70.css.map*/
package/dist/C_Map.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"C_Map.js","names":["height"],"sources":["../src/components/C_Map/data.ts","../src/components/C_Map/index.vue","../src/components/C_Map/index.vue","../src/components/C_Map/index.vue"],"sourcesContent":["export const MAP_TYPES = [\r\n { label: \"OpenStreetMap\", value: \"osm\" },\r\n { label: \"高德地图\", value: \"amap\" },\r\n] as const;\r\n\r\nexport type MapType = (typeof MAP_TYPES)[number][\"value\"];\r\n\r\nexport interface MapMarker {\r\n lat: number;\r\n lng: number;\r\n popup?: string;\r\n}\r\n\r\nexport interface MapConfig {\r\n height?: string;\r\n center?: [number, number];\r\n zoom?: number;\r\n markers?: MapMarker[];\r\n mapType?: MapType;\r\n amapKey?: string;\r\n}\r\n\r\nexport const DEFAULT_MAP_CONFIG: Required<\r\n Omit<MapConfig, \"markers\" | \"amapKey\">\r\n> = {\r\n height: \"400px\",\r\n center: [39.9042, 116.4074],\r\n zoom: 10,\r\n mapType: \"osm\",\r\n};\r\n\r\nexport const OSM_TILE_CONFIG = {\r\n url: \"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\r\n attribution: \"© OpenStreetMap contributors\",\r\n maxZoom: 19,\r\n minZoom: 1,\r\n tileSize: 256,\r\n detectRetina: true,\r\n} as const;\r\n\r\nexport const AMAP_CONFIG = {\r\n apiUrl: \"https://webapi.amap.com/maps?v=2.0&key=\",\r\n note: \"高德地图需要API Key,如需使用请申请:https://lbs.amap.com/api/javascript-api/guide/create/\",\r\n} as const;\r\n\r\nexport const MAP_ICONS = {\r\n iconRetinaUrl:\r\n \"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png\",\r\n iconUrl:\r\n \"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png\",\r\n shadowUrl:\r\n \"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png\",\r\n} as const;\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-12-02\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-map\">\r\n <div\r\n ref=\"mapContainer\"\r\n class=\"map-container\"\r\n :style=\"{ height: height }\"\r\n ></div>\r\n <div v-if=\"loading\" class=\"map-loading\">\r\n <NSpin size=\"large\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\r\nimport { NSpin } from \"naive-ui\";\r\nimport L from \"leaflet\";\r\nimport \"leaflet/dist/leaflet.css\";\r\nimport { OSM_TILE_CONFIG, AMAP_CONFIG, MAP_ICONS } from \"./data\";\r\n\r\ndefineOptions({ name: \"C_Map\" });\r\n\r\ndelete (L.Icon.Default.prototype as any)._getIconUrl;\r\nL.Icon.Default.mergeOptions(MAP_ICONS);\r\n\r\ninterface MapMarker {\r\n lat: number;\r\n lng: number;\r\n popup?: string;\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n height?: string;\r\n center?: [number, number];\r\n zoom?: number;\r\n markers?: MapMarker[];\r\n mapType?: \"osm\" | \"amap\";\r\n amapKey?: string;\r\n }>(),\r\n {\r\n height: \"400px\",\r\n center: () => [39.9042, 116.4074],\r\n zoom: 10,\r\n markers: () => [],\r\n mapType: \"osm\",\r\n amapKey: \"\",\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n ready: [map: any];\r\n markerClick: [marker: MapMarker, event: any];\r\n}>();\r\n\r\nconst mapContainer = ref<HTMLElement>();\r\nconst loading = ref(true);\r\nlet map: any = null;\r\n\r\nconst initOSMMap = async () => {\r\n if (!mapContainer.value) return;\r\n try {\r\n mapContainer.value.innerHTML = \"\";\r\n map = L.map(mapContainer.value, {\r\n center: props.center,\r\n zoom: props.zoom,\r\n zoomControl: true,\r\n preferCanvas: true,\r\n });\r\n const tileLayer = L.tileLayer(OSM_TILE_CONFIG.url, OSM_TILE_CONFIG);\r\n tileLayer.addTo(map);\r\n addMarkers();\r\n await nextTick();\r\n requestAnimationFrame(() => {\r\n map?.invalidateSize({ reset: true, pan: false });\r\n });\r\n loading.value = false;\r\n emit(\"ready\", map);\r\n } catch (error) {\r\n console.error(\"OpenStreetMap初始化失败:\", error);\r\n loading.value = false;\r\n }\r\n};\r\n\r\nconst initAMap = async () => {\r\n if (!mapContainer.value || !props.amapKey) return;\r\n try {\r\n mapContainer.value.innerHTML = \"\";\r\n const script = document.createElement(\"script\");\r\n script.type = \"text/javascript\";\r\n script.src = `${AMAP_CONFIG.apiUrl}${props.amapKey}`;\r\n script.onload = () => {\r\n if ((window as any).AMap) {\r\n const amap = new (window as any).AMap.Map(mapContainer.value, {\r\n zoom: props.zoom,\r\n center: props.center,\r\n });\r\n addAMapMarkers(amap);\r\n loading.value = false;\r\n emit(\"ready\", amap);\r\n }\r\n };\r\n script.onerror = () => {\r\n loading.value = false;\r\n };\r\n document.head.appendChild(script);\r\n } catch {\r\n loading.value = false;\r\n }\r\n};\r\n\r\nconst addMarkers = () => {\r\n if (!map || props.mapType !== \"osm\" || !props.markers) return;\r\n map.eachLayer((layer: any) => {\r\n if (layer instanceof L.Marker) {\r\n map.removeLayer(layer);\r\n }\r\n });\r\n props.markers.forEach((marker) => {\r\n const leafletMarker = L.marker([marker.lat, marker.lng]);\r\n if (marker.popup) {\r\n leafletMarker.bindPopup(marker.popup);\r\n leafletMarker.on(\"click\", (event: any) => {\r\n emit(\"markerClick\", marker, event);\r\n });\r\n }\r\n leafletMarker.addTo(map);\r\n });\r\n};\r\n\r\nconst addAMapMarkers = (amap: any) => {\r\n if (!amap || props.mapType !== \"amap\" || !props.markers) return;\r\n props.markers.forEach((marker) => {\r\n const amapMarker = new (window as any).AMap.Marker({\r\n position: [marker.lat, marker.lng],\r\n title: marker.popup || \"\",\r\n });\r\n if (marker.popup) {\r\n const infoWindow = new (window as any).AMap.InfoWindow({\r\n content: marker.popup,\r\n offset: new (window as any).AMap.Pixel(0, -30),\r\n });\r\n amapMarker.on(\"click\", () => {\r\n infoWindow.open(amap, amapMarker.getPosition());\r\n emit(\"markerClick\", marker, null);\r\n });\r\n }\r\n amapMarker.setMap(amap);\r\n });\r\n};\r\n\r\nwatch(\r\n () => props.markers,\r\n () => {\r\n if (map && props.mapType === \"osm\") addMarkers();\r\n },\r\n { deep: true },\r\n);\r\n\r\nwatch(\r\n () => props.mapType,\r\n async (newType, oldType) => {\r\n if (newType === oldType) return;\r\n if (map) {\r\n map.remove();\r\n map = null;\r\n loading.value = true;\r\n await nextTick();\r\n if (newType === \"amap\" && props.amapKey) await initAMap();\r\n else await initOSMMap();\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n [() => props.center, () => props.zoom],\r\n () => {\r\n if (map) map.setView(props.center, props.zoom);\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(async () => {\r\n await nextTick();\r\n if (props.mapType === \"amap\" && props.amapKey) await initAMap();\r\n else await initOSMMap();\r\n});\r\n\r\nonUnmounted(() => {\r\n if (map) {\r\n map.remove();\r\n map = null;\r\n }\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-12-02\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-map\">\r\n <div\r\n ref=\"mapContainer\"\r\n class=\"map-container\"\r\n :style=\"{ height: height }\"\r\n ></div>\r\n <div v-if=\"loading\" class=\"map-loading\">\r\n <NSpin size=\"large\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\r\nimport { NSpin } from \"naive-ui\";\r\nimport L from \"leaflet\";\r\nimport \"leaflet/dist/leaflet.css\";\r\nimport { OSM_TILE_CONFIG, AMAP_CONFIG, MAP_ICONS } from \"./data\";\r\n\r\ndefineOptions({ name: \"C_Map\" });\r\n\r\ndelete (L.Icon.Default.prototype as any)._getIconUrl;\r\nL.Icon.Default.mergeOptions(MAP_ICONS);\r\n\r\ninterface MapMarker {\r\n lat: number;\r\n lng: number;\r\n popup?: string;\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n height?: string;\r\n center?: [number, number];\r\n zoom?: number;\r\n markers?: MapMarker[];\r\n mapType?: \"osm\" | \"amap\";\r\n amapKey?: string;\r\n }>(),\r\n {\r\n height: \"400px\",\r\n center: () => [39.9042, 116.4074],\r\n zoom: 10,\r\n markers: () => [],\r\n mapType: \"osm\",\r\n amapKey: \"\",\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n ready: [map: any];\r\n markerClick: [marker: MapMarker, event: any];\r\n}>();\r\n\r\nconst mapContainer = ref<HTMLElement>();\r\nconst loading = ref(true);\r\nlet map: any = null;\r\n\r\nconst initOSMMap = async () => {\r\n if (!mapContainer.value) return;\r\n try {\r\n mapContainer.value.innerHTML = \"\";\r\n map = L.map(mapContainer.value, {\r\n center: props.center,\r\n zoom: props.zoom,\r\n zoomControl: true,\r\n preferCanvas: true,\r\n });\r\n const tileLayer = L.tileLayer(OSM_TILE_CONFIG.url, OSM_TILE_CONFIG);\r\n tileLayer.addTo(map);\r\n addMarkers();\r\n await nextTick();\r\n requestAnimationFrame(() => {\r\n map?.invalidateSize({ reset: true, pan: false });\r\n });\r\n loading.value = false;\r\n emit(\"ready\", map);\r\n } catch (error) {\r\n console.error(\"OpenStreetMap初始化失败:\", error);\r\n loading.value = false;\r\n }\r\n};\r\n\r\nconst initAMap = async () => {\r\n if (!mapContainer.value || !props.amapKey) return;\r\n try {\r\n mapContainer.value.innerHTML = \"\";\r\n const script = document.createElement(\"script\");\r\n script.type = \"text/javascript\";\r\n script.src = `${AMAP_CONFIG.apiUrl}${props.amapKey}`;\r\n script.onload = () => {\r\n if ((window as any).AMap) {\r\n const amap = new (window as any).AMap.Map(mapContainer.value, {\r\n zoom: props.zoom,\r\n center: props.center,\r\n });\r\n addAMapMarkers(amap);\r\n loading.value = false;\r\n emit(\"ready\", amap);\r\n }\r\n };\r\n script.onerror = () => {\r\n loading.value = false;\r\n };\r\n document.head.appendChild(script);\r\n } catch {\r\n loading.value = false;\r\n }\r\n};\r\n\r\nconst addMarkers = () => {\r\n if (!map || props.mapType !== \"osm\" || !props.markers) return;\r\n map.eachLayer((layer: any) => {\r\n if (layer instanceof L.Marker) {\r\n map.removeLayer(layer);\r\n }\r\n });\r\n props.markers.forEach((marker) => {\r\n const leafletMarker = L.marker([marker.lat, marker.lng]);\r\n if (marker.popup) {\r\n leafletMarker.bindPopup(marker.popup);\r\n leafletMarker.on(\"click\", (event: any) => {\r\n emit(\"markerClick\", marker, event);\r\n });\r\n }\r\n leafletMarker.addTo(map);\r\n });\r\n};\r\n\r\nconst addAMapMarkers = (amap: any) => {\r\n if (!amap || props.mapType !== \"amap\" || !props.markers) return;\r\n props.markers.forEach((marker) => {\r\n const amapMarker = new (window as any).AMap.Marker({\r\n position: [marker.lat, marker.lng],\r\n title: marker.popup || \"\",\r\n });\r\n if (marker.popup) {\r\n const infoWindow = new (window as any).AMap.InfoWindow({\r\n content: marker.popup,\r\n offset: new (window as any).AMap.Pixel(0, -30),\r\n });\r\n amapMarker.on(\"click\", () => {\r\n infoWindow.open(amap, amapMarker.getPosition());\r\n emit(\"markerClick\", marker, null);\r\n });\r\n }\r\n amapMarker.setMap(amap);\r\n });\r\n};\r\n\r\nwatch(\r\n () => props.markers,\r\n () => {\r\n if (map && props.mapType === \"osm\") addMarkers();\r\n },\r\n { deep: true },\r\n);\r\n\r\nwatch(\r\n () => props.mapType,\r\n async (newType, oldType) => {\r\n if (newType === oldType) return;\r\n if (map) {\r\n map.remove();\r\n map = null;\r\n loading.value = true;\r\n await nextTick();\r\n if (newType === \"amap\" && props.amapKey) await initAMap();\r\n else await initOSMMap();\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n [() => props.center, () => props.zoom],\r\n () => {\r\n if (map) map.setView(props.center, props.zoom);\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(async () => {\r\n await nextTick();\r\n if (props.mapType === \"amap\" && props.amapKey) await initAMap();\r\n else await initOSMMap();\r\n});\r\n\r\nonUnmounted(() => {\r\n if (map) {\r\n map.remove();\r\n map = null;\r\n }\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-12-02\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-map\">\r\n <div\r\n ref=\"mapContainer\"\r\n class=\"map-container\"\r\n :style=\"{ height: height }\"\r\n ></div>\r\n <div v-if=\"loading\" class=\"map-loading\">\r\n <NSpin size=\"large\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\r\nimport { NSpin } from \"naive-ui\";\r\nimport L from \"leaflet\";\r\nimport \"leaflet/dist/leaflet.css\";\r\nimport { OSM_TILE_CONFIG, AMAP_CONFIG, MAP_ICONS } from \"./data\";\r\n\r\ndefineOptions({ name: \"C_Map\" });\r\n\r\ndelete (L.Icon.Default.prototype as any)._getIconUrl;\r\nL.Icon.Default.mergeOptions(MAP_ICONS);\r\n\r\ninterface MapMarker {\r\n lat: number;\r\n lng: number;\r\n popup?: string;\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n height?: string;\r\n center?: [number, number];\r\n zoom?: number;\r\n markers?: MapMarker[];\r\n mapType?: \"osm\" | \"amap\";\r\n amapKey?: string;\r\n }>(),\r\n {\r\n height: \"400px\",\r\n center: () => [39.9042, 116.4074],\r\n zoom: 10,\r\n markers: () => [],\r\n mapType: \"osm\",\r\n amapKey: \"\",\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n ready: [map: any];\r\n markerClick: [marker: MapMarker, event: any];\r\n}>();\r\n\r\nconst mapContainer = ref<HTMLElement>();\r\nconst loading = ref(true);\r\nlet map: any = null;\r\n\r\nconst initOSMMap = async () => {\r\n if (!mapContainer.value) return;\r\n try {\r\n mapContainer.value.innerHTML = \"\";\r\n map = L.map(mapContainer.value, {\r\n center: props.center,\r\n zoom: props.zoom,\r\n zoomControl: true,\r\n preferCanvas: true,\r\n });\r\n const tileLayer = L.tileLayer(OSM_TILE_CONFIG.url, OSM_TILE_CONFIG);\r\n tileLayer.addTo(map);\r\n addMarkers();\r\n await nextTick();\r\n requestAnimationFrame(() => {\r\n map?.invalidateSize({ reset: true, pan: false });\r\n });\r\n loading.value = false;\r\n emit(\"ready\", map);\r\n } catch (error) {\r\n console.error(\"OpenStreetMap初始化失败:\", error);\r\n loading.value = false;\r\n }\r\n};\r\n\r\nconst initAMap = async () => {\r\n if (!mapContainer.value || !props.amapKey) return;\r\n try {\r\n mapContainer.value.innerHTML = \"\";\r\n const script = document.createElement(\"script\");\r\n script.type = \"text/javascript\";\r\n script.src = `${AMAP_CONFIG.apiUrl}${props.amapKey}`;\r\n script.onload = () => {\r\n if ((window as any).AMap) {\r\n const amap = new (window as any).AMap.Map(mapContainer.value, {\r\n zoom: props.zoom,\r\n center: props.center,\r\n });\r\n addAMapMarkers(amap);\r\n loading.value = false;\r\n emit(\"ready\", amap);\r\n }\r\n };\r\n script.onerror = () => {\r\n loading.value = false;\r\n };\r\n document.head.appendChild(script);\r\n } catch {\r\n loading.value = false;\r\n }\r\n};\r\n\r\nconst addMarkers = () => {\r\n if (!map || props.mapType !== \"osm\" || !props.markers) return;\r\n map.eachLayer((layer: any) => {\r\n if (layer instanceof L.Marker) {\r\n map.removeLayer(layer);\r\n }\r\n });\r\n props.markers.forEach((marker) => {\r\n const leafletMarker = L.marker([marker.lat, marker.lng]);\r\n if (marker.popup) {\r\n leafletMarker.bindPopup(marker.popup);\r\n leafletMarker.on(\"click\", (event: any) => {\r\n emit(\"markerClick\", marker, event);\r\n });\r\n }\r\n leafletMarker.addTo(map);\r\n });\r\n};\r\n\r\nconst addAMapMarkers = (amap: any) => {\r\n if (!amap || props.mapType !== \"amap\" || !props.markers) return;\r\n props.markers.forEach((marker) => {\r\n const amapMarker = new (window as any).AMap.Marker({\r\n position: [marker.lat, marker.lng],\r\n title: marker.popup || \"\",\r\n });\r\n if (marker.popup) {\r\n const infoWindow = new (window as any).AMap.InfoWindow({\r\n content: marker.popup,\r\n offset: new (window as any).AMap.Pixel(0, -30),\r\n });\r\n amapMarker.on(\"click\", () => {\r\n infoWindow.open(amap, amapMarker.getPosition());\r\n emit(\"markerClick\", marker, null);\r\n });\r\n }\r\n amapMarker.setMap(amap);\r\n });\r\n};\r\n\r\nwatch(\r\n () => props.markers,\r\n () => {\r\n if (map && props.mapType === \"osm\") addMarkers();\r\n },\r\n { deep: true },\r\n);\r\n\r\nwatch(\r\n () => props.mapType,\r\n async (newType, oldType) => {\r\n if (newType === oldType) return;\r\n if (map) {\r\n map.remove();\r\n map = null;\r\n loading.value = true;\r\n await nextTick();\r\n if (newType === \"amap\" && props.amapKey) await initAMap();\r\n else await initOSMMap();\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n [() => props.center, () => props.zoom],\r\n () => {\r\n if (map) map.setView(props.center, props.zoom);\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(async () => {\r\n await nextTick();\r\n if (props.mapType === \"amap\" && props.amapKey) await initAMap();\r\n else await initOSMMap();\r\n});\r\n\r\nonUnmounted(() => {\r\n if (map) {\r\n map.remove();\r\n map = null;\r\n }\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_Map.js","names":["height"],"sources":["../src/components/C_Map/data.ts","../src/components/C_Map/index.vue","../src/components/C_Map/index.vue","../src/components/C_Map/index.vue"],"sourcesContent":["export const MAP_TYPES = [\r\n { label: \"OpenStreetMap\", value: \"osm\" },\r\n { label: \"高德地图\", value: \"amap\" },\r\n] as const;\r\n\r\nexport type MapType = (typeof MAP_TYPES)[number][\"value\"];\r\n\r\nexport interface MapMarker {\r\n lat: number;\r\n lng: number;\r\n popup?: string;\r\n}\r\n\r\nexport interface MapConfig {\r\n height?: string;\r\n center?: [number, number];\r\n zoom?: number;\r\n markers?: MapMarker[];\r\n mapType?: MapType;\r\n amapKey?: string;\r\n}\r\n\r\nexport const DEFAULT_MAP_CONFIG: Required<\r\n Omit<MapConfig, \"markers\" | \"amapKey\">\r\n> = {\r\n height: \"400px\",\r\n center: [39.9042, 116.4074],\r\n zoom: 10,\r\n mapType: \"osm\",\r\n};\r\n\r\nexport const OSM_TILE_CONFIG = {\r\n url: \"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\r\n attribution: \"© OpenStreetMap contributors\",\r\n maxZoom: 19,\r\n minZoom: 1,\r\n tileSize: 256,\r\n detectRetina: true,\r\n} as const;\r\n\r\nexport const AMAP_CONFIG = {\r\n apiUrl: \"https://webapi.amap.com/maps?v=2.0&key=\",\r\n note: \"高德地图需要API Key,如需使用请申请:https://lbs.amap.com/api/javascript-api/guide/create/\",\r\n} as const;\r\n\r\nexport const MAP_ICONS = {\r\n iconRetinaUrl:\r\n \"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png\",\r\n iconUrl:\r\n \"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png\",\r\n shadowUrl:\r\n \"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png\",\r\n} as const;\r\n","/* unplugin-vue-components disabled */<!--\n * @Author: ChenYu ycyplus@gmail.com\n * @Date: 2025-12-02\n * @Description: 地图组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-map\">\n <div\n ref=\"mapContainer\"\n class=\"map-container\"\n :style=\"{ height: height }\"\n ></div>\n <div v-if=\"loading\" class=\"map-loading\">\n <NSpin size=\"large\" />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\nimport { NSpin } from \"naive-ui\";\nimport L from \"leaflet\";\nimport \"leaflet/dist/leaflet.css\";\nimport { OSM_TILE_CONFIG, AMAP_CONFIG, MAP_ICONS } from \"./data\";\n\ndefineOptions({ name: \"C_Map\" });\n\ndelete (L.Icon.Default.prototype as any)._getIconUrl;\nL.Icon.Default.mergeOptions(MAP_ICONS);\n\ninterface MapMarker {\n lat: number;\n lng: number;\n popup?: string;\n}\n\nconst props = withDefaults(\n defineProps<{\n height?: string;\n center?: [number, number];\n zoom?: number;\n markers?: MapMarker[];\n mapType?: \"osm\" | \"amap\";\n amapKey?: string;\n }>(),\n {\n height: \"400px\",\n center: () => [39.9042, 116.4074],\n zoom: 10,\n markers: () => [],\n mapType: \"osm\",\n amapKey: \"\",\n },\n);\n\nconst emit = defineEmits<{\n ready: [map: any];\n markerClick: [marker: MapMarker, event: any];\n}>();\n\nconst mapContainer = ref<HTMLElement>();\nconst loading = ref(true);\nlet map: any = null;\n\nconst initOSMMap = async () => {\n if (!mapContainer.value) return;\n try {\n mapContainer.value.innerHTML = \"\";\n map = L.map(mapContainer.value, {\n center: props.center,\n zoom: props.zoom,\n zoomControl: true,\n preferCanvas: true,\n });\n const tileLayer = L.tileLayer(OSM_TILE_CONFIG.url, OSM_TILE_CONFIG);\n tileLayer.addTo(map);\n addMarkers();\n await nextTick();\n requestAnimationFrame(() => {\n map?.invalidateSize({ reset: true, pan: false });\n });\n loading.value = false;\n emit(\"ready\", map);\n } catch (error) {\n console.error(\"OpenStreetMap初始化失败:\", error);\n loading.value = false;\n }\n};\n\nconst initAMap = async () => {\n if (!mapContainer.value || !props.amapKey) return;\n try {\n mapContainer.value.innerHTML = \"\";\n const script = document.createElement(\"script\");\n script.type = \"text/javascript\";\n script.src = `${AMAP_CONFIG.apiUrl}${props.amapKey}`;\n script.onload = () => {\n if ((window as any).AMap) {\n const amap = new (window as any).AMap.Map(mapContainer.value, {\n zoom: props.zoom,\n center: props.center,\n });\n addAMapMarkers(amap);\n loading.value = false;\n emit(\"ready\", amap);\n }\n };\n script.onerror = () => {\n loading.value = false;\n };\n document.head.appendChild(script);\n } catch {\n loading.value = false;\n }\n};\n\nconst addMarkers = () => {\n if (!map || props.mapType !== \"osm\" || !props.markers) return;\n map.eachLayer((layer: any) => {\n if (layer instanceof L.Marker) {\n map.removeLayer(layer);\n }\n });\n props.markers.forEach((marker) => {\n const leafletMarker = L.marker([marker.lat, marker.lng]);\n if (marker.popup) {\n leafletMarker.bindPopup(marker.popup);\n leafletMarker.on(\"click\", (event: any) => {\n emit(\"markerClick\", marker, event);\n });\n }\n leafletMarker.addTo(map);\n });\n};\n\nconst addAMapMarkers = (amap: any) => {\n if (!amap || props.mapType !== \"amap\" || !props.markers) return;\n props.markers.forEach((marker) => {\n const amapMarker = new (window as any).AMap.Marker({\n position: [marker.lat, marker.lng],\n title: marker.popup || \"\",\n });\n if (marker.popup) {\n const infoWindow = new (window as any).AMap.InfoWindow({\n content: marker.popup,\n offset: new (window as any).AMap.Pixel(0, -30),\n });\n amapMarker.on(\"click\", () => {\n infoWindow.open(amap, amapMarker.getPosition());\n emit(\"markerClick\", marker, null);\n });\n }\n amapMarker.setMap(amap);\n });\n};\n\nwatch(\n () => props.markers,\n () => {\n if (map && props.mapType === \"osm\") addMarkers();\n },\n { deep: true },\n);\n\nwatch(\n () => props.mapType,\n async (newType, oldType) => {\n if (newType === oldType) return;\n if (map) {\n map.remove();\n map = null;\n loading.value = true;\n await nextTick();\n if (newType === \"amap\" && props.amapKey) await initAMap();\n else await initOSMMap();\n }\n },\n);\n\nwatch(\n [() => props.center, () => props.zoom],\n () => {\n if (map) map.setView(props.center, props.zoom);\n },\n { deep: true },\n);\n\nonMounted(async () => {\n await nextTick();\n if (props.mapType === \"amap\" && props.amapKey) await initAMap();\n else await initOSMMap();\n});\n\nonUnmounted(() => {\n if (map) {\n map.remove();\n map = null;\n }\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-12-02\n * @Description: 地图组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-map\">\n <div\n ref=\"mapContainer\"\n class=\"map-container\"\n :style=\"{ height: height }\"\n ></div>\n <div v-if=\"loading\" class=\"map-loading\">\n <NSpin size=\"large\" />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\nimport { NSpin } from \"naive-ui\";\nimport L from \"leaflet\";\nimport \"leaflet/dist/leaflet.css\";\nimport { OSM_TILE_CONFIG, AMAP_CONFIG, MAP_ICONS } from \"./data\";\n\ndefineOptions({ name: \"C_Map\" });\n\ndelete (L.Icon.Default.prototype as any)._getIconUrl;\nL.Icon.Default.mergeOptions(MAP_ICONS);\n\ninterface MapMarker {\n lat: number;\n lng: number;\n popup?: string;\n}\n\nconst props = withDefaults(\n defineProps<{\n height?: string;\n center?: [number, number];\n zoom?: number;\n markers?: MapMarker[];\n mapType?: \"osm\" | \"amap\";\n amapKey?: string;\n }>(),\n {\n height: \"400px\",\n center: () => [39.9042, 116.4074],\n zoom: 10,\n markers: () => [],\n mapType: \"osm\",\n amapKey: \"\",\n },\n);\n\nconst emit = defineEmits<{\n ready: [map: any];\n markerClick: [marker: MapMarker, event: any];\n}>();\n\nconst mapContainer = ref<HTMLElement>();\nconst loading = ref(true);\nlet map: any = null;\n\nconst initOSMMap = async () => {\n if (!mapContainer.value) return;\n try {\n mapContainer.value.innerHTML = \"\";\n map = L.map(mapContainer.value, {\n center: props.center,\n zoom: props.zoom,\n zoomControl: true,\n preferCanvas: true,\n });\n const tileLayer = L.tileLayer(OSM_TILE_CONFIG.url, OSM_TILE_CONFIG);\n tileLayer.addTo(map);\n addMarkers();\n await nextTick();\n requestAnimationFrame(() => {\n map?.invalidateSize({ reset: true, pan: false });\n });\n loading.value = false;\n emit(\"ready\", map);\n } catch (error) {\n console.error(\"OpenStreetMap初始化失败:\", error);\n loading.value = false;\n }\n};\n\nconst initAMap = async () => {\n if (!mapContainer.value || !props.amapKey) return;\n try {\n mapContainer.value.innerHTML = \"\";\n const script = document.createElement(\"script\");\n script.type = \"text/javascript\";\n script.src = `${AMAP_CONFIG.apiUrl}${props.amapKey}`;\n script.onload = () => {\n if ((window as any).AMap) {\n const amap = new (window as any).AMap.Map(mapContainer.value, {\n zoom: props.zoom,\n center: props.center,\n });\n addAMapMarkers(amap);\n loading.value = false;\n emit(\"ready\", amap);\n }\n };\n script.onerror = () => {\n loading.value = false;\n };\n document.head.appendChild(script);\n } catch {\n loading.value = false;\n }\n};\n\nconst addMarkers = () => {\n if (!map || props.mapType !== \"osm\" || !props.markers) return;\n map.eachLayer((layer: any) => {\n if (layer instanceof L.Marker) {\n map.removeLayer(layer);\n }\n });\n props.markers.forEach((marker) => {\n const leafletMarker = L.marker([marker.lat, marker.lng]);\n if (marker.popup) {\n leafletMarker.bindPopup(marker.popup);\n leafletMarker.on(\"click\", (event: any) => {\n emit(\"markerClick\", marker, event);\n });\n }\n leafletMarker.addTo(map);\n });\n};\n\nconst addAMapMarkers = (amap: any) => {\n if (!amap || props.mapType !== \"amap\" || !props.markers) return;\n props.markers.forEach((marker) => {\n const amapMarker = new (window as any).AMap.Marker({\n position: [marker.lat, marker.lng],\n title: marker.popup || \"\",\n });\n if (marker.popup) {\n const infoWindow = new (window as any).AMap.InfoWindow({\n content: marker.popup,\n offset: new (window as any).AMap.Pixel(0, -30),\n });\n amapMarker.on(\"click\", () => {\n infoWindow.open(amap, amapMarker.getPosition());\n emit(\"markerClick\", marker, null);\n });\n }\n amapMarker.setMap(amap);\n });\n};\n\nwatch(\n () => props.markers,\n () => {\n if (map && props.mapType === \"osm\") addMarkers();\n },\n { deep: true },\n);\n\nwatch(\n () => props.mapType,\n async (newType, oldType) => {\n if (newType === oldType) return;\n if (map) {\n map.remove();\n map = null;\n loading.value = true;\n await nextTick();\n if (newType === \"amap\" && props.amapKey) await initAMap();\n else await initOSMMap();\n }\n },\n);\n\nwatch(\n [() => props.center, () => props.zoom],\n () => {\n if (map) map.setView(props.center, props.zoom);\n },\n { deep: true },\n);\n\nonMounted(async () => {\n await nextTick();\n if (props.mapType === \"amap\" && props.amapKey) await initAMap();\n else await initOSMMap();\n});\n\nonUnmounted(() => {\n if (map) {\n map.remove();\n map = null;\n }\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-12-02\n * @Description: 地图组件\n * @Migration: naive-ui-components 组件库迁移版本\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\n-->\n<template>\n <div class=\"c-map\">\n <div\n ref=\"mapContainer\"\n class=\"map-container\"\n :style=\"{ height: height }\"\n ></div>\n <div v-if=\"loading\" class=\"map-loading\">\n <NSpin size=\"large\" />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\nimport { NSpin } from \"naive-ui\";\nimport L from \"leaflet\";\nimport \"leaflet/dist/leaflet.css\";\nimport { OSM_TILE_CONFIG, AMAP_CONFIG, MAP_ICONS } from \"./data\";\n\ndefineOptions({ name: \"C_Map\" });\n\ndelete (L.Icon.Default.prototype as any)._getIconUrl;\nL.Icon.Default.mergeOptions(MAP_ICONS);\n\ninterface MapMarker {\n lat: number;\n lng: number;\n popup?: string;\n}\n\nconst props = withDefaults(\n defineProps<{\n height?: string;\n center?: [number, number];\n zoom?: number;\n markers?: MapMarker[];\n mapType?: \"osm\" | \"amap\";\n amapKey?: string;\n }>(),\n {\n height: \"400px\",\n center: () => [39.9042, 116.4074],\n zoom: 10,\n markers: () => [],\n mapType: \"osm\",\n amapKey: \"\",\n },\n);\n\nconst emit = defineEmits<{\n ready: [map: any];\n markerClick: [marker: MapMarker, event: any];\n}>();\n\nconst mapContainer = ref<HTMLElement>();\nconst loading = ref(true);\nlet map: any = null;\n\nconst initOSMMap = async () => {\n if (!mapContainer.value) return;\n try {\n mapContainer.value.innerHTML = \"\";\n map = L.map(mapContainer.value, {\n center: props.center,\n zoom: props.zoom,\n zoomControl: true,\n preferCanvas: true,\n });\n const tileLayer = L.tileLayer(OSM_TILE_CONFIG.url, OSM_TILE_CONFIG);\n tileLayer.addTo(map);\n addMarkers();\n await nextTick();\n requestAnimationFrame(() => {\n map?.invalidateSize({ reset: true, pan: false });\n });\n loading.value = false;\n emit(\"ready\", map);\n } catch (error) {\n console.error(\"OpenStreetMap初始化失败:\", error);\n loading.value = false;\n }\n};\n\nconst initAMap = async () => {\n if (!mapContainer.value || !props.amapKey) return;\n try {\n mapContainer.value.innerHTML = \"\";\n const script = document.createElement(\"script\");\n script.type = \"text/javascript\";\n script.src = `${AMAP_CONFIG.apiUrl}${props.amapKey}`;\n script.onload = () => {\n if ((window as any).AMap) {\n const amap = new (window as any).AMap.Map(mapContainer.value, {\n zoom: props.zoom,\n center: props.center,\n });\n addAMapMarkers(amap);\n loading.value = false;\n emit(\"ready\", amap);\n }\n };\n script.onerror = () => {\n loading.value = false;\n };\n document.head.appendChild(script);\n } catch {\n loading.value = false;\n }\n};\n\nconst addMarkers = () => {\n if (!map || props.mapType !== \"osm\" || !props.markers) return;\n map.eachLayer((layer: any) => {\n if (layer instanceof L.Marker) {\n map.removeLayer(layer);\n }\n });\n props.markers.forEach((marker) => {\n const leafletMarker = L.marker([marker.lat, marker.lng]);\n if (marker.popup) {\n leafletMarker.bindPopup(marker.popup);\n leafletMarker.on(\"click\", (event: any) => {\n emit(\"markerClick\", marker, event);\n });\n }\n leafletMarker.addTo(map);\n });\n};\n\nconst addAMapMarkers = (amap: any) => {\n if (!amap || props.mapType !== \"amap\" || !props.markers) return;\n props.markers.forEach((marker) => {\n const amapMarker = new (window as any).AMap.Marker({\n position: [marker.lat, marker.lng],\n title: marker.popup || \"\",\n });\n if (marker.popup) {\n const infoWindow = new (window as any).AMap.InfoWindow({\n content: marker.popup,\n offset: new (window as any).AMap.Pixel(0, -30),\n });\n amapMarker.on(\"click\", () => {\n infoWindow.open(amap, amapMarker.getPosition());\n emit(\"markerClick\", marker, null);\n });\n }\n amapMarker.setMap(amap);\n });\n};\n\nwatch(\n () => props.markers,\n () => {\n if (map && props.mapType === \"osm\") addMarkers();\n },\n { deep: true },\n);\n\nwatch(\n () => props.mapType,\n async (newType, oldType) => {\n if (newType === oldType) return;\n if (map) {\n map.remove();\n map = null;\n loading.value = true;\n await nextTick();\n if (newType === \"amap\" && props.amapKey) await initAMap();\n else await initOSMMap();\n }\n },\n);\n\nwatch(\n [() => props.center, () => props.zoom],\n () => {\n if (map) map.setView(props.center, props.zoom);\n },\n { deep: true },\n);\n\nonMounted(async () => {\n await nextTick();\n if (props.mapType === \"amap\" && props.amapKey) await initAMap();\n else await initOSMMap();\n});\n\nonUnmounted(() => {\n if (map) {\n map.remove();\n map = null;\n }\n});\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"./index.scss\";\n</style>\n"],"mappings":"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"}
package/dist/C_Map2.js CHANGED
@@ -1,2 +1,2 @@
1
- import{t as e}from"./export-helper.js";import{NSpin as t}from"naive-ui";import{createCommentVNode as n,createElementBlock as r,createElementVNode as i,createVNode as a,defineComponent as o,nextTick as s,normalizeStyle as c,onMounted as l,onUnmounted as u,openBlock as d,ref as f,unref as p,watch as m}from"vue";import h from"leaflet";import"leaflet/dist/leaflet.css";const g=[{label:`OpenStreetMap`,value:`osm`},{label:`高德地图`,value:`amap`}],_={height:`400px`,center:[39.9042,116.4074],zoom:10,mapType:`osm`},v={url:`https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`,attribution:`© OpenStreetMap contributors`,maxZoom:19,minZoom:1,tileSize:256,detectRetina:!0},y={apiUrl:`https://webapi.amap.com/maps?v=2.0&key=`,note:`高德地图需要API Key,如需使用请申请:https://lbs.amap.com/api/javascript-api/guide/create/`},b={iconRetinaUrl:`https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png`,iconUrl:`https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png`,shadowUrl:`https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png`},x={class:`c-map`},S={key:0,class:`map-loading`};var C=e(o({name:`C_Map`,__name:`index`,props:{height:{default:`400px`},center:{default:()=>[39.9042,116.4074]},zoom:{default:10},markers:{default:()=>[]},mapType:{default:`osm`},amapKey:{default:``}},emits:[`ready`,`markerClick`],setup(e,{emit:o}){delete h.Icon.Default.prototype._getIconUrl,h.Icon.Default.mergeOptions(b);let g=e,_=o,C=f(),w=f(!0),T=null,E=async()=>{if(C.value)try{C.value.innerHTML=``,T=h.map(C.value,{center:g.center,zoom:g.zoom,zoomControl:!0,preferCanvas:!0}),h.tileLayer(v.url,v).addTo(T),O(),await s(),requestAnimationFrame(()=>{T?.invalidateSize({reset:!0,pan:!1})}),w.value=!1,_(`ready`,T)}catch(e){console.error(`OpenStreetMap初始化失败:`,e),w.value=!1}},D=async()=>{if(!(!C.value||!g.amapKey))try{C.value.innerHTML=``;let e=document.createElement(`script`);e.type=`text/javascript`,e.src=`${y.apiUrl}${g.amapKey}`,e.onload=()=>{if(window.AMap){let e=new window.AMap.Map(C.value,{zoom:g.zoom,center:g.center});k(e),w.value=!1,_(`ready`,e)}},e.onerror=()=>{w.value=!1},document.head.appendChild(e)}catch{w.value=!1}},O=()=>{!T||g.mapType!==`osm`||!g.markers||(T.eachLayer(e=>{e instanceof h.Marker&&T.removeLayer(e)}),g.markers.forEach(e=>{let t=h.marker([e.lat,e.lng]);e.popup&&(t.bindPopup(e.popup),t.on(`click`,t=>{_(`markerClick`,e,t)})),t.addTo(T)}))},k=e=>{!e||g.mapType!==`amap`||!g.markers||g.markers.forEach(t=>{let n=new window.AMap.Marker({position:[t.lat,t.lng],title:t.popup||``});if(t.popup){let r=new window.AMap.InfoWindow({content:t.popup,offset:new window.AMap.Pixel(0,-30)});n.on(`click`,()=>{r.open(e,n.getPosition()),_(`markerClick`,t,null)})}n.setMap(e)})};return m(()=>g.markers,()=>{T&&g.mapType===`osm`&&O()},{deep:!0}),m(()=>g.mapType,async(e,t)=>{e!==t&&T&&(T.remove(),T=null,w.value=!0,await s(),e===`amap`&&g.amapKey?await D():await E())}),m([()=>g.center,()=>g.zoom],()=>{T&&T.setView(g.center,g.zoom)},{deep:!0}),l(async()=>{await s(),g.mapType===`amap`&&g.amapKey?await D():await E()}),u(()=>{T&&=(T.remove(),null)}),(e,o)=>(d(),r(`div`,x,[i(`div`,{ref_key:`mapContainer`,ref:C,class:`map-container`,style:c({height:e.height})},null,4),w.value?(d(),r(`div`,S,[a(p(t),{size:`large`})])):n(`v-if`,!0)]))}}),[[`__scopeId`,`data-v-a3602506`]]);export{v as i,_ as n,g as r,C as t};
1
+ import{t as e}from"./export-helper.js";import{NSpin as t}from"naive-ui";import{createCommentVNode as n,createElementBlock as r,createElementVNode as i,createVNode as a,defineComponent as o,nextTick as s,normalizeStyle as c,onMounted as l,onUnmounted as u,openBlock as d,ref as f,unref as p,watch as m}from"vue";import h from"leaflet";import"leaflet/dist/leaflet.css";const g=[{label:`OpenStreetMap`,value:`osm`},{label:`高德地图`,value:`amap`}],_={height:`400px`,center:[39.9042,116.4074],zoom:10,mapType:`osm`},v={url:`https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`,attribution:`© OpenStreetMap contributors`,maxZoom:19,minZoom:1,tileSize:256,detectRetina:!0},y={apiUrl:`https://webapi.amap.com/maps?v=2.0&key=`,note:`高德地图需要API Key,如需使用请申请:https://lbs.amap.com/api/javascript-api/guide/create/`},b={iconRetinaUrl:`https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png`,iconUrl:`https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png`,shadowUrl:`https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png`},x={class:`c-map`},S={key:0,class:`map-loading`};var C=e(o({name:`C_Map`,__name:`index`,props:{height:{default:`400px`},center:{default:()=>[39.9042,116.4074]},zoom:{default:10},markers:{default:()=>[]},mapType:{default:`osm`},amapKey:{default:``}},emits:[`ready`,`markerClick`],setup(e,{emit:o}){delete h.Icon.Default.prototype._getIconUrl,h.Icon.Default.mergeOptions(b);let g=e,_=o,C=f(),w=f(!0),T=null,E=async()=>{if(C.value)try{C.value.innerHTML=``,T=h.map(C.value,{center:g.center,zoom:g.zoom,zoomControl:!0,preferCanvas:!0}),h.tileLayer(v.url,v).addTo(T),O(),await s(),requestAnimationFrame(()=>{T?.invalidateSize({reset:!0,pan:!1})}),w.value=!1,_(`ready`,T)}catch(e){console.error(`OpenStreetMap初始化失败:`,e),w.value=!1}},D=async()=>{if(!(!C.value||!g.amapKey))try{C.value.innerHTML=``;let e=document.createElement(`script`);e.type=`text/javascript`,e.src=`${y.apiUrl}${g.amapKey}`,e.onload=()=>{if(window.AMap){let e=new window.AMap.Map(C.value,{zoom:g.zoom,center:g.center});k(e),w.value=!1,_(`ready`,e)}},e.onerror=()=>{w.value=!1},document.head.appendChild(e)}catch{w.value=!1}},O=()=>{!T||g.mapType!==`osm`||!g.markers||(T.eachLayer(e=>{e instanceof h.Marker&&T.removeLayer(e)}),g.markers.forEach(e=>{let t=h.marker([e.lat,e.lng]);e.popup&&(t.bindPopup(e.popup),t.on(`click`,t=>{_(`markerClick`,e,t)})),t.addTo(T)}))},k=e=>{!e||g.mapType!==`amap`||!g.markers||g.markers.forEach(t=>{let n=new window.AMap.Marker({position:[t.lat,t.lng],title:t.popup||``});if(t.popup){let r=new window.AMap.InfoWindow({content:t.popup,offset:new window.AMap.Pixel(0,-30)});n.on(`click`,()=>{r.open(e,n.getPosition()),_(`markerClick`,t,null)})}n.setMap(e)})};return m(()=>g.markers,()=>{T&&g.mapType===`osm`&&O()},{deep:!0}),m(()=>g.mapType,async(e,t)=>{e!==t&&T&&(T.remove(),T=null,w.value=!0,await s(),e===`amap`&&g.amapKey?await D():await E())}),m([()=>g.center,()=>g.zoom],()=>{T&&T.setView(g.center,g.zoom)},{deep:!0}),l(async()=>{await s(),g.mapType===`amap`&&g.amapKey?await D():await E()}),u(()=>{T&&=(T.remove(),null)}),(e,o)=>(d(),r(`div`,x,[i(`div`,{ref_key:`mapContainer`,ref:C,class:`map-container`,style:c({height:e.height})},null,4),w.value?(d(),r(`div`,S,[a(p(t),{size:`large`})])):n(`v-if`,!0)]))}}),[[`__scopeId`,`data-v-340816cd`]]);export{v as i,_ as n,g as r,C as t};
2
2
  //# sourceMappingURL=C_Map2.js.map