vue图片裁剪组件封装
本文封装了一个上传图片裁剪功能的组件,这里引入了vue-cropper插件。
安装
npm install vue-cropper
使用
import VueCropper from 'vue-cropper'
Vue.use(VueCropper)
创建个子组件
<template>
<div class="cropper-box" >
<VueCropper
:style="{width:option.autoCropWidth+'px',height:option.autoCropHeight+'px',margin:'0 auto'}"
ref="cropper"
:img="option.img"
:outputSize="option.outputSize"
:outputType="option.outputType"
:infoTrue="true"
:full="option.full"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:fixedBox="option.fixedBox"
:original="option.original"
:autoCrop="option.autoCrop"
:fixed="option.fixed"
:fixedNumber="option.fixedNumber"
:centerBox="option.centerBox"
:autoCropWidth="option.autoCropWidth"
:autoCropHeight="option.autoCropHeight"
:enlarge="option.enlarge"
:mode="option.mode"
></VueCropper>
<div style="margin-top:10px;text-align:center">
<el-button type="info" @click="closeFun">取消</el-button>
<el-button type="primary" @click="finish('blob')">裁剪</el-button>
<el-button type="primary" @click="useOriginImage()">使用原图</el-button>
</div>
</div>
</template>
<script>
import VueCropper from 'vue-cropper'
export default {
name: 'Cropper', // 图片裁剪
props: {
widthSize: {
type: 'Number',
default: 750
},
heightSize: {
type: 'Number',
default: 750
},
imgUrl: {
default: ''
},
ratio: {
type: 'Number',
default: 2
}
},
data() {
return {
option: {
img: '',//裁切图片的地址
outputSize: 1,//裁剪生成图片的质量 0.1-1
full: false,//是否输出原图比例的截图
outputType: 'png',//裁剪生成图片的格式
canMove: true,//图片是否允许滚轮缩放
fixedBox: true,//固定截图框大小 不允许改变
original: true,//上传图片按照原始比例渲染
canMoveBox: false,//截图框能否拖动
centerBox: false,// 截图框是否被限制在图片里面
canMove:true,// 上传图片是否可以移动
autoCropWidth: 200,
autoCropHeight: 200,
autoCrop:true,//是否默认生成截图框
// 开启宽度和高度比例
fixed: true,
fixedNumber: [1, 1], // 截图框的宽高比例
enlarge: 1, // 图片根据截图框输出比例倍数
mode: 'contain'
},
previews: {}
}
},
created() {
this.option.img = this.imgUrl;
this.option.fixedNumber = [this.widthSize, this.heightSize];
this.option.enlarge = this.ratio;
this.option.autoCropWidth = this.widthSize / this.ratio;
this.option.autoCropHeight = this.heightSize / this.ratio;
console.log('this.option.autoCropWidth', this.option.autoCropWidth);
console.log('this.option.autoCropHeight', this.option.autoCropHeight);
},
methods: {
/**
* 裁剪返回图片(base 64)
*/
finish (type) {
var that =this;
this.$refs.cropper.getCropData((data) => {
//裁切生成的base64图片
this.$emit("editCoverFunc", this.convertBase64UrlToBlob(data))
})
},
useOriginImage(){//使用原图,不做裁剪
this.$emit("useOriginImage")
},
convertBase64UrlToBlob(urlData) {
const bytes = window.atob(urlData.split(',')[1])// 去掉url的头,并转换为byte
// 处理异常,将ascii码小于0的转换为大于0
const ab = new ArrayBuffer(bytes.length)
const ia = new Uint8Array(ab)
for (var i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i)
}
return new Blob([ab], { type: 'image/jpeg' })
},
closeFun() {
this.$emit("editCoverFunc", false)
}
}
};
</script>
<style scoped>
.cropper-box{
position: relative;
z-index: 99999999999 !important
}
</style>
创建个父组件
<template>
<div>
<el-upload class="avatar-uploader" :style="{ width: styleWidth+'px' }" :auto-upload="false" :show-file-list="false"
ref="uploadCover" :on-change="handleFilechange" :before-upload="beforeAvatarUpload" name="files">
<div :class="{'imageUrl-wp': imageUrl}"
:style="{ width: styleWidth+'px' ,height:styleHeight+'px' ,lineHeight:styleHeight+'px' }">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"
:style="{ width: styleWidth+'px' ,height:styleHeight+'px' ,lineHeight:styleHeight+'px' }"></i>
</div>
</el-upload>
<span :class="explain" v-if="crop">建议尺寸: {
{widthSize}} x {
{heightSize}}</span>
<el-dialog title="图片裁剪" :visible.sync="editCover" :close-on-click-modal="false" width="60%" center append-to-body
@close="close">
<CropperIndex v-if="editCover" :widthSize="widthSize" :heightSize="heightSize" :imgUrl="imageUrl" :ratio="ratio"
@editCoverFunc="handleCropped" @useOriginImage="handleNoCrop"></CropperIndex>
</el-dialog>
</div>
</template>
<script>
import CropperIndex from "@/components/cropperIndex";
import {
uploadFile
} from "@/api/table";
export default {
components: {
CropperIndex
},
props: {
widthSize: {
type: "Number",
default: 750
},
heightSize: {
type: "Number",
default: 750
},
initImgUrl: {
default: ""
},
styleWidth: {
type: "Number",
default: 70
},
ratio: {
type: "Number",
default: 2
},
explain: {
type: String
},
crop: {
type: Boolean,
default: true
},
sizeM:{
default:3
}
},
mounted(){
console.log(this.explain);
},
data() {
return {
editCover: false, // 控制裁剪弹窗
imageUrl: this.initImgUrl, // 图片地址
imageOriginUr:"",//原图地址
needClose:true,//是否需要判断close,直接关闭需要判断
};
},
computed: {
styleHeight: function () {
return this.styleWidth * this.heightSize / this.widthSize;
}
},
created() {
console.log('imageUrl', this.imageUrl);
console.log('prop', this.initImgUrl);
},
methods: {
handleFilechange(file, files) {
let _this=this;
if (this.checkImg(file)) {
if (file.raw.type === 'image/gif'){
const formData = new FormData();
formData.append('files', file.raw);
uploadFile(formData).then(res => {
this.imageUrl = res.data;
this.needClose=false;
this.$emit('imgEdit', this.imageUrl);
});
}else{
var image=new Image();
image.onload=function(){
if(image.width==parseInt(_this.widthSize)&&image.height==parseInt(_this.heightSize)){
_this.crop=false;//如果图片的比例就是约定的比例,那么不会再触发裁剪
}
if(parseInt(image.width)*parseInt(_this.heightSize)==parseInt(image.height)*parseInt(_this.widthSize)){
_this.crop=false;//如果图片的比例与约定的比例相同,那么不再触发裁剪
}
const formData = new FormData();
formData.append('files', file.raw);
uploadFile(formData).then(res => {
_this.imageOriginUr = res.data;
if(!_this.crop){
_this.imageUrl = _this.imageOriginUr;
_this.needClose=false;
_this.$emit('imgEdit', _this.imageUrl);
}else{
_this.imageUrl = URL.createObjectURL(file.raw);
_this.editCover = true;
}
});
}
image.src=URL.createObjectURL(file.raw);
}
}
},
checkImg(file) {
const fileType = file.raw.type === 'image/jpeg' || file.raw.type === 'image/png' || file.raw.type ===
'image/gif';
const isLt3M = file.size / 1024 / 1024 < this.sizeM;
if (!fileType) {
this.$message.error('上传图片只能是 jpg/png/gif 格式!');
}
if (!isLt3M) {
this.$message.error('上传图片大小不能超过 '+this.sizeM+'MB!');
}
return fileType && isLt3M;
},
handleCropped(data) {
this.needClose=false;
if (data !== false) {
const formData = new FormData();
console.log(data)
formData.append('files', data, 'jpg');
uploadFile(formData).then(res => {
this.imageUrl = res.data;
this.$emit('imgEdit', this.imageUrl);
});
} else {
this.imageUrl = this.initImgUrl;
}
this.editCover = false;
},
handleNoCrop(){//不做裁剪,使用原图
this.imageUrl = this.imageOriginUr;
this.$emit('imgEdit', this.imageUrl);
this.needClose=false;
this.editCover = false;
},
close() {
if(this.needClose){
this.imageUrl = this.initImgUrl;
}
}
}
};
</script>
<style lang="scss" scoped>
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
border: 1px dashed #d9d9d9;
border-radius: 4px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader-icon:hover {
border-color: #409eff;
}
.imageUrl-wp {
// width: 120px;
// height: 120px;
// line-height: 120px;
// margin-right: 20px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
/*box-sizing: content-box;*/
}
.avatar {
// vertical-align: middle;
display: inline-block;
max-width: 100%;
max-height: 100%;
// display: block;
/*vertical-align: middle;*/
text-align: center;
display: table-cell;
display: inline;
}
.avatar-explain {
display: none;
}
.avatar-uploader {
line-height:0;
}
</style>
最后在最外层组件引用
<template>
<div>
<el-form ref="dataForm" :rules="rules" :model="info" label-position="left" label-width="90px" style="width: 500px; margin-left:50px;">
<el-form-item label="明星姓名" prop="starName">
<el-input v-model="info.starName" placeholder="请填写明星姓名" />
</el-form-item>
<el-form-item label="明星头像" prop="starAvatar">
<ImgEditor :widthSize="200" :heightSize="200" :initImgUrl="info.starAvatar" :styleWidth="120" :key="info.starAvatar" @imgEdit="onImgEdited"></ImgEditor>
</el-form-item>
<el-form-item label="稿件ID" prop="articleId">
<p class="tip_text">ID填写完毕后点击 回车键 自动获取标题和封面</p>
<el-input v-model="info.articleId" placeholder="请填写稿件ID" @keyup.enter.native="findArticle(info.articleId)" />
<p class="tip_text">请选择视频、图文</p>
</el-form-item>
<el-form-item label="标题" prop="title">
<el-input v-model.trim="info.title" placeholder="请填写标题" maxlength="40" show-word-limit/>
</el-form-item>
<el-form-item label="图片" prop="cover">
<ImgEditor :widthSize="750" :heightSize="600" :initImgUrl="info.cover" :styleWidth="120" :key="info.cover" @imgEdit="onImgEdited1"></ImgEditor>
</el-form-item>
</el-form>
<div style="margin:0 auto;width:200px;">
<el-button @click="close()">
取消
</el-button>
<el-button type="primary" @click="detailEdit('dataForm')">
保存
</el-button>
</div>
</div>
</template>
<script>
import ImgEditor from '@/components/imgEditor'
import { addStarVideo, updateStarVideo } from '@/api/chunjieActivity'
import { findArticle } from '@/api/table'
//校验必须为正整数
let isNumber = (rule, value, callback) => { //就是我们的回调函数,需要大家注意的是,这个没有在return的对象中,在data中
if(value.length > 0) {
var testResult = /^\d+$/.test(value);
if(!testResult) {
callback(new Error('必须为数字'));
} else {
callback();
}
} else {
callback()
}
}
export default {
components: {
ImgEditor
},
props: {
info: {
type: Object,
default: {
starName: '',
starAvatar: '',
title: '',
cover: '',
articleId: null,
}
},
},
data() {
return {
cropperImage: '',
rules: {
starName: [{
required: true,
message: '请填写明星姓名',
trigger: 'change'
}],
starAvatar: [{
required: true,
message: '请上传明星头像',
trigger: 'change'
}],
title: [{
required: true,
message: '请填写标题',
trigger: 'change'
}],
cover: [{
required: true,
message: '请上传图片',
trigger: 'change'
}],
articleId: [{
required: true,
message: '请输入稿件ID',
trigger: 'change'
}, {
validator: isNumber,
trigger: "blur"
}],
}
}
},
methods: {
onImgEdited(img) {
this.info.starAvatar = img;
},
onImgEdited1(img) {
this.info.cover = img;
},
saveItem(params) { //新增数据
addStarVideo(params).then(response => {
if(response.code == 200) {
this.$message.success('新增成功')
this.$refs.dataForm.clearValidate()
this.$emit('dialogClose', {
submitType: true
})
this.$refs.dataForm.clearValidate();
this.$refs.dataForm.resetFields();
} else {
this.$message.error(response.message)
}
})
},
updateItem(params) { //更新数据
updateStarVideo(params).then(response => {
if(response.code == 200) {
this.$message.success('修改成功')
this.$refs.dataForm.clearValidate()
this.$emit('dialogClose', {
submitType: true
})
this.$refs.dataForm.clearValidate();
this.$refs.dataForm.resetFields();
} else {
this.$message.error(response.message)
}
})
},
checkArticle(id, callback) { //要校验article的有效性
var params = {
id: id
}
findArticle(params).then(response => {
if(response.code == 200) {
if(response.data.showStatus != 1) {
this.$message.error("只能选择正常稿件");
return false;
}
var articleType = parseInt(response.data.type);
//限定是1图文 2视频
if(articleType != 1 && articleType != 2) {
this.$message.error("不能选择此类型稿件");
return false;
}
if(callback) {
callback(response);
}
} else {
this.$message.error(response.data.message)
}
})
},
//稿件信息
findArticle(id) {
this.info.title = ""
this.info.cover = ""
this.$refs.dataForm.validateField('articleId', (errorMsg) => {
if(errorMsg != "") {
return false;
}
let _this = this;
this.checkArticle(id, function(response) {
_this.info.title = response.data.title
_this.info.cover = response.data.cover
})
});
},
detailEdit(formName) {
this.$refs[formName].validate((valid) => {
if(valid) {
if(this.info.cover) {
var params = {};
if(this.info.id) {
params = {
'id': this.info.id,
'starName': this.info.starName,
'starAvatar': this.info.starAvatar,
'title': this.info.title,
'cover': this.info.cover,
'articleId': this.info.articleId
}
} else {
params = {
'starName': this.info.starName,
'starAvatar': this.info.starAvatar,
'title': this.info.title,
'cover': this.info.cover,
'articleId': this.info.articleId
}
}
let _this = this;
this.checkArticle(params.articleId, function() {
//正式提交
if(_this.info.id) {
_this.updateItem(params);
} else {
_this.saveItem(params);
}
})
} else {
return false
}
} else {
return false
}
})
},
close() {
this.$emit('dialogClose', {
submitType: false
})
this.$refs.dataForm.clearValidate();
this.$refs.dataForm.resetFields();
},
}
}
</script>
还没有评论,来说两句吧...