开始之前先上效果图,代码可直接复制前端使用,觉得不错的可以点个赞
1.需要用到的js自行下载
(1)js-xlsx.js
https://github.com/Ctrl-Ling/XLSX-Style-Utils或者https://github.com/SheetJS/js-xlsx
里面的xlsx.core.min.js或xlsx.full.min.js(本文用到xlsx.core.min.js)
(2)xlsxStyle.js
https://github.com/Ctrl-Ling/XLSX-Style-Utils
里面的xlsxStyle.core.min.js(本文用到xlsxStyle.core.min.js)
(3)xlsxStyle.utils.js
https://github.com/Ctrl-Ling/XLSX-Style-Utils
里面的xlsxStyle.utils.js
引入文件
<script src = '/xlsx.core.min.js' </script>; <script src = '/xlsxStyle.core.min.js' </script>;
//表头 let tableHead = { 'name': '姓名', 'subject': '学科', 'score': '成绩' }; //json数据 let tableData=[{ 'name': '李明', 'subject': '数学',//以下给该字段加上颜色,详情见代码 'score': '98' },{ 'name': '李明', 'subject': '语文', 'score': '96' },{ 'name': '赵寻', 'subject': {'value':'数学','fgColor':'92D050'},//这里为了演示自定义颜色数据格式 'score': '95' },{ 'name': '王力', 'subject': '数学', 'score': '80' }]; let countNum = [];//统计相同姓名的占列个数 tableData.forEach(function (item) { if (countNum.hasOwnProperty(item.name)) { countNum[item.name]+=1; } else { countNum[item.name] = 1; } }); let number = [];//将个数合并成新数组方便下面取值,(若有其他方法已拿到占列数可省略此步) Object.keys(countNum).forEach((key) => { number.push(countNum[key]) }); let merges = [];//存统计好的,行和列的合并数组 let startRow = 1;//从第二行开始处理,跳过标题行 for (let i = 0; i < number.length; i++) { // 合并单元格处理's' 开始,'e' 结束,'c' 代表列,'r' 代表行,表格中行和列都是从下标0开始 //所以:如:{s: {c: 0, r: 1}, e: {c: 0, r: 2}}表示execl中A2到A3合并 for (let y = 0; y < 1; y++) {//这里默认只合并第一列(项目中可根据需求更改y的范围) let obj = {s: {c: y, r: startRow}, e: {c: y, r: startRow + number[i] - 1}}; merges.push(obj); } startRow += number[i];//记录下一个实际要处理的行索引 } tableData.unshift(tableHead); let rowCount = tableData.length;//行数 let colCount = 3;//列数,这里传行数和列数是为了在下载方法中逐行设置背景色或字体 let colWidth = [14, 15, 15];//列宽默认从A往后排序 let filename = "file" + new Date().toISOString().replace(/[\-\:\.]/g, ""); exportxlsx_JsonMerges(tableData, filename, merges, rowCount, colCount, colWidth); /** * @param data, json数组格式如['{'name':'阿张'},{'name':'小王'}'] * @param fileName * @param int rowCount 行数 * @param int colCount 列数 * @param array colWidth 列宽默认从A开始 (数组长度等于列数) */ function exportxlsx_JsonMerges(data, fileName, merges, rowCount, colCount, colWidth) { if (isNaN(rowCount) || rowCount == 0 || isNaN(colCount) || colCount == 0) { alert(rowCount); alert(colCount); return; } let sheet = XLSX.utils.json_to_sheet(data, {skipHeader: true});//通过json_to_sheet转成单页(Sheet)数据,skipHeader为true不使用key为表头 sheet['!merges'] = merges; sheet['!cols'] = []; if (colWidth.length != 0) { for (var item of colWidth) { // item指的的就是数组每一项的值 sheet['!cols'].push({'wch': item}) } } let bool = true; for (let i = 1; i <= rowCount; i++) { if (i != 1) { bool = false } for (let j = 1; j <= colCount; j++) { let lattice = getColName(j) + i;//单元格编号 sheet[lattice].s = { border: {//添加边框颜色(表格中的线) bottom: { style: 'thin', color: '000000' }, left: { style: 'thin', color: '000000' }, right: { style: 'thin', color: '000000' }, top: { style: 'thin', color: '000000' } }, alignment: { horizontal: 'center', //水平居中 vertical: 'center',//垂直居中 }, //字体类型、大小、是否加粗 font: {//字体 name: '宋体', sz: 12, bold: bool } }; if (lattice === 'B2') { //这里固定给B2添加颜色 sheet[lattice].s = { ...sheet[lattice].s, fill: { //背景色 fgColor: {rgb: '95B3D7'} }, font: {//覆盖字体 name: '等线', sz: 10, bold: true }, } } //这里自定义颜色,若是前端以对象传值时用到,这里数据格式为{'name':{'value':'李明','fgcolor':'ff0000'}} if (sheet[lattice].hasOwnProperty('value')) { sheet[lattice].v = sheet[lattice].value; delete sheet[lattice].value; } if (sheet[lattice].hasOwnProperty('fgColor')) { if (!sheet[lattice].hasOwnProperty('fill')) { sheet[lattice].s.fill = {}; } sheet[lattice].s.fill.fgColor = {}; sheet[lattice].s.fill.fgColor.rgb = sheet[lattice].fgColor; delete sheet[lattice].fgColor; } } } finalDownload(data, fileName, sheet, true) } /** * @param data * @param fileName * @param sheet * @param style 是否带样式导出 */ function finalDownload(data, fileName, sheet, style = false) { const wb = {SheetNames: ['Sheet1'], Sheets: {}, Props: {}}; wb.Sheets['Sheet1'] = sheet; const wopts = { bookType: 'xlsx', // 要生成的文件类型 bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性 type: 'binary' }; if (style == true) { //用XlsxStyle类,可设置样式 saveAs(new Blob([s2ab(xlsxStyle.write(wb, wopts))], {type: "application/octet-stream"}), fileName + '.xlsx'); } else { //无样式下载 saveAs(new Blob([s2ab(XLSX.write(wb, wopts))], {type: "application/octet-stream"}), fileName + '.xlsx'); } } /** * @param s * @returns {ArrayBuffer} */ function s2ab(s) { var buf = new ArrayBuffer(s.length);// 字符串转ArrayBuffer var view = new Uint8Array(buf); for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } /** * @param obj * @param fileName */ function saveAs(obj, fileName) {//自定义下载文件方式 var tmpa = document.createElement("a"); tmpa.download = fileName || "下载"; tmpa.href = URL.createObjectURL(obj); //绑定a标签 tmpa.click(); //模拟点击实现下载 setTimeout(function () { //延时释放 URL.revokeObjectURL(obj); //用URL.revokeObjectURL()来释放这个object URL }, 100); } /** * 根据数字获取列名 * @colCount 列序号 * @returns 列名 */ function getColName(colCount) { if (colCount < 0) { return ''; } let wantedColName = ''; while (colCount > 0) { end = colCount % 26; wantedColName = String.fromCharCode(65 + end - 1) + wantedColName;//65开始表示A列 colCount = Math.floor(colCount / 26); } return wantedColName; }