vue3 封装文件上传组件

由于工作需要,项目中经常需要文件上传这个功能,根据业务的需求,使用vue3 简单封装通用型组件。

作用:主要是用来上传图片的一个通用型组件,当然可以上传文件。支持校验 尺寸 , 像素, 文件大小,可以多文件上传。

在下面贴上组件代码:

<template>
  <div class="upload-button">
    <template v-if="multiple">
      <input
        ref="input"
        type="file"
        multiple="multiple"
        :accept="acceptType"
        @change="handleChange"
      />
    </template>
    <template v-else>
      <input ref="input" type="file" :accept="acceptType" @change="handleChange" />
    </template>
  </div>
</template>

<script>
import { reactive, ref } from '@vue/composition-api';
import { Message } from 'element-ui';
import { post } from 'axios'; // 请求也是的,建议直接使用项目中已经封装好的请求函数来调接口。我这是自己搭建的练手的项目,就没去封装请求。
import commonApi from '@/api/common';  // 这个是接口地址,根据自己需求改。
const defaultType = [
  'bmp',
  'jpg',
  'png',
  'tif',
  'gif',
  'pcx',
  'tga',
  'exif',
  'fpx',
  'svg',
  'psd',
  'cdr',
  'pcd',
  'dxf',
  'ufo',
  'eps',
  'ai',
  'raw',
  'WMF',
  'webp',
];
export default {
  props: {
    multiple: {
      // 是否多选
      type: Boolean,
      default: false,
    },
    acceptType: {
      // 文件类型
      type: String,
      default: 'image/*',
    },
    type: {
      // 限制上传文件类型
      type: String,
      default: undefined,
    },
    size: {
      // 限制上传文件尺寸
      type: String,
      default: undefined,
    },
    bulk: {
      // 限制上传文件大小
      type: Number,
      default: 0,
    },
  },
  setup(props, c) {
    const { bulk, type, size, multiple } = reactive(props);
    const uploading = ref(false);
    const input = ref(null);
    const url = ref(undefined);
    let uploadFiles = reactive([]);

    const inputClick = () => {
      if (uploading.value) {
        return Message.warning('请等待前面的文件完成上传!');
      }
      return input.value.click();
    };

    const handleChange = ({ target }) => {
      if (multiple) uploadFiles = [...target.files];

      uploadFile(target.files[0]);
      target.value = '';
    };

    const uploadFile = async (file) => {
      try {
        if (type) await detectorType(file);
        if (bulk) await detectorBulk(file);
        if (size) await detectorSize(file);

        uploading.value = true;
        const fd = new FormData();
        url.value = commonApi.addUserEmoticon;
        fd.append('file', file);  // 可以通过这种形式,来传递其他项目中要传递的参数。
        const data = await post(url.value, fd, {
          headers: {
            'content-type': 'multipart/form-data',
          },
        });
        
        c.emit('url', data); // 将请求到的数据 抛出去
        uploaded(false);
      } catch (err) {
        uploaded(err);
      }
    };

    const detectorType = (file) => {
      return new Promise((resolve, reject) => {
        const sizeList = type.split(',');
        const fileSize = file.name.split('.');
        const fileExtension = fileSize[fileSize.length - 1].toLowerCase();
        if (!sizeList.includes(fileExtension)) {
          if (!defaultType.includes(fileExtension)) {
            Message.error('文件类型不对!');
          } else {
            Message.error('图片类型不对!');
          }
          reject(new Error());
        } else {
          resolve(true);
        }
      });
    };

    const detectorBulk = (file) => {
      return new Promise((resolve, reject) => {
        const fileSize = file.size / 1024 / 1024;
        if (fileSize > bulk) {
          Message.error(`大小超出${bulk}M`);
          reject(new Error());
        } else {
          resolve(true);
        }
      });
    };

    const detectorSize = (file) => {
      return new Promise((resolve, reject) => {
        const image = new Image();
        const URL = window.URL || window.webkitURL;

        image.onload = () => {
          const sizes = size.split(',');
          if (image.width === Number(sizes[0]) && image.height === Number(sizes[1])) {
            resolve(true);
          } else {
            Message.error({
              type: 'error',
              message: `请上传尺寸为 ${sizes.join(' x ')} 的图片`,
            });
            reject(new Error());
          }
        };
        image.src = URL.createObjectURL(file);
      });
    };

    const uploaded = (err, state = false) => {
      uploading.value = false;
      if (!err) {
        if (multiple && uploadFiles.length > 1) {
          uploadFiles = uploadFiles.slice(1, uploadFiles.length - 1);
          uploadFile(uploadFiles[0]);
        } else {
          Message.success('上传成功!');
        }
      }
    };

    return {
      input,
      inputClick,
      handleChange,
    };
  },
};
</script>

<style scoped lang="less"></style>

如何使用:
首先要修改组件里面的 接口api, 和请求方式函数。

  1. 直接引入这个组件
  2. 给一个点击事件,upload.value.inputClick(); 触发组件里面的函数
  3. 然后监听 组件抛出的 emit 事件,会传递出来url

下面是我写的小demo,随便写写,不是很严谨。

<template>
  <div>
    <div>
      <span v-if="urlList">
        <img
          v-for="(item, index) in urlList"
          :key="item.url"
          class="img"
          :src="item.url"
          fit="contain"
          style="margin-right: 10px"
          @click="getImg(index)"
        />
      </span>
      <img
        v-if="(!multiple && !urlList.length) || (multiple && urlList)"
        class="img"
        :src="`${require('@/assets/notice/unselected.png')}`"
        fit="contain"
        @click="getImg"
      />
    </div>
    <!-- <UploadButton
      ref="upload"
      style="display: none"
      type="jpg,jpeg,png"
      size="4087,2711"
      :bulk="2"
      :multiple="multiple"
      @inputpp="channelParameter"
    /> -->
    <upload
      ref="upload"
      style="display: none"
      type="jpg,jpeg,png"
      :bulk="bulk"
      size="4087,2710"
      :multiple="multiple"
      @url="channelParameter"
    />
    <button @click="clickButton">点击</button>
  </div>
</template>

<script>
import { ref, reactive } from '@vue/composition-api';
// import UploadButton from './SingleButton.vue';
import upload from './upload'; // 直接引入这个组件
export default {
  components: {
    // UploadButton,
    upload,
  },
  setup() {
    const upload = ref(null);
    const multiple = ref(false);
    const urlList = reactive([]);
    const bulk = ref(2);

    const getImg = () => {
      console.log(upload.value, 'pppupload.value');
      upload.value.inputClick();
    };
    const channelParameter = (value) => {
      if (!value.data.data) return;
      console.log(value, 'ppp');
      if (multiple) {
        //  if(index===0 || index){
        //    urlList.splice(index,)
        //  }
        urlList.push({ url: value.data.data.emoticonPath });
      } else {
        urlList.push({ url: value.data.data.emoticonPath });
      }

      console.log(urlList, 'urlList');
    };

    const clickButton = () => {
      bulk.value += 1;
      console.log(bulk.value, 'bulk.value');
    };

    return {
      // 变量
      upload,
      urlList,
      multiple,
      bulk,

      // 事件
      channelParameter,
      getImg,
      clickButton,
    };
  },
};
</script>

<style scoped lang="less">
.img {
  width: 80px;
  height: 80px;
  vertical-align: top;
  margin-left: 12px;
  border-radius: 4px;
}
</style>

你可能感兴趣的:(vue3,js,html,vue,js,javascript,html5,前端)