需求:1.点击修改图像按钮,弹出修改图像弹窗
2.图片都是上传到oss上面
3.上传图像右侧有个原型和方型图像展示实时效果
1.安装vue-cropper
npm install vue-cropper
2.组件内引入
import { VueCropper } from 'vue-cropper';
3.封装组件
<template>
<div>
<el-dialog width="500px" :title="dialogTitle" :visible.sync="visible" append-to-body :close-on-click-modal="false" :before-close="close" class="dialog-container">
<div v-loading="loading">
<div class="body">
<div class="cropp-content container">
<div class="bg-white">
<div class="cropper-cont">
<vue-cropper
ref="cropper"
:img="option.img"
:outputSize="option.size"
:outputType="option.outputType"
:info="true"
:full="option.full"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:fixedBox="option.fixedBox"
:original="option.original"
:autoCrop="option.autoCrop"
:autoCropWidth="option.autoCropWidth"
:autoCropHeight="option.autoCropHeight"
:centerBox="option.centerBox"
:high="option.high"
:infoTrue="option.infoTrue"
@realTime="realTime"
@imgLoad="imgLoad"
@cropMoving="cropMoving"
:enlarge="option.enlarge"></vue-cropper>
</div>
<div class="pre mt8">
<section class="pre-item">
<div :style="previewStyle1">
<div :style="previews.div">
<img :src="previews.url" :style="previews.img" />
</div>
</div>
</section>
<section class="pre-item">
<div :style="previewStyle2">
<div :style="previews.div">
<img :src="previews.url" :style="previews.img" />
</div>
</div>
</section>
</div>
</div>
<div class="test-button mt8">
<el-button type="primary" style="margin-right: 20px">
<label class="btn" for="uploads">选择图片</label>
</el-button>
<input type="file" id="uploads" style="position: absolute; clip: rect(0 0 0 0)" accept="image/png, image/jpeg, image/gif, image/jpg" @change="uploadImg($event, 1)" ref="uploadImg" />
<el-button type="primary" @click="finish('blob')">
<label class="btn">上传并保存</label>
</el-button>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import API from '@/api/persional';
import { VueCropper } from 'vue-cropper';
export default {
props: {
dialogVisible: {
type: Boolean,
default: false
},
dialogTitle: {
type: String,
default: ''
},
persionImg: {
type: String,
default: ''
}
},
components: { VueCropper },
computed: {
visible: function () {
return this.dialogVisible;
}
},
watch: {
persionImg: {
handler(nv) {
this.option.img = nv;
console.log(nv, 'nnn');
},
immediate: true
}
},
data() {
return {
loading: false,
model: false,
modelSrc: '',
previews: {},
option: {
img: '', //裁剪图片的地址 默认空,可选值url地址, base64, blob
outputSize: 0.8, //裁剪生成图片的质量 默认1 可选0.1 ~ 1
outputType: 'image/jpg,image/png', //裁剪生成图片的格式 jpg (jpg 需要传入jpeg) 可选:jpeg, png, webp
info: true, //裁剪框的大小信息 默认:true
canScale: false, //图片是否允许滚轮缩放
autoCrop: true, //是否默认生成截图框
// autoCropWidth: 80%, //默认生成截图框宽度 默认:80% 可选:0-max
//autoCropHeight: 80%, //默认生成截图框高度 默认:80% 可选:0-max
fixed: true, //是否开启截图框宽高固定比例
fixedNumber: [1, 1], //截图框的宽高比例 [1, 1]-[ 宽度 , 高度 ]
full: true, //是否输出原图比例的截图
fixedBox: false, //固定截图框大小 默认不允许改变 可选:false
//canMove: true,//上传图片是否可以移动
canMoveBox: false, //截图框能否拖动
original: false, //上传图片按照原始比例渲染
canterBox: false, //截图框是否被限制在图片里面
//high: true,//是否按照设备的dpr 输出等比例图片
infoTrue: true //true 为展示真实输出图片宽高 false 展示看到的截图框宽高
// maxImgSize: 2000 //限制图片最大宽度和高度 可选:0 ~ max
//enlarge: 1, //图片根据截图框输出比例倍数 默认1 可选:0 ~ max(建议不要太大不然会卡死的呢)
//mode: contain //contain , cover, 100px, 100% auto
},
example2: {
img: 'http://cdn.xyxiao.cn/Landscape_2.jpg',
info: true,
size: 1,
outputType: 'jpeg',
canScale: true,
autoCrop: true,
// 只有自动截图开启 宽度高度才生效
autoCropWidth: 300,
autoCropHeight: 250,
fixed: true,
// 真实的输出宽高
infoTrue: true,
fixedNumber: [4, 3]
},
previewStyle1: {},
previewStyle2: {}
};
},
methods: {
// 选择图片
uploadImg(e, num) {
// 上传图片
// this.option.img
let file = e.target.files[0];
const reg = /\.(gif|jpg|jpeg|png|bmp|GIF|JPG|PNG)$/;
if (!reg.test(e.target.value)) {
this.$message.warning('图片类型必须是.gif,jpeg,jpg,png,bmp中的一种');
return;
}
let reader = new FileReader();
reader.onload = ev => {
let data;
if (typeof ev.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
data = window.URL.createObjectURL(new Blob([ev.target.result]));
} else {
data = ev.target.result;
}
if (num === 1) {
this.option.img = data;
} else if (num === 2) {
this.example2.img = data;
}
this.$refs.uploadImg.value = '';
};
// 转化为base64
// reader.readAsDataURL(file)
// 转化为blob
console.log(file, 'file');
reader.readAsArrayBuffer(file);
},
// 上传并且保存
finish(type) {
// 输出
// var test = window.open('about:blank')
// test.document.body.innerHTML = '图片生成中..'
if (type === 'blob') {
this.$refs.cropper.getCropBlob(data => {
let img = window.URL.createObjectURL(data);
this.model = true;
this.modelSrc = img; //blob:http://localhost:10228/785ae593-ed77-40c8-8134-c836da6bc9bb
console.log(data, 'data'); //Blob
//data是Blob格式,转成file格式
const file = new File([data], '');
let formData = new FormData();
console.log(file, 'file');
formData.append('file', file);
this.loading = true;
API.getuploadHeadPortrait(formData)
.then(res => {
this.loading = false;
console.log(res, 'rrr');
this.$message.success(`${this.$t('change')}${this.$t('success')}`);
this.$emit('close', 'success');
})
.catch(() => {
this.loading = false;
});
console.log(this.modelSrc, 'this.modelSrc');
});
} else {
this.$refs.cropper.getCropData(data => {
this.model = true;
this.modelSrc = data;
});
}
},
// 关闭弹窗
close() {
this.$emit('close', 'close');
},
// 实时预览函数--右侧展示样式
realTime(data) {
let previews = data;
let h = 0.6; //高度固定,不然右侧预览会变形
// let w = 0.2;
this.previewStyle1 = {
width: previews.w + 'px',
height: previews.h + 'px',
overflow: 'hidden',
margin: '0',
zoom: h,
borderRadius: '50%'
};
this.previewStyle2 = {
width: previews.w + 'px',
height: previews.h + 'px',
overflow: 'hidden',
margin: '0',
zoom: h
};
this.previews = data;
},
imgLoad(msg) {
console.log(msg);
},
cropMoving(data) {
this.option.cropData = data;
}
}
};
</script>
<style scoped lang="scss">
.dialog-container {
.bg-white {
display: flex;
justify-content: space-around;
margin-bottom: 30px;
.cropper-cont {
width: 274px;
height: 274px;
}
.pre {
display: flex;
flex-direction: column;
justify-content: space-around;
align-content: space-around;
}
}
.test-button {
display: flex;
justify-content: flex-end;
align-items: center;
}
.pre-item {
padding-right: 20px;
}
}
</style>
4.组件引用
<template>
<image-cropper v-if="imagecropperShow" :dialogVisible="imagecropperShow" :persionImg="persionPic" ref="addDialog" @close="onCloseDialog"></image-cropper>
</template>
<script>
import ImageCropper from '@/components/cropper/index';
data() {
return {
imagecropperShow: false,
persionPic: '' // 图象接口返回的base64
};
},
methods: {
// 关闭裁剪弹窗
async onCloseDialog(val) {
if (val === 'success') {
await this.serchMessage();
console.log('修改');
}
this.imagecropperShow = false;
},
// 修改图象
updatePersionalImage() {
//oss图片请第二次,会报跨域,需要转成base64回显
const fileKey = this.ruleForm.headPortrait;
API.postBase64ByFileKey({ fileKey })
.then(res => (this.persionPic = 'data:image/jpg;base64,' + res.data))
.catch(err => {
this.loading = false;
this.$message.error(err.msg || '请求失败');
});
setTimeout(() => {
this.imagecropperShow = true;
}, 300);
}
}
</script>
总结:1.原始图像是oss,短时间请求两次,会报跨域,第二次回显图像编辑时,可让后端提供一个接口转成base64
2.option里面注释的属性,如果需要放开使用,在vue-cropper需要加入属性
3.获取的是Blob格式图片,需要转成file,const file = new File([data], ''); let formData = new FormData(); formData.append('file', file);
4.右侧预览需要固定宽或者高,不然选择裁剪高宽小于预览宽高,会变成椭圆,在realTime方法里