组件 <template> <div class="avatar-uploader"> <!-- 头像展示 --> <div class="avatar-show"> <img :src="avatarUrl" v-if="avatarUrl" class="avatar"> <i class="el-icon-plus avatar-uploader-icon" v-else></i> </div> <!-- 上传按钮 --> <el-upload class="avatar-upload" action="#" :show-file-list="false" :before-upload="beforeAvatarUpload" :http-request="handleUpload" > <el-button size="small" type="primary">点击上传</el-button> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2MB</div> </el-upload> <!-- 裁剪对话框 --> <el-dialog title="裁剪头像" :visible.sync="dialogVisible" width="800px"> <div class="cropper-content"> <div class="cropper"> <vue-cropper ref="cropper" :img="cropperImg" :output-size="1" :output-type="'png'" :info="true" :full="true" :can-move="true" :can-move-box="true" :fixed="true" :fixed-number="[1, 1]" :center-box="true" :auto-crop="true" :auto-crop-width="200" :auto-crop-height="200" ></vue-cropper> </div> </div> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">取 消</el-button> <el-button type="primary" @click="handleCrop">确 定</el-button> </span> </el-dialog> </div> </template> <script> import { VueCropper } from 'vue-cropper' export default { components: { VueCropper }, props: { value: String // 用于v-model绑定 }, data() { return { dialogVisible: false, cropperImg: '', // 裁剪图片的base64 // avatarUrl: this.value || require('@/assets/logo.png') // 默认头像 avatarUrl: null // 默认头像 } }, methods: { // 上传前校验 beforeAvatarUpload(file) { const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' const isLt2M = file.size / 1024 / 1024 < 2 if (!isJPG) { this.$message.error('上传头像图片只能是 JPG/PNG 格式!') } if (!isLt2M) { this.$message.error('上传头像图片大小不能超过 2MB!') } return isJPG && isLt2M }, // 处理上传 handleUpload(file) { const reader = new FileReader() reader.onload = (event) => { this.cropperImg = event.target.result this.dialogVisible = true this.$nextTick(() => { this.$refs.cropper.replace(event.target.result) }) } reader.readAsDataURL(file.file) }, // 处理裁剪 handleCrop() { this.$refs.cropper.getCropBlob((blob) => { // 创建FormData对象上传 const formData = new FormData() formData.append('file', blob, 'avatar.png') // 这里替换为你的上传API this.$axios.post('/api/upload/avatar', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(response => { this.avatarUrl = response.data.url this.$emit('input', this.avatarUrl) // 更新v-model this.dialogVisible = false this.$message.success('头像上传成功!') }).catch(error => { this.$message.error('头像上传失败!') console.error(error) }) }) }, // 也可以使用base64格式 handleCropBase64() { this.$refs.cropper.getCropData((data) => { this.avatarUrl = data this.$emit('input', data) // 更新v-model this.dialogVisible = false this.$message.success('头像裁剪成功!') }) } }, watch: { value(newVal) { this.avatarUrl = newVal } } } </script> <style scoped> .avatar-uploader { display: flex; flex-direction: column; align-items: center; } .avatar-show { width: 150px; height: 150px; border: 1px dashed #d9d9d9; border-radius: 50%; overflow: hidden; display: flex; justify-content: center; align-items: center; margin-bottom: 20px; } .avatar { width: 100%; height: 100%; object-fit: cover; } .avatar-uploader-icon { font-size: 28px; color: #8c939d; } .cropper-content { display: flex; justify-content: center; } .cropper { width: 500px; height: 500px; } .el-upload__tip { margin-top: 10px; text-align: center; } </style>
组件调用
<template>
<div>
<h2>个人头像设置</h2>
<avatar-uploader v-model="avatar"></avatar-uploader>
</div>
</template>
<script>
import AvatarUploader from '@/components/AvatarUploader'
export default {
components: {
AvatarUploader
},
data() {
return {
avatar: '' // 初始头像地址
}
}
}
</script>
注意一定要配置:output-type="'png'"