feat: 新增ImageCropping
This commit is contained in:
parent
19ccf8b518
commit
b0a43a70e6
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
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)'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
intiCropper()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.imageUrl,
|
||||||
|
async (url) => {
|
||||||
|
await nextTick()
|
||||||
|
if (url) {
|
||||||
|
unref(cropperRef)?.replace(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue