From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目
---
src/components/Cropper/src/Cropper.vue | 183 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 183 insertions(+), 0 deletions(-)
diff --git a/src/components/Cropper/src/Cropper.vue b/src/components/Cropper/src/Cropper.vue
new file mode 100644
index 0000000..871aed8
--- /dev/null
+++ b/src/components/Cropper/src/Cropper.vue
@@ -0,0 +1,183 @@
+<template>
+ <div :class="getClass" :style="getWrapperStyle">
+ <img
+ v-show="isReady"
+ ref="imgElRef"
+ :alt="alt"
+ :crossorigin="crossorigin"
+ :src="src"
+ :style="getImageStyle"
+ />
+ </div>
+</template>
+<script lang="ts" setup>
+import { CSSProperties, PropType } from 'vue'
+import Cropper from 'cropperjs'
+import 'cropperjs/dist/cropper.css'
+import { useDesign } from '@/hooks/web/useDesign'
+import { propTypes } from '@/utils/propTypes'
+import { useDebounceFn } from '@vueuse/core'
+
+defineOptions({ name: 'Cropper' })
+
+type Options = Cropper.Options
+
+const defaultOptions: Options = {
+ aspectRatio: 1,
+ zoomable: true,
+ zoomOnTouch: true,
+ zoomOnWheel: true,
+ cropBoxMovable: true,
+ cropBoxResizable: true,
+ toggleDragModeOnDblclick: true,
+ autoCrop: true,
+ background: true,
+ highlight: true,
+ center: true,
+ responsive: true,
+ restore: true,
+ checkCrossOrigin: true,
+ checkOrientation: true,
+ scalable: true,
+ modal: true,
+ guides: true,
+ movable: true,
+ rotatable: true
+}
+
+const props = defineProps({
+ src: propTypes.string.def(''),
+ alt: propTypes.string.def(''),
+ circled: propTypes.bool.def(false),
+ realTimePreview: propTypes.bool.def(true),
+ height: propTypes.string.def('360px'),
+ crossorigin: {
+ type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
+ default: undefined
+ },
+ imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
+ options: { type: Object as PropType<Options>, default: () => ({}) }
+})
+
+const emit = defineEmits(['cropend', 'ready', 'cropendError'])
+const attrs = useAttrs()
+const imgElRef = ref<ElRef<HTMLImageElement>>()
+const cropper = ref<Nullable<Cropper>>()
+const isReady = ref(false)
+
+const { getPrefixCls } = useDesign()
+const prefixCls = getPrefixCls('cropper-image')
+const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80)
+
+const getImageStyle = computed((): CSSProperties => {
+ return {
+ height: props.height,
+ maxWidth: '100%',
+ ...props.imageStyle
+ }
+})
+
+const getClass = computed(() => {
+ return [
+ prefixCls,
+ attrs.class,
+ {
+ [`${prefixCls}--circled`]: props.circled
+ }
+ ]
+})
+const getWrapperStyle = computed((): CSSProperties => {
+ return { height: `${props.height}`.replace(/px/, '') + 'px' }
+})
+
+onMounted(init)
+
+onUnmounted(() => {
+ cropper.value?.destroy()
+})
+
+async function init() {
+ const imgEl = unref(imgElRef)
+ if (!imgEl) {
+ return
+ }
+ cropper.value = new Cropper(imgEl, {
+ ...defaultOptions,
+ ready: () => {
+ isReady.value = true
+ realTimeCroppered()
+ emit('ready', cropper.value)
+ },
+ crop() {
+ debounceRealTimeCroppered()
+ },
+ zoom() {
+ debounceRealTimeCroppered()
+ },
+ cropmove() {
+ debounceRealTimeCroppered()
+ },
+ ...props.options
+ })
+}
+
+// Real-time display preview
+function realTimeCroppered() {
+ props.realTimePreview && croppered()
+}
+
+// event: return base64 and width and height information after cropping
+function croppered() {
+ if (!cropper.value) {
+ return
+ }
+ let imgInfo = cropper.value.getData()
+ const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas()
+ canvas.toBlob((blob) => {
+ if (!blob) {
+ return
+ }
+ let fileReader: FileReader = new FileReader()
+ fileReader.readAsDataURL(blob)
+ fileReader.onloadend = (e) => {
+ emit('cropend', {
+ imgBase64: e.target?.result ?? '',
+ imgInfo
+ })
+ }
+ fileReader.onerror = () => {
+ emit('cropendError')
+ }
+ }, 'image/png')
+}
+
+// Get a circular picture canvas
+function getRoundedCanvas() {
+ const sourceCanvas = cropper.value!.getCroppedCanvas()
+ const canvas = document.createElement('canvas')
+ const context = canvas.getContext('2d')!
+ const width = sourceCanvas.width
+ const height = sourceCanvas.height
+ canvas.width = width
+ canvas.height = height
+ context.imageSmoothingEnabled = true
+ context.drawImage(sourceCanvas, 0, 0, width, height)
+ context.globalCompositeOperation = 'destination-in'
+ context.beginPath()
+ context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true)
+ context.fill()
+ return canvas
+}
+</script>
+<style lang="scss">
+$prefix-cls: #{$namespace}-cropper-image;
+
+.#{$prefix-cls} {
+ &--circled {
+ .cropper-view-box,
+ .cropper-face {
+ border-radius: 50%;
+ }
+ }
+}
+</style>
--
Gitblit v1.8.0