vue3支持分片的文件组件

前言

写这个文章主要是最近在学习Vue3,然后想起来自己工作也两年了,平常工作都是直接 Cv,要不就是直接找插件,还有各种UI框架自带的组件,很少再有那个耐心自己去造轮子,所以就趁着学习vue3的间隙,顺便来回顾一下以外造过的轮子,这次的轮子就是一个基础的上传文件,另外暂时不做后端那部分,因为没时间。

明确功能

先来看看这个组件要支持哪些功能

  • 支持自定义样式

  • 支持限制文件类型

  • 支持文件限制校验

  • 支持多选并且可以限制数目

  • 支持文件切片上传

  • 支持定义切片大小

  • 支持自定义上传文件字段

  • 支持传输额外的参数

开始造

初始化一个上传的组件

首先先初始化一个Vue文件,然后将我们需要的可以直接在这个阶段完成的一些简单功能直接先完成,看一下最初的结构:

<template>
  <div>
    <slot name="upload_button">
      <div class="button-container">
        <button class="button" @click="onClick">上传文件</button>
      </div>
    </slot>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  name: 'upload',
  props: {
    // 限制文件类型
    type: {
      type: String,
      default: null,
    },
    // 大小单位mb
    size: {
      type: Number,
      default: null,
    },
    // 切片文件的大小
    chunkSize:{
      type: Number,
      default: null,
    },
    // 默认1,多选文件个数
    multiple: {
      type: Number,
      default: 1,
    },
    // 是否切片上传
    zone: {
      type: Boolean,
      default: false,
    },
    // 额外的参数
    data: {
      type: Object,
      default: () => Object(),
    },
    // 上传文件的字段名称
    upKey: {
      type: String,
      default: 'file',
    },
  },
  methods: {
    // 上传文件
    onClick() {
      const input = document.createElement('input');
      input.type = 'file';
      if (this.multiple > 1) input.multiple = true; // 大于1的时候再开启,主要是用户体验的问题
      if (this.type) input.accept = this.type; // 需要限制类型的时候添加限制类型
      input.onchange = this.onChange;
      input.click();
    },
    // 选择到的文件
    onChange(event: any) {
      const e = event.target;
      const { files } = e; // 拿到所有的文件
    },
  },
});
</script>
<style scoped lang="scss">...</style>

那么现在,这个组件已经支持功能有:

  • 支持自定义样式
  • 支持限制文件类型

这就完成了最初的初始化,接下来我们就要来添加这个组件的功能,让它更能支撑起我们的需求了。

文件限制校验

这里有一个小细节,上面的代码中有一段const { files } = e;拿到所有的文件,所以我们需要循环对文件进行校验,那为什么要这么做了,因为后面我们需要做多选的功能,所以就没有必要再去判断是否拿单独的一个文件了,接下来在methods,中添加一个validate函数,这个函数要支持 验证文件大小,验证选择文件是否超出的校验功能;然后将这个函数添加到onChange中就可以了。

// 不符合就返回直接返回错误新消息,不然就是false
validate(files: any) {
  let result: any = false;
  // 限制文件的大小
  const size = this.size * 1024;

  // 多选时判断文件是否超出限制
  if (files.length > this.multiple) {
    result = `只能选择${this.multiple}个文件`;
    return result;
  }

  // 校验文件
  for (let i = 0; i < files.length; i++) {
    if (this.size && size <= files[i].size) {
      result = `文件大小不能超过${this.size}MB!`;
      break;
    }
  }

  return result;
},

最后在onChange中添加一个普通上传的功能,先定义一个用来上传文件的函数,然后将其添加在onChange的末尾就可以了,最后的代码如下面


onChange(event: any) {
  const e = event.target;
  const { files } = e; // 拿到所有的文件

  // 校验文件是否符合规格
  const validate = this.validate(files);
  if (validate) return alert(validate);

  // 普通文件上传
  files.forEach((item) => {
    const data = { [this.upKey]: item };
    this.upload(data);
  });

  return true;
},


/**
* @description 上传文件
* @param {object} file
*/
async upload(file: any) {
  // 合并额外参数跟数据
  file = Object.assign(file, this.data);
  const formData: any = new FormData();
  // 将数据添加到表单当中
  for (const fileKey in file) {
    if (file?.hasOwnProperty(fileKey)) {
      formData.append(fileKey, file[fileKey]);
    }
  }

  // 发送到后端
  // request.post('/api').then((result) => {
  //   console.log(result);
  // });
},

这样,最基础的文件上传就已经完成了,那我们继续完善接下来的功能

最后就是切片上传啦

这里完成最后的四个工作

  • 支持文件切片上传
  • 支持定义切片大小
  • 支持自定义上传文件字段
  • 支持传输额外的参数
    那么这里需要了解一下,其实文件切片是怎么做的呢?所谓的文件切片,其实就是数据切割,因为在计算机里面,其实所有的东西都是数据而已,所以把File类型的文件转换成数据进行切割就可以了,切割的大小的话,其实就是数据的大小。
    那么大概了解了一下文件切割的操作之后,接下来就是明确切割之后需要传输的字段有什么了。
  • file:保存分片的数据
  • filename:文件名称
  • chunkKey:切片标示
    file跟filename就不用说了,chunkKey的切片标示,主要是为了做断点续传的功能,因为用户的网络不一定什么时候就会断掉,而且要是用户传输到一半就关闭了,那么这个文件就会有一部分传输不过去,所有需要标示当前是第几块切片,后端那边才可以继续拼接。
    在这里我们使用zone来决定是否使用切片上传这个功能,先添加一个专门做分片上传的函数。
// 分片上传
zoneUpload(files) {
  const chunks: any = [];
  // 多个文件上传需要 {file,filename,chunkKey}
  files.forEach((item) => {
    // 将文件切片
    const chunk = this.zoneFile(item);
    chunks.push(chunk);
  });

  // 开始上传
  chunks.forEach((chunk) => {
    chunk.forEach(async (item, index) => {
      item.chunkKey = index;// 添加文件下标
      item.endKey = item.length - 1;// 添加结束下标
      await this.upload(item);
    });
  });
},

上面的代码有zoneFile切割函数,完善这个函数

// 切片函数将文件转换为blob,并且切开
zoneFile(file: any) {
  const files: any = [];
  const filename = Base64.encode(file.name);
  // 每个切片大小
  const zoneSize: number = this.chunkSize * 1024 * 1024; 
  const { size } = file;
  // 根据文件大小切片总数
  let totalPieces: number = Math.ceil(size / zoneSize); 
  let index = 0; // 开始下标
  const chunk: any = { filename };
  while (totalPieces) {
    chunk[this.upKey] = file.slice(index, index + zoneSize);
    files.push(chunk);
    totalPieces -= 1;
    index += zoneSize;
  }
  return files;
},

然后修改一下onChange函数,将切片上传的功能也添加上去,就完成我们本次的工作了,最后onChange函数的代码如下:

onChange(event: any) {
 const e = event.target;
 const { files } = e; // 拿到所有的文件

 // 校验文件是否符合规格
 const validate = this.validate(files);
 if (validate) return alert(validate);

 // 需要切片的时候
 if (this.zone) {
   this.zoneUpload(files);
   return false;
 }

 // 普通文件上传
 files.forEach((item) => {
   const data = { [this.upKey]: item };
   this.upload(data);
 });

 return true;
},

到这里,一个最基础版本的上传文件的功能的完成了,具体的源代码github
如果有需要改进的地方,欢迎提出,毕竟有交流才有进步,哈哈哈

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

推荐阅读更多精彩内容