基础知识之上传与下载

缘起

上传和下载是两个经典场景,做项目的时候遇到了这两种情况。

上传

设置上传按钮

const upoadImgCom = () => {
  return (
    <>
      <label className={styles["upload-button"]} htmlFor="fileInputCompanyLogo">
        +
      </label>
      <input
        id="fileInputCompanyLogo"
        accept={acceptFileTypeList}
        onChange={uploadFileAction}
        type="file"
        multiple
      />
    </>
  );
};

相应样式文件

.upload-button {
  color: #c3c3c3;
  background: #f2f2f2;
  width: 100px;
  display: flex;
  height: 40px;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  &:hover {
    color: #008cff;
    border: 1px solid #008cff;
  }
}
input {
  display: none;
}

获取上传文件

注意这里获取到的uploadFile.files是类数组对象,不能用数组方法。[...uploadFileEle.files]转化为数组类型后,判断是否通过上传校验(大小、文件类型等)

const uploadFileAction = (e) => {
  const uploadFileEle = document.querySelector("#fileInputCompanyLogo");
  if (!uploadFileEle.files.length) return;
  // 校验上传文件是否符合准入条件
  const files = [...uploadFileEle.files];
  const isValid = beforeUpload(files, fileSizeLimit, legTypeList);
  if (!isValid) return;

  const file = uploadFileEle.files[0];
  dispatch({ type: "uploadImg", payload: file });
  // onchange监听的为input的value值,只有再内容发生改变的时候去触发
  // 成功后将value值置空,使得能够检测到下一次上传内容
  e.target.value = "";
  // }
};

关于beforeUpload方法

以上传 20MB,类型为图片文件为例

// 文件大小限制
const fileSizeLimit = 20 * 1024 * 1024;
// 文件类型限制
const legTypeList = ["png", "jpg", "jpeg"];

// 1 KB = 1024 B
// 转换文件大小为可读单位
export const getFileSize = (fileSize) => {
  let result = "";

  if (fileSize >= 1073741824) {
    // B => GB
    result =
      fileSize % 1073741824 === 0
        ? `${fileSize / 1073741824}G`
        : `${Math.trunc(fileSize / 1073741824)}G`;
  } else if (fileSize >= 1048576) {
    // B => MB
    result =
      fileSize % 1048576 === 0
        ? `${fileSize / 1048576}MB`
        : `${Math.trunc(fileSize / 1048576)}MB`;
  } else if (fileSize >= 1024) {
    // B => KB
    result =
      fileSize % 1024 === 0
        ? `${fileSize / 1024}KB`
        : `${Math.trunc(fileSize / 1024)}KB`;
  } else if (fileSize !== undefined) {
    result = `${fileSize}B`;
  }
  return result;
};

// 根据文件名后缀获取当前文件类型
export const getLastFileTypeByName = (
  value,
  isExactlyMatchFileTypeCase = false
) => {
  const dotArray = value.split(".");
  const lastDotArray = dotArray[dotArray.length - 1];
  // 如果开启了精准匹配大小写 则 大写的文件类型不能通过校验
  if (isExactlyMatchFileTypeCase) {
    return lastDotArray;
  }
  // 注意 这里全部返回小写的!!!
  return lastDotArray.toLowerCase();
};

export const getFileTypeBySource = (
  file,
  legitTypeList,
  isExactlyMatchFileTypeCase = false
) => {
  // 注意这里的类型判断一定要从文件名读取! ios 无 file.type!!!
  const exactFileValue = file.name;
  const lastFileTypeByName = getLastFileTypeByName(
    exactFileValue,
    isExactlyMatchFileTypeCase
  );

  // 实测移动端 不走相册 直接选图片文件 尾缀为大写PNG 这里多加一层逻辑
  // ↑ 从H5抄过来的 问题不大
  // 这里全部转换成小写 进入匹配
  let isLegitType = false;
  // 转换小写的逻辑提前
  isLegitType = legitTypeList.some((key) => lastFileTypeByName.includes(key));
  return isLegitType;
};

// 上传前校验
// isExactlyMatchFileTypeCase 是否精准匹配文件类型大小写
export const beforeUpload = (
  files,
  fileSizeLimit,
  legTypeList,
  kind = "文件",
  isExactlyMatchFileTypeCase = false
) => {
  const readableMaxFileSize = getFileSize(fileSizeLimit);
  const fileSupportTypeText = legTypeList.join("、");

  if (!(files && files.length)) {
    message.error(`请添加${kind}`);
    return false;
  }

  let flag = true;

  // eslint-disable-next-line consistent-return
  files.forEach((file) => {
    const isLegFileType = getFileTypeBySource(
      file,
      legTypeList,
      isExactlyMatchFileTypeCase
    );

    if (!isLegFileType) {
      message.error(`文件支持格式: ${fileSupportTypeText}`);
      flag = false;
    }
    if (file.size > fileSizeLimit) {
      message.error(`文件最大支持${readableMaxFileSize}`);
      flag = false;
    }
  });
  return flag;
};

上传图片后可临时预览

https://juejin.cn/post/7240486780578480189

FileReader 转换为 base64 格式

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>图片本地预览示例</title>
  </head>
  <body>
    <h3>阿宝哥:图片本地预览示例</h3>
    <input type="file" accept="image/*" onchange="loadFile(event)" />
    <img id="previewContainer" />

    <script>
      const loadFile = function (event) {
        const reader = new FileReader();
        reader.onload = function () {
          const output = document.querySelector("#previewContainer");
          output.src = reader.result;
        };
        reader.readAsDataURL(event.target.files[0]);
      };
    </script>
  </body>
</html>


下载

图片、pdf、xml 的链接点击是预览
需要转换成 blob 文件流

测试链接

图片
https://i.hd-r.cn/da8b8e4fa1fc3a694af2544f8025a7da.png
pdf
http://lwzb.stats.gov.cn/pub/lwzb/bztt/202306/W020230605413585326652.pdf

五种方法(含文件名的处理) 以及 对比

https://juejin.cn/post/6844904069958467592?searchId=20231215174642867C7A34315E52A747FB#heading-20

浏览器打开图片链接是直接预览还是下载

取决于图片链接的响应头 Content-Disposition 的属性
Content-Disposition 为 inline 在浏览器打开直接预览(默认为 inline)
Content-Disposition 为 attachment 在浏览器打开直接下载

链接:https://juejin.cn/post/7177346491059535932

通用下载

import axios from "axios";
import qs from "qs";
import { message } from "antd";

export default function downloadFile(url, params) {
  const download = (response) => {
    // for IE
    if (window.navigator && "" in window.navigator) {
      let filename = response.headers; // 下载后文件名
      filename = filename["content-disposition"];
      const [, name] = filename.split(";");
      const [, fileName] = name.split("filename=");
      filename = fileName;
      const blob = new Blob([response.data]);
      typeof window.navigator.msSaveOrOpenBlob === "function" &&
        window.navigator.msSaveOrOpenBlob(blob, filename);
    } else {
      let filename = response.headers; // 下载后文件名
      filename = filename["content-disposition"];
      const [, name] = filename.split(";");
      const [, fileName] = name.split("filename=");
      filename = decodeURIComponent(fileName);
      const blob = new Blob([response.data]);
      const downloadElement = document.createElement("a");
      const href = window.URL.createObjectURL(blob); // 创建下载的链接
      downloadElement.href = href;
      downloadElement.download = filename;
      document.body.appendChild(downloadElement);
      downloadElement.click(); // 点击下载
      document.body.removeChild(downloadElement); // 下载完成移除元素
      window.URL.revokeObjectURL(href); // 释放掉blob对象
    }
  };

  axios({
    method: "get",
    url: `${url}?${qs.stringify(params)}`,
    responseType: "blob",
  }).then((response) => {
    const reader = new FileReader();
    reader.readAsText(response.data, "utf-8");
    reader.onload = (e) => {
      try {
        // 说明是普通对象数据 如果传的是报错信息,就会是
        // JSON.stringify({message: 'aaaaa'})
        const jsonData = JSON.parse(e.target.result.toString());
        message.error(jsonData.message);
      } catch (err) {
        // 正常二进制流,就会走下面
        // 流文件
        download(response);
      }
    };
  });
}

已知下载文件名的下载

// 注意这里得到的是axios的post返回
const res = await axios({
  method: "post",
  url,
  responseType: "blob", // 必须,服务器返回的数据类型
  headers: {
    "Content-Type": "application/json",
  },
  data: deliver,
});

// 下载正常处理
downloadFileByBlob(res, fileName);

// 文件流下载
export const downloadFileByBlob = (res, fileName) => {
  // 兼容ie11
  if (window.navigator.msSaveOrOpenBlob) {
    try {
      const blobObject = new Blob([res.data]);
      window.navigator.msSaveOrOpenBlob(blobObject, fileName);
    } catch (e) {
      console.log(e);
    }
    return;
  }
  // a标签实现下载
  const curUrl = window.URL || window.webkitURL || window.moxURL;
  const link = document.createElement("a");
  link.style.display = "none";

  // 创建下载链接,将文件流转化为一个文件地址
  link.href = curUrl.createObjectURL(new Blob([res.data]));
  link.setAttribute("download", fileName); // 下载的文件名
  document.body.appendChild(link);
  link.click(); // 触发点击事件执行下载
  document.body.removeChild(link); // 下载完成进行释放
};

值得一看的参考文章

前端二进制相关知识
https://juejin.cn/post/6846687590783909902?searchId=20231218154246BF9FEDEBB27E085C4D08

前端开发,谈谈图片 File、Blob、Base64、Hex 格式转换、及压缩裁剪那些事儿
https://juejin.cn/post/7262754051271032891?searchId=2023121416035302BFFAA3DF21DC00CAF9

posted @ 2023-12-15 19:56  乐盘游  阅读(7)  评论(0编辑  收藏  举报