码农之家

文件上传,给老夫爬

前言

本文主要是对文件上传,下载,导出等常用的功能进行了描述,借用了 ant-design-vue(antdv) 组件库中的 upload 来实现(经观察 element-ui ,view-design 同样适用,属性和方法基本一致)。如果遇到类似文件上传开发需求或不太熟悉文件上传的同学,可以放心食用。

上传文件

需求:文件多选,前端进行文件解析名单,计算总数和重复数等,仅支持xlsx、csv格式,上传后需要能够预览或者下载,名单上传给后端的数据要求是字符串文本,而不是文件,即”chinidenaiyou@qq.com\nchinidenaiyou@qq.com\n”。注意:\n用于换行

.xlsx上传

template部分

<a-upload-dragger
  v-model:fileList="fileList"
  accept=".xlsx, .csv"   // 支持啥后缀写啥后缀,','用来隔开
  :beforeUpload="beforeFileUpload"
  @change="handleChange"
>
    --框内的,爱放啥放啥  --
</a-upload-dragger>

处理表格信息

先借助 xlsx 第三方依赖库把文件转化成数组

// 1.js
const reader = new FileReader();
reader.readAsBinaryString(item.originFileObj);

reader.onload = (e: any) => {
  //  拿到文件的内容
  const data = e.target.result;
  // 以二进制格式读取文件信息,即设置type为binary1const workbook = XLSX.read(data, { type: "binary" });
  【2let sheetData = XLSX.utils
    .sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], {
      header: ["data"],
    })
    .map((t: any) => Object.values(t)[0]);
  resolve(sheetData);
};

【1】workbook 的部分信息

【2】通过 workbook 拿到所有的工作表,即 workbook.Sheets ,但通常我们拿的都是第一张表,即 workbook.SheetNames[0] ,因此通过 XLSX 把表格转化成了 json 对象,再通过 Object.values 把 json 对象转化成数组,方便后续计算长度数量。

注意:此时我们需要设置表头为”data”,如果不进行设置的话,会把第一行默认为表头,即会丢失第一行的数据.

但需要了解的是,onload 是异步任务,因此需要进行 promise 处理。
每个 promise 返回的都是一个数组,因此 promise.all 后的结果是个二维数组,借助 flat 方法进行扁平化。

// 2.js
let promises = fileList
        .map((item: any) => {
          return new Promise((resolve) => {
              ...上述代码,即1.js
              ...
              ...
          })
          
return Promise.all(promises).then((results) => {
    let sumArray = results.flat(); // 合并数组
    return sumArray;
});

获取到了最终的数组结果,就需要根据产品提出的需求进行相应的计算了

const readAndCombineFiles = (fileList: any) => {
    ... 2.js
}

const handleChange = (info) => {
     readAndCombineFiles(info.fileList).then((sumArray: any) => {
          let uniqueArray = [...new Set(sumArray)]; // 进行去重处理
          receiverCnt.value = sumArray.length; // 名单总人数
          uniqueCnt.value = uniqueArray.length; // 去重后的人数
          res = uniqueArray.join('\n') // 文本字符串,即最终的数据
    }
}

.csv 或者 .xlsx 文件下载

产品要求上传后需要有下载或者预览,所以在上传之前给文件加了url,使其能够下载。

const beforeFileUpload = (file: any) => {
  // 创建一个文件类型的blob对象
  const blob = new Blob([file], { type: file.type }); 

  // 创建一个本地临时的下载链接
  file.url = URL.createObjectURL(blob);
  return false;
};

html 文件上传

由于要求单选,所以 multiple 属性得设为 false ,但这只是针对选择文件时的控制(即不能按住 ctrl 进行多选),如果选完上一个再接着选择一个文件,展示的文件列表数据仍然是多个,所以需要对文件列表进行控制。

const handleChange = (info) => {
    // 拿到所有文件列表
    let resFileList = [...info.fileList];
    
    // 拿到最后即最新的文件列表
    resFileList = resFileList.slice(-1);
    
    // 把它赋值给antdv组件中的文件列表值
    fileList.value = resFileList;
    
    // 然后传给后端最新的文件,拿到token(保存的时候需要用到)
    const file = resFileList[0]?.originFileObj;
    postTemplate(file).then((resp: any) => {
      mailTemplate.value = resp.upload_key;
    });
}

然后再把文件以formData的形式传给后端

export function postTemplate(file: File) {
  const formdata = new FormData();
  formdata.append("file", file);
  return instance.post(`/apis/v1/upload/email`, formdata, {
    headers: {
      "Content-Type": "multipart/form-data;charset=UTF-8",
    },
  });
}

.html 预览

.csv 预览

下载文件

有上传的功能就必有下载的需求,点击下载后,后端不返回文件流,而是返回字符串信息,例如 html 文档的字符串,表格数据文本的字符串。我们需要做的就是把字符串信息转成相应的 blob 对象,再利用 a 标签创建一个临时的本地地址提供下载。

<a-form-item label="邮件内容" class="emailDetai-form-item">
    <a @click.prevent="downLoadFile(rowDetail?.mailTemplate, 'template')">
        {{str.substring(str.length - 15)}}
      <DownloadOutlined/>
    </a>
</a-form-item>

.html文件的下载

path: 该路径表示的是存放在阿里云服务器中的文件路径

其中url为路径,tp表示下载的类型
  if (tp === "template") {
    // 获取到相应的字符串信息后进行处理
    dowanload({ path: url, tp }).then((resp: any) => {
      // 将字符串内容保存为Blob对象
      // resp.data为html文档字符串
      const blob = new Blob([resp.data], {
        type: "text/html;charset=utf-8;",
      });

      // 创建一个下载链接
      const link = document.createElement("a");
      link.href = URL.createObjectURL(blob);
      
      // 设置文件名,要求保留文件名保留10位数
      link.download = url.substring(url.length - 15);

      // 将链接添加到DOM中,模拟点击触发下载
      document.body.appendChild(link);
      link.click();

      // 清理临时链接
      document.body.removeChild(link);
    });
  }

.csv文件的下载

else {
    dowanload({ path: url, tp }).then((resp: any) => {
      // 将字符串内容保存为Blob对象
      // resp.data为字符串文本,带换行的那种,即'\n'
      const blob = new Blob([resp.data], {
        type: "text/csv;charset=utf-8;",
      });

     // 后续代码同上,即一样创建a标签进行下载
    });
  }

编辑时文件上传的页面

在页面初始的时候发送请求拿到数据,然后把该数据转化成本地的下载链接,对组件库的列表进行赋值,其中 name 为文件名,status 为上传的状态,url 为下载的地址,也就是主动创建了一个初始化的文件列表。

dowanload({ path: value, tp: "receive" }).then((resp: any) => {
  // 将字符串内容保存为Blob对象
  const blob = new Blob([resp.data], {
    type: "text/csv;charset=utf-8;",
  });

  fileList.value = [
    {
      uid: "-1",
      name: str.substring(str.length - 15),
      status: "done",
      url: URL.createObjectURL(blob),
    },
  ];
});

导出文件

其实和文件下载一样,都是后端直接返回,不过由于是列表数据,我们需要将数据转成表格,再进行 a 标签下载。

const exportToExcel = () => {
      // 定义中文表头1const header = {
        receiver: "接收者",
        campaign_id: "活动ID",
        unsubscribe_time: "取消订阅时间",
      };
      
 【2const ws = XLSX.utils.json_to_sheet([header, ...resData.value], {
        skipHeader: true,
      });
      const wb = XLSX.utils.book_new();
      XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
      const blob = XLSX.write(wb, {
        bookType: "xlsx", // 工作簿类型
        bookSST: true,
        type: "array",  // 工作簿数据类型,即json_to_sheet中的第一个参数
      });

      const link = document.createElement("a");
      link.href = URL.createObjectURL(new Blob([blob]));
      link.download = "取消订阅列表数据.xlsx";
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
}

【1】header 为创建表格的表头,自定义的中文表头代替数据中的 key ,即 key:”自定义的中文表头”,如果不设置表头,会默认的以数据中的 key 为表头,并且表头设置了几个就会展示几列数据。

【2】通过 XLSX 库中的 json_to_sheet 方法,把 json 数据转化成表格,第一个参数 [header, …resData.value] 中数组第一个参数 header 为自定义的表头,而数组第二个参数为后端返回的数据,需要展开。第二个参数中的 skipHeader 为是否跳过默认的表头,如果不设置的话,则会由默认的表头,如下图所示。

book_new 方法为创建一个新的工作簿,book_append_sheet 则是把新的工作簿,数据,以及工作簿的名字添加到 sheet 表格中,然后把有数据的工作簿创建成一个 blob 对象,再利用a标签进行下载。

遇到的问题

取消 andv 组件库中 action 属性默认发送上传请求

由于产品设计要求,因此想单纯的利用了 antdv 的文件上传组件,但发现,不管你设不设置 action 属性,它都会文件上传发送请求,区别在于本地还是你设置的

这里我没有设置 action 属性或者 action 属性为空,它却自发的发送了请求。

关键在于你发送请求还是勉强可以接受,但上传文件后一直报红,就很难受,因为根本不需要上传后的状态

并且,此时使用 antdv 上传组件,会发现 change 事件触发了三次。(上传中、完成、失败都会调用这个函数。)

解决办法: 在文件上传前返回false,停止文件上传的请求

const beforeFileUpload = (file) => {
    return false;
}

原文链接:https://juejin.cn/post/7326578130180522018 作者:吃腻的奶油