前端实现excel导出和导入(react项目)

13 篇文章 0 订阅
6 篇文章 0 订阅

最近公司有个新需求,需要前端导出规定模板的excel,导入的时候前端解析excel文件,数据处理一下传给后台,下面分享下纯前端实现excel的导出和导入:

excel导出

  handleClick = () => {
    // 定制化改动地方
    let excelData = this.state.excelData // 数据是后台返回渲染导出的excel数据的
    let data = []
    if (excelData.length) {
      for (let i = 0; i < excelData.length; i++) {
        let obj = {
          '监控面编号': excelData[i].taskName,
          '监控点编号': excelData[i].monitorPointNumber,
          '高程(m)': '',
        };
        data.push(obj);
      }
    } else {
      let obj = {
        '监控面编号': '',
        '监控点编号': '',
        '高程(m)': '',
      };
      data.push(obj);
    }
    // 表格标题

    const options = {
      dataTitle: '表名:哈哈哈',
      reportCompany: '说明:XXXX',
      date: `日期`,
      reportType: `施工工况`,
    }
    // 配置文件类型
    const wopts = { bookType: 'xlsx', bookSST: true, type: 'binary', cellStyles: true };
    this.recordExcelDown(data, wopts, options);
  }
  recordExcelDown = (json, type, options) => {
    var tmpdata = json[0];
    // 定制化改动地方
    json.unshift({}, {}, {}, {}); // 向表格数据中插入4行位置(规定的模板)
    const keyMap = []; // 获取keys
    for (const k in tmpdata) { // 为插入的4行位置添加数据
      keyMap.push(k);
      // 定制化改动地方
      json[3][k] = k; // json[3][k] = k  3为插入4行的最后一行索引,用于展示列头
    }
    var tmpdata = []; // 用来保存转换好的json
    json
      .map((v, i) => {
        const data = keyMap.map((k, j) => {
          return Object.assign(
            {},
            {
              v: v[k],
              position: (j > 25 ? this.getCharCol(j) : String.fromCharCode(65 + j)) + (i + 2), // 表格数据的位置
            }
          );
        });
        return data;
      })
      .reduce((prev, next) => prev.concat(next))
      .forEach(
        (v, i) =>
        (tmpdata[v.position] = {
          v: v.v,
        })
      );
    let outputPos = Object.keys(tmpdata); // 设置区域,比如表格从A1到D10
    // 定制化改动地方
    tmpdata.A1 = { v: options.dataTitle };  // A1-A4区域的内容
    tmpdata.A2 = { v: options.reportCompany };
    tmpdata.A3 = { v: options.date };
    tmpdata.A4 = { v: options.reportType };
    tmpdata.C3 = { v: '2020-1-1' };
    tmpdata.C4 = { v: '1号梁段挂篮就位' };
    tmpdata.C6 = { v: '1.001' };
    tmpdata.C1 = { v: 'tid' };
    tmpdata.D1 = { v: this.props.taskId };
    tmpdata.E1 = { v: 'bid' };
    tmpdata.F1 = { v: this.props.bridgeRecord.id };
    // 定制化改动地方
    outputPos = ['A1'].concat(['A2'], ['A3'], ['A4'], ['C1'], ['C4'], ['C5'], ['C6'], ['D1'], ['D3'], ['D4'], ['D6'], ['E1'], ['F1'], outputPos);
    // 定制化改动地方
    tmpdata.A1.s = {
      font: { sz: 14, bold: true, vertAlign: true },
      alignment: { vertical: 'center', horizontal: 'center' }, // 垂直、水平
      fill: { bgColor: { rgb: 'E8E8E8' }, fgColor: { rgb: 'E8E8E8' } },
    }; // <====设置xlsx单元格样式
    tmpdata.A2.s = {
      font: { sz: 12, bold: true, vertAlign: true },
      alignment: { vertical: 'center', horizontal: 'bottom' },
    }; // <====设置xlsx单元格样式
    tmpdata.A3.s = {
      font: { sz: 12, bold: true, vertAlign: true },
      alignment: { vertical: 'center', horizontal: 'bottom' },
    }; // <====设置xlsx单元格样式
    tmpdata.A4.s = {
      font: { sz: 12, bold: true, vertAlign: true },
      alignment: { vertical: 'center', horizontal: 'bottom' },
    }; // <====设置xlsx单元格样式
    // s-e 代表区域 c-r 代表列-行的索引
    // 定制化改动地方
    tmpdata['!merges'] = [
      {
        s: { c: 0, r: 0 },
        e: { c: 1, r: 0 },
      },
      {
        s: { c: 0, r: 1 },
        e: { c: 9, r: 1 },
      },
      {
        s: { c: 0, r: 2 },
        e: { c: 1, r: 2 },
      },
      {
        s: { c: 0, r: 3 },
        e: { c: 1, r: 3 },
      },
    ]; // <====合并单元格
    let dataArrWidth = []
    // 定制化改动地方
    json.forEach(item => {
      dataArrWidth.push({ wpx: 100 })
    })
    tmpdata['!cols'] = dataArrWidth;// <====设置一列宽度, 代表20列都是300宽
    const tmpWB = {
      SheetNames: ['mySheet'], // 保存的表标题
      Sheets: {
        mySheet: Object.assign(
          {},
          tmpdata, // 内容
          {
            '!ref': `${outputPos[0]}:${outputPos[outputPos.length - 1]}`, // 设置填充区域(表格渲染区域)
          }
        ),
      },
    };
    const tmpDown = new Blob(
      [
        this.s2ab(
          XLSX.write(
            tmpWB,
            { bookType: type == undefined ? 'xlsx' : type.bookType, bookSST: false, type: 'binary' } // 这里的数据是用来定义导出的格式类型
          )
        ),
      ],
      {
        type: '',
      }
    );
    // 定制化改动地方
    this.saveAs(tmpDown, `${'记录模板' + '.'}${type.bookType == 'biff2' ? 'xls' : type.bookType}`);
  }

这边也是借鉴了百度的大佬,合并单元格可以生效的,不过样式的修改并没有生效,有需要的可以自己研究下。

excel导入

我选择的是原始的input type=file 来实现文件的上传解析,因为初始的这个标签效果并不是很好看,右边还默认带着一个未选择任何文件的字样,又改不掉样式,这边我用了个小技巧分享出来:
原理:input的样式设置display: none 用过div绝对定位在这儿,取原始input的id 并添加点击事件,从而实现点击div实现文件上传
html部分代码:

        <Col span={20} className={style.btnSpec}>
        <input
          type='file' accept='.xlsx, .xls' onChange={this.excelImport} style={{ height: '30px', lineHeight: '26px', display: 'none' }} id="batchUpload" />
          <div onClick={this.centerUploadBox} className={style.centerUploadBox}>
            文件上传
          </div>
        </Col>

js部分代码:

  // div按钮实现上传
  centerUploadBox = () => {
    var me = this;
    let batchUpload = document.querySelector('#batchUpload');
    batchUpload.click();
    let filesList = document.querySelector('#batchUpload').files;
    batchUpload.addEventListener('change', function () {
      filesList = document.querySelector('#batchUpload').files;
      if (filesList.length == 0) {
        return;
      }
    })
  }
  //导入excel
  dataImport = file => {
    const { dispatch } = this.props
    // 获取上传的文件对象
    const { files } = file.target;
    // 通过FileReader对象读取文件
    const fileReader = new FileReader();
    fileReader.onload = event => {
      // try {
      const { result } = event.target;
      // 以二进制流方式读取得到整份excel表格对象
      const workbook = XLSX.read(result, { type: 'binary' });
      // 存储获取到的数据
      let params = []
      let listRow = []; // 多少列的数组
      let maxHeight = 0;
      let indexK = 0

      console.log(workbook)
      let oriData = workbook.Sheets.mySheet;
      // 这边就是读取了excel的所有内容
      let excelFilterData = []

      for (let k in oriData) {
        ++indexK
        if (indexK == 1) {
          maxHeight = oriData[k].substring(oriData[k].lastIndexOf(":") + 2)
        } else {
          listRow.push(k.substring(0, 1))
        }

      }

      listRow = Array.from(new Set(listRow))
      listRow.splice(listRow.length - 1, 1)

      // 字母排序
      listRow = listRow.sort((s, t) => {
        let a = s.toLowerCase();
        let b = t.toLowerCase();
        if (a < b) return -1;
        if (a > b) return 1;
        return 0;
      })
      //找到最大列 eg.9
      //找到最大行 eg.39
      //这边主要是处理后台需要的数据
      for (let i = 2; i <= listRow.length - 1; i++) {
        //循环行
        if (oriData[listRow[i] + '3']) {
          if (oriData[listRow[i] + '3'].h) {
            excelFilterData.push(oriData[listRow[i] + '3'].h)
          } else if (oriData[listRow[i] + '3'].v) {
            excelFilterData.push(this.formatDate(oriData[listRow[i] + '3'].v, '-'))
          }
        }


        for (let j = 6; j <= Number(maxHeight); j++) {
          let reg = /^[0-9,.]*$/, item = {}

          if (oriData[listRow[i] + j] && oriData[listRow[i] + j].h && reg.test(oriData[listRow[i] + j].h)) {
            item = {
              calculationElevation: oriData[listRow[i] + j] ? oriData[listRow[i] + j].h : "",
              constructionMonitorPoint: {
                monitorPointNumber: oriData[`B${j}`] ? oriData[`B${j}`].h : "",
              },
              constructionMonitorTask: {
                taskName: oriData[`A${j}`] ? oriData[`A${j}`].h : "",
              },
              constructionMonitorTaskCollection: {
                checkTime: oriData[listRow[i] + '3'] ? oriData[listRow[i] + '3'].h ? oriData[listRow[i] + '3'].h : this.formatDate(oriData[listRow[i] + '3'].v, '-') : "",
                constructCondition: oriData[listRow[i] + '4'] ? oriData[listRow[i] + '4'].h : "",
              }
            }
            params.push(item)
          }
        }
      }
      this.setState({ excelFilterData: excelFilterData })
      console.log(params)
      this.setState({ filterVisible: true, excelParams: params })


    };
    // 以二进制方式打开文件
    fileReader.readAsBinaryString(files[0]);
  }

div按钮的样式:

.centerUploadBox {
    margin-top: 8px;
    height: 30px;
    line-height: 26px;
    width: 89px;
    float: right;
    margin-right: 623px;
    cursor: pointer;
    background: #fafafa;
    padding: 0 10px;
    padding-top: 1px;
    font-size: 16px;
    border-radius: 0px;
    color: #000000e6;
    border: solid 1px #000;
    border-color: rgba(0, 0, 0, 0.31);
}

在这里插入图片描述
按钮就是这样的 哈哈哈哈哈哈 也不是很好看 根据需求和页面布局自行画吧 ~
以上就是纯前端实现excel导出和导入!!! 不足之处就是设置样式没有生效 ,导入的时候不能做到识别合并单元格,所以说很复杂或者规定样式的excel可能还得后台画,也有很大可能我是不会的-_-zzzzzzzz~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值