效果图
组件代码:CropperImg.vue
<template>
<van-popup
v-model:show="show"
position="bottom"
:lock-scroll="false"
:style="{ height: '100vh', overflow: 'hidden' }"
>
<div style="width: 100vw; height: 100vh; overflow: hidden">
<VueCropper
ref="cropperRef"
class="cropper"
:img="img"
:autoCropHeight="86"
:autoCropWidth="86"
:autoCrop="true"
:centerBox="true"
:canScale="false"
:canMove="false"
outputType="png"
@realTime="realTime"
></VueCropper>
<div class="preview" @click="onPreview">预览</div>
<div class="cancel" @click="onClose"><van-icon name="cross" /></div>
<div class="save" @click="onSave"><van-icon name="success" /></div>
<van-popup v-model:show="showPreview">
<div style="padding: 5px">
<div :style="previews.div" class="show-preview">
<img v-if="previews.url" :src="previews.url" :style="previews.img" />
</div>
</div>
</van-popup>
</div>
</van-popup>
</template>
<script setup>
import { ref } from 'vue'
import { base64ToFile } from '@/utils'
import 'vue-cropper/dist/index.css'
import { VueCropper } from 'vue-cropper'
const emits = defineEmits('confirm')
const show = ref(false)
const img = ref('')
const cropperRef = ref()
// 打开
const onOpen = (cropperImg) => {
img.value = cropperImg
show.value = true
}
// 实时预览
const previews = ref('')
const realTime = (data) => {
previews.value = data
}
const showPreview = ref(false)
const onPreview = () => {
showPreview.value = true
}
// 取消
const onClose = () => {
show.value = false
previews.value = ''
}
// 提交
const onSave = () => {
cropperRef.value.getCropData((data) => {
emits('confirm', base64ToFile(data, '头像.png'))
})
}
defineExpose({
onOpen,
onClose,
})
</script>
<style lang="less" scoped>
.cropper {
background: rgba(0, 0, 0, 1);
}
.preview,
.cancel,
.save {
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
position: absolute;
color: #fff;
}
.preview {
top: 20px;
right: 20px;
font-size: 16px;
}
.cancel {
bottom: 50px;
left: 20px;
font-size: 26px;
}
.save {
bottom: 50px;
right: 20px;
font-size: 30px;
}
.show-preview {
overflow: hidden;
border: 1px solid #d7d7d7;
border-radius: 50%;
}
</style>
使用示例:
<template>
<van-cell
title="头像"
label="请点击头像"
is-link
center
class="ptb-14"
value-class="flex-no line-height-0"
>
<template #value>
<van-uploader :after-read="onAvatarAfterRead" max-count="1">
<img :src="headImg" class="avatar-img" />
</van-uploader>
</template>
</van-cell>
<CropperImg ref="cropperRef" @confirm="onCropperConfirm"></CropperImg>
</template>
<script setup>
import { ref } from 'vue'
import { showLoadingToast, closeToast, showToast } from 'vant'
import { uploadFile } from '@/api/file'
import CropperImg from '../CropperImg.vue'
const headImg = ref('')
// 图片裁剪
const cropperRef = ref()
const onAvatarAfterRead = (file) => {
if (file.file.type.indexOf('image/') == -1) {
showToast('请上传图片类型文件!')
return false
}
if (file.file.size / 1024 / 1024 > 5) {
showToast('文件大小不能超过5MB!')
return false
}
cropperRef.value.onOpen(file.objectUrl)
}
const onCropperConfirm = async (file) => {
showLoadingToast('正在提交...')
try {
const { data } = await uploadFile(file)
if (!data) {
showToast('上传失败')
return
}
// todo
} catch (e) {
console.error(e)
}
closeToast()
}
</script>
<style lang="less" scoped>
.avatar-img {
width: 50px;
height: 50px;
border-radius: 25px;
}
</style>
<style lang="less">
.line-height-0 {
line-height: 0;
}
.flex-no {
flex: none !important;
}
</style>