效果

前情提要
后端传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下支持)*/
处理数据
左边的数据变成右边数据

得到列表范围
[''!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" >`
);
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>;
}