如题,做一个图片裁剪功能,前端使用vue-cropper (https://github.com/xyxiao001/vue-cropper)将图片裁剪之后请求Nodejs写的接口,并且上传到阿里云OSS
贴出关键的代码,UI组件是自己写的,所以不用管,装依赖,main.js中先引入组件,装B一气呵成,当然你也可以在页面中单独引入
npm install vue-cropper
import VueCropper from 'vue-cropper'
Vue.use(VueCropper)
前端关键代码
<template>
<div class="kom-transition-box">
<div class="upload-photo">上传照片<input type="file" @change="changeUpload($event)"/></div>
<!-- 一个弹出层 -->
<kom-popup v-model="showImagePopover" position="center" width="90%">
<div>
<div class="cropper-content">
<div class="cropper">
<vueCropper
ref="cropper"
:img="option.img"
:outputSize="option.size"
:outputType="option.outputType"
:info="true"
:full="option.full"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:original="option.original"
:autoCrop="option.autoCrop"
:fixed="option.fixed"
:fixedNumber="option.fixedNumber"
:centerBox="option.centerBox"
:infoTrue="option.infoTrue"
:fixedBox="option.fixedBox"
></vueCropper>
</div>
</div>
<div>
<kom-button size="large" type="primary" @click.native="finish" :loading="loading">确认</kom-button>
<kom-button size="large" type="hollow" @click.native="showImagePopover = false">取 消</kom-button>
</div>
</div>
</kom-popup>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data() {
return {
imgData: {
accept: 'image/gif, image/jpeg, image/png, image/jpg'
},
showImagePopover: false,
option: { // 裁剪组件的基础配置option
img: '', // 裁剪图片的地址
info: true, // 裁剪框的大小信息
outputSize: 0.4, // 裁剪生成图片的质量
outputType: 'jpeg', // 裁剪生成图片的格式
canScale: true, // 图片是否允许滚轮缩放
autoCrop: true, // 是否默认生成截图框
autoCropWidth: 100, // 默认生成截图框宽度
autoCropHeight: 100, // 默认生成截图框高度
fixedBox: true, // 固定截图框大小 不允许改变
fixed: true, // 是否开启截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例
full: false, // 是否输出原图比例的截图
canMoveBox: false, // 截图框能否拖动
original: true, // 上传图片按照原始比例渲染
centerBox: true, // 截图框是否被限制在图片里面
infoTrue: false // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
},
loading: false, // 防止重复提交
imgTargetInfo: {} // 选择的图片信息
}
},
computed: {
...mapState({ // 从vuex中获取用户信息
userInfo: state => state.userInfo
})
},
methods: {
changeUpload(event) {
let imgTarget = event.target.files[0];
if(!imgTarget){ // 防止不选择文件直接关闭弹窗获取不到event报错的问题
return;
}
let type = imgTarget.type; // 文件的类型,判断是否是图片
let size = imgTarget.size; // 文件的大小,判断图片的大小
if (this.imgData.accept.indexOf(type) == -1) {
this.$toast("格式不支持", {
location: 'bottom'
});
return false;
}
if (size > 1024 * 1024 * 5) {
this.$toast("请选择5M以内的图片", {
location: 'bottom'
});
return false;
}
/* 关键代码 */
var reader = new FileReader();
reader.onload = (e) => {
let data;
if (typeof e.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
data = window.URL.createObjectURL(new Blob([e.target.result]));
}
else {
data = e.target.result;
}
// 上传成功后将图片地址赋值给裁剪框显示图片
this.$nextTick(() => {
this.option.img = data;
this.showImagePopover = true;
})
};
// 转化为base64
// reader.readAsDataURL(imgTarget);
// 转化为blob
reader.readAsArrayBuffer(imgTarget);
/* // 关键代码 */
this.imgTargetInfo = {
fileName: imgTarget.name
}
},
// 点击裁剪,这一步是可以拿到处理后的地址
finish() {
this.$refs.cropper.getCropBlob((data) => {
this.loading = true;
let formss = new FormData();
formss.append('file', data);
formss.append('fileName', this.imgTargetInfo.fileName);
formss.append('userId', this.userInfo.id);
// 这里是自己封装的一个axios方法,请求路径是下面Nodejs中的/upload
this.$ajax.photoUpload(formss).then((response) => {
console.log(response);
this.showImagePopover = false;
this.loading = false;
if (response.code == 0) {
this.$toast("上传成功", {
location: 'bottom'
});
} else {
this.$toast(response.msg, {
location: 'bottom'
});
}
}).catch(error => {
this.$toast("上传图片出错", {
location: 'bottom'
});
this.showImagePopover = false;
this.loading = false;
})
})
}
},
mounted() {
}
}
</script>
<style scoped lang="less">
.upload-photo {
display: block;
position: relative;
margin-top: .3rem;
padding: 8px 12px;
background: #D0EEFF;
border: 1px solid #99D3F5;
overflow: hidden;
color: #1E88C7;
text-align: center;
border-radius: 2px;
input {
position: absolute;
font-size: 24px;
right: 0;
top: 0;
opacity: 0;
}
&:hover {
background: #AADFFD;
border-color: #78C3F3;
color: #004974;
text-decoration: none;
}
}
// 截图
.cropper-content {
.cropper {
width: auto;
height: 300px;
}
}
</style>
Nodejs接口代码
const express = require("express");
const multiparty = require('multiparty');
const router = express.Router();
const CONFIG = require("../config"); // ALIYUN OSS配置
let OSS = require('ali-oss');
let ossClient = new OSS(CONFIG.ALIYUN_OSS);
router.post("/upload", function (req, res) {
let form = new multiparty.Form();
form.parse(req, function(err, fields, file){
if (err) {
res.send({code: -1, msg: "服务异常"});
} else {
upload (res, fields, file);
}
});
});
async function upload (res, fields, file) {
let userId = fields.userId[0];
let size = file.file[0].size;
if(userId === undefined || userId === "undefined"){
res.send({code: 1001, msg: "参数异常"});
return;
}
// 大小5MB
if(size > 5 * 1024 * 1024){
res.send({code: 1002, msg: "请选择5M以内的图片"});
return false;
}
// 上传到OSS
try {
let fileName = userId + '-' + new Date().getTime() +'.jpg';
let resOss = await ossClient.put(useTo +'/'+ fileName, file.file[0].path);
let resUrl = resOss.url;
if(resOss.res.statusCode == 200){
res.send({code: 0, result: {url: fileName}});
// 省略保存文件名和路径到数据库的代码...
}else{
res.send({code: -1, msg: "OSS上传图片服务异常"});
}
} catch (e) {
res.send({code: -1, msg: "OSS上传图片服务异常"});
}
}
module.exports = router;