Vue3+SheetJS 导出表格

316 阅读1分钟

效果如图: 图片.png

图片.png

思路

  1. 参照官方文档,安装xlsx-0.20.3
  2. 简单表格根据数据导出,将对象数组整理成二维数组(Array of Arrays, aoa),调用aoa_to_sheet创建工作表,将工作表加入新建的工作簿,导出xlsx文件
  3. 复杂表格根据DOM导出,获取<table>元素(若使用组件库,可能需要自行拼接),调用table_to_book创建工作簿,导出xlsx文件

代码

Vue函数、生命周期钩子等已通过unplugin-auto-import自动导入,Element Plus已通过unplugin-vue-components自动导入

// src/utils/xlsx.js
import * as XLSX from 'xlsx'

export const elt2xlsx = (elt, filename = 'export') => {
  const wb = XLSX.utils.table_to_book(elt)
  XLSX.writeFile(wb, `${filename}.xlsx`, { bookType: 'xlsx', bookSST: true })
}

export const aoa2xlsx = (aoa, filename = 'export') => {
  const ws = XLSX.utils.aoa_to_sheet(aoa)
  const wb = XLSX.utils.book_new()
  XLSX.utils.book_append_sheet(wb, ws)
  XLSX.writeFile(wb, `${filename}.xlsx`, { bookType: 'xlsx', bookSST: true })
}

以上为封装导出函数

<template>
  <div>
    <div style="margin-block: 8px">
      <el-button @click="exportByAoa">导出(数据)</el-button>
    </div>
    <el-table :data="tableData">
      <el-table-column prop="date" label="日期" width="150" />
      <el-table-column prop="name" label="姓名" width="120" />
      <el-table-column prop="address" label="地址" />
    </el-table>

    <div style="margin-block: 8px">
      <el-button @click="exportByDom">导出(DOM)</el-button>
    </div>
    <el-table id="table" :data="tableData2">
      <el-table-column v-for="col in tableCols" :key="col[0]" :label="col[0]">
        <el-table-column
          v-for="subCol in col[1]"
          :key="subCol"
          :prop="subCol"
          :label="subCol.split('_')[1]"
        />
      </el-table-column>
    </el-table>
  </div>
</template>

<script setup>
import { elt2xlsx, aoa2xlsx } from '@/utils/xlsx'

const tableData = Array.from({ length: 3 }, (_, i) => ({
  date: `2024-08-1${i}`,
  name: 'Tom',
  address: 'No. 189, Grove St, Los Angeles'
}))

const exportByAoa = () => {
  const headMap = new Map([
    ['日期', 'date'],
    ['姓名', 'name'],
    ['地址', 'address']
  ])
  const cols = Array.from(headMap.values())
  const aoa = tableData.map((row) => cols.map((col) => row[col]))
  aoa.unshift([...headMap.keys()])
  aoa2xlsx(aoa, 'ExportByAoa')
}

const mockData = Array.from({ length: 3 }, () => ({
  条目1: {
    值1: Math.random() * 100,
    值2: Math.random() * 100
  },
  条目2: {
    值1: Math.random() * 100
  },
  条目3: {
    值1: Math.random() * 100,
    值2: Math.random() * 100
  }
}))
const tableData2 = shallowRef([])
const tableCols = shallowRef([])
const renderTable = () => {
  const colMap = new Map()
  tableData2.value = mockData.map((di) => {
    const row = {}
    Object.entries(di).forEach(([key, subItems]) => {
      const subCols = colMap.get(key) ?? []
      Object.entries(subItems).forEach(([subKey, val]) => {
        const newKey = `${key}_${subKey}` // 条目1.值1 -> '条目1_值1'
        row[newKey] = val.toFixed(2)
        if (subCols.includes(newKey)) return
        subCols.push(newKey)
      })
      colMap.set(key, subCols)
    })
    return row
  })
  tableCols.value = Array.from(colMap) // ['条目1', ['条目1_值1', '条目1_值2']][]
}

const exportByDom = () => {
  const tableEl = document.querySelector('#table')
  const tHeadEl = tableEl.querySelector('.el-table__header').querySelector('thead').cloneNode(true)
  const tBodyEl = tableEl.querySelector('.el-table__body').querySelector('tbody').cloneNode(true)
  const elt = document.createElement('table')
  elt.appendChild(tHeadEl)
  elt.appendChild(tBodyEl)
  elt2xlsx(elt, 'ExportByDom')
}

onBeforeMount(() => {
  renderTable()
})
</script>

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