封装图片/文件上传组件

257 阅读5分钟

封装图片/文件上传组件

element-ui vue2 图片上传

前言

​ 刚过端午,突然想起来这个月还没更新文章,所以发一篇😆。在后台管理系统中,图片或者文件上传用的还是蛮多的,之前的时候每次都是从element-ui中或者之前的项目中copy一大坨代码过来,但是代码的重复度还是蛮高的,无非就是改改路径之类的,所以对图片和文件上传做了封装。样式还是沿用的element-ui的照片墙和列表文件,并没有做太大更改,在功能上除了常规的增删之外还做了图片回显,点击预览,超出隐藏等功能。需要说明的一点就是==本组件图片或文件上传均是手动上传(就是选择图片或文件后不会立即上传,而是手动调用上传函数进行上传)==,因为做过的业务中大多数都是手动上传,所以是对这种类型的进行封装。

效果

图片上传的效果

图片模式.gif

文件上传的效果

文件模式.gif

1. 在模板基础上修改及常规增删处理
<template>
  <div class="base-file-upload">
    <el-upload 
      :class="{ hide: !addButtonShow }" 
      ref="upload" 
      action="#" 
      :list-type="uploadOptions.listType" 
      multiple
      :auto-upload="false"
      :file-list="fileLists" 
      :limit="limit" 
      :accept="uploadOptions.accept"
      :on-remove="handleFileRemove" 
      :on-exceed="onExceed"
      :on-change="handleChange">
      <!-- 上传按钮 -->
      <i v-if="fileType === 'image'" slot="default" class="el-icon-plus"></i>
      <el-button v-else size="small" type="primary">点击上传</el-button>
      <div v-if="fileType !== 'image'" slot="tip" class="upload-tip el-upload__tip">上传文件大小不超过{{ fileSize }}MB</div>
      <!-- 上传后文件展示及工具,仅图片会展示 -->
      <div v-if="fileType === 'image'" slot="file" slot-scope="{ file }">
        <!-- 上传后图片 -->
        <el-image class="upload-img" :src="file.url" fit="cover"></el-image>
        <!-- 工具栏 -->
        <span v-if="file.url" class="el-upload-list__item-actions">
          <!-- 查看 -->
          <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
            <i class="el-icon-zoom-in"></i>
          </span>
          <!-- 删除 -->
          <span class="el-upload-list__item-delete" @click="handleRemove(file)">
            <i class="el-icon-delete"></i>
          </span>
        </span>
      </div>
    </el-upload>
    <!-- 查看图片 -->
    <el-dialog title="查看图片" :visible.sync="seeImage" append-to-body width="800">
      <img width="100%" :src="seeImageUrl" alt="" />
    </el-dialog>
  </div>
</template>

图片上传和文件上传要区分,所以删除用了两个,文件删除是用on-remove的绑定的函数,图片删除则是通过点击删除icon来实现的。

图片还有一个点击查看图片的icon。

删除函数

    // 点击删除
    handleRemove(file) {
      this.removeFile(file);
    },
    handleFileRemove(file) {
      if(this.fileType !== 'image') {
        this.removeFile(file);
      }
    },
    // 对fileList移除文件
    removeFile(file) {
      const uid = file.uid;
      if(uid) {
        const findFileIndex = this.fileLists.findIndex(item => {
          return item.uid === uid;
        })
        this.fileLists.splice(findFileIndex, 1)
      }
    },
2. 上传类型、文件大小及数量超出的处理

大小限制放在on-change绑定的函数中。

    // 状态改变触发(将文件的校验放到这里进行处理)
    handleChange(file, files) {
      // console.log('状态改变触发file:', file);
      if(file.status === 'ready') {
        this.fileLists.push(file);  // 先放入再删除
      }
      // 只做大小检验
      const uploadError = {
        type: "",
        msg: "",
      };
      const fileSize = (file.size / 1024 / 1024).toFixed(1); // 获取图片大小(MB)
      // 图片大小出错处理
      if (Number(fileSize) > this.fileSize) {
        uploadError.type = "size error";
        uploadError.msg =
          `图片大小超出限制!上传图片大小不能超过` + this.fileSize + "MB!";
        if (this.isShowTip) {
          this.$message({
            message: uploadError.msg,
            type: 'warning'
          });
        }
        this.$emit("upload-error", uploadError);
        this.removeFile(file);
        return
      }
      // console.log('状态改变触发files:', files);
    }

这里选择文件后先将图片/文件放入文件列表中,如果大小超出限制,再将文件从文件列表中删除。

文件超出则是在on-exceed绑定的函数中。

    // 超出个数限制
    // 如果一次性上传超出数量限制,不会触发before-upload,on-change钩子
    // files为本次超出上传的所有文件
    onExceed(files) {
      console.log('超出', files);
      const uploadError = {
        type: "",
        msg: "",
      };
      uploadError.type = "limit error";
      uploadError.msg = `超出数量限制,最多接受文件数为:` + this.limit;
      if (this.isShowTip) {
        this.$message({
          message: uploadError.msg,
          type: 'warning'
        });
      }
      this.$emit("upload-error", uploadError);
    }

原本是想实现超出的话,先将文件填满再将多余的部分忽略并提示,但是在测试的时候发现==文件超出只会走on-exceed绑定的函数,并不会走on-change绑定的函数,所以只做了超出提示==。

超出隐藏则是通过计算属性,比较限制文件数和文件列表的长度,如果超出就隐藏图片上传的“+”号。==文件上传模式未做超出隐藏==。

文件类型这里提供了两个props,一个是fileType,只接受三个参数image video file用来快速定义类型;一个是supportType,跟el-uploadaccept一样。

  computed: {
  	// 类型限制
    uploadOptions() {
      const options = {};
      if(this.fileType === 'image') {
        options.listType = 'picture-card';
        options.accept = 'image/*'
      } else if(this.fileType === 'video') {
        options.listType = 'text';
        options.accept = 'video/*'
      } else {
        options.listType = 'text';
        options.accept = ''
      }
      if(this.supportType) {
        options.accept = this.supportType
      }
      return options
    },
    // 超出隐藏
    addButtonShow() {
      let length = this.fileLists.length;
      return length < this.limit
    }
  },

可以同时传入fileType和supportType,一个用来决定上传样式,一个用来决定接受的文件类型。

3. 回显文件的处理

回显文件是指接受已经上传过的文件,通过props preImgs来接受一个由文件路径组成的数组。

  watch: {
    preImgs: {
      immediate: true,
      handler(newValue, oldValue) {
        this.fileLists = [];
        const preFileLists = newValue.map(url => {
          const random = Math.floor(Math.random () * 900) + 100;
          const time = Date.now();
          const uid = Number(random + '' + time);
          const fileName = url.slice(url.lastIndexOf('/') + 1);
          return {
            uid: uid,
            name: fileName,
            url,
          }
        })
        this.fileLists = this.fileLists.concat(preFileLists);
      },
    },
  },

uid采用时间戳加随机数的组合,文件名则是截取路径最后一个斜杠后的内容。

4. 手动上传处理

这里只说明一下我这里是怎么处理的:循环文件列表,将文件对象通过FormData包装后进行上传,最后返回上传后返回的文件路径。因为每个公司在图片处理方面的业务也不一样,所以仅供参考。

    // 上传
    // 需要填写上传的url及响应后的地址字段
    // 返回值为url组成的数组
    async mySubmit(httpUrl, httpRes) {
      console.log('上传');
      // console.log(this.fileLists);
      let result = [];
      for(let file of this.fileLists) {
        const rawFile = file.raw;  // file对象
        if(rawFile) {  // 本地图片
          const form = new FormData();
          form.append("file", rawFile);
          let timeout = parseFloat((file.size / 1024 / 1024).toFixed(2)) * 5000;
          if (timeout < 10000) {
            // 默认时长10000
            timeout = 10000;
          }
          try {
            let res = await this.myRequestApi(httpUrl, form, timeout);
            result.push(res[httpRes])
          } catch (error) {
            break
          }
        } else {  // 网络图片
          result.push(file.url)
        }
      }
      return result
    },
    myRequestApi(url, form, timeout) {},
使用示例
<template>
  <div class="upload-form">
    <el-form ref="form" :model="form" label-width="80px">
      <el-form-item label="上传图片">
        <ImgUpload ref="imgUpload" :pre-imgs="pres" />
      </el-form-item>
    </el-form>
    <el-button type="primary" @click="getUploadRes">提交表单</el-button>
  </div>
</template>

<script>
import ImgUpload from "@/components/BaseFileUpload";

export default {
  name: "uploadForm",
  components: { ImgUpload },
  data() {
    return {
      form: {},
      pres: []
    };
  },
  mounted() {
    // 图片回显
    this.pres = ['https://element.eleme.cn/static/guide.0a8462c.png', 'https://element.eleme.cn/static/guide.0a8462c.png']
  },
  methods: {
    async getUploadRes() {
      // 校验
      const files = this.$refs.imgUpload.getFileList();
      if (files.length < 1) {
        console.log("请至少上传一张图片");
        return;
      }
      // 上传
      try {
        const files1 = await this.$refs.imgUpload.mySubmit("url", "fileName");
        this.form.frontPic = files1.join(",");
      } catch (error) {
        console.log("图片上传失败");
      }
    },
  },
};
</script>

<style lang='scss' scoped>
.upload-form {
  width: 500px;
}
</style>
组件文档及源码地址
  • 组件文档
Attribute
参数说明类型可选值默认值
limit最大允许上传个数Number3
fileSize文件大小限制(单位MB)Number10
fileType上传文件类型Stringimage/video/fileimage
supportType上传具体类型String
isShowTip是否采用消息提示Booleantrue
preImgs预览图片数组Array
  1. fileType有三个可选值,image是照片墙的形式,其他为默认形式,image上传文件类型为image/*,video的上传文件类型为video/*,file没有上传形式限制。
  2. supportType用来明确具体的文件类型,值的形式参考el-uploadaccept参数。
  3. isShowTip会默认在图片超出大小,数量超出限制的时候进行提示,安装了element ui即可使用。
  4. preImgs类型为由单个字符串组成的数组,例如:[' https://element.eleme.cn/static/guide.0a8462c.png',' https://element.eleme.cn/static/guide.0a8462c.png']
Methods
方法名说明参数
getFileList获取当前文件列表
mySubmit手动上传(async/await函数)(httpUrl:上传的地址,httpRes:返回后的字段)
reset清空上传列表

mySubmit返回值为由返回字段内容(上传后的url)组成的数组。

Events
事件名称说明回调参数
upload-error上传失败时触发的回调提示内容

提示内容为对象格式,格式为{type: "", msg: ""},其中type为类型,msg为出错说明。

  • 示例源码

源码地址:

示例代码 - 码云 - 开源中国 (gitee.com)

相关链接:

组件 | Element | upload