个人记录:不同前端项目架构下的导出下载代码实现

不同架构模式下的前端导出下载实现

前言

做中后台项目时,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 BlobcreateObjectURL<a>.click()revokeObjectURL。不应该知道站点名称、筛选日期。
  • 页面调用层:负责业务状态——组装查询参数、判断是否有数据可导出、生成业务文件名、控制 loading、防重复提交、在 finally 中恢复状态。

文件名和 loading 不应下沉到 download 工具,这些信息只有页面最清楚。

五、落地 Checklist

  • responseType: 'blob' 是否已设置。
  • 导出接口成功时返回的是 Blob,不是业务 JSON。
  • token、租户、超时由谁处理(优先走统一拦截器)。
  • 文件名由页面侧生成,loading 和防重复提交也在页面维护。
  • finally 中恢复导出状态,下载后调用 revokeObjectURL
  • TypeScript 项目中,导出 API 和 download 工具是否补清了 Blob 类型边界。

总结

前端导出下载的底层链路不复杂——responseType: 'blob'BlobcreateObjectURLa.click()revokeObjectURL

真正要判断的是代码放在哪里:简单项目可用一站式函数快速串起请求和下载;有统一请求层和下载工具的项目,更适合拆成 API 层、download 工具层和页面调用层。

原则:API 层只拿文件流,download 工具只触发浏览器下载,页面层保留业务参数、文件名、loading 和防重复提交。这样看导出代码时,先问"请求层和下载工具怎么分工",而不是"这是 Vue2 写法还是 Vue3 写法"。

posted @ 2026-06-17 19:31  bleemyoung  阅读(9)  评论(0)    收藏  举报