|
@@ -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)
|
|
|
+ imageUrl: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ cropBoxWidth: {
|
|
|
+ type: Number,
|
|
|
+ default: 200
|
|
|
+ },
|
|
|
+ cropBoxHeight: {
|
|
|
+ type: Number,
|
|
|
+ default: 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})`
|
|
|
- }
|
|
|
+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 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'
|
|
|
+watch(
|
|
|
+ () => props.imageUrl,
|
|
|
+ async (url) => {
|
|
|
+ await nextTick()
|
|
|
+ if (url) {
|
|
|
+ unref(cropperRef)?.replace(url)
|
|
|
+ }
|
|
|
}
|
|
|
+)
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ unref(cropperRef)?.destroy()
|
|
|
})
|
|
|
|
|
|
-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)'
|
|
|
- }
|
|
|
+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>
|