记录:excel导入导出js-xlsx,处理合并

效果

image

前情提要

后端传excel坐标数据,前端自己处理模板,找资料后,选择直接载入xlsx方式。

准备工作

npm i xlsx

import * as XLSX from 'xlsx'

方法一:数据处理渲染

导入

提取数据

    let reader = new FileReader()
    //读入file
    reader.readAsBinaryString(file)
    reader.onload = (e) => {
      let data = e.target.result
      //读取file, 提取数据
      let workbook = XLSX.read(data, { type: 'binary', cellStyles: true })
	  //workbook.Styles不太对应
      let sheetNames = workbook.SheetNames
      let sheets = workbook.Sheets
      parsingTable(sheets[sheetNames[0]])
    }
/**
1. base64: 以base64方式读取;
2. binary: BinaryString格式(byte n is data.charCodeAt(n))
3. string: UTF8编码的字符串;
4. buffer: nodejs Buffer;
5. array: Uint8Array,8位无符号数组;
6. file: 文件的路径(仅nodejs下支持)*/

处理数据

左边的数据变成右边数据
image

    得到列表范围
    ['!ref'])
    得到合并数据
    ['!merges']

const parsingTable = (table) => {
    let header = [] //表格列
    let dataSource = [] //表格数据
    let maxRowIndex = 0 //最大行数
    let keys = Object.keys(table)
    const range = XLSX.utils.decode_range(table['!ref'])
    maxRowIndex = range['e']['r'] - range['s']['r']
    for (let [i, h] of keys.entries()) {
      //提取key中的英文字母
      let col = h.replace(/[^A-Z]/g, '')
      //单元格是以A-1的形式展示的,所以排除包含!的key
      h.indexOf('!') === -1 && header.indexOf(col) === -1 && header.push(col)
      //如果!ref不存在时,  设置某一列最后一个单元格的索引为最大行数
      if (
        (!table['!ref'] || !table['!ref'].includes(':')) &&
        header.some((c) => table[`${c}${i}`])
      ) {
        maxRowIndex = i > maxRowIndex ? i : maxRowIndex
      }
    }
    header = header.sort((a, b) => a.localeCompare(b)) //按字母顺序排序 [A, B, ..., E, F]
    //excel的行表示为 1, 2, 3, ......, 所以index起始为1
    //从1开始,maxRowIndex需要+1
    for (let index = 1; index <= maxRowIndex + 1; index++) {
      let row = [] //行
      //每行的单元格集合, 例: [A1, ..., F1]
      row = header.map((item) => {
        let key = `${item}${index}`
        let cell = table[key]
        return {
          key,
          name: cell ? cell.v : ''
        }
      })
      dataSource.push(row)
    }
    console.log(dataSource)
    //setArrList(dataSource)
    //合并单元格
    if (table['!merges']) {
      for (let item of table['!merges']) {
        //s开始  e结束  c列  r行  (行、列的索引都是从0开始的)
        for (let r = item.s.r; r <= item.e.r; r++) {
          for (let c = item.s.c; c <= item.e.c; c++) {
            // console.log('=======', r, c)
            //查找单元格时需要r+1
            //例:单元格A1的位置是{c: 0, r:0}
            let rowIndex = r + 1
            let cell = dataSource[r].find((a) => a.key === `${header[c]}${rowIndex}`)
            if (cell) {//除了第一行都置为0
              if(c === item.s.c&&r === item.s.r){
                cell.rowspan = item.e.r - item.s.r + 1 //纵向合并
                cell.colspan = item.e.c - item.s.c + 1 //横向合并
              }else{
                cell.rowspan = 0
                cell.colspan = 0
              }
            }
          }
        }
      }
    }
    setArrList(dataSource)
  }

渲染

react写法,vue大差不差

 <Spin spinning={loading}>
      <div id="can">
        {/* <input type="file" ref={inputFile} onChange={fileChange} /> 
        <button onClick={createBook}>导出</button>*/}
        <table id="tableView">
          <tbody>
            {arrList.map((item, index) => {
              return (
                <tr key={index}>
                  {item.map((i, k) => {
                    return i.rowspan !== 0 && i.colspan !== 0 && (
                      <td key={k} colSpan={i.colspan} rowSpan={i.rowspan}>
                        {i.name ??
                          data[`${i.key.replace(/[^A-Z]/g, '')},${i.key.replace(/[^0-9]/g, '')}`]}
                      </td>
                    )
                  })}
                </tr>
              )
            })}
          </tbody>
        </table>
      </div>
    </Spin>

导出

创建blob

const createBook = () => {
    //使用table_to_sheet或table_to_book其中一种方法
    //table_to_sheet的用法
    //console.log(inputFile);
    // let files = inputFile.current.files
    console.log(document.getElementById('tableView'))
    let worksheet = XLSX.utils.table_to_sheet(document.getElementById('tableView'))
    let workbook = {
      SheetNames: [],
      Sheets: {}
    }
    workbook.SheetNames.push('sheet1')
    worksheet['!cols'] = [{ wch: 20 }] //设置第一列的列宽
    workbook.Sheets['sheet1'] = worksheet
    //table_to_book的用法
    // let workbook = XLSX.utils.table_to_book(document.getElementById('tableView'));
    let data = XLSX.write(workbook, {
      bookType: 'xlsx', // 要生成的文件类型
      type: 'array'
    })
    let blobData = new Blob([data], { type: 'application/octet-stream' })
    exportFn(blobData)
  }

下载

const exportFn = (blob) => {
    const fileName = '料场报表.xlsx'
    let downloadElement = document.createElement('a')
    let href = window.URL.createObjectURL(blob) //创建下载的链接
    downloadElement.href = href
    downloadElement.download = fileName //下载后文件名
    document.body.appendChild(downloadElement)
    downloadElement.click() //点击下载
    document.body.removeChild(downloadElement) //下载完成移除元素
    window.URL.revokeObjectURL(href) //释放blob
    message.success('已成功导出!')
  }

完整代码

点击查看代码
import React, { useState, useEffect, useRef } from 'react'
import * as XLSX from 'xlsx'
import axios from 'axios'
import { Spin, message } from 'antd'
export default function Canvas(props) {
  const { data, loading } = props
  const [arrList, setArrList] = useState([])
  const inputFile = useRef(null)
  useEffect(() => {
    getNetworkFile()
  }, [])
  const getNetworkFile = () => {
    axios({
      url: '/rep.xlsx',
      method: 'get',
      responseType: 'blob'
    }).then((blobData) => {
      console.log(blobData)
      //将blob转为file类型
      let file = new File([blobData.data], '报表', { type: blobData.type })
      fileReader(file)
    })
  }
  //导出
  const createBook = () => {
    //使用table_to_sheet或table_to_book其中一种方法
    //table_to_sheet的用法
    //console.log(inputFile);
    // let files = inputFile.current.files
    console.log(document.getElementById('tableView'))
    let worksheet = XLSX.utils.table_to_sheet(document.getElementById('tableView'))
    let workbook = {
      SheetNames: [],
      Sheets: {}
    }
    workbook.SheetNames.push('sheet1')
    worksheet['!cols'] = [{ wch: 20 }] //设置第一列的列宽
    workbook.Sheets['sheet1'] = worksheet
    //table_to_book的用法
    // let workbook = XLSX.utils.table_to_book(document.getElementById('tableView'));
    let data = XLSX.write(workbook, {
      bookType: 'xlsx', // 要生成的文件类型
      type: 'array'
    })
    let blobData = new Blob([data], { type: 'application/octet-stream' })
    exportFn(blobData)
  }
  const exportFn = (blob) => {
    const fileName = '料场报表.xlsx'
    let downloadElement = document.createElement('a')
    let href = window.URL.createObjectURL(blob) //创建下载的链接
    downloadElement.href = href
    downloadElement.download = fileName //下载后文件名
    document.body.appendChild(downloadElement)
    downloadElement.click() //点击下载
    document.body.removeChild(downloadElement) //下载完成移除元素
    window.URL.revokeObjectURL(href) //释放blob
    message.success('已成功导出!')
  }
  const fileReader = (file) => {
    let reader = new FileReader()
    //读入file
    reader.readAsBinaryString(file)
    reader.onload = (e) => {
      let data = e.target.result
      //读取file, 提取数据
      let workbook = XLSX.read(data, { type: 'binary', cellStyles: true })
      let sheetNames = workbook.SheetNames
      let sheets = workbook.Sheets
      console.log(e);
      parsingTable(sheets[sheetNames[0]])
    }
  }
  const fileChange = () => {
    let files = inputFile.current.files
    console.log(files)
    fileReader(files[0])
  }
  //对数据进行处理,实现表格合并展示的功能
  const parsingTable = (table) => {
    let header = [] //表格列
    let dataSource = [] //表格数据
    let maxRowIndex = 0 //最大行数
    let keys = Object.keys(table)
    const range = XLSX.utils.decode_range(table['!ref'])
    maxRowIndex = range['e']['r'] - range['s']['r']
    for (let [i, h] of keys.entries()) {
      //提取key中的英文字母
      let col = h.replace(/[^A-Z]/g, '')
      //单元格是以A-1的形式展示的,所以排除包含!的key
      h.indexOf('!') === -1 && header.indexOf(col) === -1 && header.push(col)
      //如果!ref不存在时,  设置某一列最后一个单元格的索引为最大行数
      if (
        (!table['!ref'] || !table['!ref'].includes(':')) &&
        header.some((c) => table[`${c}${i}`])
      ) {
        maxRowIndex = i > maxRowIndex ? i : maxRowIndex
      }
    }
    header = header.sort((a, b) => a.localeCompare(b)) //按字母顺序排序 [A, B, ..., E, F]
    //excel的行表示为 1, 2, 3, ......, 所以index起始为1
    //从1开始,maxRowIndex需要+1
    for (let index = 1; index <= maxRowIndex + 1; index++) {
      let row = [] //行
      //每行的单元格集合, 例: [A1, ..., F1]
      row = header.map((item) => {
        let key = `${item}${index}`
        let cell = table[key]
        return {
          key,
          name: cell ? cell.v : ''
        }
      })
      dataSource.push(row)
    }
    console.log(dataSource)
    //setArrList(dataSource)
    //合并单元格
    if (table['!merges']) {
      for (let item of table['!merges']) {
        //s开始  e结束  c列  r行  (行、列的索引都是从0开始的)
        for (let r = item.s.r; r <= item.e.r; r++) {
          for (let c = item.s.c; c <= item.e.c; c++) {
            // console.log('=======', r, c)
            //查找单元格时需要r+1
            //例:单元格A1的位置是{c: 0, r:0}
            let rowIndex = r + 1
            let cell = dataSource[r].find((a) => a.key === `${header[c]}${rowIndex}`)
            if (cell) {//除了第一行都置为0
              if(c === item.s.c&&r === item.s.r){
                cell.rowspan = item.e.r - item.s.r + 1 //纵向合并
                cell.colspan = item.e.c - item.s.c + 1 //横向合并
              }else{
                cell.rowspan = 0
                cell.colspan = 0
              }
            }
          }
        }
      }
    }
    setArrList(dataSource)
  }
  return (
    <Spin spinning={loading}>
      <div id="can">
        {/* <input type="file" ref={inputFile} onChange={fileChange} /> 
        <button onClick={createBook}>导出</button>*/}
        <table id="tableView">
          <tbody>
            {arrList.map((item, index) => {
              return (
                <tr key={index}>
                  {item.map((i, k) => {
                    return i.rowspan !== 0 && i.colspan !== 0 && (
                      <td key={k} colSpan={i.colspan} rowSpan={i.rowspan}>
                        {i.name ??
                          data[`${i.key.replace(/[^A-Z]/g, '')},${i.key.replace(/[^0-9]/g, '')}`]}
                      </td>
                    )
                  })}
                </tr>
              )
            })}
          </tbody>
        </table>
      </div>
    </Spin>
  )
}

方法二:直接导出html

 useEffect(() => {
    getNetworkFile();
  }, []);
  const getNetworkFile = () => {
    axios({
      url: "/rep.xlsx",
      method: "GET",
      responseType: "blob",
    }).then((blobData) => {
      //将blob转为file类型
      let file = new File([blobData.data], "报表", { type: blobData.data.type });
      fileReader(file);
    });
  };
  const fileReader = (file) => {
    let reader = new FileReader();
    //读入file
    reader.readAsBinaryString(file);
    reader.onload = (e) => {
      let side = e.target.result;
      //读取file, 提取数据
      let workbook = XLSX.read(side, { type: "binary", cellStyles: true });
      // let workbook = XLSX.readFile('./rep.xlsx')
      let html = "";

      workbook.SheetNames.forEach(function (name, index) {
        let ws = workbook.Sheets[name];
        let str = xlsl.utils.sheet_to_html(ws, { header: 1, defval: "" });
        console.log("html");
        // 只截取table的内容
        let startNo = str.indexOf(`<table>`);
        let endNo = str.indexOf(`</table>`);
        str = str.substring(startNo, endNo + `</table>`.length);
        str = str.replace(/(\b(?:t|v)=".*?")/g, "");
        str = str.replace(
          "<table>",
          `<table border="1" style="border-collapse:collapse; width: 100%; border:1px solid #666666; margin-bottom:5px;font-size:14px;margin: 15px 0;">`
        );
        html += str;
      });
      document.getElementById("can").innerHTML = html;
      setData();
    };
  };
  const setData = () => {
    if (data) {
      // Object.keys(data).map((i) => {
      //   let s = i.split(",").join("");
      //   document.getElementById(`sjs-${s}`).innerHTML = data[s];
      // });
    }
  };

  return <div id="can"></div>;
}
posted @ 2022-08-17 15:23  流云君  阅读(1482)  评论(0编辑  收藏  举报