vue图片剪裁上传组件

2021年10月28日 vue

头像上传剪切组件的封装

因为antd vue2.2.0并没有剪切图片的功能所以自己封装

<template>
  <div ref="cropper-upload" class="cropper-upload">
    <a-upload
      accept="image/*"
      list-type="picture-card"
      class="avatar_uploader"
      :show-upload-list="false"
      :before-upload="beforeUpload"
      :disabled="disabled"
    >
      <!-- 可以编辑 -->
      <div v-if="!disabled">
        <!-- 没的头像可以上传 -->
        <div v-if="!imageUrl2">
          <upload-outlined :style="{ fontSize: '16px' }" />
          <div class="ant-upload-text">上传头像</div>
        </div>
        <!-- 展示 -->
        <img
          v-else
          class="avatar_uploader"
          :src="`/api/media/api/v1/media/showImage/${imageUrl2 ?? 'avatar'}`"
        />
      </div>
      <!-- 当不能编辑时 -->
      <div v-else>
        <div v-if="!imageUrl2"></div>
        <img
          v-else
          class="avatar_uploader"
          :src="`/api/media/api/v1/media/showImage/${imageUrl2 ?? 'avatar'}`"
        />
      </div>
    </a-upload>
    <a-modal
      v-model:visible="visible"
      title="编辑图片"
      width="520px"
      wrap-class-name="cropper_upload_madal"
      :centered="true"
      :destroy-on-close="true"
      :mask-closable="false"
      @ok="handelOk"
    >
      <img ref="imageRef" :src="imageUrl" class="before-cropper" />
      <img class="before" />
    </a-modal>
  </div>
</template>
<script lang="ts">
import { computed, defineComponent, nextTick, ref, watch } from 'vue';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import { message } from 'ant-design-vue';
import { UploadImageAction } from '@/store/modules/StudentManageStore/actions';
import { useStore } from 'vuex';
import { UploadOutlined } from '@ant-design/icons-vue';
​
interface FileItem {
  uid: string;
  name?: string;
  status?: string;
  response?: string;
  url?: string;
  type?: string;
  size: number;
  originFileObj: Blob;
  preview?: string;
  file: string | Blob;
  lastModified: number;
  arrayBuffer: Buffer;
  slice: string;
  stream: Blob;
  text: string;
}
​
export default defineComponent({
  name: 'CropperUpliad',
  components: {
    UploadOutlined,
  },
  props: {
    // 默认值
    portraitId: { type: String, required: true },
    // 是否禁用
    disabled: { type: Boolean },
  },
  emits: ['getId'],
  setup(props, content) {
    const store = useStore();
    // 保存剪裁对象
    let myCropper = ref<null | Cropper>(null);
    // imageDom对象
    const imageRef = ref<HTMLImageElement>();
    // 剪切之后的数据---指的是上传之后接口返回的文件id
    const portraitId = computed(() => {
      console.log(props.portraitId);
      return props.portraitId as string;
    });
​
    const imageUrl2 = ref<string | null>(null);
    watch(
      () => portraitId.value,
      () => {
        imageUrl2.value = portraitId.value;
      },
    );
    // 源数据
    const imageUrl = ref<string>('');
    // 弹窗是否可见
    const visible = ref<boolean>(false);
    //剪切之后的数据
    const afterImage = ref<Blob | null>(null);
    // 文件名称
    const fileName = ref<string | null | undefined>(null);
​
    // copper初始化函数
    const init = () => {
      if (!visible.value) return;
      if (!imageRef.value) return;
      myCropper.value = new Cropper(imageRef.value, {
        viewMode: 1,
        dragMode: 'none',
        initialAspectRatio: 1,
        aspectRatio: 1,
        preview: '.before',
        background: false,
        autoCropArea: 0.6,
        zoomOnWheel: false,
      });
    };
​
    // File转Base64
    const transformFile = (file) => {
      return new Promise((resolve) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => {
          resolve(reader.result as string);
        };
      });
    };
​
    const beforeUpload = async (file: FileItem) => {
      const isJpgOrPng =
        file.type === 'image/jpeg' || file.type === 'image/png';
      if (!isJpgOrPng) {
        message.error('你只能上传JPG/PNG文件!');
      }
      const isLt2M = file.size / 1024 / 1024 < 10;
      if (!isLt2M) {
        message.error('图片大小要小于10MB!');
      }
      if (isJpgOrPng && isLt2M) {
        // 转base64
        const result = await transformFile(file);
        imageUrl.value = result as string;
        fileName.value = file.name;
        visible.value = true;
        // 初始化
        nextTick(() => {
          init();
        });
      }
      // 手动上传
      return new Promise((resolve, reject) => {
        reject();
      });
    };
​
    // 点击上传
    const handelOk = () => {
      // 有些浏览器不支持Canvas.toBlob
      if (!myCropper.value) return;
      if (!HTMLCanvasElement.prototype.toBlob) {
        Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
          value: function (callback, type, quality) {
            var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
              len = binStr.length,
              arr = new Uint8Array(len);
​
            for (var i = 0; i < len; i++) {
              arr[i] = binStr.charCodeAt(i);
            }
​
            callback(new Blob([arr], { type: type || 'image/png' }));
          },
        });
      }
      myCropper.value
        .getCroppedCanvas({
          imageSmoothingQuality: 'high',
        })
        .toBlob(
          function (blob) {
            store
              .dispatch(UploadImageAction, {
                file: blob,
                fileName: fileName.value,
              })
              .then(() => {
                if (store.state.StudentManageStore.result) {
                  visible.value = false;
                  content.emit(
                    'getId',
                    store.state.StudentManageStore.addMediaRes,
                  );
                  imageUrl2.value = store.state.StudentManageStore.addMediaRes;
                }
              });
          },
          'image/jpeg',
          1,
        );
    };
​
    return {
      myCropper,
      beforeUpload,
      visible,
      imageRef,
      transformFile,
      imageUrl,
      imageUrl2,
      afterImage,
      store,
      fileName,
      handelOk,
    };
  },
});
</script>
<style lang="less">
.before-cropper {
  width: 472px;
  height: 472px;
  object-fit: cover;
  // size: 472px;
}
.cropper_upload_madal {
  .ant-modal-body {
    padding: 10px 0;
    .cropper-modal {
      padding: 0 20px;
    }
  }
}
</style>

a-upload 时antd的上传组件

参数解释:接受图片类型、以图片形式展示上传的文件、但是不展示文件列表、上传之前做的操作、是否禁用;

中间部分是禁用时的展示以及上传之后显示最新的图片;

最下面弹窗是通过beforeUpload调起的;

props:portraitid是头像,disabled是是否禁用

beforeUpload函数首先校验是否是图片、然后校验大小、最后把图片转换为base64、nextTick下一帧则调用Cropper将图片文件imageRef传入、return Promise是为了禁止antd组件的自动上传、点击上传先看浏览器有无Canvas.toBlob没有就走封装的方法、有的话就调用getCroppedCanvas().toBlob转blob完成上传

最后通过事件将后端返回的文件id抛出组件外

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值