基础知识之上传与下载
缘起
上传和下载是两个经典场景,做项目的时候遇到了这两种情况。
上传
设置上传按钮
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