跳到主要内容Axios 实现 Excel 文件上传与下载方案详解 | 极客日志JavaScriptNode.js大前端
Axios 实现 Excel 文件上传与下载方案详解
综述由AI生成使用 Axios 进行文件上传与下载的技术方案。内容包括 FormData 的使用、单文件及多文件上传、上传进度监听与防抖处理。重点讲解了 Blob 对象处理、responseType 配置、文件名解析(Content-Disposition)及通用下载函数封装。此外,还涵盖了跨域问题、超时设置、拦截器统一处理错误与 Token,以及报表异步导出、批量导入预览等实际应用场景的解决方案。
极光33 浏览 Axios 实现 Excel 文件上传与下载方案详解
前端在处理文件上传与下载时,常遇到二进制流处理、跨域、进度监听等问题。本文基于 Axios 库,详细讲解如何高效解决这些技术难点。
核心概念:FormData 与 Blob
Blob (Binary Large Object)
浏览器用于存储二进制数据的容器,可视为内存中的虚拟文件。使用 window.URL.createObjectURL 可生成临时链接触发下载,使用后需调用 revokeObjectURL 释放内存。
FormData
HTML5 API,用于模拟表单提交。由于 JSON 无法直接传输文件,上传时需将文件放入 FormData 对象中。注意:不要手动设置 Content-Type: multipart/form-data,否则会导致 boundary 丢失,后端无法解析。
文件上传实现
基础版:单文件上传
const uploadFile = async (file) => {
const formData = new FormData();
formData.append('file', file);
formData.append('userId', '9527');
formData.append('bizType', 'avatar');
try {
const response = await axios.post('/api/upload', formData,
{ headers: { 'Content-Type': undefined } }
);
console.log('上传成功:', response.data);
return response.data;
} catch (error) {
console.error('上传失败:', error);
throw error;
}
};
进阶版:多文件上传
const uploadMultipleFiles = async (fileList) => {
const formData = new FormData();
fileList.forEach((file, index) => {
formData.append('files', file);
formData.append(`fileNames[${index}]`, file.name);
});
try {
const response = await axios.post('/api/upload/batch', formData,
{ headers: { 'Content-Type': undefined } }
);
return response.data;
} catch (error) {
console.error('批量上传失败:', error);
throw error;
}
};
带进度条的上传
利用 onUploadProgress 回调监听 XMLHttpRequest 的 progress 事件。
const uploadWithProgress = (file, onProgress) => {
const formData = new FormData();
formData.append('file', file);
return axios.post('/api/upload', formData,
{
headers: { 'Content-Type': undefined },
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
if (onProgress) onProgress(percentCompleted);
},
timeout: 0
}
);
};
防抖处理
import { debounce } from 'lodash';
const debouncedUpload = debounce(async (file, callback) => {
try {
const result = await uploadWithProgress(file, callback);
return result;
} catch (error) {
console.error(error);
}
}, 300, { leading: true, trailing: false });
文件下载实现
最简版:基础下载
必须设置 responseType: 'blob',否则返回乱码字符串。
const downloadExcel = async () => {
try {
const response = await axios({
url: '/api/export/user-list',
method: 'GET',
responseType: 'blob',
params: { startDate: '2024-01-01', endDate: '2024-12-31' }
});
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = '用户列表.xlsx';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('下载失败:', error);
}
};
文件名解析
从响应头 Content-Disposition 中提取文件名,支持中文编码。
const downloadWithFileName = async () => {
const response = await axios({
url: '/api/export/report',
method: 'POST',
responseType: 'blob',
data: { reportType: 'monthly' }
});
const contentDisposition = response.headers['content-disposition'];
let fileName = '下载文件.xlsx';
if (contentDisposition) {
const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i);
if (utf8Match && utf8Match[1]) {
fileName = decodeURIComponent(utf8Match[1]);
} else {
const normalMatch = contentDisposition.match(/filename="([^"]+)"/i);
if (normalMatch && normalMatch[1]) {
fileName = normalMatch[1];
}
}
}
const blob = new Blob([response.data], { type: response.headers['content-type'] });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
};
通用下载函数封装
export const downloadFile = async (url, config = {}, defaultName = 'download') => {
try {
const response = await axios({ url, method: 'GET', responseType: 'blob', ...config });
const contentDisposition = response.headers['content-disposition'];
let fileName = defaultName;
if (contentDisposition) {
const match = contentDisposition.match(/filename\*?=(?:UTF-8'')?([^;]+)/i);
if (match) {
fileName = decodeURIComponent(match[1].replace(/['"]/, ''));
}
}
const blob = new Blob([response.data], {
type: response.headers['content-type'] || 'application/octet-stream'
});
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
setTimeout(() => {
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
}, 100);
return { success: true, fileName };
} catch (error) {
console.error('下载失败:', error);
throw error;
}
};
带下载进度的大文件下载
const downloadLargeFile = async (url, fileName, onProgress) => {
const response = await axios({
url,
method: 'GET',
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
if (progressEvent.total) {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress?.(percent);
}
},
timeout: 0
});
const blob = new Blob([response.data]);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
link.click();
window.URL.revokeObjectURL(downloadUrl);
};
拦截器与错误处理
统一 Token 与错误处理
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
axios.interceptors.response.use(response => response, error => {
if (error.response?.status === 401) {
window.location.href = '/login';
}
return Promise.reject(error);
});
处理 Blob 类型的错误响应
当下载接口返回非 200 状态码时,可能返回 JSON 错误信息而非文件流。
axios.interceptors.response.use(async response => {
if (response.config.responseType === 'blob' && response.status !== 200) {
const text = await response.data.text();
try {
const error = JSON.parse(text);
return Promise.reject(error);
} catch {
return Promise.reject(new Error('下载失败'));
}
}
return response;
}, error => Promise.reject(error));
实际落地场景
场景一:报表导出(异步生成)
const exportReport = async (params) => {
const { data: { taskId } } = await axios.post('/api/export/task', params);
const checkStatus = () => new Promise((resolve, reject) => {
const timer = setInterval(async () => {
try {
const { data } = await axios.get(`/api/export/task/${taskId}/status`);
if (data.status === 'completed') {
clearInterval(timer);
resolve(data.downloadUrl);
} else if (data.status === 'failed') {
clearInterval(timer);
reject(new Error('生成失败'));
}
} catch (error) {
clearInterval(timer);
reject(error);
}
}, 2000);
});
try {
const downloadUrl = await checkStatus();
await downloadFile(downloadUrl, {}, '报表.xlsx');
} catch (error) {
message.error(error.message);
}
};
场景二:批量导入 + 实时预览
先上传到预览接口,解析后展示错误行,确认无误后再正式入库。
const UploadWithPreview = () => {
const [previewData, setPreviewData] = useState([]);
const [errorRows, setErrorRows] = useState([]);
const handleUpload = async (file) => {
const formData = new FormData();
formData.append('file', file);
const { data } = await axios.post('/api/import/preview', formData,
{ headers: { 'Content-Type': undefined } }
);
setPreviewData(data.list);
setErrorRows(data.errors);
};
const confirmImport = async () => {
await axios.post('/api/import/confirm', { data: previewData });
message.success('导入成功');
};
return (
<div>
<Upload beforeUpload={handleUpload} fileList={[]}>
<Button>上传 Excel 预览</Button>
</Upload>
{/* 表格展示逻辑略 */}
</div>
);
};
场景三:图片压缩上传
import Compressor from 'compressorjs';
const compressAndUpload = (file) => {
if (!['image/jpeg', 'image/png'].includes(file.type)) {
message.error('只支持 jpg/png 格式');
return;
}
if (file.size > 10 * 1024 * 1024) {
message.error('图片不能超过 10MB');
return;
}
new Compressor(file, {
quality: 0.6,
maxWidth: 1920,
maxHeight: 1080,
success(result) {
const compressedFile = new File([result], file.name, { type: result.type });
uploadFile(compressedFile);
},
error(err) {
message.error('压缩失败');
}
});
};
常见问题排查
- 下载乱码:检查是否设置
responseType: 'blob',以及后端是否返回了正确的流。
- 跨域问题:确保后端 CORS 配置包含
Access-Control-Expose-Headers: Content-Disposition。
- 超时问题:大文件操作需调大
timeout 或设为 0。
进阶优化技巧
- 全局上传下载管理器:使用 Context 或 Redux 管理任务队列,支持暂停、取消。
- 拦截器统一标识:通过
X-Upload-Request 等自定义 Header 区分请求类型。
- 文件类型校验工具:封装校验函数,限制 MIME 类型和文件大小。
总结
Axios 配合 Blob 与 FormData 是前端处理文件交互的标准方案。通过合理配置响应类型、利用拦截器统一处理鉴权与错误、并结合实际业务场景(如异步导出、断点续传需求),可以构建稳定高效的文件服务模块。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online