feat: 新增ImageCropping

This commit is contained in:
kailong321200875 2023-11-15 09:59:17 +08:00
parent 19ccf8b518
commit b0a43a70e6
4 changed files with 87 additions and 60 deletions

View File

@ -35,6 +35,7 @@
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.6.0", "axios": "^1.6.0",
"cropperjs": "^1.6.1",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"driver.js": "^1.3.0", "driver.js": "^1.3.0",
"echarts": "^5.4.3", "echarts": "^5.4.3",

View File

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

View File

@ -1,14 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { ImageCropping } from '@/components/ImageCropping' import { ImageCropping } from '@/components/ImageCropping'
import { ref, unref } from 'vue'
import { ElButton, ElInput } from 'element-plus'
const cropperExpose = ref<InstanceType<typeof ImageCropping>>()
const base64 = ref('')
const getBase64 = () => {
base64.value = unref(cropperExpose)?.cropperExpose()?.getCroppedCanvas()?.toDataURL() ?? ''
}
</script> </script>
<template> <template>
<ContentWrap title="图片裁剪"> <ContentWrap title="图片裁剪">
<ElButton type="primary" class="mb-20px" @click="getBase64">裁剪</ElButton>
<ElInput v-model="base64" class="mb-20px" type="textarea" />
<ImageCropping <ImageCropping
:box-width="350" ref="cropperExpose"
:box-height="380" image-url="https://hips.hearstapps.com/hmg-prod/images/%E5%AE%8B%E6%99%BA%E5%AD%9D-1597774015.jpg?crop=0.500xw:1.00xh;0.500xw,0&resize=640:*"
image-url="https://images6.alphacoders.com/657/thumbbig-657194.webp"
/> />
</ContentWrap> </ContentWrap>
</template> </template>

View File

@ -152,7 +152,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
'vue-json-pretty', 'vue-json-pretty',
'@zxcvbn-ts/core', '@zxcvbn-ts/core',
'dayjs', 'dayjs',
'mockjs' 'mockjs',
'cropperjs'
] ]
} }
} }