效果展示
思路
- 点击按钮打开dialog弹窗
- 选择图片文件,打开vue-cropper来调整、裁剪图片
- 点击确定开始裁剪图片,然后将图片发送到后端,后端上传到OSS对象存储
- 最后,后端返回裁剪好的图片在OSS的路径,显示出这个裁剪好的图片
完整代码
CutPortrait.vue
<template>
<div>
<el-button @click="isShowDialog = true">编辑头像</el-button>
<el-dialog title="设置头像" :visible.sync="isShowDialog" width="1000px" @close="closeDialog">
<input type="file" style="display:none" id="input-file" accept="image/png, image/jpeg, image/jpg, image/bmp" @change="uploadImg($event)" />
<el-row>
<el-col :span="16" style="height: 400px;">
<div class="container-left">
<div v-if="isShowFileButton" class="btn-box">
<el-popover width="200" trigger="hover" :content="'图片不可大于 ' + imageMaxSize + ' MB'">
<div class="choose-file-btn" @click="changeFile" slot="reference">选 择 图 片</div>
</el-popover>
</div>
<div v-else class="cropper-box">
<vueCropper
style="width:550px;height:400px;margin-left: 40px"
ref="cropper"
:img="options.img"
:auto-crop="options.autoCrop"
:fixed-box="options.fixedBox"
:can-move-box="options.canMoveBox"
:auto-crop-width="options.autoCropWidth"
:auto-crop-height="options.autoCropHeight"
:center-box="options.centerBox"
@realTime="realTime"
>
</vueCropper>
</div>
</div>
</el-col>
<el-col :span="8" style="height: 400px;">
<h2 style="text-align: center; line-height: 50px">头像预览</h2>
<div class="show-preview">
<div style="height: 200px; width: 200px" class="preview">
<img :src="previews.url" :style="previews.img" alt="" />
</div>
</div>
<div v-show="!isShowFileButton" class="preview-footer">
<el-button @click="changeFile">更 换 图 片</el-button>
</div>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="isShowDialog = false">取 消</el-button>
<el-button type="primary" @click="commitCut" v-loading.fullscreen.lock="fullscreenLoading" :disabled="Object.keys(previews).length === 0">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'CutPortrait',
props: {
// 上传地址
UploadUrl: {
type: String,
require: true
},
// 上传文件的附带参数数据
UploadData: {
type: Object,
require: true
},
// 图片的大小限制,默认 10 MB
imageMaxSize: {
type: Number,
default: 10
}
},
data() {
return {
isShowDialog: false, // 弹窗状态
isShowFileButton: true, // 选择文件的按钮状态
options: { // vue-cropper的参数
img: '', //裁剪的图片blob地址
autoCrop: true, //默认生成截图框
fixedBox: true, //固定截图框大小
canMoveBox: false, //截图框不能拖动
autoCropWidth: 200, //截图框宽度
autoCropHeight: 200, //截图框高度
centerBox: true //截图框被限制在图片里面
},
previews: {}, // 预览图对象
fullscreenLoading: false, // 全屏加载遮罩状态
imageUrl: '' // 上传后的图片地址
}
},
methods: {
// 关闭截图对话框弹窗
closeDialog() {
this.isShowDialog = false
this.previews = {}
this.isShowFileButton = true
},
// 点击选择图片或更换图片按钮,变更裁剪图片
changeFile() {
document.getElementById('input-file').click()
},
// 变更裁剪图片
uploadImg(e) {
let file = e.target.files[0]
// 判断文件格式是否符合要求规范
if (!/\.(jpg|jpeg|png|bmp|JPG|PNG)$/.test(e.target.value)) {
this.$message.error('图片类型必须是,jpeg,jpg,png,bmp中的一种')
return false
}
// 判断文件是否超出大小限制
if (file.size / 1024 / 1024 > this.imageMaxSize) {
this.$message.error('图片不可大于 ' + this.imageMaxSize + ' MB')
return false
}
//fileReader 接口,用于异步读取文件数据
let reader = new FileReader()
reader.readAsDataURL(file) //重要 以dataURL形式读取文件
reader.onload = e => {
// this.options.img = window.URL.createObjectURL(new Blob([e.target.result])) 转化为blob格式
this.options.img = e.target.result
// 转化为base64
// reader.readAsDataURL(file)
// 转化为blob
}
this.isShowFileButton = false
},
// 同步实时预览的数据
realTime(data) {
this.previews = data
},
//确认截图,上传到后台
commitCut() {
this.fullscreenLoading = true
let formData = new FormData()
this.$refs.cropper.getCropBlob(res => {
//res是裁剪后图片的blob对象,遍历组件上传数据的对象 => 添加到formData对象中
formData.append('file', res)
for (let key in this.UploadData) {
formData.append(key, this.UploadData[key])
}
this.$http.post(this.UploadUrl, formData).then(res => {
res = res.data
if (res.code === '000000') {
console.log(res.msg)
this.imageUrl = res.data.path
this.closeDialog()
this.fullscreenLoading = false
this.SetUrl()
} else {
console.log('%c上传失败', 'color:#a00')
this.fullscreenLoading = false
}
})
})
},
// 将裁剪后的图片传出的父组件
SetUrl() {
this.$emit('getUrl', this.imageUrl)
}
}
}
</script>
<style lang="scss" scoped>
.container-left {
width: 100%;
height: 100%;
.btn-box {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.cropper-box {
width: 100%;
height: 100%;
}
}
.choose-file-btn {
width: 160px;
height: 80px;
font-size: 22px;
font-weight: 600;
line-height: 80px;
text-align: center;
color: #333;
border: #aaa 1px solid;
cursor: pointer;
transition: 0.25s;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.3);
}
.choose-file-btn:hover {
color: #fff;
background: #00d7c6;
border: #00d7c6 1px solid;
transform: scale(1.15);
}
.show-preview {
display: flex;
justify-content: center;
}
.preview {
border-radius: 50%;
overflow: hidden;
border: 1px solid #ccc;
background: #ccc;
}
.preview-footer {
width: 100%;
display: flex;
justify-content: center;
padding-top: 15px;
}
</style>
调用组件的 Test.vue
<template>
<div style="height: 100%; width: 100%; padding-top: 50px">
<div style="height: 300px; width: 300px; margin: auto">
<img v-if="imgPath === ''" src="../static/image/person-default.png" />
<img v-else :src="imgPath" />
<CutPortrait :upload-data="UploadData" :upload-url="UploadUrl" @getUrl="getUrl"></CutPortrait>
</div>
</div>
</template>
<script>
import CutPortrait from './CutPortrait'
export default {
name: 'Test',
components: {
CutPortrait
},
data() {
return {
// 上传地址
UploadUrl: 'http://localhost:8080/test/upload',
// 上传时,附带的参数
UploadData: {
userId: '1234567890000'
},
imgPath: '' // 裁剪好的头像
}
},
methods: {
// 获取头像裁剪组件返回的路径
getUrl(path) {
this.imgPath = path
}
}
}
</script>
<style scoped></style>
Java后端代码
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.annotations.ResultMap;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
@CrossOrigin
@Controller
@RequestMapping("/test")
public class TestUpload {
@RequestMapping("/upload")
@ResponseBody
public String upload(@RequestParam String userId,
@RequestParam("file") MultipartFile file) throws IOException {
String fileName = userId + ".jpg"; // 头像文件名为 用户id.jpg
System.out.println("userId:" + userId);
System.out.println("文件名:" + fileName);
/* 上传到OSS,并返回文件地址 */
String imgURL = OSSTool.uploadFile(fileName, file);
Map data = new HashMap();
Map map = new HashMap();
data.put("path", imgURL);
if (imgURL != null) {
map.put("code", "000000");
map.put("msg", "上传成功");
map.put("data", data);
} else {
map.put("code", "23333");
map.put("msg", "上传失败");
}
return JSON.toJSONString(map);
}
}
参考
https://blog.csdn.net/weixin_39327044/article/details/89765109