最近在写前端的时候遇到了excel导入导出的问题,其实后端做excel导入导出是更好的,不过我们这个属于是小项目,直接用前端会更快更方便!
目录
前言最近用js来实现前端EXCEL导入导出等一系列相关功能。接下来我将介绍xlsx、xlsx-style、exceljs库的使用,如果时间急,可以直接看exceljs,因为这个可以实现以上两种的所有功能。
一开始只需要实现导入功能,我使用到了xlsx这个npm库;
后来要实现导出且有样式需求,我是用到了xlsx+xlsx-style这个npm库;
最后导出需求升级,一要合并单元格,二要其中一列的数据变化依赖于另一列的数据,三要更丰富的样式,四要设置下拉框等样式。用到了最全面的库:exceljs。
一、使用xlsx库实现简单的excel导入功能需求解析:导入的表的示例如下:
1. 使用步骤以下代码均基于前端是vue。当然自己修改也很容易,毕竟主要也是js。
1.1 引入库代码如下(示例):
npm install xlsx 1.2 写前端html组件代码 <div style="float: right; margin:0 0 10px 10px;"> <el-upload action="" ref="upload" accept=".xls,.xlsx" :on-remove="handleRemove" :before-remove="beforeRemove" :on-preview="handlePreview" multiple :limit="1" :on-exceed="handleExceed" :file-list="fileList" :on-change="handleChange" :show-file-list="false" :auto-upload="false"> <el-button @click="clearFilter" icon="el-icon-upload" style="border: 1px solid #409EFF;color: #409EFF;" >Import Excel</el-button> </el-upload> <el-link type="success" v-show="formName" class="el-icon-circle-close" @click="deleteFormName">{{formName}}</el-link> </div>以下是几个方法的含义解析,我们主要的代码写在handleChange里面:
handleRemove: 当文件被移除时触发的方法。这个方法会在文件被移除之前调用,并且可以通过返回一个 Promise 来决定是否允许移除文件。
beforeRemove: 在删除文件之前执行的方法。如果返回 false 或者一个 reject 的 Promise,则文件将不会被删除。可以用来做一些额外的校验或操作。
handlePreview: 文件预览时执行的方法。该方法会在点击文件列表中的文件名或预览图标时调用。
handleExceed: 当文件超出设定的数量限制时触发的方法。超出限制的文件不会被添加到文件列表中。
handleChange: 选择文件后触发的方法。在该方法中,可以对文件进行一些校验或其他操作。
clearFilter: 清除筛选条件。点击该按钮时会调用该方法。
deleteFormName: 删除表单名称。点击 el-link 标签时会调用该方法。
这些方法用于处理文件上传组件的各种事件,包括添加/移除文件、限制文件数量、文件预览、文件校验等操作。
1.3 写前端js方法解析excel先是前四个方法以及deleteFormName,这些方法比较简单且固定,可以直接抄。
handleRemove(file, fileList) { console.log(file, fileList); }, handlePreview(file) { console.log(file); }, handleExceed(files, fileList) { this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`); }, beforeRemove(file, fileList) { return this.$confirm(`确定移除 ${ file.name }?`); }, deleteFormName(){ this.formName = ""; this.fileList = []; },然后是最重要的方法handleChange,这个方法是实现excel导入的主要方法,其含义是在选择文件后触发操作,我们实现的主要步骤可分解为:
1. 导入xlsx库;
2.通过库来解析excel文件的表头和内容;
3.将内容封装成对象;
4.将对象列表作为请求参数传递给后端,实现数据的导入;
5.做结尾工作,给用户提示信息。
那么主要代码如下(分解步骤看注释就ok了~):
handleChange(file) { //1.设置加载动画 const loading = this.$loading({ lock: true, text: 'Loading', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' }); //2.定义参数并通过FileReader读取文件的二进制信息 const _this = this _this.tableData = [] _this.fileName = file.name; _this.formName = file.name; const reader = new FileReader(); reader.readAsArrayBuffer(file.raw); reader.onload = function () { const buffer = reader.result; const bytes = new Uint8Array(buffer); const length = bytes.byteLength; let binary = ""; for (let i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } //3.引入xlsx库,也可以在script开头通过import导入 const XLSX = require("xlsx"); const wb = XLSX.read(binary, { type: "binary", });//work book 工作簿 const outdata = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); outdata.forEach((i) => { _this.tableData.push(i); }); console.log(_this.tableData); //4.获取到解析的excel数据,对每一行数据进行解析、校验、异常处理,这里结合业务自定义就行 let tableData = _this.tableData; let addProjectList = []; let index = 0; for (let data of tableData){ if (index++ ==0) continue; let bmNumber = data["BM Number"]==undefined?null:data["BM Number"]; let projectName = data["Project Name"]==undefined?null:data["Project Name"]; let customerOem = data["OEM Customer"]==undefined?null:data["OEM Customer"]; let customerTier = data["Tier Customer"]==undefined?null:data["Tier Customer"]; let eelCostStatus = data["Released Cost\r\n(KCNY)"]==undefined?null:data["Released Cost\r\n(KCNY)"]; let swDlvCostStatus = data["SW DLV \r\n(KEUR)"]==undefined?null:data["SW DLV \r\n(KEUR)"]; let remark = data.Remark==undefined?null:data.Remark; let addProject = { bmNumber: bmNumber, customerOem: customerOem, customerTier: customerTier, eelCostStatus: eelCostStatus, projectName: projectName, remark: remark, swDlvCostStatus: swDlvCostStatus, } let addProjectQuery = {}; addProjectQuery['addProject'] = addProject; addProjectList.push(addProjectQuery); }; //异常处理 if (addProjectList.length == 0){ _this.$message({ message: 'data is empty', type: 'error' }); loading.close(); return; } //5.将数据传递给后端,并反馈给用户提示信息。 createProjectList(addProjectList).then(response => { console.log(response); _this.$notify({ title: 'Success', message: 'Update Successfully', type: 'success', duration: 2000 }); //6.重新刷新数据 _this.getList(); loading.close(); }) }; }; 二、使用xlsx+xlsx-style实现简单的excel导出功能导出excel的时候因为要考虑一些样式问题,比如说颜色、字体大小等,所以还要用到xlsx-style。
1.使用步骤 1.1 引入库 npm install xlsx-style 1.2 写前端html组件代码我这边的需求是先挑选表格中的数据,选中被挑选数据行的check框,然后再点击导出按钮,命名好导出的EXCEL的文件名和文件类型,即可导出EXCEL数据。
这个是点击导出的按钮
<el-button :loading="downloadLoading" style="float: right; margin:0 0 10px 10px;border: 1px solid #409EFF;color: #409EFF;" icon="el-icon-thumb" @click="handleexport"> Export Excel </el-button>这个是弹出的export dialog
<!--0.Excel Confirm Dialog--> <el-dialog title="Export Comfirm" :visible.sync="exportVisible" :close-on-click-modal="false" size="mini" width="500px"> <el-input v-model="filename" placeholder="Export Name" style="margin:0 0 10px 10px; width: 150px" /> <el-select v-model="bookType" placeholder="Book Type" class="item" style="margin:0 0 10px 10px;"> <el-option v-for="item in BookTypes" :key="item.value" :label="item.label" :value="item.Pvalue" /> </el-select> <el-button :loading="downloadLoading" style="margin:0 0 10px 10px;" type="primary" icon="el-icon-download" @click="handleDownload"> Confirm </el-button> </el-dialog> 1.1 写前端js方法导出excel首先挑选表格数据,然后点击export按钮,弹出export的dialog,填写导出文件的文件名和文件类型,最后点击Confirm实现EXCEL的数据导出。
弹出方法:
handleexport() { this.exportVisible = true },最终的Confirm方法,实现步骤包括:
1.全局加载框渲染,也可以使用局部自定义的形式。
2.设置表头
3.设置数据过滤器,将list中的数据安装表头需要进行过滤
4.获取挑选的表格行数据
5.修改状态,将数字类型转换为对应的字符串
6.设置导出的excel的属性
7.执行导出函数
8.加载器关闭,清空挑选框
其中2,4,6,7是常规的操作,其他都是结合我的业务操作的。代码如下:
handleDownload() { if (this.multipleSelection.length) { // 这个是判断挑选框的数量,属于业务内容,也可以去掉 //1.全局加载框渲染,也可以使用局部自定义的形式。 this.downloadLoading = true //2.设置表头 let tHeader = []; tHeader.push("BM Number") tHeader.push("Category") tHeader.push("VehicleType") tHeader.push("Project Status") tHeader.push("Project Type") tHeader.push("Project Name") tHeader.push("Released Cost\n(KCNY)") tHeader.push("SW DLV \n(KEUR)") tHeader.push("Risk Status") tHeader.push("Remark") //3.设置数据过滤器,将list中的数据安装表头需要进行过滤 let filterVal = [] filterVal.push("bmNumber") filterVal.push("category") filterVal.push("vehicleType") filterVal.push("projectStatus") filterVal.push("projectType") filterVal.push("projectName") filterVal.push("eelCostStatus") filterVal.push("swDlvCostStatus") filterVal.push("priority") filterVal.push("remark") //4.获取挑选的表格行数据 let list = []; this.multipleSelection.forEach(item => { list.push(deepClone(item)) }) //5.修改状态,将数字类型转换为对应的字符串 list.forEach(item => { item.projectStatus = this.getStatusText(item.projectStatus); item.category = this.getCategoryText(item.category); item.projectType = this.getTypeText(item.projectType); item.priority = this.getPriorityText(item.priority) item.vehicleType = this.getVehicleText(item.vehicleType) }) //data是过滤后的数据 const data = this.formatJson(filterVal, list) // 需要导出的数据 data.unshift(tHeader); //6.设置导出的excel的属性 let filenamefront = this.filename == null?"Export_Excel":this.filename; let bookTypeback = this.bookType==null?'xlsx':this.bookType; let filename = filenamefront+'.'+bookTypeback; //6.1Excel第一个sheet的名称 const ws_name = 'Sheet1' const wb = XLSX.utils.book_new() const ws = XLSX.utils.aoa_to_sheet(data) //6.2定义表头样式 const headerStyle = { fill: { fgColor: { rgb: "1F497D" } // 红色填充 }, font: { sz: 14, // 字体大小 color: { rgb: "FFFFFFFF" }, // 字体颜色白色 bold: true, // 加粗 italic: false, underline: false }, alignment: { horizontal: "center", // 水平居中 vertical: "center" // 垂直居中 } }; //6.3应用样式到第一行表头 tHeader.forEach((header, index) => { const cellAddress = XLSX.utils.encode_cell({ c: index, r: 0 }); // 获取单元格地址 if (!ws[cellAddress]) ws[cellAddress] = { t: 's', v: header }; // 确保单元格存在 ws[cellAddress].s = headerStyle; // 应用样式 }); //6.4设置列宽 // 设置每列的宽度//返回的是一个对象数组[{},{},{}] const colWidths = tHeader.map((header, index) => { if (index === 13) { // N列是第14列(因为索引从0开始) return { wch: 34.75 }; // N列宽度设置为25 }else if (index>=14 && index<= 29){ return {wch: 20}; }else if (index === 34){ return {wch: 74}; }else if (index>=35 && index<=42){ return {wch: 20}; } return { wch: 15 }; // 其他列宽度设置为15 }); ws['!cols'] = colWidths; //6.5设置行高 const rowHeight = 400; // 行高为100 if (!ws['!rows']) ws['!rows'] = []; ws['!rows'] = [{ hpx: rowHeight }]; // 应用行高到第一行 //7.执行导出函数 XLSX.utils.book_append_sheet(wb, ws, ws_name) // 将数据添加到工作薄 let bookType = this.bookType==null?'xlsx':this.bookType; //导出Excel, 注意这里用到的是XLSXS对象 let wbout = XLSXS.write(wb, { bookType: bookType, bookSST: false, type: 'binary' }) FileSaver.saveAs( new Blob([this.s2ab(wbout)], { type: 'application/octet-stream' }), filename ) //8.加载器关闭,清空挑选框 this.$refs.filterTable.clearSelection() this.downloadLoading = false } else { this.$message({ message: 'Please select at least one item', type: 'warning' }) } }, 2.导出样式 三、使用exceljs实现复杂的excel导出功能,包括数据依赖、表格样式、单元格合并这个需求是因为有下载模板的需要,数据依赖指的是:B列是一个下拉框,其中的数据是依赖于A列的,A列是不同的数据,那么下拉框中的数据也会相应变化;表格样式跟xlsx-style差不多但是更强大;单元格合并就是字面意思。
1.exceljs库介绍非常强大的一个工具,可以直接看github上的中文教程,我也是看教程慢慢学的。
https://github.com/exceljs/exceljs/blob/master/README_zh.md
2.使用步骤 2.1 引入库 npm install exceljs 2.2 写前端html组件代码就是个导出的按钮
<el-button :loading="downloadLoading" style="float: right; margin:0 0 10px 10px;border: 1px solid #409EFF;color: #409EFF;" icon="el-icon-download" @click="downLoadTemplate"> Download Excel Template </el-button> 2.3 写前端js方法下载EXCEL模板 import ExcelJS from 'exceljs/dist/exceljs.min.js'; // 使用适合浏览器的版本 import { header } from 'express/lib/request'; export async function createAndFillWorkbook(ca,ts,hw,ws,sw) { //1.创建工作表和工作簿 const workbook = new ExcelJS.Workbook(); //工作簿 const worksheet = workbook.addWorksheet('My Sheet'); //工作表 //2.创建列名 let tHeader = []; tHeader.push("Template Remark") tHeader.push("BM Number") tHeader.push("Category") tHeader.push("VehicleType") //3.创建表头名,根据不同的列设置不同的宽度 let columnsHeader = []; for(let index=0;index<tHeader.length;index++){ if (index === 14) { // N列是第14列(因为索引从0开始 columnsHeader.push({header:tHeader[index],key:tHeader[index],width:34.75}); }else if (index>=15 && index<= 30){ columnsHeader.push({header:tHeader[index],key:tHeader[index],width:20}); }else if (index === 35 || index === 0){ columnsHeader.push({header:tHeader[index],key:tHeader[index],width:74}); }else if (index>=3 && index<=43){ columnsHeader.push({header:tHeader[index],key:tHeader[index],width:20}); }else{ columnsHeader.push({header:tHeader[index],key:tHeader[index],width:15}); } } //将创建的表头设置到工作表中 worksheet.columns = columnsHeader; //4.添加下拉框,这个是用dataValidation来添加的,数据校验,具体可以看github上的详解。默认添加到80行 for(let i=2;i<=80;i++){ //vehicleType worksheet.getCell('D'+i).dataValidation = { type: 'list', allowBlank: false, formulae: ['"纯电车,燃油车,混动车"'] }; } //5.设置公式 这个也是下拉框的一部分,将依赖数据放到sheet2中去。 //I.数据填充 const worksheet2 = workbook.addWorksheet("sheet2"); //最终的数据肯定是从family中获取,这边模拟一下family的数据 let caFamilyList = ca; worksheet2.getColumn("A").values = caFamilyList //II.数据验证来源 for(let i=3;i<=80;i++){ //ecu product family worksheet.getCell('AG'+i).dataValidation = { type: 'list', allowBlank: false, formulae: ['=INDIRECT($C$'+i+')'] }; //drive product family worksheet.getCell('AI'+i).dataValidation = { type: 'list', allowBlank: false, formulae: ['=INDIRECT($C$'+i+')'] }; } //III.设置公式名,这个设置了数据校验才能生效。 for(let i=1;i<=caFamilyList.length;i++){ worksheet2.getCell('A'+i).name = 'CA'; } //6.设置header样式 for(let i=0;i<=43;i++){ //填充+字体+对齐 if (i<=25){ let char = String.fromCharCode(65 + i); worksheet.getCell(char+1).fill = { type: 'pattern', pattern:'solid', fgColor:{argb:'1F497D'} }; worksheet.getCell(char+1).font = { size: 20, // 字体大小 color: { argb: 'FFFFFFFF' }, // 字体颜色 bold: true, // 加粗 italic: false, underline: false, vertAlign: 'superscript' }; worksheet.getCell(char+1).alignment = { vertical: 'middle', horizontal: 'center',wrapText: true }; }else{ let char = String.fromCharCode(65 + i-26); worksheet.getCell('A'+char+1).fill = { type: 'pattern', pattern:'solid', fgColor:{argb:'1F497D'} }; worksheet.getCell('A'+char+1).font = { size: 20, // 字体大小 color: { argb: 'FFFFFFFF' }, // 字体颜色 bold: true, // 加粗 italic: false, underline: false, vertAlign: 'superscript' }; worksheet.getCell('A'+char+1).alignment = { vertical: 'middle', horizontal: 'center',wrapText: true }; } } //7.下载excel文件 try { // 写入缓冲区 const buffer = await workbook.xlsx.writeBuffer(); // 创建 Blob 对象 const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); // 创建一个链接并模拟点击以下载文件 const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'PM_Project_Template.xlsx'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } catch (error) { console.error('Error writing Excel file:', error); } }最难的部分是添加下拉框这里,因为首先github里没有完整的解决方案,首先你得会excel的操作,知道怎么通过数据校验和公式绑定来实现,然后你要会将这个公式插入到exceljs当中去,这个过程挺复杂的,网上也没找到,是自己实现的,如果对你有帮助可以点赞收藏谢谢。
3.导出样式sheet2存数据
总结以上就是今天要讲的内容,本文介绍了xlsx、xlsx-style、exceljs的使用方式,篇幅有限,如果你还是没有看懂,欢迎私聊或评论区。