前端解决下载的excel文件无法打开问题
【背景介绍】
项目中涉及到需要对导入的excel文件进行模板的下载,涉及到了下载的相关实现,那么我们话不多说,直接开讲:
【项目涉及到的技术栈】
Vue3+Arco-design
【开发过程中碰到的问题】
1.后端返回的数据是直接在response里面的
2.后端接收过来的数据一直显示是string类型,但转换成blob下载下来的文件又打不开
3.后端在postman中可以直接下载到文件,并且能够正常的打开;前端通过接口获取到的数据,在浏览器下载下来的文件大小与实际的文件大小不符,且无法正常的打开文件
总结下来,核心的问题就是打不开!打不开!打不开!!!!!!

【关于产生原因的思考】
在浏览器、豆包、deepseek中查找了很多相关的帖子,总结下来基本就是:
1.要确认请求的接口对不对(都能下载到文件,证明接口肯定是对的啊)
2.要设置responseType;(可以很确定的说,我已经在请求的时候加上了)
3.要设置Content-Type,值要根据要下载的文档类型需要,选择对应的值
例如:vnd.openxmlformats-officedocument.spreadsheetml.sheet excel表格使用的类型
4.一堆杂七杂八的属性(防止xss攻击的啊、限制必须下载设置的type类型的啊等等,反正没有一个解决了我的问题)
后来又自己思考了一下可能造成这样情况的原因:
1.按照网上描述,接口返回数据不应该直接在response里面,而是在response.data里面,是否是拦截器有问题
2.接到数据之后对数据进行了转换,是否是在转换的途中出现了问题
【最终如何解决的】
在cursor中,让cursor解决了,并让它封装了一个下载的函数,可以看到有相应的注释
import { Message } from '@arco-design/web-vue';
import { getToken } from '@/utils/auth';
/**
* 通用文件下载工具
*
* 使用fetch API下载文件,支持各种文件类型
* 自动处理身份验证、错误处理和资源清理
*/
/**
* 下载配置接口
*/
interface DownloadConfig {
/** 下载地址 */
url: string;
/** 文件名(包含扩展名) */
filename: string;
/** 请求方法,默认GET */
method?: 'GET' | 'POST';
/** 额外的请求头 */
headers?: Record<string, string>;
/** 请求体数据(POST请求时使用) */
data?: any;
/** 是否显示成功提示,默认true */
showSuccessMessage?: boolean;
/** 是否显示错误提示,默认true */
showErrorMessage?: boolean;
}
/**
* 使用fetch下载文件
*
* @param config 下载配置
* @returns Promise<void>
*/
export async function downloadFile(config: DownloadConfig): Promise<void> {
const {
url,
filename,
method = 'GET',
headers = {},
data,
showSuccessMessage = true,
showErrorMessage = true
} = config;
try {
console.log(`开始下载文件: ${filename}`);
// 1. 获取API基础地址和用户token
const baseURL = import.meta.env.VITE_API_URL;
const token = getToken();
// 2. 准备请求配置
const requestConfig: RequestInit = {
method,
headers: {
// 默认请求头
'Authorization': `Bearer ${token}`,
'RSC': import.meta.env.VITE_CLIENT_RSC || '',
// 用户自定义请求头
...headers
}
};
// 3. 如果是POST请求且有数据,添加请求体
if (method === 'POST' && data) {
requestConfig.body = JSON.stringify(data);
requestConfig.headers = {
...requestConfig.headers,
'Content-Type': 'application/json'
};
}
// 4. 发起下载请求
const fullUrl = url.startsWith('http') ? url : `${baseURL}${url}`;
const response = await fetch(fullUrl, requestConfig);
// 5. 检查响应状态
if (!response.ok) {
throw new Error(`下载失败: HTTP ${response.status} ${response.statusText}`);
}
// 6. 将响应转换为Blob
const blob = await response.blob();
// 7. 验证文件数据
if (blob.size === 0) {
throw new Error('下载的文件为空');
}
console.log(`文件下载完成:`, {
文件名: filename,
文件大小: `${(blob.size / 1024).toFixed(2)} KB`,
文件类型: blob.type || '未知类型'
});
// 8. 创建下载链接并触发下载
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();
document.body.removeChild(link);
// 9. 清理临时URL,防止内存泄漏
window.URL.revokeObjectURL(downloadUrl);
// 10. 显示成功提示
if (showSuccessMessage) {
Message.success(`${filename} 下载成功`);
}
} catch (error: any) {
// 错误处理
console.error('文件下载失败:', error);
if (showErrorMessage) {
const errorMessage = error?.message || '未知错误';
Message.error(`下载失败: ${errorMessage}`);
}
// 重新抛出错误,让调用方可以进一步处理
throw error;
}
}
/**
* 下载Excel文件的便捷方法
*
* @param url 下载地址
* @param filename 文件名
* @param data 请求数据(可选)
*/
export async function downloadExcel(
url: string,
filename: string,
data?: any
): Promise<void> {
return downloadFile({
url,
filename,
method: data ? 'POST' : 'GET',
data,
headers: {
'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
}
});
}
/**
* 下载PDF文件的便捷方法
*/
export async function downloadPDF(
url: string,
filename: string,
data?: any
): Promise<void> {
return downloadFile({
url,
filename,
method: data ? 'POST' : 'GET',
data,
headers: {
'Accept': 'application/pdf'
}
});
}
/**
* 下载测试报告 - 使用GET请求
*
* @param params 下载参数
* @returns Promise<void>
*/
export const downloadReport = async (params) => {
const { downloadExcel } = await import('@/utils/download');
// 构建GET请求的URL参数
const searchParams = new URLSearchParams({
要传入的参数
});
return downloadExcel(
`后端接口的url`,
`文件名称.xlsx`,
);
};
【鉴于有小伙伴不知道如何调用这个定义的接口,现补充调用接口相关的板块代码】
/**
* 下载报告 - 使用GET请求
*
* @param record 任务记录数据
*/
const handleDownload = async (record) => {
try {
// 调用API下载报告(GET请求)
await downloadReport({
要传入到接口的参数
});
} catch (error) {
console.error('下载测试报告失败:', error);
message.error('下载测试报告失败');
}
};
总结:
以上就是我对该问题的解决,就我目前的理解来讲, 我认为应该是我之前传入给window.URL.createObjectURL函数的参数不对,之前传入的参数是blob开头,而正常传入的应该是携带有项目请求转接使用的请求头才对,写这篇文章的目的主要就是为了记录碰到的下载问题,以便后续如果碰到类似问题,可以解决,欢迎来评论探讨呀
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)