vxetable数据导出

发布于:2025-09-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

1. 说明

  1. vxe-table有默认的自带导出功能,但导出只支持默认表头是一级的,如果表头是多级嵌套的,导出后就只有最低级的表头
  2. 自定义实现导出多层级表头

2. 代码相关依赖

  1. 安装依赖
npm i xlsx@0.16.9 file-saver@2.0.5
  1. 引入外部 xslx
<script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
  1. 本地自定义代码实现

3.1 export.js

import { defaultViewValueFormat, defaultViewValueFormatType, getCellValueAlign, getCellViewTitle } from './exportUtil.js'
import XlsxTool from 'xlsx'
import FileSaver from 'file-saver'
export class Export {
  constructor(gloabelConfig = {}) {
    this.XlsxTool = XlsxTool
    this.xlsx = window.xlsx
    this.aTag = document.createElement('a')
    this.exportHistory = [] // 导出历史记录
    this.exportDefaultConfig = {
      saveType: '.xlsx',
      fileName: 'export', // 文件名
      dataType: 'fullData',
      isExportOnlySourceField: false, // 是否只导出数据源表头字段,
      isExportOnlyViewTitle: false, // 是否只导出数据表头名称,
      isExportHead: true, // 是否导出表头
      exportViewTitleType: 'nestTitle', // nestTitle(嵌套表头), singTitle(单表头)
      isExportFooter: false, // 是否导出表尾部
      isExportOriginalData: false, // 是否导出源数据
      isExportData: true, // 是否导出数据
      columns: [], // 表头配置

      datas: [], // 源数据,
      selection: [], // 选中数据
      isExportTree: false, // 是否是树形数据
      index: true, // 是否添加序号,
      ignoreColsTypes: ['dragSort', 'checkbox', 'radio', 'optionRow', 'expand', 'attach', 'ach', 'list', 'attachlist', 'gloableOptionRow'], // 忽略导出的列类型
      viewValueFormat(value, row, column) { // 视图数据格式化方法
        // return value
      },
      footerMethods({ columns, data }) {
        return columns.map((column, columnIndex) => {
          //  combinedType: ['average', 'total'],
          let columnIndexText = '合计'
          let combinedType = 'total'
          if (columnIndex === 0) {
            return columnIndexText
          } else if (Array.isArray(column.combinedType) && column.combinedType.indexOf(combinedType) >= 0) {
            try {
              let result = data.map((item, index) => {
                let value = (typeof (item[column.field]) === 'number' || typeof (item[column.field]) === 'string') ? parseFloat(item[column.field].toString().split(',').join('')) : 0
                return isNaN(parseFloat(value)) ? 0 : parseFloat(value)
              })
              if (result.length) {
                result = result.reduce((prev, next) => {
                  return prev + next
                })
                result = (result).toFixed(2)
                return result
              } else {
                return 0
              }
            } catch (e) {
              throw (e)
            }
          } else {
            return ''
          }
        })
      }
    }
    this.exportDefaultConfig = Object.assign({}, this.exportDefaultConfig, gloabelConfig)
  }
  exportExcel(curUserConfig = {}, context, cb) {
    this.context = context
    // 导出excel
    this.init(curUserConfig)
    this.xGridContext = context.$refs.xGrid
    this.curExportConfig.columns = this.curExportConfig.columns.filter(item => !item.isHide) // 过滤不显示项
    let columns = this.curExportConfig.columns
    this.generateExportRowsMap(this.curExportConfig.columns)
    this.curExportConfig.datas = this.curExportConfig.datas || []
    // this.curExportConfig.datas = this.curExportConfig[this.curExportConfig.dataType]
    // moneyUnitTransform 标识导出金额是否按选取的单位元和万元展示转换
    if (this.curExportConfig.isExportOnlySourceField || this.curExportConfig.isExportOnlyViewTitle) {
      this.generateExportSourceField(this.curExportConfig.isExportOnlySourceField ? 'field' : 'title')
    } else {
      if (this.curExportConfig.isExportData) {
        this.generateExportViewDataSource()
      }
      if (this.curExportConfig.isExportOriginalData) {
        this.generateExportOriginalDataSource()
      }
    }
    if (this.curExportConfig.saveType === '.xlsx') {
      this.exportDownload(this.Xlsx)
    } else {
      this.downloadCsv()
    }
  }
  // 获取金额表格项
  getMoneyColumns(dataColums = [], type = 'money') {
    let result = {}
    getNewDataColumns(dataColums)
    function getNewDataColumns(dataColums) {
      dataColums.forEach(item => {
        if (item.children) {
          getNewDataColumns(item.children)
        } else {
          if (item.type === type && !result[item.field]) {
            result[item.field] = item
          }
        }
      })
    }
    return Object.values(result)
  }
  init(obj) {
    // 初始化并记录上次导出历史
    this.headerRows = 1
    this.dataColMap = []
    this.headerRowsMap = {}
    this.curExportConfig = Object.assign({}, this.exportDefaultConfig, obj)
    this.curExportConfig.viewValueFormat = defaultViewValueFormat
    this.curExportConfig.viewValueFormatType = defaultViewValueFormatType
    this.setExportColumns()
    // let xlsx = window.xlsx
    this.Xlsx = new this.xlsx.File()
    this.exportHistory.push({ exportConfig: this.curExportConfig, exportDataSource: this.Xlsx })
    this.exportHistory = this.exportHistory.reverse().slice(0, 20).reverse()
  }
  setExportColumns() {
    // 设置导出列
    let self = this
    this.curExportConfig.columns = this.curExportConfig.columns.filter((item) => {
      return self.curExportConfig.ignoreColsTypes.indexOf(item.type) < 0
    })
  }
  generateExportRowsMap(columns, curRowIndex = 1, pcCount) {
    // 生成视图数据导出元数据 列映射数据
    let self = this
    columns.forEach((rowConlum, index) => {
      if (curRowIndex > self.headerRows) {
        self.headerRows = curRowIndex
      }
      if (Array.isArray(rowConlum.children) && rowConlum.children.length) {
        self.generateExportRowsMap(rowConlum.children, curRowIndex + 1, rowConlum.children.length)
      } else {
        // if (rowConlum !== undefined) {
        //   rowConlum.pcCount = pcCount
        //   // self.dataColCellsMap[rowConlum.field] = pcCount
        // }
        self.dataColMap.push(rowConlum)
      }
    })
  }
  generateVisibleHeaderRowsMap() {
    // 生成嵌套表头行映射数据
    for (let i = 1; i <= (this.curExportConfig.exportViewTitleType === 'nestTitle' ? this.headerRows : 1); i++) {
      this.headerRowsMap['rows' + i] = this.sheetVisibleData.addRow()
    }
  }
  generateExportSourceField(type) {
    let self = this
    if (this.curExportConfig.exportViewTitleType === 'nestTitle' && this.curExportConfig.isExportOnlyViewTitle) {
      this.sheetVisibleData = this.Xlsx.addSheet(this.curExportConfig.fileName.replace('.xlsx', ''))
      this.generateVisibleHeaderRowsMap()
      this.riverGenerateExportNestedHeader(this.curExportConfig.columns)
      this.setSheetCol(this.sheetVisibleData, 'field')
    } else {
      this.sheetFieldData = this.Xlsx.addSheet(this.curExportConfig.fileName.replace('.xlsx', ''))
      let newrow = self.sheetFieldData.addRow()
      self.dataColMap.forEach((column, columnIndex) => {
        let cell = newrow.addCell()
        cell.value = column[type]
        cell.hMerge = 0
        cell.vMerge = 0
        self.generateCellVisibletitleStyle(cell)
      })
    }
  }
  generateExportViewDataSource() {
    // 生成视图数据导出数据源
    if (this.curExportConfig.isExportOriginalData) {
      this.sheetVisibleData = this.Xlsx.addSheet('视图数据')
    } else {
      this.sheetVisibleData = this.Xlsx.addSheet(this.curExportConfig.fileName.replace('.xlsx', ''))
    }
    this.generateVisibleHeaderRowsMap()
    if (this.curExportConfig.isExportHead) {
      if (this.curExportConfig.exportViewTitleType === 'nestTitle') {
        this.riverGenerateExportNestedHeader(this.curExportConfig.columns)
      } else {
        this.riverGenerateExportNestedHeader(this.dataColMap)
      }
    }
    this.setSheetCol(this.sheetVisibleData)
    // 过滤掉选中导出树数据重复数据
    if (
      this.curExportConfig.isExportTree &&
      this.curExportConfig.datas.length &&
      this.curExportConfig.datas[0].children &&
      Array.isArray(this.curExportConfig.datas[0].children) &&
      this.curExportConfig.datas[0].children?.length > 0
    ) {
      this.curExportConfig.datas = this.curExportConfig.datas.filter((item) => {
        return item.children && item.children.length
      })
    }
    this.generateExportViewDataBody(this.curExportConfig.datas)
    if (this.curExportConfig.isExportFooter) {
      this.generateExportViewDatafooter(this.curExportConfig.datas)
    }
  }
  setSheetCol(sheet, type = 'title') {
    // 设置 列信息
    // collapsed: false
    // hidden: false
    // max: 1
    // min: 1
    // numFmt: ""
    // outlineLevel: 0
    // style: Style
    // align: (...)
    // applyAlignment: (...)
    // applyBorder: (...)
    // applyFill: (...)
    // applyFont: (...)
    // border: (...)
    // fill: (...)
    // font: (...)
    // width:0
    // namedStyleIndex: (...)
    let self = this
    sheet.cols.forEach((col, colIndex) => {
      col.width = self.getColWidth(colIndex, type)
      col.max = 2000
      col.field = this.dataColMap[colIndex]['field']
      if (this.dataColMap && this.dataColMap[colIndex] && this.dataColMap[colIndex]['numFmt']) { // 设置单元格式处理(处理二级测算导出模板,定密时间按 2025-07-30 输入后,xslx默认转为2025/07/30显示,导入后获取值是一串数字,与原值不一致)
        col.numFmt = this.dataColMap[colIndex]['numFmt']
      }
    })
  }
  getColWidth(colIndex, type) {
    // 获取colwidth
    let text = this.dataColMap[colIndex][type]
    if (!isNaN(parseInt(this.dataColMap[colIndex].width))) {
      return Math.ceil(this.dataColMap[colIndex].width / 10)
    } else if (text.length < 5) {
      return 15
    }
    return Math.ceil(this.getStrByte(text) * 2)
  }
  getStrByte(str) {
    //  获取字符串字节数
    let arr = str.split('')
    let len = arr.length
    let count = 0
    for (let i = 0; i < len; i++) {
      let s = escape(arr[i])
      // 例如:s = '你'; escape(a); "%u4F60"
      if (s.indexOf('%u') >= 0) {
        count = count + 2
      } else {
        count++
      }
    }
    return count
  }
  generateExportOriginalDataSource() {
    // 生成源数据导出数据源
    this.sheetOriginalData = this.Xlsx.addSheet('源数据')
    if (this.curExportConfig.isExportHead) {
      this.generateExportOriginalDataHeader()
    }
    this.generateExportOriginalDataBody(this.curExportConfig.datas)
    this.setSheetCol(this.sheetOriginalData, 'field')
  }
  riverGenerateExportNestedHeader(columns, curRowIndex = 1, pCell) {
    // 递归生成嵌套表头
    let self = this
    columns.forEach((column, index) => {
      if (Array.isArray(column.children) && column.children.length) {
        let cell0 = self.generateCurNestedHeaderCell(self.headerRowsMap['rows' + curRowIndex], column, curRowIndex, pCell)
        self.riverGenerateExportNestedHeader(column.children, curRowIndex + 1, cell0)
      } else {
        column.title = column.title.split('(')[0]
        self.generateCurNestedHeaderCell(self.headerRowsMap['rows' + curRowIndex], column, curRowIndex, pCell)
      }
    })
  }
  generateCurNestedHeaderCell(row, column, curRows, pCell) {
    // 生成表头数据并补齐空位
    // cellType: (...)
    // date1904: (...)
    // formula: (...)
    // hMerge: (...)
    // hidden: (...)
    // numFmt: (...)
    // row: (...)
    // vMerge: (...)
    // _style: (...)
    // _value: (...)
    let cell0 = {}
    // moneyUnitTransform 标识导出金额是否按选取的单位元和万元展示转换
    const unit = '元'
    if (Array.isArray(column.children) && column.children.length) {
      let { floorLength } = this.getFloorData(column.children)
      // for (let i = 0; i < column.children.length; i++) {
      for (let i = 0; i < floorLength; i++) {
        let cell = row.addCell()
        if (i === 0) {
          cell.value = getCellViewTitle(column, unit)
          cell.hMerge = floorLength - 1
          cell.vMerge = 0
          cell0 = cell
        } else {
          cell.value = getCellViewTitle(column, unit)
          cell.hMerge = 0
          cell.vMerge = 0
        }
        this.generateCellVisibletitleStyle(cell)
      }
    } else {
      let cellSing = row.addCell()
      cellSing.value = getCellViewTitle(column, unit)
      cellSing.hMerge = 0
      cellSing.vMerge = this.curExportConfig.exportViewTitleType === 'nestTitle' ? this.headerRows - curRows : 0
      cell0 = cellSing
      this.generateCellVisibletitleStyle(cellSing)
      if (this.curExportConfig.exportViewTitleType === 'nestTitle') {
        this.supplementHeaderVMergeCells(row, column, curRows, pCell)
      }
      return cell0
    }
  }
  supplementHeaderVMergeCells(row, column, curRows, pCell) {
    // 当表头有合计行时补齐行单元格空位
    for (let i = curRows + 1; i <= this.headerRows; i++) {
      if (pCell) {
        let cells = pCell.vMerge
        for (let i = 0; i < cells; i++) {
          this.headerRowsMap['rows' + i].addCell()
        }
      } else {
        this.headerRowsMap['rows' + i].addCell()
      }
    }
  }
  getFloorData(treeData) {
    // 获取当前嵌套子集的单元格个数以及深度
    let max = 0
    let floorCols = []
    function each(data, floor) {
      data.forEach((rowConlum, index) => {
        if (floor > max) {
          max = floor
        }
        if (Array.isArray(rowConlum.children) && rowConlum.children.length) {
          each(rowConlum.children, floor + 1, rowConlum.children.length)
        } else {
          floorCols.push(rowConlum)
        }
      })
    }
    each(treeData, 1)
    return {
      floor: max,
      floorLength: floorCols.length
    }
  }
  generateExportViewDataBody(data, pRowIndex) {
    // 生成视图数据导出主体数据
    let self = this
    data.forEach((row, rowIndex) => {
      let newrow = self.sheetVisibleData.addRow()
      row.seqIndex = pRowIndex === undefined ? rowIndex + 1 : pRowIndex + '.' + (+rowIndex + 1)
      self.dataColMap.forEach((column, columnIndex) => {
        let cell = newrow.addCell()
        self.generateCellViewValue(cell, row, column, rowIndex + 1, pRowIndex)
      })
      if (self.curExportConfig.isExportTree && Array.isArray(row.children) && row.children.length > 0) {
        self.generateExportViewDataBody(row.children, row.seqIndex)
      }
    })
  }
  generateExportViewDatafooter(data) {
    let self = this
    let footData = this.curExportConfig.footerMethods({ columns: self.dataColMap, data: data })
    let newrow = this.sheetVisibleData.addRow()
    this.dataColMap.forEach((column, columnIndex) => {
      let cell = newrow.addCell()
      self.generateFooterCellViewValue(cell, footData[columnIndex], column)
    })
  }
  generateCellViewValue(cell, item, column, rowIndex, pRowIndex) {
    let render = column.editRender || column.cellRender
    let items = JSON.parse(JSON.stringify(item))
    // 生成body单元格数据
    if (column.field === 'seqIndex') {
      cell.value = pRowIndex !== undefined ? pRowIndex + '.' + rowIndex : rowIndex
    } else {
      cell.value = this.getViewCellValue(items, column)
      cell.cellType = this.getViewCellValueType(items, column)
    }
    this.generateCellViewValueStyle(cell, column)
  }
  generateFooterCellViewValue(cell, value, column) {
    // 生成footer单元格数据
    cell.value = value
    this.generateCellViewValueStyle(cell, column)
  }
  getViewCellValueType(item, column) {
    return this.curExportConfig.viewValueFormatType(item[column.field], item, column)
  }
  getViewCellValue(item, column) {
    // 获取body单元格视图数据
    return this.curExportConfig.viewValueFormat(item[column.field], item, column, this)
    // item[column.field]
  }
  generateCellVisibletitleStyle(cell) {
    // 生成表头样式
    cell.style.align = {
      indent: 0,
      shrinkToFit: false,
      textRotation: 0,
      wrapText: false,
      h: 'center',
      v: 'center'
    }
    cell.style.border = {
      left: 'thin',
      right: 'thin',
      top: 'thin',
      bottom: 'thin',
      leftColor: 'FF000000',
      rightColor: 'FF000000',
      topColor: 'FF000000',
      bottomColor: 'FF000000'
    }
    cell.style.font = {
      color: '00000000',
      bold: true,
      family: 0,
      charset: 0,
      italic: false,
      underline: false,
      size: 12,
      name: 'Verdana'
    }
    cell.style.fill = {
      bgColor: 'ffffffff',
      fgColor: 'ffD2E9FF',
      patternType: 'solid'
    }
  }
  generateCellViewValueStyle(cell, column) {
    cell.style.border = {
      left: 'thin',
      right: 'thin',
      top: 'thin',
      bottom: 'thin',
      leftColor: 'FF000000',
      rightColor: 'FF000000',
      topColor: 'FF000000',
      bottomColor: 'FF000000'
    }
    cell.style.align = {
      indent: 0,
      shrinkToFit: false,
      textRotation: 0,
      wrapText: false,
      h: getCellValueAlign(column),
      v: 'center'
    }
  }
  generateExportOriginalDataHeader() {
    // 设置表头部样式
    /**
     * Cell intended to provide user access to the contents of Cell within an xlsx.Row.
     *
     * ```js
     * const cell = row.addCell();
     * cell.value = 'I am a cell!';
     * cell.hMerge = 2;
     * cell.vMerge = 1;
     * cell.style.fill.patternType = 'solid';
     * cell.style.fill.fgColor = '00FF0000';
     * cell.style.fill.bgColor = 'FF000000';
     * cell.style.align.h = 'center';
     * cell.style.align.v = 'center';
     * ```
     *
     * Set the cell value
     *
     * ```js
     * const cell = row.addCell();
     * // Date type
     * cell.setDate(new Date());
     * // Number type
     * cell.setNumber(123456);
     * cell.numFmt = '$#,##0.00';
     * ```
     */
    let self = this
    let newrow = self.sheetOriginalData.addRow()
    self.dataColMap.forEach((column, columnIndex) => {
      let cell = newrow.addCell()
      self.generateCellVisibletitleStyle(cell, column)
      cell.value = column.field || ''
    })
  }
  generateExportOriginalDataBody(data, pRowIndex) {
    // 生成body源数据
    let self = this
    data.forEach((row, rowIndex) => {
      let newrow = self.sheetOriginalData.addRow()
      row.seqIndex = pRowIndex === undefined ? rowIndex + 1 : pRowIndex + '.' + (+rowIndex + 1)
      self.dataColMap.forEach((column, columnIndex) => {
        let cell = newrow.addCell()
        self.generateCellOrangeValue(cell, row, column, rowIndex + 1, pRowIndex)
      })
      if (self.curExportConfig.isExportTree && Array.isArray(row.children) && row.children.length > 0) {
        self.generateExportOriginalDataBody(row.children, row.seqIndex)
      }
    })
  }
  generateCellOrangeValue(cell, item, column, rowIndex, pRowIndex) {
    // 生成body单元格数据
    if (column.field === 'seqIndex') {
      cell.value = pRowIndex !== undefined ? pRowIndex + '.' + rowIndex : rowIndex
    } else {
      cell.value = item[column.field] === undefined ? '' : item[column.field]
    }
    this.generateCellViewValueStyle(cell, column)
  }
  toBuffer(wbout) {
    const buf = new ArrayBuffer(wbout.length)
    const view = new Uint8Array(buf)
    for (let index = 0; index !== wbout.length; ++index) view[index] = wbout.charCodeAt(index) & 0xFF
    return buf
  }
  exportDownload(exportXlsx) {
    // 下载
    let self = this
    // const wbout = this.XlsxTool.write(exportXlsx, { bookType: 'xlsx', bookSST: false, type: 'binary' })
    // const blob = new Blob([this.toBuffer(wbout)], { type: 'application/octet-stream' })
    exportXlsx.saveAs('blob').then(function (content) {
      const blob = new Blob([content], {
        type: 'application/octet-stream'
      })
      // var blob = new Blob(['Hello, world!'], { type: 'text/plain;charset=utf-8' })
      FileSaver.saveAs(blob, self.curExportConfig.fileName + self.curExportConfig.saveType)
      // let objectUrl = URL.createObjectURL(blob)
      // if ('download' in document.createElement('a')) {
      //   let aTag = self.aTag
      //   aTag.setAttribute('href', objectUrl)
      //   aTag.setAttribute('download', self.curExportConfig.fileName + self.curExportConfig.saveType)
      //   aTag.click()
      //   self.curExportConfig.successCb(self.curExportConfig)
      // } else {
      //   // IE10+下载
      //   navigator.msSaveBlob(blob, self.curExportConfig.fileName)
      // }
    })
  }
  downloadCsv(exportXlsx) {
    let worksheet = this.sheetVisibleData // 这里我们只读取第一张sheet
    let csv = this.XlsxTool.utils.sheet_to_csv(worksheet)
    let blob = new Blob(['\uFEFF' + csv], {
      type: 'text/plaincharset=utf-8'
    })
    let aTag = this.aTag
    aTag.setAttribute('download', this.curExportConfig.fileName + this.curExportConfig.saveType)
    aTag.href = URL.createObjectURL(blob)
    aTag.click()
    URL.revokeObjectURL(blob)
  }
}

3.2 exportUtil.js

export function defaultViewValueFormat(value, row, column, context) {
  let val = (value + '').replace(/null|undefined|root|NaN/ig, '')
  return val
}
export function defaultViewValueFormatType(value, row, column) {
  return 'TypeString'
}

export function defaultViewValueFormatOld(value, row, column) {
  return value
}
export function getCellValueAlign(column) {
  return 'left'
}

export function getCellViewTitle(column, unit = '元') {
  return column.title
}

3.使用说明

// 1. 引入export.js
import { Export } from '/export'
// 2. 实例化
this.$Export = new Export()
// 3. 调用
const exportModalFormData = Object.assign({}, this.$Export.exportDefaultConfig, {
datas: this.$refs.pageList.$refs.pemsTable.$refs.table.getTableData().fullData,
  columns: this.tableColumns,
   fileName: this.exportConfig && this.exportConfig.fileName || 'export'
 })
 this.$Export.exportExcel(exportModalFormData, this)

网站公告

今日签到

点亮在社区的每一天
去签到