首页 > 技术文章 > vue 利用xlsx、xlsx-style、file-saver实现前端导出excel表格 (包括设置单元格居中、边框等样式) antdesignvue、elementui、vxetable 等都适用

lilelile 2022-04-24 15:41 原文

我用的方法是在表格的根组件外层赋一个div用来导出整个表格,所以antdesignvue、elementui、vxetable 或者原生的table写法应该全都适用,此处我用的框架为antdesignvue,包含合并单元格与二级标题,先看最终样式以及导出的效果:

 

 

 导出的excel:

 

 

 

 首先要引入三个库:

npm install file-saver --save
 
npm install xlsx --save
 
npm install xlsx-style --save

 

vue 2.0版本xlsx-style ./cptable' 报错:Can't resolve './cptable' in 'xxxx\nautical-front\node_modules_xlsx 

解决:在vue的config文件中加如下代码

'./cptable': 'var cptable'

 

为了防止代码一致却有运行错误的情况 这里再列出我开发时用的插件的版本号:

"file-saver": "^2.0.5",
"xlsx": "^0.17.0",
"xlsx-style": "^0.8.13"

全部安装好后引入插件:

import XLSX from "xlsx";
import XLSXStyle from "xlsx-style";
import FileSaver from "file-saver";

先根据不同的框架渲染好表格的页面,外层套一层div:

<template>
  <a-card :bordered="false">
    <a-button @click="exportToExcel" >导出</a-button>
    <div id="exportData"  class="css_page_body" ref="css_page_body">
      <a-table :columns="columns"
               :data-source="data"
               bordered
               :pagination="false">
      </a-table>

    </div>
  </a-card>
</template>

下面是导出的方法:

 exportToExcel () {
      let ws = XLSX.utils.table_to_sheet(document.getElementById('exportData'))
      let ws2 = XLSX.utils.table_to_sheet(document.getElementById('exportData'))
      //创建一个workbook对象
      let wb = XLSX.utils.book_new()
      //把worksheet对象添加进workbook对象,第三个参数是excel中sheet的名字
      XLSX.utils.book_append_sheet(wb, ws, '月度统计报表')
      XLSX.utils.book_append_sheet(wb, ws2, '隔离库')
      this.setExlStyle(wb['Sheets']['月度统计报表']); // 设置列宽 字号等 如果无需多余的样式则省略
      this.addRangeBorder(wb['Sheets']['月度统计报表']['!merges'],wb['Sheets']['月度统计报表']) //设置合并行的border
      let wb_out = XLSXStyle.write(wb, { type: 'buffer'})

      try {
        FileSaver.saveAs(new Blob([wb_out], {
          type: 'application/octet-stream'
        }), 'WMS统计报表.xlsx');   // 导出的文件名
      } catch (e) {
        console.log(e, wb_out) ;
      }
      return wb_out;
    },
setExlStyle(data) {
      let borderAll = {  //单元格外侧框线
        top: {
          style: 'thin',
        },
        bottom: {
          style: 'thin'
        },
        left: {
          style: 'thin'
        },
        right: {
          style: 'thin'
        }
      };
      data['!cols'] = [];
      for (let key in data) {
        // console.log(key)
        if (data[key] instanceof Object) {
          data[key].s = {
            border: borderAll,
            alignment: {
              horizontal: 'center',   //水平居中对齐
              vertical:'center'
            },
            font:{
              sz:11
            },
            bold:true,
            numFmt: 0
          }
          data['!cols'].push({wpx: 115});
        }
      }
      return data;
    },
    addRangeBorder (range, ws) {
      let cols = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
      range.forEach(item => {
        console.log(item)
        let style = {
          s: {
            border: {
              top: { style: 'thin' },
              left: { style: 'thin' },
              bottom: { style: 'thin' },
              right: { style: 'thin' }
            }
          }
        }
        // 处理合并行
        for (let i = item.s.c; i <= item.e.c; i++) {
          ws[`${cols[i]}${Number(item.e.r) + 1}`] = ws[`${cols[i]}${Number(item.e.r) + 1}`] || style
          // 处理合并列
          for (let k = item.s.r + 2; k <= item.e.r + 1; k++) {
            ws[cols[i] + k] = ws[cols[k] + item.e.r] || style
          }
        }
      })
      return ws;
    },

以上就是全部有关导出表格的内容,以下是全部的代码:

<template>
  <a-card :bordered="false">
    <a-button @click="exportToExcel" >导出</a-button>
    <div id="exportData"  class="css_page_body" ref="css_page_body">
      <a-table :columns="columns"
               :data-source="data"
               bordered
               :pagination="false">
      </a-table>

    </div>
  </a-card>
</template>

<script>
import XLSX from "xlsx";
import XLSXStyle from "xlsx-style";
import FileSaver from "file-saver";

export default {
  data () {
    return {
      columns:[
        {
          title: '学校', dataIndex: 'school', width: '25%',
          scopedSlots: { customRender: 'school' }, align: 'center', key: 'school',
          customRender (_, row) {
            return {
              children: row.school,
              attrs: {
                rowSpan: row.schoolRowSpan
              }
            }
          }
        },
        {
          title: '年级', dataIndex: 'grade', width: '25%',
          scopedSlots: { customRender: 'grade' }, align: 'center', key: 'grade',
          customRender (_, row) {
            return {
              children: row.grade,
              attrs: {
                rowSpan: row.gradeRowSpan
              }
            }
          }
        },
        {
          title: '班级', dataIndex: 'class', width: '25%',
          scopedSlots: { customRender: 'class' }, align: 'center'
        },
        {
          title: '姓名', dataIndex: 'name', width: '25%',
          scopedSlots: { customRender: 'name' }, align: 'center'
        },
        {
          title: '4月1日',
          children: [
            {
              title: '周五',
              dataIndex: 'companyAddress',
              width: 100,
            }
          ],
        },
        {
          title: '4月1日',
          children: [
            {
              title: '周五',
              dataIndex: 'companyAddress2',
              width: 100,
            }
          ],
        },
        {
          title: '4月1日',
          children: [
            {
              title: '周五',
              dataIndex: 'companyAddress3',
              width: 100,
            }
          ],
        },
        {
          title: '4月1日',
          children: [
            {
              title: '周五',
              dataIndex: 'companyAddress4',
              width: 100,
            }
          ],
        },
      ],
      data:[

      ]
    }
  },
  methods: {
    // 合并单元格
    rowSpan (key, data) {
      const arr = data
        .reduce((result, item) => {
          if (result.indexOf(item[key]) < 0) {
            result.push(item[key])
          }
          return result
        }, [])
        .reduce((result, keys) => {
          const children = data.filter(item => item[key] === keys)
          result = result.concat(
            children.map((item, index) => ({
              ...item,
              [`${key}RowSpan`]: index === 0 ? children.length : 0
            }))
          )
          return result
        }, [])
      return arr
    },
    // 表格合并
    mergeRowCell (data) {
      let tableData = this.rowSpan('school', data)
      tableData = this.rowSpan('grade', tableData)
      this.data = tableData

    },
    exportToExcel () {
      let ws = XLSX.utils.table_to_sheet(document.getElementById('exportData'))
      let ws2 = XLSX.utils.table_to_sheet(document.getElementById('exportData'))
      //创建一个workbook对象
      let wb = XLSX.utils.book_new()
      //把worksheet对象添加进workbook对象,第三个参数是excel中sheet的名字
      XLSX.utils.book_append_sheet(wb, ws, '月度统计报表')
      XLSX.utils.book_append_sheet(wb, ws2, '隔离库')
      this.setExlStyle(wb['Sheets']['月度统计报表']); // 设置列宽 字号等
      this.addRangeBorder(wb['Sheets']['月度统计报表']['!merges'],wb['Sheets']['月度统计报表'])
      let wb_out = XLSXStyle.write(wb, { type: 'buffer'})

      try {
        FileSaver.saveAs(new Blob([wb_out], {
          type: 'application/octet-stream'
        }), 'WMS统计报表.xlsx');   //trade-publish.xlsx 为导出的文件名
      } catch (e) {
        console.log(e, wb_out) ;
      }
      return wb_out;
    },
    setExlStyle(data) {
      let borderAll = {  //单元格外侧框线
        top: {
          style: 'thin',
        },
        bottom: {
          style: 'thin'
        },
        left: {
          style: 'thin'
        },
        right: {
          style: 'thin'
        }
      };
      data['!cols'] = [];
      for (let key in data) {
        // console.log(key)
        if (data[key] instanceof Object) {
          data[key].s = {
            border: borderAll,
            alignment: {
              horizontal: 'center',   //水平居中对齐
              vertical:'center'
            },
            font:{
              sz:11
            },
            bold:true,
            numFmt: 0
          }
          data['!cols'].push({wpx: 115});
        }
      }
      return data;
    },
    addRangeBorder (range, ws) {
      let cols = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
      range.forEach(item => {
        console.log(item)
        let style = {
          s: {
            border: {
              top: { style: 'thin' },
              left: { style: 'thin' },
              bottom: { style: 'thin' },
              right: { style: 'thin' }
            }
          }
        }
        // 处理合并行
        for (let i = item.s.c; i <= item.e.c; i++) {
          ws[`${cols[i]}${Number(item.e.r) + 1}`] = ws[`${cols[i]}${Number(item.e.r) + 1}`] || style
          // 处理合并列
          for (let k = item.s.r + 2; k <= item.e.r + 1; k++) {
            ws[cols[i] + k] = ws[cols[k] + item.e.r] || style
          }
        }
      })
      return ws;
    },
  },
  mounted() {
    this.data = []
    for(let i=0;i<4;i++){
      this.data.push( { school: '林州一中', grade: '高一', class: '二班', name: '徐强' } )
      this.data.push( { school: '林州二中', grade: '高三', class: '一班', name: '徐强子' } )
    }
    this.mergeRowCell(this.data)
  }
}
</script>

<style lang="less" scoped>
</style>

 参考的文章:

vue table复杂表格导出excel(支持多表头、合并单元格、边框、居中、背景等自定义样式)

前端复杂表格导出excel,一键导出 Antd Table 看这篇就够了(附源码)

vue页面table导出excel

vue导出Excel表格,utils未定义是版本原因

js xlsx使用说明(主要讲导出表格与设置表头相关)

XLSX-STYLE 的用法

JavaScript导出excel文件,并修改文件样式

推荐阅读