带截图的图片上传组件

适用于vue3+ts+ant-design-vue开发的项目

1.组件文件

<template>
  <div class="d-inline d-flex">
    <div class="d-inline upload-image d-flex" v-if="fileList">
      <span
        class="d-inline img-li d-relative mr20"
        v-for="(item, index) in fileList"
      >
        <img
          :src="IMGURL + item.filePath"
          alt=""
          @click="handlePreview(item.filePath)"
        />
        <CloseCircleOutlined
          v-if="!props.disabled"
          @click="remove(item, index)"
          class="closeImg"
        />
      </span>
    </div>
    <a-upload
      :action="uploadUrl"
      list-type="picture-card"
      @change="handleChange"
      :before-upload="beforeUpload"
      :disabled="props.disabled"
    >
      <div v-if="fileList.length < length">
        <plus-outlined />
      </div>
    </a-upload>

    <a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
      <img alt="example" style="width: 100%" :src="IMGURL + previewImage" />
    </a-modal>
    <a-modal
      :visible="cropperVisible"
      title="上传图片裁剪"
      okText="确定"
      cancelText="取消"
      :width="
        (options.autoCropWidth > 400 ? options.autoCropWidth : 400) + 80 + 'px'
      "
      @cancel="handleCancel1"
      @ok="cropperSuccess"
    >
      <!-- 已上传图片 -->
      <div
        v-show="options.img"
        class="avatar-crop"
        :style="{
          width: options.autoCropWidth + 'px',
          height: options.autoCropHeight + 'px'
        }"
      >
        <VueCropper
          class="crop-box"
          ref="cropper"
          id="capture"
          :img="options.img"
          :autoCrop="options.autoCrop"
          :fixedBox="options.fixedBox"
          :canMoveBox="options.canMoveBox"
          :autoCropWidth="options.autoCropWidth"
          :autoCropHeight="options.autoCropHeight"
          :centerBox="options.centerBox"
          :fixed="options.fixed"
          :canMove="options.canMove"
          :canScale="options.canScale"
          :outputType="options.outputType"
          :original="true"
        ></VueCropper>
      </div>
      <div class="d-flex-jc-c mt25 wfull d-wrap">
        <a-button class="mr20 mb5 mt5" type="primary" ghost @click="noCutting"
          >不裁剪直接上传</a-button
        >
        <a-button class="mr20 mb5 mt5" type="primary" ghost @click="rotateLeft"
          ><RotateLeftOutlined />左旋转</a-button
        >
        <a-button class="mr20 mb5 mt5" type="primary" ghost @click="rotateRight"
          ><RotateRightOutlined />右旋转</a-button
        >
        <a-button
          class="mr20 mb5 mt5"
          type="primary"
          ghost
          @click="changeScale(1)"
          ><ZoomInOutlined />放大</a-button
        >
        <a-button class="mb5 mt5" type="primary" ghost @click="changeScale(-1)"
          ><ZoomOutOutlined />缩小</a-button
        >
      </div>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, reactive } from "vue";
import { message } from "ant-design-vue";
import { publicMethods } from "@/utils/publicMethods"; // 数据处理方法
import { UploadApi, uploadScenicSpot, UploadApiNews } from "@/api/login"; // 接口
import { VueCropper } from "vue-cropper";  // 截图插件,需提前安装

const { getBase64, dataURLtoFile } = publicMethods();

const props = withDefaults(
  defineProps<{ // 调用是要传的参数
    fileList: any; // 图片数据
    length: number; // 最大上传数量
    type: number; // 图片类型
    width: number; // 截图框的宽度
    height: number; // 截图框的高度
    cutting: boolean; // 是否截图 默认截图
    disabled: boolean; // 是否禁止使用
    urlType: number; // 图片上传地址类型,根据类型上传到不同的地址
  }>(), 
  { // 默认参数
    width: 480,
    height: 320,
    cutting: true,
    disabled: false,
    urlType: 1
  }
);
let uploadUrl = UploadApi;
if (props.urlType == 2) {
  uploadUrl = UploadApiNews;
}
const IMGURL: any = process.env.UPLOAD_IMGURL;

// 裁剪
const options: any = reactive({
  img: "", // 原图文件
  outputSize: 0.2, // 裁剪生成图片的质量(可选 0.1-1)
  autoCrop: true, // 默认生成截图框
  fixedBox: true, // 固定截图框大小
  canMoveBox: false, // 截图框可以拖动
  autoCropWidth: "", // 截图框宽度
  autoCropHeight: "", // 截图框高度
  fixed: false, // 截图框宽高固定比例
  fixedNumber: [4, 3], // 截图框的宽高比例
  centerBox: false, // 截图框被限制在图片里面
  canMove: true, // 上传图片不允许拖动
  canScale: true, // 上传图片不允许滚轮缩放
  outputType: "jpeg" // 裁剪生成图片的格式(jpeg || png || webp)
});

const cropper = ref<any>(null);

const infoFileList = ref<any>();

const previewVisible = ref<boolean>(false);
const cropperVisible = ref<boolean>(false);
const previewImage = ref<string | undefined>("");
const nweSrc = ref<any>("");
const fileData = ref<any>("");

// 大图展示取消按钮
const handleCancel = (type: number) => {
  previewVisible.value = false;
};

// 裁剪取消按钮
const handleCancel1 = (type: number) => {
  cropperVisible.value = false;
};

// 上传前操作
const beforeUpload = async (file: any) => {
  fileData.value = file;
  if (props.cutting) {
    let img = await getBase64(file);
    options.img = img;
    cropperVisible.value = true;
  }
  return new Promise((resolve, reject) => {
    if (props.cutting) {
      return reject(true);
    } else {
      return resolve(true);
    }
  });
};

// 向左旋转
const rotateLeft = () => {
  cropper.value.rotateLeft();
};
// 向右旋转
const rotateRight = () => {
  cropper.value.rotateRight();
};
// 图片缩放
const changeScale = (num: any) => {
  num = num || 1;
  cropper.value.changeScale(num);
};

// 裁剪完成
const cropperSuccess = () => {
  const el: any = document.querySelector("#capture");
  cropper.value.getCropData((data: any) => {
    nweSrc.value = data;
    let file: any = dataURLtoFile(data);
    requestUpload(file);
  });
  cropperVisible.value = false;
};

// 不裁剪
const noCutting = () => {
  props.cutting = false;
  requestUpload(fileData.value);
  cropperVisible.value = false;
};

// 上传图片
const requestUpload = (file: any) => {
  const param = new FormData();
  param.append("file", file);

  uploadScenicSpot(param, props.urlType).then((res: any) => {
    if (res.code === 200) {
      let info = {
        file: "",
        fileList: [
          {
            response: {
              code: 200,
              data: res.data
            }
          }
        ],
        msg: "请求成功"
      };
      emit("uploadImgChange", {
        type: props.type,
        data: info.fileList
      });
      infoFileList.value = info;
    }
  });
};

// 选择要放大的图片
const handlePreview = (file: any) => {
  previewImage.value = file;
  previewVisible.value = true;
};

const emit = defineEmits(["uploadImgChange"]);
const handleChange = (info: any) => {
  if (info.file.status === "uploading") {
    return;
  }
  if (info.file.status === "done") {
    emit("uploadImgChange", {
      type: props.type,
      data: info.fileList
    });
    infoFileList.value = info;
    
  }
  if (info.file.status === "error") {
    message.error("upload error");
  }
};

const remove = (item: any, index: number) => {
  const ind = props.fileList.findIndex(
    (file: any) => file.filePath === item.filePath
  );
  props.fileList.splice(ind, 1);
  if (infoFileList.value) {
    infoFileList.value.fileList.splice(index, 1);
  }
};

onMounted(() => {
  options.autoCropWidth = props.width;
  options.autoCropHeight = props.height;
});
</script>

<style scoped lang="less">
/* you can make up upload button and sample style by using stylesheets */
.avatar-uploader > .ant-upload {
  width: 128px;
  height: 128px;
}
.ant-upload-select-picture-card i {
  font-size: 32px;
  color: #999;
}

.ant-upload-select-picture-card .ant-upload-text {
  margin-top: 8px;
  color: #666;
}

/deep/ .ant-upload-list-picture-card {
  display: none !important;
}

.upload-image {
  .img-li {
    img {
      width: 104px;
      height: 104px;
      margin-right: 8px;
      margin-bottom: 8px;
    }
    .closeImg {
      position: absolute;
      top: -5px;
      right: 1px;
      color: #ff3aa5;
      background-color: #efefef;
      border-radius: 50%;
      font-size: 16px;
    }
  }
}

.avatar-crop {
  margin: 0px auto;
}
</style>

2.接口文件

//上传图片 给ant-design-vue的上传组件提供的地址
export const UploadApi =
  process.env.UPLOAD +
  request.AxiosLink.UPLOAD_IMG_API +
  "/sysUserUrl/upload/scenic";

//上传图片 给ant-design-vue的上传组件提供的地址 不同的图片上传到相对应的地址
export const UploadApiNews =
  process.env.UPLOAD +
  request.AxiosLink.UPLOAD_IMG_API +
  "/sysUserUrl/upload/news";


//上传图片 手动上传时使用
export const uploadScenicSpot = (params: any, type: any) => {
  let file = "scenicSpot";
  if (type == 2) {
    file = "news";
  }
  return axios
    .post(
      `${request.AxiosLink.UPLOAD_IMG_API}/sysUserUrl/upload/` + file,
      params
    )
    .then((res) => res);
};

3.数据处理方法文件

import { ref } from "vue";
import DICLIST from "@/utils/dictionary-config.js"; // 状态字典文件,该组件中是用不到

export const publicMethods = () => {
  const getBase64 = (file: File) => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result);
      reader.onerror = (error) => reject(error);
    });
  };

  // base 64 转成二进制文件流
  const dataURLtoFile = (urlData: any) => {
    if (typeof urlData != "string") {
      return;
    }
    var arr: any = urlData.split(",");
    var type = arr[0].match(/:(.*?);/)[1];
    var fileExt = type.split("/")[1];
    var bstr = atob(arr[1]);
    var n = bstr.length;
    var u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], "filename." + fileExt, {
      type: type
    });
  };

  // const getBase64Image = (img: any) => {
  //   var canvas = document.createElement("canvas");
  //   canvas.width = img.width;
  //   canvas.height = img.height;
  //   let ctx: any = canvas.getContext("2d");
  //   ctx.drawImage(img, 0, 0, img.width, img.height);
  //   var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
  //   var dataURL = canvas.toDataURL("image/" + ext);
  //   return dataURL;
  // };
  const getBase64Image = (Img: any) => {
    let dataURL = "";
    const canvas = document.createElement("canvas");
    const { width } = Img;
    const { height } = Img;
    canvas.width = width;
    canvas.height = height;
    canvas.getContext("2d")!.drawImage(Img, 0, 0, width, height);
    dataURL = canvas.toDataURL("image/jpeg"); //dataURL 图片base64 类型
    return dataURL;
  };

  // 根据状态数值和类型返回状态文字描述
  const dictionary = (status: number, type: string) => {
    let statusName = "";
    DICLIST.forEach((item: any) => {
      if (item.key === type) {
        const dic = item.data.find((value: any) => value.key === status);
        statusName = dic ? dic.value : "";
      } else {
        statusName = "";
      }
    });
    return statusName;
  };

  interface statusType {
    key: number;
    value: string;
    color: string;
    bgColor: string;
  }

  // 根据状态数值和类型返回状态所有值
  const dictionaryData = (status: number, type: string) => {
    let statusName = ref<statusType>();
    DICLIST.forEach((item: any) => {
      if (item.key === type) {
        const dic = item.data.find((value: any) => value.key === status);
        statusName = dic;
      }
    });
    return statusName;
  };

  const MapLoader = () => {
    return new Promise((resolve, reject) => {
      if (window.AMap) {
        resolve(window.AMap);
      }
      window.initAMap = () => {
        resolve(window.AMap);
      };
    });
  };

  // 排序
  const compare = (prop: any) => {
    return (obj1: any, obj2: any) => {
      var val1 = obj1[prop];
      var val2 = obj2[prop];
      if (val1 < val2) {
        return -1;
      } else if (val1 > val2) {
        return 1;
      } else {
        return 0;
      }
    };
  };

  return {
    getBase64,
    dataURLtoFile,
    getBase64Image,
    dictionary,
    dictionaryData,
    MapLoader,
    compare
  };
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值