个人记录:不同前端项目架构下的导出下载代码实现
不同架构模式下的前端导出下载实现
前言
做中后台项目时,Excel 导出是很常见的需求。按钮点下去,前端带着筛选条件请求接口,后端返回文件流,浏览器触发下载。
这件事看起来简单,但一到具体项目里,代码经常长得不一样。
有的项目是:
await exportExcel(url, params, fileName)
一个函数内部完成请求和下载。
有的项目是:
const res = await exportTrendData(params)
download.excel(res as Blob, fileName)
API 层先拿到 Blob,再交给下载工具处理。
这两个例子时常被分别对应到 Vue2 和 Vue3 项目,顺理成章地贴上"Vue2 写法"和"Vue3 写法"的标签。但拆开看会发现,真正决定代码形态的不是框架版本,而是项目架构——是请求和下载合在一个函数里,还是拆成 API 层 + download 工具。
一、导出下载的共同底层链路
前端导出下载的核心链路比较稳定,关键动作只有几步。
请求时必须设置:
responseType: 'blob'
这个配置让浏览器把响应体按二进制文件流处理,前端拿到的不是业务 JSON,而是 Blob。
拿到 Blob 后,浏览器侧下载:
const blob = new Blob([data], { type: mimeType })
const href = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = href
a.download = fileName
a.click()
URL.revokeObjectURL(href)
所以不管 Vue2 还是 Vue3,底层都绕不开这几步。真正不同的是:这些代码放在哪一层。
二、一站式导出函数
一种常见做法是封装一个一站式函数:
exportExcel(url, params, fileName)
调用方只传接口地址、查询参数和文件名,函数内部自己完成请求和下载。通常会处理拼接接口、发起 axios、注入 token、设置 responseType: 'blob'、包装 Blob、触发 <a> 下载、释放 URL 等一系列动作。
页面调用很轻:
await exportExcel('/operator/a/inventory/exportDailyRecyclingData', params, fileName)
优势:直接,页面不用关心 Blob 和下载细节。
适合:旧项目导出入口少、没有成熟下载工具、请求层不宜大改、快速补齐导出能力。
代价:请求、鉴权、文件包装、下载耦合在一个函数里。后续要复用下载能力到 Word、Zip 等其他文件类型,或调整请求层时,函数会很难拆。
三、API 层加 download 工具的分层实现
更好的做法是分层。
API 层只负责请求导出接口并拿到文件流:
exportTrendData: async (params) => {
return await request.postOriginal({
url: '/api/operator/a/inventory/exportDailyRecyclingData',
data: params,
responseType: 'blob'
})
}
页面拿到 Blob 后,交给下载工具:
const res = await SiteHomeApi.exportTrendData(params)
const fileName = `回收趋势${siteName}${dateRange}.xlsx`
download.excel(res as Blob, fileName)
download 工具只处理下载动作:
const download0 = (data: Blob, fileName: string, mimeType: string) => {
const blob = new Blob([data], { type: mimeType })
const href = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = href
a.download = fileName
a.click()
URL.revokeObjectURL(href)
}
职责更清楚——API 层表达"我要拿到 Blob",download 表达"给我 Blob 和文件名,我来触发下载",页面表达"我知道当前筛选条件、站点日期、loading 状态"。
适合:项目已有统一 axios 请求层;token、租户、超时等由拦截器处理;多页面有下载需求;除 Excel 外还有 Word、Zip、JSON 等下载场景;TypeScript 项目需明确返回值类型。
代价:调用链更长。如果请求封装类型不够精确,组件里可能出现 res as unknown as Blob。导出场景多时,最好在 API 层明确 Blob 返回类型。
四、职责划分原则
导出下载代码按三层判断:
- API 层:只关心怎么请求到文件流——接口地址、请求方法、参数、
responseType: 'blob'。token 等通用能力优先走统一请求拦截器。不建议直接操作 DOM。 - download 工具层:只关心怎么把 Blob 变成浏览器下载——
new Blob、createObjectURL、<a>.click()、revokeObjectURL。不应该知道站点名称、筛选日期。 - 页面调用层:负责业务状态——组装查询参数、判断是否有数据可导出、生成业务文件名、控制 loading、防重复提交、在
finally中恢复状态。
文件名和 loading 不应下沉到 download 工具,这些信息只有页面最清楚。
五、落地 Checklist
responseType: 'blob'是否已设置。- 导出接口成功时返回的是
Blob,不是业务 JSON。 - token、租户、超时由谁处理(优先走统一拦截器)。
- 文件名由页面侧生成,loading 和防重复提交也在页面维护。
finally中恢复导出状态,下载后调用revokeObjectURL。- TypeScript 项目中,导出 API 和 download 工具是否补清了 Blob 类型边界。
总结
前端导出下载的底层链路不复杂——responseType: 'blob' → Blob → createObjectURL → a.click() → revokeObjectURL。
真正要判断的是代码放在哪里:简单项目可用一站式函数快速串起请求和下载;有统一请求层和下载工具的项目,更适合拆成 API 层、download 工具层和页面调用层。
原则:API 层只拿文件流,download 工具只触发浏览器下载,页面层保留业务参数、文件名、loading 和防重复提交。这样看导出代码时,先问"请求层和下载工具怎么分工",而不是"这是 Vue2 写法还是 Vue3 写法"。

浙公网安备 33010602011771号