|
|
|
|
@@ -1,76 +1,90 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { useDesign } from '@/hooks/web/useDesign'
|
|
|
|
|
import { propTypes } from '@/utils/propTypes'
|
|
|
|
|
import { CSSProperties, computed } from 'vue'
|
|
|
|
|
import { nextTick, unref, ref, watch, onBeforeUnmount, onMounted } from 'vue'
|
|
|
|
|
import Cropper from 'cropperjs'
|
|
|
|
|
import 'cropperjs/dist/cropper.min.css'
|
|
|
|
|
|
|
|
|
|
// interface CropperOptions extends /* @vue-ignore */ Cropper.Options {
|
|
|
|
|
// imageUrl: string
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
const { getPrefixCls } = useDesign()
|
|
|
|
|
|
|
|
|
|
const prefixCls = getPrefixCls('image-cropping')
|
|
|
|
|
|
|
|
|
|
const bgIcon =
|
|
|
|
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC'
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
imageUrl: propTypes.string,
|
|
|
|
|
boxWidth: propTypes.oneOfType([propTypes.number, propTypes.string]).def('100%'),
|
|
|
|
|
boxHeight: propTypes.oneOfType([propTypes.number, propTypes.string]).def('100%'),
|
|
|
|
|
dragWidth: propTypes.oneOfType([propTypes.number, propTypes.string]).def(200),
|
|
|
|
|
dragHeight: propTypes.oneOfType([propTypes.number, propTypes.string]).def(200),
|
|
|
|
|
cropWidth: propTypes.oneOfType([propTypes.number, propTypes.string]).def(200),
|
|
|
|
|
cropHeight: propTypes.oneOfType([propTypes.number, propTypes.string]).def(200)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const boxStyles = computed((): CSSProperties => {
|
|
|
|
|
return {
|
|
|
|
|
width: (typeof props.boxWidth === 'number' ? `${props.boxWidth}px` : props.boxWidth) ?? '100%',
|
|
|
|
|
height:
|
|
|
|
|
(typeof props.boxHeight === 'number' ? `${props.boxHeight}px` : props.boxHeight) ?? '100%',
|
|
|
|
|
position: 'relative',
|
|
|
|
|
backgroundImage: `url(${bgIcon})`
|
|
|
|
|
imageUrl: {
|
|
|
|
|
type: String,
|
|
|
|
|
default: '',
|
|
|
|
|
required: true
|
|
|
|
|
},
|
|
|
|
|
cropBoxWidth: {
|
|
|
|
|
type: Number,
|
|
|
|
|
default: 200
|
|
|
|
|
},
|
|
|
|
|
cropBoxHeight: {
|
|
|
|
|
type: Number,
|
|
|
|
|
default: 200
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const dragStyles = computed((): CSSProperties => {
|
|
|
|
|
return {
|
|
|
|
|
width: (typeof props.dragWidth === 'number' ? `${props.dragWidth}px` : props.dragWidth) ?? 200,
|
|
|
|
|
height:
|
|
|
|
|
(typeof props.dragHeight === 'number' ? `${props.dragHeight}px` : props.dragHeight) ?? 200,
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
top: '50%',
|
|
|
|
|
left: '50%',
|
|
|
|
|
transform: 'translate(-50%, -50%)',
|
|
|
|
|
zIndex: 1,
|
|
|
|
|
boxShadow: '0 0 0 1px var(--el-color-primary),0 0 0 10000px rgba(0,0,0,.5)',
|
|
|
|
|
cursor: 'move'
|
|
|
|
|
}
|
|
|
|
|
const imgRef = ref<HTMLImageElement>()
|
|
|
|
|
const cropperRef = ref<Cropper>()
|
|
|
|
|
const intiCropper = () => {
|
|
|
|
|
if (!unref(imgRef)) return
|
|
|
|
|
const imgEl = unref(imgRef)!
|
|
|
|
|
cropperRef.value = new Cropper(imgEl, {
|
|
|
|
|
aspectRatio: 1,
|
|
|
|
|
viewMode: 1,
|
|
|
|
|
dragMode: 'move',
|
|
|
|
|
cropBoxResizable: false,
|
|
|
|
|
cropBoxMovable: false,
|
|
|
|
|
toggleDragModeOnDblclick: false,
|
|
|
|
|
checkCrossOrigin: false,
|
|
|
|
|
ready() {
|
|
|
|
|
const containerData = unref(cropperRef)?.getContainerData()
|
|
|
|
|
unref(cropperRef)?.setCropBoxData({
|
|
|
|
|
width: props.cropBoxWidth,
|
|
|
|
|
height: props.cropBoxHeight,
|
|
|
|
|
left: (containerData?.width || 0) / 2 - 100,
|
|
|
|
|
top: (containerData?.height || 0) / 2 - 100
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
intiCropper()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const cropStyles = computed((): CSSProperties => {
|
|
|
|
|
return {
|
|
|
|
|
width: (typeof props.cropWidth === 'number' ? `${props.cropWidth}px` : props.cropWidth) ?? 300,
|
|
|
|
|
height:
|
|
|
|
|
(typeof props.cropHeight === 'number' ? `${props.cropHeight}px` : props.cropHeight) ?? 300,
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
top: '50%',
|
|
|
|
|
left: '80px',
|
|
|
|
|
transform: 'translate(0, -50%)',
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
borderRadius: '50%',
|
|
|
|
|
border: '1px solid var(--el-border-color)'
|
|
|
|
|
watch(
|
|
|
|
|
() => props.imageUrl,
|
|
|
|
|
async (url) => {
|
|
|
|
|
await nextTick()
|
|
|
|
|
if (url) {
|
|
|
|
|
unref(cropperRef)?.replace(url)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
unref(cropperRef)?.destroy()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
cropperExpose: () => unref(cropperRef)
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div :class="prefixCls" class="flex">
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<div :style="boxStyles">
|
|
|
|
|
<img :src="imageUrl" class="w-full absolute top-[50%] left-[50%]" alt="" srcset="" />
|
|
|
|
|
<div :style="dragStyles"> </div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="relative w-full">
|
|
|
|
|
<div :style="cropStyles"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div :class="prefixCls" class="flex justify-center items-center">
|
|
|
|
|
<img
|
|
|
|
|
ref="imgRef"
|
|
|
|
|
:src="imageUrl"
|
|
|
|
|
class="block max-w-full"
|
|
|
|
|
crossorigin="anonymous"
|
|
|
|
|
alt=""
|
|
|
|
|
srcset=""
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|