我学会了,写一个前端下载文件功能

简介: 过去有很多次文件下载的功能,但是都没有记录下来,这次有空就把文件下载的功能从0写一遍,于是就有了这篇文章。 我会从简到难的方式去实现下载功能。从直接下载字符串到简单请求下载文件,最终通过后端返回的文件名来实现动态下载文件。

前言

过去有很多次文件下载的功能,但是都没有记录下来,这次有空就把文件下载的功能从0写一遍,于是就有了这篇文章。
我会从简到难的方式去实现下载功能。从直接下载字符串到简单请求下载文件,最终通过后端返回的文件名来实现动态下载文件。

简单下载

常见的文件下载就是office系列格式文件了,然后再就是txt格式的文件,前端下载通过a标签来实现下载的,其实也是调用浏览器内置功能。

那么把文件格式的mimeType列举一下:
这个篇文章中更全:百度文库

const mimeTypeMapping = {
    xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    xls: 'application/vnd.ms-excel',
    docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    doc: 'application/msword',
    pdf: 'application/pdf',
    default: 'text/plain' // 默认的文件下载方式
};

浏览器内置的下载功能,虽然知道有新出的api,但是我记得得谷歌浏览器97版本及之后才能使用,于是我用了兼容性最好的方式,a标签 + URL.createObjectURL + blob。

const link = document.createElement('a');
const urlObject = window.URL || window.webkitURL || window;
link.href = urlObject.createObjectURL(
new Blob([data], {
    type: mimeTypeMapping[fileType]
})
);

link.download = fileName;
document.body.appendChild(link);

它其实通过创建对象字符串,赋值为a标签的href属性,然后通过超链接下载协议实现文件的下载。

完整代码如下:

// 下载文件
export function downloadFile(data, fileName = '未知文件', callback) {
  const mimeTypeMapping = {
    xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    xls: 'application/vnd.ms-excel',
    docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    doc: 'application/msword',
    pdf: 'application/pdf',
    default: 'text/plain' // 默认的文件下载方式
  };

  const fileType = fileName.split('.')[1] || 'default';
  const link = document.createElement('a');
  const urlObject = window.URL || window.webkitURL || window;
  link.href = urlObject.createObjectURL(
    new Blob([data], {
      type: mimeTypeMapping[fileType]
    })
  );

  link.download = fileName;
  document.body.appendChild(link);
  link.click();
  if (typeof callback === 'function') {
    callback('success');
  }
  document.body.removeChild(link);
}

通过请求下载文件

通过请求下载文件,需要将请求头部的responseType设置为 arrayBuffer 或者 blob。
其它的就是简单的基本操作了。这里我用到的是umi-request,axios或者jquery都是类似的逻辑。

import request from 'umi-request';
// 简单下载,超级简单,只能下载前端已知的文件类型的文件,不支持后端动态设置类型。
export async function simpleDownload(url, data = {}, method = 'get', fileName) {
  const options = {
    responseType: 'arrayBuffer' // blob
  };

  let requestMethod;
  if (method.toLocaleLowerCase() === 'get') {
    requestMethod = request.get(url, { params: data, ...options });
  } else {
    requestMethod = request.post(url, { data, ...options });
  }

  return new Promise((resolve, reject) => {
    requestMethod
      .then(res => {
        // 这里拿到res 直接是 data
        const blob = new Blob([res]);
        downloadFile(blob, fileName, resolve);
      })
      .catch(reject);
  });
}

根据后端返回的文件名及类型来动态下载文件

也是通过请求来下载文件,只是会根据后端返回的响应头部的信息content-disposition来下载文件,这时候你能够知道文件类型及文件名称,而不是前端写死的。

import React from 'react';
import { message } from 'antd';
import request from 'umi-request';
// 发请求下载文件,复杂一点,支持后端动态设置文件类型
export async function download(url, data = {}, method = 'get') {
  const options = {
    responseType: 'arrayBuffer' // blob
  };

  let requestMethod;
  if (method.toLocaleLowerCase() === 'get') {
    // umi-request 中设置getResponse,才能在下面获得到response,否者拿到的直接是data
    requestMethod = request.get(url, { prefix, timeout, ...options, getResponse: true }); 
  } else {
    // umi-request 中设置getResponse,才能在下面获得到response,否者拿到的直接是data
    requestMethod = request.post(url, { data, prefix, timeout, ...options, getResponse: true });
  }

  return new Promise((resolve, reject) => {
    requestMethod
      .then(res => {
        // 这里拿到的res,分别是 response 和 data,因为请求时的配置设置了getResponse
        const { response, data } = res;
        // 状态码不对,报错
        if (response.status < 200 || response.status >= 300) {
          const errorData = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(data)));
          message.error(errorData.message);

          return reject('error');
        }

        // 文件名提取不出来,报错
        // const disposition = response.headers?.['content-disposition']; headers是一个Map结构,得使用map的get方法才行
        const disposition = response.headers?.get('content-disposition');
        const matchGroup = /filename=(.*);?/.exec(`${disposition}`);
        let fileName = matchGroup && window.decodeURI(matchGroup[1]);
        if (['', null, undefined].includes(fileName)) {
          if (data.msg) {
            message.error(data.msg);
          }
          return reject('error');
        }
        
        // 去掉字符串两边的 " 符号
        fileName = fileName.replace(/^\"?([\s\S]{0,})\"?$/, "$1")

        downloadFile(response.data, fileName, resolve);
      })
      .catch(() => {
        resolve('error');
      });
  });
}

总结

这些都是比较常见的下载文件的方式,其实也没啥难度,只不过你稍不注意,可能就会踩坑了,比如我从写到测试,就花了近三个小时。
有时你觉得很简单的东西,兴许也会变成很费时费力的东西,所以习惯记录,不然脑子是不会把发生的每一件事每一个细节都给记的死死的,时间久了,就容易忘记,然后就会出现很多小问题。

BUG fix更新 2022-06-16

由于框架不同,umi-request 和 axios中有不一样的地方,比如then中拿到结果会根据参数配置项中getResponse而改变,比如header不是一个普通对象而是一个map。
所以要做点额外处理。

目录
相关文章
|
8天前
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
102 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
2月前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
129 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
1月前
|
前端开发
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
43 1
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
|
4月前
|
JSON 前端开发 搜索推荐
惊!这些前端技术竟然能让你的网站实现个性化推荐功能!
【10月更文挑战第30天】随着互联网技术的发展,个性化推荐已成为提升用户体验的重要手段。前端技术如JavaScript通过捕获用户行为数据、实时更新推荐结果等方式,在实现个性化推荐中扮演关键角色。本文将深入解析这些技术,并通过示例代码展示其实际应用。
166 4
|
4月前
|
前端开发 API
前端界面生成PDF并导出下载
【10月更文挑战第21天】利用合适的第三方库,你可以在前端轻松实现界面生成 PDF 并导出下载的功能,为用户提供更方便的文档分享和保存方式。你还可以根据具体的需求进一步优化和定制生成的 PDF 文件,以满足不同的业务场景要求。
|
5月前
|
前端开发 JavaScript
前端中的“+”连接符,居然有鲜为人知的强大功能!
【10月更文挑战第9天】前端中的“+”连接符,居然有鲜为人知的强大功能!
76 0
前端中的“+”连接符,居然有鲜为人知的强大功能!
|
5月前
|
Web App开发 存储 前端开发
前端开发必备:requestAnimationFrame、setInterval、setTimeout——功能解析与优劣对比
前端开发必备:requestAnimationFrame、setInterval、setTimeout——功能解析与优劣对比
265 0
|
5月前
|
资源调度 前端开发 安全
前端实战:基于Verdaccio搭建私有npm仓库,轻松上传与下载自定义npm插件包
前端实战:基于Verdaccio搭建私有npm仓库,轻松上传与下载自定义npm插件包
267 0
|
5月前
|
JavaScript 前端开发 应用服务中间件
vue前端开发中,通过vue.config.js配置和nginx配置,实现多个入口文件的实现方法
vue前端开发中,通过vue.config.js配置和nginx配置,实现多个入口文件的实现方法
301 0
|
5月前
|
前端开发 JavaScript API
前端基于XLSX实现数据导出到Excel表格,以及提示“文件已经被损坏,无法打开”的解决方法
前端基于XLSX实现数据导出到Excel表格,以及提示“文件已经被损坏,无法打开”的解决方法
387 0

热门文章

最新文章

  • 1
    前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
  • 2
    从前端视角聊聊通义灵码使用经验,如何更好地提升研发效率
  • 3
    VSCode AI提效工具,通义灵码前端开发体验
  • 4
    【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
  • 5
    大前端之前端开发接口测试工具postman的使用方法-简单get接口请求测试的使用方法-简单教学一看就会-以实际例子来说明-优雅草卓伊凡
  • 6
    以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
  • 7
    【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
  • 8
    无前端经验如何快速搭建游戏站:使用 windsurf 从零到上线的详细指南
  • 9
    AI程序员:通义灵码 2.0应用VScode前端开发深度体验
  • 10
    构建高效Java后端与前端交互的定时任务调度系统