赞
踩
最近公司有个新需求,需要前端导出规定模板的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}`); }
这边也是借鉴了百度的大佬,合并单元格可以生效的,不过样式的修改并没有生效,有需要的可以自己研究下。
我选择的是原始的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~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。