Vue+axios+WebApi+NPOI导出Excel文件实例方法


Posted in Javascript onJune 05, 2019

一、前言

项目中前端采用的Element UI 框架, 远程数据请求,使用的是axios,后端接口框架采用的asp.net webapi,数据导出成Excel采用NPOI组件。其业务场景,主要是列表页(如会员信息,订单信息等)表格数据导出,如表格数据进行了条件筛选,则需要将条件传至后端api,筛选数据后,导出成Excel。

思考过前端导出的3种方案:

1.使用location.href 打开接口地址.缺点: 不能传token至后端api, 无法保证接口的安全性校验,并且接口只能是get方式请求.

2.采用axios请求接口,先在筛选后的数据服务端生成文件并保存,然后返回远程文件地址,再采用 location.href打开文件地址进行下载. 缺点: 实现复杂,并且每次导出会在服务端生成文件,但是又没有合适的时机再次触发删除文件,会在服务端形成垃圾数据。优点:每次导出都可以有记录。

3. 采用axios请求接口,服务端api返回文件流,前端接收到文件流后,采用blob对象存储,并创建成url, 使用a标签下载. 优点:前端可传token参数校验接口安全性,并支持get或post两种方式。

因其应用场景是导出Excel文件之前,必须筛选数据,并需要对接口安全进行校验,所以第3种方案为最佳选择。在百度之后,发现目前使用最多的也是第3种方案。

二、Vue + axios 前端处理

1.axios 需在response拦截器里进行相应的处理(这里不再介绍axios的使用, 关于axios的用法,具体请查看Axios中文说明 ,我们在项目中对axios进行了统一的拦截定义). 需特别注意: response.headers['content-disposition'],默认是获取不到的,需要对服务端webapi进行配置,请查看第三点中webapi CORS配置

// respone拦截器
service.interceptors.response.use(
 response => {
 // blob类型为文件下载对象,不论是什么请求方式,直接返回文件流数据
 if (response.config.responseType === 'blob') {
  const fileName = decodeURI(
  response.headers['content-disposition'].split('filename=')[1]
  )
// 返回文件流内容,以及获取文件名, response.headers['content-disposition']的获取, 默认是获取不到的,需要对服务端webapi进行配置
  return Promise.resolve({ data: response.data, fileName: fileName })
 }
 // 依据后端逻辑实际情况,需要弹窗展示友好错误
 },
 error => {
 let resp = error.response
 if (resp.data) {
  console.log('err:' + decodeURIComponent(resp.data)) // for debug
 }
 // TODO: 需要依据后端实际情况判断
 return Promise.reject(error)
 }
)

2. 点击导出按钮,请求api. 需要注意的是接口请求配置的响应类型responseType:'blob' (也可以是配置arrayBuffer) ; 然IE9及以下浏览器不支持createObjectURL. 需要特别处理下IE浏览器将blob转换成文件。

exportExcel () {
  let params = {}
  let p = this.getQueryParams() // 获取相应的参数
  if (p) params = Object({}, params, p)
  axios
  .get('接口地址', {
   params: params,
   responseType: 'blob'
  })
  .then(res => {
   var blob = new Blob([res.data], {
   type: 'application/vnd.ms-excel;charset=utf-8'
   })
   // 针对于IE浏览器的处理, 因部分IE浏览器不支持createObjectURL
   if (window.navigator && window.navigator.msSaveOrOpenBlob) {
   window.navigator.msSaveOrOpenBlob(blob, res.fileName)
   } else {
   var downloadElement = document.createElement('a')
   var href = window.URL.createObjectURL(blob) // 创建下载的链接
   downloadElement.href = href
   downloadElement.download = res.fileName // 下载后文件名
   document.body.appendChild(downloadElement)
   downloadElement.click() // 点击下载
   document.body.removeChild(downloadElement) // 下载完成移除元素
   window.URL.revokeObjectURL(href) // 释放掉blob对象
   }
  })
 }

三、WebApi + NPOI 后端处理

1. 需要通过接口参数,查询数据

为了保持与分页组件查询接口一直的参数,采用了get请求方式,方便前端传参。webapi接口必须返回IHttpActionResult 类型

[HttpGet]
  public IHttpActionResult ExportData([FromUri]Pagination pagination, [FromUri] OrderReqDto dto)
  {
   //取出数据源
   DataTable dt = this.Service.GetMemberPageList(pagination, dto.ConvertToFilter());
   if (dt.Rows.Count > 65535)
   {
    throw new Exception("最大导出行数为65535行,请按条件筛选数据!");
   }
   foreach (DataRow row in dt.Rows)
   {
    var isRealName = row["IsRealName"].ToBool();
    row["IsRealName"] = isRealName ? "是" : "否";
   }
   var model = new ExportModel();
   model.Data = JsonConvert.SerializeObject(dt);
   model.FileName = "会员信息";
   model.Title = model.FileName;
   model.LstCol = new List<ExportDataColumn>();
   model.LstCol.Add(new ExportDataColumn() { prop = "FullName", label = "会员名称" });
   model.LstCol.Add(new ExportDataColumn() { prop = "RealName", label = "真实姓名" });
   model.LstCol.Add(new ExportDataColumn() { prop = "GradeName", label = "会员等级" });
   model.LstCol.Add(new ExportDataColumn() { prop = "Telphone", label = "电话" });
   model.LstCol.Add(new ExportDataColumn() { prop = "AreaName", label = "区域" });
   model.LstCol.Add(new ExportDataColumn() { prop = "GridName", label = "网格" });
   model.LstCol.Add(new ExportDataColumn() { prop = "Address", label = "门牌号" });
   model.LstCol.Add(new ExportDataColumn() { prop = "RegTime", label = "注册时间" });
   model.LstCol.Add(new ExportDataColumn() { prop = "Description", label = "备注" });
   return ExportDataByFore(model);
  }

2.关键导出函数 ExportDataByFore的实现

[HttpGet]
  public IHttpActionResult ExportDataByFore(ExportModel dto)
  {
   var dt = JsonConvert.DeserializeObject<DataTable>(dto.Data);
   var fileName = dto.FileName + DateTime.Now.ToString("yyMMddHHmmssfff") + ".xls";
   //设置导出格式
   ExcelConfig excelconfig = new ExcelConfig();
   excelconfig.Title = dto.Title;
   excelconfig.TitleFont = "微软雅黑";
   excelconfig.TitlePoint = 25;
   excelconfig.FileName = fileName;
   excelconfig.IsAllSizeColumn = true;
   //每一列的设置,没有设置的列信息,系统将按datatable中的列名导出
   excelconfig.ColumnEntity = new List<ColumnEntity>();
   //表头
   foreach (var col in dto.LstCol)
   {
    excelconfig.ColumnEntity.Add(new ColumnEntity() { Column = col.prop, ExcelColumn = col.label });
   }
   //调用导出方法
   var stream = ExcelHelper.ExportMemoryStream(dt, excelconfig); // 通过NPOI形成将数据绘制成Excel文件并形成内存流
   var browser = String.Empty;
   if (HttpContext.Current.Request.UserAgent != null)
   {
    browser = HttpContext.Current.Request.UserAgent.ToUpper();
   }
   HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
   httpResponseMessage.Content = new StreamContent(stream);
   httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); // 返回类型必须为文件流 application/octet-stream
   httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") // 设置头部其他内容特性, 文件名
   {
    FileName =
     browser.Contains("FIREFOX")
      ? fileName
      : HttpUtility.UrlEncode(fileName)
   };
   return ResponseMessage(httpResponseMessage);
  }

3. web api 的CORS配置

采用web api 构建后端接口服务的同学,都知道,接口要解决跨域问题,都需要进行api的 CORS配置, 这里主要是针对于前端axios的响应response header中获取不到 content-disposition属性,进行以下配置

Vue+axios+WebApi+NPOI导出Excel文件实例方法

四、总结

以上就是我在实现axios导出Excel文件功能时,遇到的一些问题,以及关键点进行总结。因为项目涉及到前后端其他业务以及公司架构的一些核心源码。要抽离一个完整的demo,比较耗时,所以没有完整demo展示. 但我已把NPOI相关的操作函数源码,整理放至github上。https://github.com/yinboxie/BlogExampleDemo

Javascript 相关文章推荐
Extjs Gird 支持中文拼音排序实现代码
Apr 15 Javascript
解析window.open的使用方法总结
Jun 19 Javascript
微信小程序(应用号)开发新闻客户端实例
Oct 24 Javascript
基于JS实现bookstore静态页面的实例代码
Feb 22 Javascript
pm2 部署 node的三种方法示例
Oct 20 Javascript
js生成word中图片处理方法
Jan 06 Javascript
bootstrap中日历范围选择插件daterangepicker的使用详解
Apr 17 Javascript
vue使用Font Awesome的方法步骤
Feb 26 Javascript
JavaScript onclick事件使用方法详解
May 15 Javascript
小程序实现图片移动缩放效果
May 26 Javascript
vue-cli4.0多环境配置变量与模式详解
Dec 30 Vue.js
JavaScript ES6的函数拓展
Jan 18 Javascript
js实现随机8位验证码
Jul 24 #Javascript
Vue中全局变量的定义和使用
Jun 05 #Javascript
详解express使用vue-router的history踩坑
Jun 05 #Javascript
laravel-admin 与 vue 结合使用实例代码详解
Jun 04 #Javascript
用webpack4开发小程序的实现方法
Jun 04 #Javascript
JS实现的对象去重功能示例
Jun 04 #Javascript
JS数组中对象去重操作示例
Jun 04 #Javascript
You might like
php数据库备份还原类分享
2014/03/20 PHP
php中PDO方式实现数据库的增删改查
2015/05/17 PHP
PHP缓冲区用法总结
2016/02/14 PHP
PHP 以POST方式提交XML、获取XML,解析XML详解及实例
2016/10/26 PHP
php实现base64图片上传方式实例代码
2017/02/22 PHP
PHP文件系统管理(实例讲解)
2017/09/19 PHP
PHP定义字符串的四种方式详解
2018/02/06 PHP
javascript 面向对象全新理练之数据的封装
2009/12/03 Javascript
JavaScript 图像动画的小demo
2012/05/23 Javascript
JS实现图片预加载无需等待
2012/12/21 Javascript
jquery如何实现在加载完iframe的内容后再进行操作
2013/09/10 Javascript
js中的异常处理try...catch使用介绍
2013/09/21 Javascript
jQuery内容过滤选择器用法分析
2015/02/10 Javascript
iframe中子父类窗口调用JS的方法及注意事项
2015/08/25 Javascript
JavaScript学习笔记之ES6数组方法
2016/03/25 Javascript
使用jQuery.Qrcode插件在客户端动态生成二维码并添加自定义Logo
2016/09/01 Javascript
使用bat打开多个cmd窗口执行gulp、node
2017/02/17 Javascript
vue2.0s中eventBus实现兄弟组件通信的示例代码
2017/10/25 Javascript
详解vue+css3做交互特效的方法
2017/11/20 Javascript
Vue路由切换时的左滑和右滑效果示例
2018/05/29 Javascript
基于vue实现可搜索下拉框定制组件
2020/03/26 Javascript
详解vue2.0 资源文件assets和static的区别
2018/11/27 Javascript
jquery实现上传文件进度条
2020/03/26 jQuery
Python爬虫:通过关键字爬取百度图片
2017/02/17 Python
Python制作豆瓣图片的爬虫
2017/12/28 Python
Python:二维列表下标互换方式(矩阵转置)
2019/12/02 Python
如何配置关联Python 解释器 Anaconda的教程(图解)
2020/04/30 Python
让IE6、IE7、IE8支持CSS3的脚本
2010/07/20 HTML / CSS
英国领先的杂志订阅网站:Magazine.co.uk
2018/01/25 全球购物
Vero Moda西班牙官方购物网站:丹麦BESTSELLER旗下知名女装品牌
2018/04/27 全球购物
小学模范班主任事迹材料
2014/05/13 职场文书
道德大讲堂实施方案
2014/05/14 职场文书
国庆节演讲稿
2014/05/27 职场文书
电子商务专业应届生求职信
2014/05/28 职场文书
鉴史问廉观后感
2015/06/10 职场文书
vue中div禁止点击事件的实现
2022/04/02 Vue.js