最近公司有个新需求,需要前端导出规定模板的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~