LuckySheet 实现 excel 的导入与导出

需求:前端使用了 LuckySheet 来实现表格,现在需要实现对这个表格带有样式的导入及导出。在导入时只导入绿色背景单元格中的数据,其他背景颜色单元格的数据将有 LuckySheet 公式自动计算得出。

安装的库

首先安装 luckysheet







然后是其他一些库

   "dependencies": {
    ...
    "luckyexcel": "1.0.1",
    "xlsx": "https://cdn.sheetjs.com/xlsx-0.18.9/xlsx-0.18.9.tgz",
    "xlsx-style": "file:./src/libs/xlsx-style" // 在 xlsx 中有一些代码异常,需要
  },

其中 xlsx-style 项目有一个 bug,所以我把项目放到本地进行了小改动。所以可以看到 xlsx-style 的路径是本地路径。

全局配置

// config.js
export default {
  SHEET_COLUMN_KEYS: [ 'a', 'b', 'c' ],
  VALUE_CELL_COLORS: ['#00B050'], // 可以编辑区域的背景色
  DOWNLOAD_FILE_NAME: 'file',
}

导出 Excel

// exportExcel.js
import * as XLSX from 'xlsx'
import * as XLSXStyle from 'xlsx-style'
import config from './config'

export default function () {
  // 1. 获取到要下载的数据
  const allSheetData = window.luckysheet.getluckysheetfile()
  const sheet1 = allSheetData[0]
  const downOriginData = sheet1.data

  let arr = [] // 所有的单元格数据组成的二维数组
  const fConfig = {}
  let bgConfig = {}
  let percentageReg = /%$/
  //列下标 数字转字母
  function chatatABC (n) {
    var orda = 'a'.charCodeAt(0)
    var ordz = 'z'.charCodeAt(0)
    var len = ordz - orda + 1
    var s = ''
    while (n >= 0) {
      s = String.fromCharCode((n % len) + orda) + s
      n = Math.floor(n / len) - 1
    }
    return s.toUpperCase()
  }

  // 获取单元格的背景色
  function setBackground (row, col, bg) {
    var colA = chatatABC(col)
    var key = colA + (row + 1)
    bgConfig[key] = bg.replace(/\#?/, '')
  }

  function setFormat (row, col, f) {
    var colA = chatatABC(col)
    var key = colA + (row + 1)
    fConfig[key] = f
  }

  // 判断值类型是否为百分比 %
  function isPercentage (value) {
    return percentageReg.test(value.m) && value.ct && value.ct.t === 'n'
  }

  // 获取二维数组
  for (let row = 0; row < downOriginData.length; row++) {
    let arrRow = []
    for (let col = 0; col < downOriginData[row].length; col++) {
      const cellValue = downOriginData[row][col]
      // if (row === 0) console.log('cellValue', cellValue)

      if (cellValue) {
        // 处理单元格的背景颜色
        if (cellValue.bg) {
          setBackground(row, col, cellValue.bg)
        }
        if (cellValue.f) {
          setFormat(row, col, cellValue.f)
        }
        if (cellValue.ct != null && cellValue.ct.t == 'd') {
          //  d为时间格式  2019-01-01   或者2019-01-01 10:10:10
          arrRow.push(new Date(cellValue.m.replace(/\-/g, '/'))) //兼容IE
        } else if (cellValue.m && isPercentage(cellValue)) {
          //百分比问题
          arrRow.push(cellValue.m)
        } else {
          arrRow.push(cellValue.v)
        }
      } else {
        arrRow.push(undefined)
      }
    }
    arr.push(arrRow)
  }

  // 2. 通过SheetJs将数据转化为excel格式数据
  let opts = {
    dateNF: 'm/d/yy h:mm',
    cellDates: true,
    cellStyles: true
  }
  let ws = XLSX.utils.aoa_to_sheet(arr, opts)

  // 3. 设置单元格的类型以及单元格样式
  let reg = /[\u4e00-\u9fa5]/g
  for (let key in ws) {
    let item = ws[key]
    if (item.t === 'd') {
      if (item.w) {
        //时间格式的设置
        let arr = item.w.split(' ')
        if (arr[1] && arr[1] == '0:00') {
          ws[key].z = 'm/d/yy'
        } else {
          item.z = 'yyyy/m/d h:mm:ss'
        }
      }
    } else if (item.t === 's') {
      //百分比设置格式
      if (item.v && !item.v.match(reg) && item.v.indexOf('%') > -1) {
        item.t = 'n'
        item.z = '0.00%'
        item.v = Number.parseFloat(item.v) / 100
      } else if (item.v && item.v.match(reg)) {
        //含有中文的设置居中样式
        item['s'] = {
          alignment: { vertical: 'center', horizontal: 'center' }
        }
      }
    }
    // 设置单元格样式
    if (bgConfig[key]) {
      ws[key].s = {
        alignment: { vertical: 'center', horizontal: 'center' },
        fill: { bgColor: { indexed: 32 }, fgColor: { rgb: bgConfig[key] } },
        border: {
          top: { style: 'thin', color: { rgb: '999999' } },
          bottom: { style: 'thin', color: { rgb: '999999' } },
          left: { style: 'thin', color: { rgb: '999999' } },
          right: { style: 'thin', color: { rgb: '999999' } }
        }
      }
    }
    if (fConfig[key]) {
      ws[key].f = fConfig[key]
    }
  }

  console.log('ws', ws)

  // 4. 组装下载数据格式
  let name = 'sheet1'
  let tmpWB = {
    SheetNames: [name], //保存的表标题
    Sheets: {
      [name]: Object.assign({}, ws) //内容
    }
  }

  // 5. 合并单元格配置
  let mergeConfig = sheet1.config.merge
  let mergeArr = []
  if (JSON.stringify(mergeConfig) !== '{}') {
    mergeArr = handleMergeData(mergeConfig)
    tmpWB.Sheets[name]['!merges'] = mergeArr
  }
  //处理合并单元格config数据
  function handleMergeData (origin) {
    let result = []
    if (origin instanceof Object) {
      var r = 'r',
        c = 'c',
        cs = 'cs',
        rs = 'rs'
      for (var key in origin) {
        var startR = origin[key][r]
        var endR = origin[key][r]
        var startC = origin[key][c]
        var endC = origin[key][c]

        // 如果只占一行 为1 如果占两行 为2
        if (origin[key][cs] > 0) {
          endC = startC + (origin[key][cs] - 1)
        }
        if (origin[key][rs] > 0) {
          endR = startR + (origin[key][rs] - 1)
        }
        // s为合并单元格的开始坐标  e为结束坐标
        var obj = { s: { r: startR, c: startC }, e: { r: endR, c: endC } }
        result.push(obj)
      }
    }
    return result
  }

  // 6. 写入文件
  let fileName = config.DOWNLOAD_FILE_NAME
  // sheetjs js-xlsx 的方法 ,导出不带有样式的表格
  // XLSX.writeFile(tmpWB, fileName + '.xlsx')

  /**
   * 通过 xlsx-style 来下载带有颜色的表格数据
   * 目前存在一个小问题,需要去 node_modules 里面清理一行代码否则会报错。
   * https://github.com/protobi/js-xlsx/issues/78#issuecomment-315063340
   */

  function s2ab (s) {
    if (typeof ArrayBuffer !== 'undefined') {
      var buf = new ArrayBuffer(s.length)
      var view = new Uint8Array(buf)
      for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
      return buf
    } else {
      var buf = new Array(s.length)
      for (var i = 0; i != s.length; ++i) buf[i] = s.charCodeAt(i) & 0xff
      return buf
    }
  }

  function saveAs (obj, fileName) {
    var tmpa = document.createElement('a')
    tmpa.download = fileName || 'download'
    tmpa.href = URL.createObjectURL(obj)
    tmpa.click()
    setTimeout(function () {
      URL.revokeObjectURL(obj)
    }, 100)
  }

  ws = new Blob([
    s2ab(
      XLSXStyle.write(tmpWB, {
        bookType: 'xlsx',
        bookSST: false,
        type: 'binary'
      }) //这里的数据是用来定义导出的格式类型
    )
  ])
  saveAs(ws, fileName + '.xlsx')
}

导入 excel

// importExcel.js
import LuckyExcel from 'luckyexcel'
import InitExcelJson from './data'
import config from './config'

import { Message } from 'element-ui'

export default function (files, cb) {
  if (files == null || files.length == 0) {
    Message.warning('取消导入文件!')
    return
  }

  let name = files[0].name
  let suffixArr = name.split('.'),
    suffix = suffixArr[suffixArr.length - 1]
  if (suffix != 'xlsx') {
    Message.warning('当前只支持导入 xlsx 文件!')
    return
  }

  // 得到xlsx文件后
  LuckyExcel.transformExcelToLucky(
    files[0],
    function (exportJson, luckysheetfile) {
      console.log('import', exportJson, luckysheetfile)
      // 转换后获取工作表数据
      if (exportJson.sheets == null || exportJson.sheets.length == 0) {
        Message.warning('读取 xlsx 文件失败!')
        return
      }

      updateSheet(exportJson)
      if (cb) {
        cb()
      }
      // const mixinJson = mixinStyle(exportJson) // 进行一次转化

      // window.luckysheet.destroy()

      // window.luckysheet.create({
      //   container: 'luckysheet', //luckysheet is the container id
      //   showinfobar: false,
      //   data: mixinJson.sheets,
      //   title: mixinJson.info.name,
      //   userInfo: mixinJson.info.name.creator
      // })
    },
    function (error) {
      console.log('error', error)
      // 如果抛出任何错误,则处理错误
    }
  )
}

// 通过代码算法来保持原有 data.js 配置
function mixinStyle (json) {
  const result = InitExcelJson

  const sourceCellData = result.sheets[0].celldata
  const targetCellData = json.sheets[0].celldata

  const targetCellMap = {}
  targetCellData.forEach(cell => {
    targetCellMap[`${cell.r}-${cell.c}`] = cell
  })

  sourceCellData.forEach(cell => {
    const findCell = targetCellMap[`${cell.r}-${cell.c}`]
    if (findCell) {
      if (findCell.v.v) cell.v.v = findCell.v.v
      if (findCell.v.m) cell.v.m = findCell.v.m
    }
  })

  return result
}

function updateSheet (json) {
  const { VALUE_CELL_COLORS } = config // 可以编辑区域的背景色

  const sourceCellData = InitExcelJson.sheets[0].celldata
  const targetCellData = json.sheets[0].celldata

  const targetCellMap = {}
  targetCellData.forEach(cell => {
    targetCellMap[`${cell.r}-${cell.c}`] = cell
  })

  sourceCellData.forEach(cell => {
    if (VALUE_CELL_COLORS.includes(cell.v.bg)) {
      const findCell = targetCellMap[`${cell.r}-${cell.c}`]
      if (findCell) {
        window.luckysheet.setCellValue(cell.r, cell.c, {
          v: findCell.v.v
        })
      }
    }
  })
}

具体到 Vue 项目中的使用:

 
    importExcel(evt) {
      this.loading = true;
      importExcel(evt.target.files);
      setTimeout(() => {
        this.loading = false;
        this.$refs.importInput.value = ''
      }, 1000);
    },

最后

最后吐槽一句,xlsx-style 的坑挺多的,而且这玩意已经不维护了,请注意~

参考资料

  • 关于 xlsx-style 导出 excel 文件无法应用公式解决方案
  • 解决xlsx-style导出带样式和公式的excel表格遇到的坑
  • ERROR CORRECT => module was not found: ./cptable in ./node_modules/[email protected]@xlsx-style/dist/cpexcel.js
  • xlsx 文档
  • LuckySheet 文档
  • xlsx-style 文档 - npm
  • LuckyExcel

你可能感兴趣的:(LuckySheet 实现 excel 的导入与导出)