likes
comments
collection
share

在Vue项目中使用xlsx导出表格和下载文件

作者站长头像
站长
· 阅读数 4

对于页面上的表格,经常需要导出。一般有两种场景,一种没有分页的表格前端自己可以直接导出,另一种需要请求后端去下载文件。本文介绍基于在Vue项目中实现这两种场景。


1、前端导出表格

这种场景我们需要借助xlsx插件库。首先安装一下: npm install xlsx -D 然后直接在vue文件中使用,代码如下:

    <template>
      <!-- 这里注意用div包裹 -->
      <div ref="exportTableRef">
        <el-table :data="tableData">
          <el-table-column prop="date" label="Date"  />
          <el-table-column prop="name" label="Name" />
        </el-table>
      </div>
    </template>

    <script setup>
    import { utils, writeFileXLSX } from 'xlsx'

    // 直接导出页面展示的Table
    const exportTableRef = ref()
    const exportTable = () => {
    //这里传递导出Table的DOM
      const wb = utils.table_to_book(exportTableRef.value.getElementsByTagName("TABLE")[0])
      writeFileXLSX(wb, '导出文件名.xlsx')
    }
    exportTable()
    </script>

1.1、注意传递的Dom

上述代码中,我们先看看table_to_book传递的DOM是什么样子的,如下图:

在Vue项目中使用xlsx导出表格和下载文件

哦!是一个table的DOM,这样传递没问题。但如果遇到ElementUI的表格是分header和body的情况,如下图:

在Vue项目中使用xlsx导出表格和下载文件

这也就是为什么需要div包裹table,如果出现header和body分开的情况,直接传递tableau.value这个外层div即可。我们再来看看这个tableau.value是啥,如下图:

在Vue项目中使用xlsx导出表格和下载文件

1.2、额外的逻辑处理

除了直接导出页面上展示的表格,我们通常还需要在此基础上处理表格数据,比如导出的excel内容里小数改为百分比、去掉某一列等等。

这种情况,首先复制一份表格,把它隐藏,导出时操作这个隐藏的表格即可。代码如下:

    <template>
      <!-- 页面上展示的表格 -->
      <el-table :data="tableData">
         <el-table-column prop="date" label="Date"  />
         <el-table-column prop="name" label="Name" />
      </el-table>
      <!-- 需要导出的Table,隐藏起来。注意如果需要操作数据,则复制一份 -->
      <div ref="exportTableRef" v-show="false">
        <el-table ref="table" :data="tableDataCopy">
          <el-table-column prop="date" label="Date"  />
          <el-table-column prop="name" label="Name" />
        </el-table>
      </div>
    </template>

    <script setup>
    import { utils, writeFileXLSX } from 'xlsx'

    // 根据展示的表格数据,同步导出的表格数据tableDataCopy
    watch(
      () => tableData,
      (val) => {
        tableDataCopy = val
      }
    )

    // 经过处理导出文件
    const exportTableRef = ref(), table = ref()
    const exportTable = async () => {

        // 处理逻辑,得到tableDataCopy
        // ......
        
        // 如果你修改了表格,需要等待页面渲染喔~
        await nextTick()

        const wb = utils.table_to_book(exportTableRef.value.getElementsByTagName("TABLE")[0])
        writeFileXLSX(wb, '导出文件名.xlsx')
     
    }
    //调用导出表格功能
    exportTable()
    </script>

1.3、百分比变小数?年月份输出需要格式化?

如果我们的表格里有百分比,但是通过上述代码导出后,发现变成了小数,如下图:

在Vue项目中使用xlsx导出表格和下载文件

有两种解决方式:

1、使用el-table的话,在table_to_book参数增加一项raw,如下:

    const wb = utils.table_to_book(tableau.value, { raw: true })

2、如果使用自定义表格的话,在td增加data-t="s"。当指定了raw:true选项时,xlsx解析器将生成文本单元格。当未指定该选项或将其设置为false时,xlsx解析器将尝试解释每个TD元素的文本。

要覆盖特定单元格的转换,可以将以下数据属性添加到各个TD元素:

在Vue项目中使用xlsx导出表格和下载文件

我们来实际使用下,看看效果:

    <table border id="abc">
          <tr>
            <th>普通</th>
            <th>data-t=s</th>
            <th>data-v</th>
            <th>data-z</th>
            <th>data-z2</th>
          </tr>
          <tr>
            <td>2012-12-03</td>
            <td data-t="s">2012-12-03</td>
            <td data-t="n" data-v="41246">2012-12-03</td>
            <td data-t="n" data-v="41246" data-z="yyyy-mm-dd">2012-12-03</td>
            <td data-t="n" data-v="41246" data-z="yyyy年mm月dd日">2012-12-03</td>
          </tr>
          <tr>
            <td>100%</td>
            <td data-t="s">100%</td>
            <td data-t="n" data-v="100">100%</td>
            <td data-t="n" data-v="0.25" data-z="0.00%">25.00%</td>
            <td data-t="n" data-v="1000" data-z="#,##0">1000</td>
          </tr>
        </table>
        /* find the table element in the page */
        var tbl = document.getElementById('abc');
        /* create a workbook */
        var wb = XLSX.utils.table_to_book(tbl);
        /* export to file */
        XLSX.writeFile(wb, "SheetJSTable.xlsx");

页面上的表格:

在Vue项目中使用xlsx导出表格和下载文件

导出的excel:

在Vue项目中使用xlsx导出表格和下载文件



2、后端传递,前端下载

后端一般写的都是get请求,前端有两种方式下载:

1、创建a标签模拟点击下载;

2、获取blob文件流。

2.1、创建a标签点击下载

这种方式最简单,不需要前端处理。

    const link = document.createElement('a')
    link.style.display = 'none'
    link.href = '后端的url'
    // 不要加下面这句,会导致页面闪烁,用户体验不好
    // link.setAttribute('target', '_blank') 
    document.body.appendChild(link)
    link.click()

2.2、获取blob文件流

如果需要前端获取文件名、分段下载,则适用这种方法。这个一般都是写成工具类。

首先看看它的响应头:

在Vue项目中使用xlsx导出表格和下载文件

其中Content-Disposition携带了filname,这样我们可以拿到后端的文件名。(如果没有,让后端在请求头里带一个fileName就好啦)

接着,展示:

第一步,api请求写一个get blob(api/index.js)

    export function getExportFile(url) {
      return request({
        url: url, // 后端提供的url
        method: 'get',
        responseType: 'blob', // 使用Blob格式接收
      })
    }

第二步,axios设置拦截(utils/request.js)

    /**
     * respone拦截器
     */
    service.interceptors.response.use(
      (response) => {
        const res = response.data
        // 拦截blob文件流,取下文件名
        if (response.config.responseType === 'blob') {
          if (res.type && res.type == 'application/force-download') {
            // 看下响应头里的Content-Disposition,里面会有文件名
            // 也可以让后端把文件名放在响应头里,像这里,后端给我放在了filename里
            res.fileName = response.headers['filename'] || ''
          }
          return res
        }
        return res
      },
      (error) => {}
    )

第三步,写download工具类(utils/FileUtils.js)

/**
     * 文件导出函数,支持多种文件类型和浏览器
     * @param {Blob} res - 下载流,应为Blob对象
     * @param {string} fileName - 导出文件名
     */
    import { ElMessage } from 'element-plus'
    export function download(res, fileName) {
      // 如果下载流不存在,直接返回
      if (!res) {
        return
      }
      // 判断下载流的类型
      if (
        res.type === 'application/octet-stream' ||
        res.type === 'application/force-download' ||
        res.type === 'text/xml' ||
        res.type === 'application/zip' ||
        res.type === 'application/csv' ||
        res.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
      ) {
        // 如果是IE或IE内核的浏览器
        if (window.navigator.msSaveBlob) {
          // IE以及IE内核的浏览器
          try {
            // 使用msSaveBlob方法保存文件
            window.navigator.msSaveBlob(res, fileName)
          } catch (e) {
            console.log(e)
          }
        } else {
          // 如果是其他浏览器,创建一个链接并模拟点击来下载文件
          const url = window.URL.createObjectURL(new Blob([res]))
          const link = document.createElement('a')
          link.style.display = 'none'
          link.href = url
          link.setAttribute('download', fileName) // 文件名
          document.body.appendChild(link)
          link.click()
          // 下载完成后移除链接元素并释放Blob对象
          document.body.removeChild(link)
          // 释放掉blob对象
          window.URL.revokeObjectURL(url)
        }
      } else {
        // 如果下载流的类型不是文件,尝试解析为JSON
        var reader = new FileReader()
        reader.onload = function(event) {
          const result = JSON.parse(reader.result)
          // 如果解析结果中包含错误信息,显示警告
          if (result.code !== undefined && result.code !== '1') {
            warn(result.desc)
          } else if (result.errorInfo && result.errorInfo.length > 0) {
            warn(result.errorInfo[0].errorMessage)
          }
        }
        reader.readAsText(new Blob([res]))
      }
    }

    /**
     * 显示警告消息
     * @param {string} message - 消息内容
     */
    export const warn = message => {
      ElMessage({
        message: message != null ? message : '操作失败', // 如果消息内容不为空,则显示消息内容,否则显示默认消息"操作失败"
        type: 'warning', // 消息类型为警告
        showClose: true, // 显示关闭按钮
        center: true, // 消息居中显示
        duration: 1000 // 消息显示持续时间为1秒
      })
    }

第四步,使用(index.vue)

    import { download } from '@/utils/FileUtils'

    API.getExportFile(res.data)
      .then((res) => {
        if (res.size > 0) {
          //res.fileName是axios拦截请求时获取的,decodeURIComponent解码一下
          let filename = decodeURIComponent(res.fileName)
          filename = '文件名'+ '.' + filename.split('.')[1]
          // 工具类download
          download(res, filename)
        }
    })
    .catch(() => {
      exportLoading.value = false
    })

以上就是本文的所有内容啦,遇到了就记下来。主要介绍了前端使用xlsx下载表格和格式化表格内容、接收后端文件流下载文件。献丑了,欢迎大神批评指教~~~

如果对你有帮助的话,不妨帮我评论点赞啦!!!