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>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值