@robot-admin/naive-ui-components 0.3.1 → 0.3.3
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.
- package/dist/C_ActionBar-nnfbZCea.css.map +1 -0
- package/dist/C_ActionBar2.js +2 -2
- package/dist/C_ActionBar2.js.map +1 -1
- package/dist/C_AntV-DGjscTWa.css.map +1 -0
- package/dist/C_AntV2.js +6 -6
- package/dist/C_AntV2.js.map +1 -1
- package/dist/C_Barcode-DjTmDkbQ.css.map +1 -0
- package/dist/C_Barcode2.js +1 -1
- package/dist/C_Barcode2.js.map +1 -1
- package/dist/C_Captcha-Ccq3DMrR.css.map +1 -0
- package/dist/C_Captcha2.js +1 -1
- package/dist/C_Captcha2.js.map +1 -1
- package/dist/C_Cascade-IUUHIh7r.css.map +1 -0
- package/dist/C_Cascade2.js +2 -2
- package/dist/C_Cascade2.js.map +1 -1
- package/dist/C_City-Cv5BESaN.css.map +1 -0
- package/dist/C_City2.js +2 -2
- package/dist/C_City2.js.map +1 -1
- package/dist/C_Code-DPZlNSxL.css.map +1 -0
- package/dist/C_Code2.js +8 -7
- package/dist/C_Code2.js.map +1 -1
- package/dist/C_CollapsePanel-Fap-lv_5.css.map +1 -0
- package/dist/C_CollapsePanel2.js +1 -1
- package/dist/C_CollapsePanel2.js.map +1 -1
- package/dist/C_Cron-C0-8b5af.css.map +1 -0
- package/dist/C_Cron2.js +15 -14
- package/dist/C_Cron2.js.map +1 -1
- package/dist/C_Date2.js +1 -1
- package/dist/C_Date2.js.map +1 -1
- package/dist/C_Draggable-Bq6o0qXn.css.map +1 -0
- package/dist/C_Draggable2.js +1 -1
- package/dist/C_Draggable2.js.map +1 -1
- package/dist/C_Editor-OlxIF9-5.css.map +1 -0
- package/dist/C_Editor2.js +1 -1
- package/dist/C_Editor2.js.map +1 -1
- package/dist/C_FilePreview-B4XgTv-h.css.map +1 -0
- package/dist/C_FilePreview.cjs +1 -0
- package/dist/C_FilePreview.js +1 -0
- package/dist/C_FilePreview2.js +10 -9
- package/dist/C_FilePreview2.js.map +1 -1
- package/dist/C_Form-Cr9oX037.css.map +1 -0
- package/dist/C_Form.cjs +1 -0
- package/dist/C_Form.js +1 -0
- package/dist/C_Form2.js +69 -72
- package/dist/C_Form2.js.map +1 -1
- package/dist/C_FormSearch-DlIEoh7X.css.map +1 -0
- package/dist/C_FormSearch2.js +2 -2
- package/dist/C_FormSearch2.js.map +1 -1
- package/dist/C_FormulaEditor-Cm0CokN5.css.map +1 -0
- package/dist/C_FormulaEditor2.js +6 -6
- package/dist/C_FormulaEditor2.js.map +1 -1
- package/dist/C_FullCalendar-BULCIlVK.css.map +1 -0
- package/dist/C_FullCalendar2.js +2 -2
- package/dist/C_FullCalendar2.js.map +1 -1
- package/dist/C_Guide2.js +1 -1
- package/dist/C_Guide2.js.map +1 -1
- package/dist/C_Icon2.js.map +1 -1
- package/dist/C_ImageCropper-DrmUlaLi.css.map +1 -0
- package/dist/C_ImageCropper2.js +4 -4
- package/dist/C_ImageCropper2.js.map +1 -1
- package/dist/C_Language2.js +1 -1
- package/dist/C_Language2.js.map +1 -1
- package/dist/C_Map-WUMXSAfy.css.map +1 -0
- package/dist/C_Map2.js +2 -2
- package/dist/C_Map2.js.map +1 -1
- package/dist/C_Markdown-Dmv8yaM4.css.map +1 -0
- package/dist/C_Markdown2.js +4 -4
- package/dist/C_Markdown2.js.map +1 -1
- package/dist/C_NotificationCenter-DbgBiyqB.css.map +1 -0
- package/dist/C_NotificationCenter2.js +21 -20
- package/dist/C_NotificationCenter2.js.map +1 -1
- package/dist/C_Progress2.js +1 -1
- package/dist/C_Progress2.js.map +1 -1
- package/dist/C_QRCode-G7fiAkm4.css.map +1 -0
- package/dist/C_QRCode2.js +1 -1
- package/dist/C_QRCode2.js.map +1 -1
- package/dist/C_Signature-es-ZNPzr.css.map +1 -0
- package/dist/C_Signature2.js +2 -2
- package/dist/C_Signature2.js.map +1 -1
- package/dist/C_SplitPane-Br2eK8IG.css.map +1 -0
- package/dist/C_SplitPane2.js +1 -1
- package/dist/C_SplitPane2.js.map +1 -1
- package/dist/C_Steps-P9Qj9iDd.css.map +1 -0
- package/dist/C_Steps2.js +1 -1
- package/dist/C_Steps2.js.map +1 -1
- package/dist/C_Table-DAwAxr72.css.map +1 -0
- package/dist/C_Table.cjs +1 -0
- package/dist/C_Table.js +1 -0
- package/dist/C_Table2.js +3 -3
- package/dist/C_Table2.js.map +1 -1
- package/dist/C_Theme2.js +1 -1
- package/dist/C_Theme2.js.map +1 -1
- package/dist/C_Time-Bd_e1YDN.css.map +1 -0
- package/dist/C_Time2.js +2 -2
- package/dist/C_Time2.js.map +1 -1
- package/dist/C_Tree-DnGc_MPb.css.map +1 -0
- package/dist/C_Tree2.js +2 -2
- package/dist/C_Tree2.js.map +1 -1
- package/dist/C_Upload-i8LB_29U.css.map +1 -0
- package/dist/C_Upload2.js +5 -5
- package/dist/C_Upload2.js.map +1 -1
- package/dist/C_VideoPlayer-rm0MODUv.css.map +1 -0
- package/dist/C_VideoPlayer2.js +21 -20
- package/dist/C_VideoPlayer2.js.map +1 -1
- package/dist/C_VtableGantt-BpY-Rng3.css.map +1 -0
- package/dist/C_VtableGantt2.js +2 -2
- package/dist/C_VtableGantt2.js.map +1 -1
- package/dist/C_WaterFall-HWB-gPON.css.map +1 -0
- package/dist/C_WaterFall2.js +2 -2
- package/dist/C_WaterFall2.js.map +1 -1
- package/dist/C_WorkFlow-CSO86Cuc.css.map +1 -0
- package/dist/C_WorkFlow2.js +6 -6
- package/dist/C_WorkFlow2.js.map +1 -1
- package/dist/constants.d.ts +4 -4
- package/dist/constants2.d.ts +4 -4
- package/dist/constants3.d.ts +4 -4
- package/dist/constants4.d.ts +5 -5
- package/dist/constants5.d.ts +2 -2
- package/dist/data.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.vue.d.ts +1 -1
- package/dist/index10.vue.d.ts +7 -7
- package/dist/index11.vue.d.ts +2 -2
- package/dist/index12.vue.d.ts +5 -5
- package/dist/index12.vue.d.ts.map +1 -1
- package/dist/index13.vue.d.ts +1 -1
- package/dist/index14.vue.d.ts +1 -1
- package/dist/index16.vue.d.ts +1 -1
- package/dist/index2.vue.d.ts +6 -6
- package/dist/index3.vue.d.ts +2 -2
- package/dist/index4.vue.d.ts +2 -2
- package/dist/index5.vue.d.ts +2 -2
- package/dist/index6.vue.d.ts +1 -1
- package/dist/index8.vue.d.ts +3 -3
- package/dist/resolver.js.map +1 -1
- package/dist/style.css +1555 -1555
- package/dist/useCalendarEvents.d.ts +2 -2
- package/dist/useCollapsePanel.d.ts +2 -2
- package/dist/useCropperCore.d.ts +3 -3
- package/dist/useDraggableLayout.d.ts +8 -8
- package/dist/useDynamicFormState.d.ts +111 -111
- package/dist/useDynamicFormState.d.ts.map +1 -1
- package/dist/useEdgeInteraction.d.ts +2 -2
- package/dist/useInfiniteScroll.d.ts +1 -1
- package/dist/useModalEdit.d.ts +4 -4
- package/dist/useModalEdit.d.ts.map +1 -1
- package/dist/useQRCode.d.ts +4 -4
- package/dist/useQRCode.d.ts.map +1 -1
- package/dist/useSearchState.d.ts +2 -2
- package/dist/useSignatureHistory.d.ts +4 -4
- package/dist/useSplitResize.d.ts +6 -6
- package/dist/useSplitResize.d.ts.map +1 -1
- package/dist/useTimeSelection.d.ts +2 -2
- package/dist/useTreeOperations.d.ts +6 -6
- package/dist/useWorkflowValidation.d.ts +5 -5
- package/package.json +2 -1
- package/dist/C_ActionBar-DWN-woTc.css.map +0 -1
- package/dist/C_AntV-AFKyK6hH.css.map +0 -1
- package/dist/C_Barcode-P_EFj8dC.css.map +0 -1
- package/dist/C_Captcha-C-ef41xw.css.map +0 -1
- package/dist/C_Cascade-D9kNsjsV.css.map +0 -1
- package/dist/C_City-BCQ4ipiK.css.map +0 -1
- package/dist/C_Code-C9kvvEmO.css.map +0 -1
- package/dist/C_CollapsePanel-BUJHuYcU.css.map +0 -1
- package/dist/C_Cron-yx2Ob4Jl.css.map +0 -1
- package/dist/C_Draggable-C483syRC.css.map +0 -1
- package/dist/C_Editor-Bp0SyIEw.css.map +0 -1
- package/dist/C_FilePreview-CPqvhoCy.css.map +0 -1
- package/dist/C_Form-Jx7PY3sT.css.map +0 -1
- package/dist/C_FormSearch-DvRgxlRn.css.map +0 -1
- package/dist/C_FormulaEditor-DtGkt4T_.css.map +0 -1
- package/dist/C_FullCalendar-BF7H0YIx.css.map +0 -1
- package/dist/C_ImageCropper-BVJfUufl.css.map +0 -1
- package/dist/C_Map-DpzeuWdX.css.map +0 -1
- package/dist/C_Markdown-BEjxknqd.css.map +0 -1
- package/dist/C_NotificationCenter-0l3TY2Gn.css.map +0 -1
- package/dist/C_QRCode-DbdiAIPg.css.map +0 -1
- package/dist/C_Signature-zhHCbra9.css.map +0 -1
- package/dist/C_SplitPane-C6sBsfKY.css.map +0 -1
- package/dist/C_Steps-CODHN5Hs.css.map +0 -1
- package/dist/C_Table-DSNsntmT.css.map +0 -1
- package/dist/C_Time-BvZLYraL.css.map +0 -1
- package/dist/C_Tree-0GDv--jX.css.map +0 -1
- package/dist/C_Upload-BXd3YYLx.css.map +0 -1
- package/dist/C_VideoPlayer-DYG3RL0Q.css.map +0 -1
- package/dist/C_VtableGantt-fhItIiHE.css.map +0 -1
- package/dist/C_WaterFall-8sQDFXKg.css.map +0 -1
- package/dist/C_WorkFlow-J-dyIuh9.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","<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","<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-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","<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","<!--\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","<!--\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 */<!--\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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"}
|
package/dist/C_Language2.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { computed, createBlock, createElementVNode, createVNode, defineComponent, h, openBlock, unref, withCtx } from "vue";
|
|
2
1
|
import { NButton, NDropdown } from "naive-ui";
|
|
2
|
+
import { computed, createBlock, createElementVNode, createVNode, defineComponent, h, openBlock, unref, withCtx } from "vue";
|
|
3
3
|
|
|
4
4
|
//#region src/components/C_Language/index.vue?vue&type=script&setup=true&lang.ts
|
|
5
5
|
var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
package/dist/C_Language2.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"C_Language2.js","names":["modelValue"],"sources":["../src/components/C_Language/index.vue","../src/components/C_Language/index.vue","../src/components/C_Language/index.vue"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"C_Language2.js","names":["modelValue"],"sources":["../src/components/C_Language/index.vue","../src/components/C_Language/index.vue","../src/components/C_Language/index.vue"],"sourcesContent":["/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-11-19\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 <NDropdown\r\n size=\"small\"\r\n trigger=\"hover\"\r\n :options=\"finalOptions\"\r\n :value=\"modelValue\"\r\n @select=\"handleLanguageChange\"\r\n >\r\n <NButton text>\r\n <div class=\"flex items-center\">\r\n <span class=\"i-mdi:language\"></span>\r\n </div>\r\n </NButton>\r\n </NDropdown>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, h } from \"vue\";\r\nimport { NDropdown, NButton } from \"naive-ui\";\r\n\r\ndefineOptions({ name: \"C_Language\" });\r\n\r\nexport interface LanguageOption {\r\n key: string;\r\n label: string;\r\n iconClass?: string;\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n modelValue?: string;\r\n options?: LanguageOption[];\r\n }>(),\r\n {\r\n modelValue: \"zh-cn\",\r\n options: () => [\r\n { key: \"zh-cn\", label: \"简体中文\", iconClass: \"i-mdi:alpha-c\" },\r\n { key: \"en\", label: \"English\", iconClass: \"i-mdi:alpha-u\" },\r\n { key: \"ja\", label: \"日本語\", iconClass: \"i-mdi:alpha-j\" },\r\n { key: \"ko\", label: \"한국어\", iconClass: \"i-mdi:alpha-k\" },\r\n ],\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n \"update:modelValue\": [key: string];\r\n change: [key: string];\r\n}>();\r\n\r\nconst finalOptions = computed(() =>\r\n props.options.map((opt) => ({\r\n key: opt.key,\r\n label: opt.label,\r\n icon: opt.iconClass ? () => h(\"span\", { class: opt.iconClass }) : undefined,\r\n })),\r\n);\r\n\r\nconst handleLanguageChange = (key: string) => {\r\n if (key === props.modelValue) return;\r\n emit(\"update:modelValue\", key);\r\n emit(\"change\", key);\r\n};\r\n</script>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-11-19\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 <NDropdown\r\n size=\"small\"\r\n trigger=\"hover\"\r\n :options=\"finalOptions\"\r\n :value=\"modelValue\"\r\n @select=\"handleLanguageChange\"\r\n >\r\n <NButton text>\r\n <div class=\"flex items-center\">\r\n <span class=\"i-mdi:language\"></span>\r\n </div>\r\n </NButton>\r\n </NDropdown>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, h } from \"vue\";\r\nimport { NDropdown, NButton } from \"naive-ui\";\r\n\r\ndefineOptions({ name: \"C_Language\" });\r\n\r\nexport interface LanguageOption {\r\n key: string;\r\n label: string;\r\n iconClass?: string;\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n modelValue?: string;\r\n options?: LanguageOption[];\r\n }>(),\r\n {\r\n modelValue: \"zh-cn\",\r\n options: () => [\r\n { key: \"zh-cn\", label: \"简体中文\", iconClass: \"i-mdi:alpha-c\" },\r\n { key: \"en\", label: \"English\", iconClass: \"i-mdi:alpha-u\" },\r\n { key: \"ja\", label: \"日本語\", iconClass: \"i-mdi:alpha-j\" },\r\n { key: \"ko\", label: \"한국어\", iconClass: \"i-mdi:alpha-k\" },\r\n ],\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n \"update:modelValue\": [key: string];\r\n change: [key: string];\r\n}>();\r\n\r\nconst finalOptions = computed(() =>\r\n props.options.map((opt) => ({\r\n key: opt.key,\r\n label: opt.label,\r\n icon: opt.iconClass ? () => h(\"span\", { class: opt.iconClass }) : undefined,\r\n })),\r\n);\r\n\r\nconst handleLanguageChange = (key: string) => {\r\n if (key === props.modelValue) return;\r\n emit(\"update:modelValue\", key);\r\n emit(\"change\", key);\r\n};\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-11-19\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 <NDropdown\r\n size=\"small\"\r\n trigger=\"hover\"\r\n :options=\"finalOptions\"\r\n :value=\"modelValue\"\r\n @select=\"handleLanguageChange\"\r\n >\r\n <NButton text>\r\n <div class=\"flex items-center\">\r\n <span class=\"i-mdi:language\"></span>\r\n </div>\r\n </NButton>\r\n </NDropdown>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, h } from \"vue\";\r\nimport { NDropdown, NButton } from \"naive-ui\";\r\n\r\ndefineOptions({ name: \"C_Language\" });\r\n\r\nexport interface LanguageOption {\r\n key: string;\r\n label: string;\r\n iconClass?: string;\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n modelValue?: string;\r\n options?: LanguageOption[];\r\n }>(),\r\n {\r\n modelValue: \"zh-cn\",\r\n options: () => [\r\n { key: \"zh-cn\", label: \"简体中文\", iconClass: \"i-mdi:alpha-c\" },\r\n { key: \"en\", label: \"English\", iconClass: \"i-mdi:alpha-u\" },\r\n { key: \"ja\", label: \"日本語\", iconClass: \"i-mdi:alpha-j\" },\r\n { key: \"ko\", label: \"한국어\", iconClass: \"i-mdi:alpha-k\" },\r\n ],\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n \"update:modelValue\": [key: string];\r\n change: [key: string];\r\n}>();\r\n\r\nconst finalOptions = computed(() =>\r\n props.options.map((opt) => ({\r\n key: opt.key,\r\n label: opt.label,\r\n icon: opt.iconClass ? () => h(\"span\", { class: opt.iconClass }) : undefined,\r\n })),\r\n);\r\n\r\nconst handleLanguageChange = (key: string) => {\r\n if (key === props.modelValue) return;\r\n emit(\"update:modelValue\", key);\r\n emit(\"change\", key);\r\n};\r\n</script>\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECmCA,MAAM,QAAQ;EAgBd,MAAM,OAAO;EAKb,MAAM,eAAe,eACnB,MAAM,QAAQ,KAAK,SAAS;GAC1B,KAAK,IAAI;GACT,OAAO,IAAI;GACX,MAAM,IAAI,kBAAkB,EAAE,QAAQ,EAAE,OAAO,IAAI,WAAW,CAAC,GAAG;GACnE,EAAE,CACJ;EAED,MAAM,wBAAwB,QAAgB;AAC5C,OAAI,QAAQ,MAAM,WAAY;AAC9B,QAAK,qBAAqB,IAAI;AAC9B,QAAK,UAAU,IAAI;;;uBA3DnB,YAYY,MAAA,UAAA,EAAA;IAXV,MAAK;IACL,SAAQ;IACP,SAAS,aAAA;IACT,OAAOA,KAAAA;IACP,UAAQ;;2BAMC,CAJV,YAIU,MAAA,QAAA,EAAA,EAJD,MAAA,IAAI,EAAA;4BAGL,OAAA,OAAA,OAAA,KAAA,CAFN,mBAEM,OAAA,EAFD,OAAM,qBAAmB,EAAA,CAC5B,mBAAoC,QAAA,EAA9B,OAAM,kBAAgB,CAAA"}
|
|
@@ -0,0 +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"}
|
package/dist/C_Map2.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as export_helper_default } from "./export-helper.js";
|
|
2
|
-
import { createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeStyle, onMounted, onUnmounted, openBlock, ref, unref, watch } from "vue";
|
|
3
2
|
import { NSpin } from "naive-ui";
|
|
3
|
+
import { createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeStyle, onMounted, onUnmounted, openBlock, ref, unref, watch } from "vue";
|
|
4
4
|
import L from "leaflet";
|
|
5
5
|
import "leaflet/dist/leaflet.css";
|
|
6
6
|
|
|
@@ -192,7 +192,7 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
|
|
|
192
192
|
|
|
193
193
|
//#endregion
|
|
194
194
|
//#region src/components/C_Map/index.vue
|
|
195
|
-
var C_Map_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
195
|
+
var C_Map_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-a3602506"]]);
|
|
196
196
|
|
|
197
197
|
//#endregion
|
|
198
198
|
export { OSM_TILE_CONFIG as i, DEFAULT_MAP_CONFIG as n, MAP_TYPES as r, C_Map_default as t };
|
package/dist/C_Map2.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"C_Map2.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","<!--\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","<!--\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_Map2.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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_Markdown-Dmv8yaM4.css","names":[],"sources":["../src/components/C_Markdown/index.vue?vue&type=style&index=0&scoped=cfe5e2cb&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//* C_Markdown 组件样式文件 */\n.c-markdown-wrapper[data-v-cfe5e2cb] {\n position: relative;\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor {\n border-radius: 6px;\n border: 1px solid var(--c-border);\n background-color: var(--c-bg-card);\n /* 禁用状态样式 */\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor:hover {\n border-color: var(--c-primary);\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor:focus-within {\n border-color: var(--c-primary);\n box-shadow: 0 0 0 2px var(--c-primary-opacity, rgba(24, 160, 88, 0.2));\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor.disabled {\n background-color: var(--c-bg-disabled, #fafafc);\n cursor: not-allowed;\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor.disabled .v-md-editor__editor {\n cursor: not-allowed;\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar {\n background-color: var(--c-bg-card);\n border-bottom: 1px solid var(--c-border);\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor .v-md-editor__editor-wrapper {\n background-color: var(--c-bg-card);\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor .v-md-editor__editor {\n background-color: var(--c-bg-card);\n color: var(--c-text-1);\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor .v-md-editor__editor textarea {\n color: var(--c-text-1);\n caret-color: var(--c-text-1);\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor .v-md-editor__preview-wrapper {\n background-color: var(--c-bg-card);\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor .v-md-editor__preview {\n background-color: var(--c-bg-card);\n color: var(--c-text-1);\n}\n.c-markdown-wrapper[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toc-nav-wrapper {\n background-color: var(--c-bg-card);\n border-left: 1px solid var(--c-border);\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-item {\n color: #ffffff;\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-item:hover {\n background-color: rgba(255, 255, 255, 0.08);\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-item.is-active {\n background-color: rgba(255, 255, 255, 0.12);\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-item svg {\n fill: #ffffff;\n opacity: 0.9;\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-item:hover svg {\n opacity: 1;\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-right .v-md-editor__toolbar-item {\n background-color: transparent;\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-right .v-md-editor__toolbar-item svg {\n fill: #ffffff;\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-right .v-md-editor__toolbar-item:hover {\n background-color: rgba(255, 255, 255, 0.08);\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__toolbar-divider {\n background-color: var(--c-border);\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__editor::-webkit-scrollbar,\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__preview::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__editor::-webkit-scrollbar-track,\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__preview::-webkit-scrollbar-track {\n background-color: rgba(255, 255, 255, 0.05);\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__editor::-webkit-scrollbar-thumb,\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__preview::-webkit-scrollbar-thumb {\n background-color: rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n}\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__editor::-webkit-scrollbar-thumb:hover,\n.c-markdown-wrapper.is-dark[data-v-cfe5e2cb] .v-md-editor .v-md-editor__preview::-webkit-scrollbar-thumb:hover {\n background-color: rgba(255, 255, 255, 0.3);\n}\n.c-markdown-wrapper .word-count[data-v-cfe5e2cb] {\n position: absolute;\n bottom: 8px;\n right: 12px;\n font-size: 12px;\n color: var(--c-text-4);\n background: var(--c-bg-card);\n padding: 2px 6px;\n border-radius: 4px;\n z-index: 10;\n}\n.c-markdown-wrapper .word-count.exceed[data-v-cfe5e2cb] {\n color: var(--c-error);\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|