vue的图片剪切插件VueCopper及上传

GitHub:vue-cropper

利用vue-cropper做的关于图片裁剪、压缩、上传、预览等做的一个公共组件

自己做的弹窗裁剪

<template>
  <Modal
    class="ct-personal-info"
    v-model="modalVisible"
    width="550"
    footer-hide
  >
    <h2 slot="header">中教信息</h2>
    <div class="ct-personal-info-content" v-if="!showCropper">
      <div
        class="ct-head-portrait"
        @click="handlerChangePhoto"
        @mouseenter="mouseenterImg"
        @mouseleave="mouseleaveImg"
      >
        <img class="head-photo-img" :src="headPhotoSrc" />
        <div v-if="showCameraIcon" class="camera-icon-wrapper">
          <Icon type="ios-camera" class="camera-icon" size="40" />
        </div>
      </div>
      <input
        ref="fileInput"
        type="file"
        class="file-hidden"
        accept="image/*"
        @change="handlerGetImg($event)"
      />
      <div class="ct-name">
        <label>姓名:</label>
        <Input class="ct-name-ipt" v-model="ctName" placeholder="请输入姓名" />
      </div>
      <div class="ct-info-change-icon">
        <Icon
          class="tabel-teacher-houseNumAll-icon-1 change-icon"
          custom="iconfont icon-bianji_moren-"
          @click="handlerSubmitInfo"
        />
      </div>
    </div>
    <div class="tailor-photo" v-if="showCropper">
      <div class="cropper-wrapper">
        <VueCropper
          ref="cropper"
          :img="option.imgUrl"
          :outputSize="option.size"
          :outputType="option.outputType"
          :autoCrop="option.autoCrop"
          :autoCropWidth="option.autoCropWidth"
          :autoCropHeight="option.autoCropHeight"
          :fixedBox="option.fixedBox"
        ></VueCropper>
      </div>
      <div class="btns-group">
        <Button class="photo-btn" @click="handlerCancelCropper">取消</Button>
        <Button class="photo-btn" type="primary" @click="handlerRotateRight"
          >顺时针旋转</Button
        >
        <Button class="photo-btn" type="primary" @click="handlerRotateLeft"
          >逆时针旋转</Button
        >
        <Button type="primary" @click="handlerFinishPhoto">确定</Button>
      </div>
    </div>
  </Modal>
</template>

<script type="text/javascript">
import { ajaxHelper, mapState, message } from 'utils';
import { postUpdateStudyPlanCtPhotoApi } from '@/api/studentDetail';
import regexp from 'const/regexp';
import { VueCropper } from 'vue-cropper';

export default {
  name: 'ct-personal-info',
  components: { VueCropper },
  data() {
    return {
      modalVisible: true,
      ctName: '',
      option: {
        imgUrl: '',
        size: 0,
        autoCrop: true,
        autoCropWidth: 200,
        autoCropHeight: 200,
        fixedBox: true,
        outputType: 'png',
      },
      showCropper: false,
      headPhotoSrc: '',
      showCameraIcon: false,
      finishCropperData: {},
      fileTargetVal: {},
    };
  },

  computed: {
    ...mapState({
      email: state => state.user.email,
      learningPlanEmployeeName: state => state.user.learningPlanEmployeeName,
      learningPlanEmployeeAvatar: state =>
        state.user.learningPlanEmployeeAvatar,
    }),
  },

  watch: {
    modalVisible: {
      handler(newValue) {
        if (newValue) {
          this.ctName = this.learningPlanEmployeeName;
          this.headPhotoSrc = this.learningPlanEmployeeAvatar;
        }
      },
      immediate: true,
    },

    showCropper(newValue, oldValue) {
      if (!newValue) {
        this.option.imgUrl = '';
      }
    },
  },

  methods: {
    handlerChangePhoto() {
      this.$refs.fileInput.click();
      this.showCropper = true;
    },

    handlerCancelCropper() {
      this.showCropper = false;
    },

    mouseenterImg() {
      this.showCameraIcon = true;
    },

    mouseleaveImg() {
      this.showCameraIcon = false;
    },

    //选择本地图片
    handlerGetImg(e) {
      let _this = this;
      //上传图片
      let file = e.target.files[0];
      this.fileTargetVal = file;

      if (!regexp.imgType.test(e.target.value)) {
        message('warning', '图片类型必须是jpeg,jpg,png,bmp中的一种');
        return false;
      }
      let 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.option.imgUrl = data;
      };
      //转化为base64;
      // reader.readAsDataURL(file);
      // 转化为blob
      reader.readAsArrayBuffer(file);
    },

    //顺时针旋转
    handlerRotateRight() {
      this.$refs.cropper.rotateRight();
    },

    //逆时针旋转
    handlerRotateLeft() {
      this.$refs.cropper.rotateLeft();
    },

    handlerFinishPhoto() {
      // 获取截图的blob数据
      this.$refs.cropper.getCropBlob(data => {
        this.finishCropperData = data;
        //关闭裁剪
        this.showCropper = false;
        //取消相机icon
        this.mouseleaveImg();
        //blob转回imgsrc用于预览
        this.headPhotoSrc = window.URL.createObjectURL(data);
      });
    },

    async postUpdateStudyPlanCtPhoto() {
      let res = await ajaxHelper(
        postUpdateStudyPlanCtPhotoApi.bind(
          null,
          this.reqUpdateStudyPlanCtPhoto()
        )
      );
      res && this.resUpdateStudyPlanCtPhoto();
    },

    reqUpdateStudyPlanCtPhoto() {
      let formData = new FormData();
      formData.append(
        'learning_plan_employee_avatar_file',
        this.finishCropperData
      );
      formData.append('learning_plan_employee_name', this.ctName);
      formData.append('employee', this.email);

      return formData;
    },

    resUpdateStudyPlanCtPhoto() {
      message('success', '保存成功');
      //关闭弹窗
      // this.modalVisible=false;
    },

    handlerSubmitInfo() {
      //判断昵称和头像src有无即可,不需要判断blob对象
      if (!this.ctName || !this.headPhotoSrc) {
        message('warning', '请补充完整信息');
        return;
      }
      this.postUpdateStudyPlanCtPhoto();
    },
  },
};
</script>

<style scoped lang="scss">
.ct-personal-info {
  .ct-personal-info-content {
    display: flex;
    align-items: center;
  }
  .ct-head-portrait {
    position: relative;
    width: 100px;
    height: 100px;
    border-radius: 50%;
    border: solid 1px;
    margin-right: 8px;
    overflow: hidden;
    .head-photo-img {
      position: absolute;
      width: 100%;
      height: 100%;
      z-index: 10;
    }
    .camera-icon-wrapper {
      width: 100%;
      height: 100%;
      position: absolute;
      text-align: center;
      z-index: 100;
      background-color: rgba(4, 6, 8, 0.2);
      &::before {
        content: ' ';
        display: inline-block;
        height: 100%;
        width: 1%;
        vertical-align: middle;
      }
    }
  }
  .file-hidden {
    display: none;
  }
  .ct-name {
    display: flex;
    align-items: center;
    margin-right: 8px;
    & > label {
      width: 40px;
    }
    .ct-name-ipt {
      width: 240px;
    }
  }
  .change-icon {
    cursor: pointer;
    &:hover {
      color: rgba(24, 144, 255, 1);
    }
  }

  .tailor-photo {
    .cropper-wrapper {
      width: 500px;
      height: 500px;
    }
    .btns-group {
      display: flex;
      justify-content: flex-end;
      margin-top: 20px;
      .photo-btn {
        margin-right: 8px;
      }
    }
  }
}
</style>

注意

上传文件流需要使用FormData对象,将需要参数通过append方式加入,最终axios请求只需要data:formData即可

例如:

 reqUpdateStudyPlanCtPhoto() {
      let formData = new FormData();
      //将参数一一添加
      formData.append(
        'learning_plan_employee_avatar_file',
        this.finishCropperData
      );
      formData.append('learning_plan_employee_name', this.ctName);
      formData.append('employee', this.email);

      return formData;
    },
    
    //data:formData
    function postUpdateStudyPlanCtPhotoApi(formData) {
  return Axios({
    url: ' /api/permission/employee/update/',
    method: 'post',
    data: formData,
  });
}

???

不知道为啥,默认裁剪框宽高无效。难受。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容