论如何优雅的用Vue从前台导入excel(并处理单元格合并问题)

最近接到一个需求 导入excel后还要进行编辑再保存,所以采用了前台导入的方式,使用的xlsx

假设表格是这样的

第一步 使用input组件进行文件上传

<span>
    <input ref="fileInput" type="file" @change="fileChangeHandle" style="display: none;">
    <el-button type="primary" size="small" icon="el-icon-folder-add" @click="clickHandle"><slot></slot></el-button>
</span>
clickHandle() {
    this.$refs.fileInput.click()
},
fileChangeHandle(e) {
    let file = this.$refs.fileInput.files[0];// 通过DOM取文件数据
    let formData = new FormData();//new一个formData事件
    formData.append("file", file)
    this.$emit('changeHandle', {data: formData, clear: () => e.target.value = null, file: file})
}

这样就拿到了上传的文件

第二步 使用xlsx读取文件,并对合并的单元格进行处理

read(sheet, cbk) {
    //拿到所导入文件的名字
    const fileName = this.file
    //定义reader,存放文件读取方法
    const reader = new FileReader()
    //启动函数
    reader.readAsBinaryString(fileName)
    //onload在文件被读取时自动触发
    reader.onload = (e) => {
      //workbook存放excel的所有基本信息
      const workbook = XLSX.read(e.target.result, {type: 'binary'});
      //定义sheetList中存放excel表格的sheet表,就是最下方的tab
      let worksheet = workbook.Sheets[workbook.SheetNames[sheet]]; // 只能通过工作表名称来获取指定工作表
      this.excelData = worksheet
      this.initMerge() //初始化合并单元格信息
      cbk(this)
    }
  }

initMerge() {
    const merges = this.excelData['!merges'];
    merges.forEach(p => {
      let columnIndex = p['s']['c']
      const endColumn = p['e']['c']
      let rowIndex = p['s']['r']
      const endRow = p['e']['r']
      const startColumn = String.fromCharCode(A + p['s']['c'])
      const startRow = p['s']['r'] + 1
      while (columnIndex <= endColumn) {
        rowIndex = p['s']['r']
        while (rowIndex <= endRow) {
          let column = String.fromCharCode(A + columnIndex);
          let row = rowIndex + 1
          this.mergeMap[column + row] = this.excelData[startColumn + startRow]['v']
          rowIndex++
        }
        columnIndex++
      }

    })
  }

excel 默认合并的单元格只会保留第一格的值,进行处理后将所有值存入了变量mergeMap

打印一下处理好的合并单元格map

第三步 数据提取

参数:

titleLine: 标题行

startRow:数据开始行

startColumn:数据开始列

alias:别名 传入一个AliasMap对象

toArray(titleLine = 1, startRow = 1, startColumn = 'A', alias = null) {
    if (this.excelData == null || !this.excelData) {
      return []
    }
    const resultArr = []

    const length = this.excelData['!rows'].length
    for (let i = startRow; i < length; i++) {
      const item = {}
      let column = startColumn
      do {
        //拿到属性名 如果没有就去合并单元格里面找
        let attrName = ''
        if(this.excelData[column + titleLine]) {
          attrName = this.excelData[column + titleLine]['v']
        } else {
          attrName = this.mergeMap[column + titleLine]
        }

        //如果设置了别名
        if (alias != null) {
          let prev = ''
          if (alias.prevLine && alias.prevLine > 0) {
            prev = this.mergeMap[column + alias.prevLine] + '-'
          }
          attrName = alias.getAlias(prev + attrName)
        }
        if(this.excelData[column + i]) {
          item[attrName] = this.excelData[column + i]['v']
        } else {
          item[attrName] = ''
        }
        column = this.chartAdd(column)
      } while (this.excelData[column + titleLine])
      resultArr.push(item)
    }
    return resultArr

  }

这里我进行了取别名的操作 因为excel里面读取的是汉字 将属性名映射为对应的字段名

const map = {
          '班级': 'banji',
          '语文': 'yuwen',
          '数学': 'shuxue',
          '英语': 'yingyu',
          '计算机基础': 'jisuanjijichu',
          '算法': 'suanfa',
          '数据结构': 'shujujiegou'
        }
 e.toArray(3, 4, 'A', new AliasMap(map));

打印一下读取结果

但是这还不够,有时候会有这样的表格

这样他的表头会有重复的时候,只需要构建别名时加入前缀即可

 

 读取结果:

完整代码:

import XLSX from "xlsx";

/**
 * Excel读取类
 * @author: Savitar
 * @datetime: 2021-10-14
 */

const A = 65

export class ExcelReader {
    constructor(file, sheet, cbk) {
        this.file = file
        this.excelData = null
        this.mergeMap = {}
        this.read(sheet, cbk)
    }

    read(sheet, cbk) {
        //拿到所导入文件的名字
        const fileName = this.file
        //定义reader,存放文件读取方法
        const reader = new FileReader()
        //启动函数
        reader.readAsBinaryString(fileName)
        //onload在文件被读取时自动触发
        reader.onload = (e) => {
            //workbook存放excel的所有基本信息
            const workbook = XLSX.read(e.target.result, {type: 'binary'});
            //定义sheetList中存放excel表格的sheet表,就是最下方的tab
            let worksheet = workbook.Sheets[workbook.SheetNames[sheet]]; // 只能通过工作表名称来获取指定工作表
            this.excelData = worksheet
            this.initMerge()
            cbk(this)
        }
    }

    /**
     * 初始合并单元格
     * excel合并单元格后默认只有左上角的单元格保留值 此方法会将所有合并的单元格都赋值
     */



    initMerge() {
        const merges = this.excelData['!merges'];
        if(!merges) return
        merges.forEach(p => {
            let columnIndex = p['s']['c']
            const endColumn = p['e']['c']
            let rowIndex = p['s']['r']
            const endRow = p['e']['r']
            const startColumn = createCol(p['s']['c'])
            const startRow = p['s']['r'] + 1
            while (columnIndex <= endColumn) {
                rowIndex = p['s']['r']
                while (rowIndex <= endRow) {
                    let column = createCol(columnIndex);
                    let row = rowIndex + 1
                    this.mergeMap[column + row] = this.excelData[startColumn + startRow]['v']
                    rowIndex++
                }
                columnIndex++
            }

        })
    }

    toArray(titleLine = 1, startRow = 1, startColumn = 'A', alias = null, endRow = 999) {
        if (this.excelData == null || !this.excelData) {
            return []
        }
        const resultArr = []
        let length;
        if(this.excelData['!rows'] && this.excelData['!rows'].length) {
            length = this.excelData['!rows'].length
        } else {
            length = endRow;
        }

        for (let i = startRow; i <= length; i++) {
            const item = {}
            let column = startColumn
            let columnIndex = startColumn.charCodeAt()
            do {
                //拿到属性名
                let attrName = ''
                if (this.excelData[column + titleLine]) {
                    attrName = this.excelData[column + titleLine]['v']
                } else {
                    attrName = this.mergeMap[column + titleLine]
                }

                //如果设置了别名
                if (alias != null) {
                    let prev = ''
                    if (alias.prevLine && alias.prevLine > 0) {
                        prev = this.mergeMap[column + alias.prevLine] + '-'
                    }
                    attrName = alias.getAlias(prev + attrName)
                }
                if (this.excelData[column + i]) {
                    item[attrName] = this.excelData[column + i]['v']
                } else {
                    item[attrName] = ''
                }
                column = this.chartAdd(columnIndex)
                columnIndex++
            } while (this.excelData[column + titleLine])
            resultArr.push(item)
        }
        return resultArr

    }

    chartAdd(columnIndex) {
        return createCol(columnIndex - A)
    }
}

function createCol(n) {
    const ordA = 'A'.charCodeAt(0)
    const ordZ = 'Z'.charCodeAt(0)
    const len = ordZ - ordA + 1
    let str = ""
    while (n >= 0) {
        str = String.fromCharCode(n % len + ordA) + str
        n = Math.floor(n / len) - 1
    }
    return str
}

/***
 *
 * @param file
 * @param sheet
 * @param cbk
 */

/***
 * 别名map
 */
export class AliasMap {
    constructor(map = {}, prevLine) {
        this.map = map;
        this.prevLine = prevLine
    }

    getAlias(name) {
        return this.map[name] || name
    }

}


  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值