Libon

纯前端导出 Excel 文件

通过 xlsx 和 file-saver 实现纯前端导出下载 Excel 文件

npm install xlsx file-saver
import { saveAs } from 'file-saver'
import XLSX from 'xlsx'

function datenum(v, date1904) {
  if (date1904) v += 1462
  const epoch = Date.parse(v)

  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}

function sheet_from_array_of_arrays(data) {
  const ws = {}
  const range = {
    s: {
      c: 10000000,
      r: 10000000
    },
    e: {
      c: 0,
      r: 0
    }
  }

  for (let R = 0; R !== data.length; ++R) {
    for (let C = 0; C !== data[R].length; ++C) {
      if (range.s.r > R) range.s.r = R
      if (range.s.c > C) range.s.c = C
      if (range.e.r < R) range.e.r = R
      if (range.e.c < C) range.e.c = C
      const cell = {
        v: data[R][C]
      }

      if (cell.v === null) continue
      const cell_ref = XLSX.utils.encode_cell({
        c: C,
        r: R
      })

      if (typeof cell.v === 'number') cell.t = 'n'
      else if (typeof cell.v === 'boolean') cell.t = 'b'
      else if (cell.v instanceof Date) {
        cell.t = 'n'
        cell.z = XLSX.SSF._table[14]
        cell.v = datenum(cell.v)
      } else cell.t = 's'

      ws[cell_ref] = cell
    }
  }
  if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range)

  return ws
}

function Workbook() {
  if (!(this instanceof Workbook)) return new Workbook()
  this.SheetNames = []
  this.Sheets = {}
}

function s2ab(s) {
  const buf = new ArrayBuffer(s.length)
  const view = new Uint8Array(buf)

  for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff

  return buf
}

export function export_json_to_excel({
  autoWidth = true,
  bookType = 'xlsx',
  data,
  filename,
  header,
  merges = [],
  multiHeader = []
} = {}) {
  filename = filename || 'excel-list'
  data = [...data]
  data.unshift(header)

  for (let i = multiHeader.length - 1; i > -1; i--) {
    data.unshift(multiHeader[i])
  }

  const ws_name = 'SheetJS'
  const wb = new Workbook()
  const ws = sheet_from_array_of_arrays(data)

  if (merges.length > 0) {
    if (!ws['!merges']) ws['!merges'] = []
    merges.forEach((item) => {
      ws['!merges'].push(XLSX.utils.decode_range(item))
    })
  }

  if (autoWidth) {
    /* 设置worksheet每列的最大宽度*/
    const colWidth = data.map((row) =>
      row.map((val) => {
        /* 先判断是否为null/undefined*/
        if (val === null) {
          return {
            wch: 12
          }
        } else if (val.toString().charCodeAt(0) > 255) {
          /* 再判断是否为中文*/
          return {
            wch: val.toString().length * 3
          }
        } else {
          return {
            wch: val.toString().length + 10
          }
        }
      })
    )
    /* 以第一行为初始值*/
    const result = colWidth[0]

    for (let i = 1; i < colWidth.length; i++) {
      for (let j = 0; j < colWidth[i].length; j++) {
        if (result[j].wch < colWidth[i][j].wch) {
          result[j].wch = colWidth[i][j].wch
        }
      }
    }
    ws['!cols'] = result
  }

  /* add worksheet to workbook */
  wb.SheetNames.push(ws_name)
  wb.Sheets[ws_name] = ws

  const wbout = XLSX.write(wb, {
    bookType,
    bookSST: false,
    type: 'binary'
  })

  saveAs(
    new Blob([s2ab(wbout)], {
      type: 'application/octet-stream'
    }),
    `${filename}.${bookType}`
  )
}

/**
 * 格式化JSON数据
 * @param filterVal
 * @param jsonData
 * @returns {*}
 */
function formatJson(filterVal, jsonData) {
  return jsonData.map((v) =>
    filterVal.map((j) => {
      return v[j] ? v[j] : ''
    })
  )
}

导出单个 sheet

/**
 * JSON数据导出excel
 * @param { name, header, headerKeys, data } 参数集合
 */
export function exportJsonToExcel({ data, filename, header, headerKeys }) {
  filename = filename || `tr-export-${new Date().getTime()}`
  const resultData = formatJson(headerKeys, data)

  export_json_to_excel({
    header,
    data: resultData,
    filename
  })
}

调用

const data = [{name: 'Bob', age: 18}, {name: 'Joe', age: 18}]
const header = ['姓名', '年龄']
const headerKeys = ['name', 'age']

exportJsonToExcel({ data, header, headerKeys })

导出多个 sheet

export function json2excelMuti(tableJson, filenames) {
  const tHeader = []
  const dataArr = []
  const sheetnames = []
  const multiHeader = []
  const merges = []

  for (const i in tableJson) {
    tHeader.push(tableJson[i].tHeader)
    dataArr.push(formatJson(tableJson[i].filterVal, tableJson[i].tableDatas))
    sheetnames.push(tableJson[i].sheetName)
    multiHeader.push(tableJson[i].multiHeader || [])
    merges.push(tableJson[i].merges || [])
  }

  export_json_to_excel({
    multiHeader,
    merges,
    header: tHeader,
    data: dataArr,
    sheetname: sheetnames,
    filename: filenames
  })
}

调用

const data = [
  { data: [{name: 'Bob', age: 18}, {name: 'Joe', age: 18}], tHeader: ['姓名', '年龄'], filterVal: ['name', 'age'], sheetName: 'sheet1' },
  { data: [{name: 'Bob', age: 18}, {name: 'Joe', age: 18}], tHeader: ['姓名', '年龄'], filterVal: ['name', 'age'], sheetName: 'sheet2' }
]

json2excelMuti(data, '导出文件名')
cd ../