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 相关文章推荐
jquery.blockUI.js上传滚动等待效果实现思路及代码
Mar 18 Javascript
JavaScript动态操作表格实例(添加,删除行,列及单元格)
Nov 25 Javascript
JQuery 给元素绑定click事件多次执行的解决方法
Sep 09 Javascript
浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入
Jan 19 Javascript
jQuery事件绑定on()与弹窗实现代码
Apr 28 Javascript
AngularJs bootstrap搭载前台框架——准备工作
Sep 01 Javascript
利用百度echarts实现图表功能简单入门示例【附源码下载】
Jun 10 Javascript
layer弹出框确定前验证:弹出消息框的方法(弹出两个layer)
Sep 21 Javascript
Element InfiniteScroll无限滚动的具体使用方法
Jul 27 Javascript
JS实现audio音频剪裁剪切复制播放与上传(步骤详解)
Jul 28 Javascript
原生js实现拖拽移动与缩放效果
Aug 24 Javascript
Antd的table组件表格的序号自增操作
Oct 27 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
Win7 64位系统下PHP连接Oracle数据库
2014/08/20 PHP
php字符串截取函数用法分析
2014/11/25 PHP
php防止sql注入简单分析
2015/03/18 PHP
Smarty foreach控制循环次数的一些方法
2015/07/01 PHP
PHP实现求两个字符串最长公共子串的方法示例
2017/11/17 PHP
浅谈PHP进程管理
2019/03/08 PHP
javascript代码编写需要注意的7个小细节小结
2011/09/21 Javascript
Jquery图形报表插件 jqplot简介及参数详解
2012/10/10 Javascript
基于JavaScript实现通用tab选项卡(通用性强)
2016/01/07 Javascript
原生javascript实现匀速运动动画效果
2016/02/26 Javascript
基于BootStrap的Metronic框架实现页面链接收藏夹功能按钮移动收藏记录(使用Sortable进行拖动排序)
2016/08/29 Javascript
详解nodejs的express如何自动生成项目框架
2017/07/12 NodeJs
纯html+css+javascript实现楼层跳跃式的页面布局(实例代码)
2017/10/25 Javascript
360提示[高危]使用存在漏洞的JQuery版本的解决方法
2017/10/27 jQuery
JS获取url参数,JS发送json格式的POST请求方法
2018/03/29 Javascript
vue-cli3 项目优化之通过 node 自动生成组件模板 generate View、Component
2019/04/30 Javascript
vue-父子组件和ref实例详解
2019/11/10 Javascript
vue中使用腾讯云Im的示例
2020/10/23 Javascript
用Python编写生成树状结构的文件目录的脚本的教程
2015/05/04 Python
Python面向对象之静态属性、类方法与静态方法分析
2018/08/24 Python
pandas和spark dataframe互相转换实例详解
2020/02/18 Python
Python 实现国产SM3加密算法的示例代码
2020/09/21 Python
python中append函数用法讲解
2020/12/11 Python
俄罗斯街头服装品牌:Black Star Wear
2017/03/01 全球购物
澳大利亚第一的设计师礼服租赁网站:GlamCorner
2017/08/13 全球购物
美国时尚假发购物网站:Wigsbuy
2019/04/06 全球购物
工程造价与管理专业应届生求职信
2013/11/23 职场文书
2014乡镇“三八”国际劳动妇女节活动总结
2014/03/01 职场文书
高中班主任评语大全
2014/04/25 职场文书
幸福中国演讲稿
2014/09/12 职场文书
乡镇镇长个人整改措施
2014/10/01 职场文书
党的群众路线教育实践活动心得体会(乡镇)
2014/11/03 职场文书
食堂采购员岗位职责
2015/04/03 职场文书
2015年店长工作总结范文
2015/04/08 职场文书
你真的了解redis为什么要提供pipeline功能
2021/06/22 Redis
Mysql 一主多从的部署
2022/05/20 MySQL