Ultra-detailed pure front-end export excel and complete various style modifications (xlsx-style)

Ultra-detailed pure front-end export excel and complete various style modifications (xlsx-style)

One bar is uploading...re-upload cancel

December 08, 2020 17:53 Read 6247

I. Introduction

The recent project involved the export of excel. I really spent a lot of time on this part. At first, the requirement did not require modification at the style level, so I chose XLSX.JS without thinking too much about the modification of the style. However, as the project progressed, the customer raised the need to modify the style according to the format, so he could only search for information related to modifying the excel style. I wanted to use XLSX.js directly, but obviously not. The basic version of XLSX.js only has width and height. , merging cells and other relatively basic modifications, if you want to modify more complicated styles, you have to upgrade the pro version. How to say, spending money on this is not our style. After searching a lot of information, I found that the xlsx file can be exported and can meet the needs. The only plug-in for style modification in XLSX-STYLE is XLSX-STYLE (maybe there are suitable tools but I don’t know about it), but XLSX-STYLE seems to have not been maintained for a long time, so there are still many problems in the process of getting started .

2. Sort out the needs

In my project, I need to export the table of antd in the page, and also need to process the data to achieve the statistical effect. The content of the exported excel should be determined according to the needs. The style needs to be modified including width, height, and background color , font, font size, merged cells, cell borders, font color, font bold and centered, etc. At the same time, it needs to be compatible to ie11. According to the priority of the requirements, let's sort them

  • 1. Implement excel export
  • 2. Compatible with ie11
  • 3. Statistics
  • 4. Modify width and height, merge cells
  • 5. Modifying other styles  The summed up content does not seem to be much, but it is not easy to implement. The function of exporting and compatible with ie11 has been perfected before, and we will elaborate on it later, and the focus is on statistical data, that is to say , we don’t want to restore the antd table 1:1, what we want is to define the data by ourselves, organize and calculate, and then export, so we have to integrate all the data and then calculate it into a two-dimensional array that xlsx accepts. Of course, XLSX also supports exporting directly through the table tag, which we don't use here, so we won't mention it for now.

3. Preliminary preparation

  • 1. npm install xlsx-styleImport the plugin into the project
  • 2. import XLSX from 'xlsx-style'Introduce XLSX in the component
  • 3. This relative module was not found:./cptable in ./node_modules/[email protected]@xlsx-style/dist/cpexcel.jsPerfect error reporting
  • 4. Find the line 807 in \node_modules\xlsx-style\dist\cpexcel.js var cpt = require('./cpt' + 'able');and replace it with var cpt = cptable;save and continue.
  • 5. Error: Can't resolve 'fs' in 'E:\\xlsx-style'Perfect error reporting
  • 6. This error should come from the fs module of node used in the xlsx source code during the compilation process. The fs module is a server-side module. Although webpack can load the fs code to the front end, the browser cannot interpret and run it, so , to fix this error, either find a way to modify the code so that the operation of calling fs occurs on the server side, or find a way to install a browser plug-in that enables the browser to recognize fs, so that fs can be recognized and run in the browser, in Our requirements only involve the export of excel, so the fs module should not be used, so we choose to add related configuration items to the configuration file to make fs not used, so we add it to the configuration of webpack.config.js, node: { fs: 'empty', }everyone The scaffolding of different projects may be different. You can try it yourself where the configuration should be placed. Here I can only tell you that it is under the topmost configuration item.
  • 7. After the error has been repaired, start to expand the incomplete xlsx-style. First of all, find the xlsx.js under the xlsx-style directory, and find the defaultCellStyle. From the name, the defaultCellStyle is all cells The default style, we change it to the overall style we need, which can greatly reduce the time it takes us to modify the style later.
  • 8. Extend the function of modifying the line height: xlsx-style does not include the function of modifying the line height. Here we need to expand it, of course not write it by ourselves, because xlsx-style comes from xlsxjs, so the source code of the two is very similar. We npm install xlsxjs , and then find the corresponding method to modify the row height, just copy and expand to xlsx-style, as follows
// 本部分代码来源CSDN
// xlsx-style版本0.8.13
// xlsx版本0.14.1 
//这是xlsx-style文件中的xlsx.js的需要修改的代码,是从xlsx文件夹中的xlsx.js中复制出来的
// write_ws_xml_data找到这个方法名字,全部替换
// 把xlsx中能修改高度的代码复制到xlsx-style中
var DEF_PPI = 96, PPI = DEF_PPI;
function px2pt(px) { return px * 96 / PPI; }
function pt2px(pt) { return pt * PPI / 96; }
function write_ws_xml_data(ws, opts, idx, wb) {
	var o = [], r = [], range = safe_decode_range(ws['!ref']), cell="", ref, rr = "", cols = [], R=0, C=0, rows = ws['!rows'];
	var dense = Array.isArray(ws);
	var params = ({r:rr}), row, height = -1;
	for(C = range.s.c; C <= range.e.c; ++C) cols[C] = encode_col(C);
	for(R = range.s.r; R <= range.e.r; ++R) {
		r = [];
		rr = encode_row(R);
		for(C = range.s.c; C <= range.e.c; ++C) {
			ref = cols[C] + rr;
			var _cell = dense ? (ws[R]||[])[C]: ws[ref];
			if(_cell === undefined) continue;
			if((cell = write_ws_xml_cell(_cell, ref, ws, opts, idx, wb)) != null) r.push(cell);
		}
		if(r.length > 0 || (rows && rows[R])) {
			params = ({r:rr});
			if(rows && rows[R]) {
				row = rows[R];
				if(row.hidden) params.hidden = 1;
				height = -1;
				if (row.hpx) height = px2pt(row.hpx);
				else if (row.hpt) height = row.hpt;
				if (height > -1) { params.ht = height; params.customHeight = 1; }
				if (row.level) { params.outlineLevel = row.level; }
			}
			o[o.length] = (writextag('row', r.join(""), params));
		}
	}
	if(rows) for(; R < rows.length; ++R) {
		if(rows && rows[R]) {
			params = ({r:R+1});
			row = rows[R];
			if(row.hidden) params.hidden = 1;
			height = -1;
			if (row.hpx) height = px2pt(row.hpx);
			else if (row.hpt) height = row.hpt;
			if (height > -1) { params.ht = height; params.customHeight = 1; }
			if (row.level) { params.outlineLevel = row.level; }
			o[o.length] = (writextag('row', "", params));
		}
	}
	return o.join("");
}
复制代码
  • 9. If you can’t find the defaultCellStyle, then there may be no corresponding method for converting a two-dimensional array to a worksheet object. You can copy it from the official document to the corresponding code block, and then extend it to XLSX.Util, as shown in the figure below 

     In order to reduce the time for everyone to search, the official code is posted here:
// 从json转化为sheet,xslx中没有aoaToSheet的方法,该方法摘自官方test
function aoa_to_sheet(data){
	var defaultCellStyle = {
		font: { name: "Meiryo UI", sz: 11, color: { auto: 1 } },
		  border: {
			top: {
			style:'thin',
			color: { 
				  auto: 1 
				}
		   },
		   left: {
			style:'thin',
			color: { 
				  auto: 1 
				}
		   },
		   right: {
			style:'thin',
			color: { 
				  auto: 1 
				}
		   },
		   bottom: {
			style:'thin',
			color: { 
				  auto: 1 
				}
		   }
		  },
		  alignment: {
			 /// 自动换行
			wrapText: 1,
			  // 居中
			horizontal: "center",
			vertical: "center",
			indent: 0
	   }
	  };
	function dateNum(date){
		let year = date.getFullYear();
		let month = (date.getMonth()+1)>9?date.getMonth()+1:'0'+(date.getMonth()+1);
		let day = date.getDate()>9?date.getDate():'0'+date.getDate();
		return year+'/'+month+'/'+day;
	};
	const ws = {};
	const range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
	  for(let R = 0; R !== data.length; ++R) {
	  for(let C = 0; C !== data[R].length; ++C) {
		if(range.s.r > R) range.s.r = R;
		if(range.s.c > C) range.s.c = C;
		if(range.e.r < R) range.e.r = R;
		if(range.e.c < C) range.e.c = C;
		/// 这里生成cell的时候,使用上面定义的默认样式
		const cell = {v: data[R][C], s: defaultCellStyle};
		const cell_ref = XLSX.utils.encode_cell({c:C,r:R});
  
		/* TEST: proper cell types and value handling */
		if(typeof cell.v === 'number') cell.t = 'n';
		else if(typeof cell.v === 'boolean') cell.t = 'b';
		else if(cell.v instanceof Date) {
		cell.t = 'n'; cell.z = XLSX.SSF._table[14];
		cell.v = dateNum(cell.v);
		}
		else cell.t = 's';
		ws[cell_ref] = cell;
	  }
	}
	/* TEST: proper range */
	if(range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
	return ws;
  };
复制代码
  • 10. Expand the function of generating excel based on dom nodes: Although this function is not used in the project, it is still convenient to adjust it. Find the table_to_sheet() method of xlsxjs utils, copy it to xlsx-style, and put other related references Function variables, etc. are all transplanted into xlsx-style until it can run normally, try a demo, it works normally, perfect.
  • 11. Expand the function of generating excel based on two-dimensional arrays: Same as above, find the aoa_to_sheet method in the original xlsx for transplantation.

4. Export excel and be compatible with ie11

  • 1. Use xlsx to export excel process: convert data into excel sheet data according to two-dimensional array (aoa_to_sheet) or dom node (table_to_sheet) -> convert sheet data into binary large object blob -> URL.createObjectURL(blob) to create url- >Others: Create a tag, initialize the src attribute of the a tag, trigger the click event, ie11: window.navigator.msSaveOrOpenBlob .
  • 2. Compatible with ie11, as mentioned above, the export method is different in the last step, the following is the code of the entire export process


export default function downLoadExcel(data, type, config, ele) {
    console.log(config, 'excel配置参数');
    var blob = IEsheet2blob(ws);
    if (IEVersion() !== 11) {
        openDownloadXLSXDialog(blob, `${type}.xlsx`);
    } else {
        window.navigator.msSaveOrOpenBlob(blob, `${type}.xlsx`);
    }


}

function dislodgeLetter(str) {
    var result;
    var reg = /[a-zA-Z]+/; //[a-zA-Z]表示bai匹配字母,dug表示全局匹配
    while (result = str.match(reg)) { //判断str.match(reg)是否没有字母了
        str = str.replace(result[0], ''); //替换掉字母  result[0] 是 str.match(reg)匹配到的字母
    }
    return str;
}



function IEsheet2blob(sheet, sheetName) {
    try {
        new Uint8Array([1, 2]).slice(0, 2);
    } catch (e) {
        //IE或有些浏览器不支持Uint8Array.slice()方法。改成使用Array.slice()方法
        Uint8Array.prototype.slice = Array.prototype.slice;
    }
    sheetName = sheetName || 'sheet1';
    var workbook = {
        SheetNames: [sheetName],
        Sheets: {}
    };
    workbook.Sheets[sheetName] = sheet;
    // 生成excel的配置项
    var wopts = {
        bookType: 'xlsx', // 要生成的文件类型
        bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
        type: 'binary'
    };
    var wbout = XLSX.write(workbook, wopts);
    var blob = new Blob([s2ab(wbout)], {
        type: "application/octet-stream"
    });
    // 字符串转ArrayBuffer
    function s2ab(s) {
        var buf = new ArrayBuffer(s.length);
        var view = new Uint8Array(buf);
        for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
        return buf;
    }
    return blob;
}



function getDefaultStyle() {
    let defaultStyle = {
        fill: {
            fgColor: {
                rgb: ''
            }
        },
        font: {
            name: "Meiryo UI",
            sz: 11,
            color: {
                rgb: ''
            },
            bold: true
        },
        border: {
            top: {
                style: 'thin',
                color: {
                    auto: 1
                }
            },
            left: {
                style: 'thin',
                color: {
                    auto: 1
                }
            },
            right: {
                style: 'thin',
                color: {
                    auto: 1
                }
            },
            bottom: {
                style: 'thin',
                color: {
                    auto: 1
                }
            }
        },
        alignment: {
            /// 自动换行
            wrapText: 1,
            // 居中
            horizontal: "center",
            vertical: "center",
            indent: 0
        }
    };
    return defaultStyle;
}

function IEVersion() {
    var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串  
    var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; //判断是否IE<11浏览器  
    var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器  
    var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
    if (isIE) {
        var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
        reIE.test(userAgent);
        var fIEVersion = parseFloat(RegExp["$1"]);
        if (fIEVersion == 7) {
            return 7;
        } else if (fIEVersion == 8) {
            return 8;
        } else if (fIEVersion == 9) {
            return 9;
        } else if (fIEVersion == 10) {
            return 10;
        } else {
            return 6; //IE版本<=7
        }
    } else if (isEdge) {
        return 'edge'; //edge
    } else if (isIE11) {
        return 11; //IE11  
    } else {
        return -1; //不是ie浏览器
    }
}



function openDownloadXLSXDialog(url, saveName) {
    if (typeof url == 'object' && url instanceof Blob) {
        url = URL.createObjectURL(url); // 创建blob地址
    }
    var aLink = document.createElement('a');
    aLink.href = url;
    aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
    var event;
    if (window.MouseEvent) event = new MouseEvent('click');
    else {
        event = document.createEvent('MouseEvents');
        event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    }
    aLink.dispatchEvent(event);
}
复制代码

Five, the understanding and use of xlsx

  • 1. Worksheet object Worksheet object: It can be understood as an object that stores excel data, each of which does not contain! The properties of all represent a cell, and our modification of the style is actually an operation on the cell here.
  • 2. Cell object, as mentioned above, each cell is stored as a separate object in the worksheet, and the worksheet attribute is called cell coordinates, that is, the cell position displayed in the excel table, following the format of excel: 'A1 ','AB2', etc., the format of each cell object is {t:'n',s:{},v:'值'}, here t is the type of value, n represents a number, then it is easy to guess, the value of t s represents a string; s is a style object, there are many The field can configure the style of the cell. We will talk about it later, v is the value of the cell.
  • 3. !col: A special attribute in the worksheet object, the data format is an array such as: [{wpx:100},{wpx:100},{wpx:80}], setting the value of !col can modify the column width, where wpx represents the width in px, of course there are other writing methods such as wch wait
  • 4. !merge: The configuration attribute used for merging cells. The data format is an array such as: , [{s:{r:0,c:2},e:{r:0,c:6}},{s:{r:1,c:3},e:{r:8,c:3}}]s is the starting cell coordinates, r row, c column, and e is the ending cell coordinates. The example here represents merging row 0, row 2 to 6 cells and cells 1 to 8 of the third column.
  • 5. Workbook: The object that stores the worksheet, the data format is {sheetName:[],sheets:{}}, sheetName stores the worksheet name, sheets stores the worksheet object, the field name is the worksheet name, and the multi-table file can be exported by processing the workbook.
  • 6. Cell range conversion
    //编码行号
    XLSX.utils.encode_row(2);  //"3"
    //解码行号
    XLSX.utils.decode_row("2"); //1

    //编码列标
    XLSX.utils.encode_col(2);  //"C"
    //解码列标 
    XLSX.utils.decode_col("A"); //0

    //编码单元格
    XLSX.utils.encode_cell({ c: 1, r: 1 });  //"B2"
    //解码单元格
    XLSX.utils.decode_cell("B1"); //{c: 1, r: 0}

    //编码单元格范围
    XLSX.utils.encode_range({ s: { c: 1, r: 0 }, e: { c: 2, r: 8 } });  //"B1:C9"
    //解码单元格范围
    XLSX.utils.decode_range("B1:C9"); //{s:{c: 1, r: 0},e: {c: 2, r: 8}}
复制代码
  • 7. Cell styles, here are the commonly used style configurations
s:{
    font: { //字体相关样式
        name: '宋体', //字体类型
        sz: 11, //字体大小
        color: { rgb: '' }, //字体颜色
        bold: true, //是否加粗
    },
    fill: { //背景相关样式
        bgColor: { rgb: '' }, //背景色,填充图案背景
        fgColor: { rgb: '' }, //前背景色,项目中修改单元格颜色用这项
    },
    border: [{   //边框相关样式
    },{
    },{
    },{
    }],
    alignment: {  // 对齐方式相关样式
        vertical: 'center', //垂直对齐方式
        horizontal: 'center', //水平对齐方式
        wrapText: true,  //自动换行
    }
}
//更详细的配置参数如下图,图片来源知乎
复制代码

  • 8. Other configurations about printing or other functions are not involved in my project. You can understand by yourself. There are also parameters related to generating blobs and wpot. You can search for information by yourself according to the code and needs

6. Style modification and related packaging

  • 1. Analysis of project modification style rules: This part of the processing is to allow the code to have a reasonable format. We organize a specific style configuration data format, and then use function processing to grab and replace the style configuration information accepted by the sheet. In this way, The code of this project can be configured according to this data format in any subsequent projects. To design the data format, we must consider the common style modification schemes in all projects. First of all, the head and bottom of the table must be The most common object to modify, and it is often necessary to modify its background color, font color, whether the font is bold, and the font size, so we encapsulate a config object. The merge attribute in the object is used to set the cell merge, and the size attribute is used It is used to set the row height and column width (row row height, col column width) , and then set a field myStyle to store the style information we want to customize. The second point, after processing the header, it is natural to modify the bottom Processing, the bottom processing is similar to the head, but it should be noted that the number of rows needs to be marked at the bottom, otherwise the position of the bottom row cannot be located; the third point, the processing for a specific row, sometimes requires special processing for a whole row or many rows , so a configuration is needed to modify the style for a specific row; there are rows and columns naturally, the fourth point, like the modification for the row, define an attribute for the modification of a specific column (colCells), the fifth point, the special header Style processing, the table header is not necessarily a uniform style, so there needs to be a configuration for processing the corresponding table header; the sixth point, highlight processing, cells that meet the conditions may need to be highlighted, here is a highlight configuration ; Temporarily the last point, the cells merged through the table_to_sheet operation may lose their borders, and a configuration information to fill in the borders is required.
  • 2. The above mentioned are combined as shown in the figure below

  • 3. The encapsulation and processing I have done is still very rough, but it basically satisfies the use of the project. If you have a better processing method, welcome to exchange and discuss. The following code is the logic code I use to process the sheet for my data format , placed after generating the worksheet and before generating the blob.
  if (config.merge.length != 0) {
        ws['!merges'] = config.merge;
    }
    ws['!cols'] = config.size.cols;

    if (config.myStyle.all) { //作用在所有单元格的样式,必须在最顶层,然后某些特殊样式在后面的操作中覆盖基本样式
        Object.keys(ws).forEach((item, index) => {
            if (ws[item].t) {
                ws[item].s = config.myStyle.all;
            }
        });
    }
    if (config.myStyle.headerColor) {
        if (config.myStyle.headerLine) {
            let line = config.myStyle.headerLine;
            let p = /^[A-Z]{1}[A-Z]$/;
            Object.keys(ws).forEach((item, index) => {
                for (let i = 1; i <= line; i++) {
                    if (item.replace(i, '').length == 1 || (p.test(item.replace(i, '')))) {
                        let myStyle = getDefaultStyle();
                        myStyle.fill.fgColor.rgb = config.myStyle.headerColor;
                        myStyle.font.color.rgb = config.myStyle.headerFontColor;
                        ws[item].s = myStyle;
                    }
                }
            });

        }
    }
    if (config.myStyle.specialCol) {
        config.myStyle.specialCol.forEach((item, index) => {
            item.col.forEach((item1, index1) => {
                Object.keys(ws).forEach((item2, index2) => {
                    if (item.expect && item.s) {
                        if (item2.includes(item1) && !item.expect.includes(item2)) {
                            ws[item2].s = item.s;
                        }
                    }
                    if (item.t) {
                        if (item2.includes(item1) && item2.t) {
                            ws[item2].t = item.t;
                        }
                    }
                });
            });

        });
    }
    if (config.myStyle.bottomColor) {
        if (config.myStyle.rowCount) {
            Object.keys(ws).forEach((item, index) => {
                if (item.indexOf(config.myStyle.rowCount) != -1) {
                    let myStyle1 = getDefaultStyle();
                    myStyle1.fill.fgColor.rgb = config.myStyle.bottomColor;
                    ws[item].s = myStyle1;
                }
            })
        }
    }
    if (config.myStyle.colCells) {
        Object.keys(ws).forEach((item, index) => {
            if (item.split('')[0] === config.myStyle.colCells.col && item !== 'C1' && item !== 'C2') {
                ws[item].s = config.myStyle.colCells.s;
            }
        })
    }
    if (config.myStyle.mergeBorder) { //对导出合并单元格无边框的处理
        let arr = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
        let range = config.myStyle.mergeBorder;
        range.forEach((item, index) => {
            if (item.s.c == item.e.c) { //行相等,横向合并
                let star = item.s.r;
                let end = item.e.r;
                for (let i = star + 1; i <= end; i++) {
                    ws[arr[i] + (Number(item.s.c) + 1)] = {
                        s: ws[arr[star] + (Number(item.s.c) + 1)].s
                    }
                }
            } else { //列相等,纵向合并
                let star = item.s.c;
                let end = item.e.c;
                for (let i = star + 1; i <= end; i++) {
                    ws[arr[item.s.r] + (i + 1)] = {
                        s: ws[arr[item.s.r] + (star + 1)].s
                    }
                }
            }
        });
    }
    if (config.myStyle.specialHeader) {
        config.myStyle.specialHeader.forEach((item, index) => {
            Object.keys(ws).forEach((item1, index1) => {
                if (item.cells.includes(item1)) {
                    ws[item1].s.fill = {
                        fgColor: {
                            rgb: item.rgb
                        }
                    };
                    if (item.color) {
                        ws[item1].s.font.color = {
                            rgb: item.color
                        };
                    }
                }
            });
        });
    }
    if (config.myStyle.heightLightColor) {
        Object.keys(ws).forEach((item, index) => {
            if (ws[item].t === 's' && ws[item].v && ws[item].v.includes('%') && !item.includes(config.myStyle.rowCount)) {
                if (Number(ws[item].v.replace('%', '')) < 100) {
                    ws[item].s = {
                        fill: {
                            fgColor: {
                                rgb: config.myStyle.heightLightColor
                            }
                        },
                        font: {
                            name: "Meiryo UI",
                            sz: 11,
                            color: {
                                auto: 1
                            }
                        },
                        border: {
                            top: {
                                style: 'thin',
                                color: {
                                    auto: 1
                                }
                            },
                            left: {
                                style: 'thin',
                                color: {
                                    auto: 1
                                }
                            },
                            right: {
                                style: 'thin',
                                color: {
                                    auto: 1
                                }
                            },
                            bottom: {
                                style: 'thin',
                                color: {
                                    auto: 1
                                }
                            }
                        },
                        alignment: {
                            /// 自动换行
                            wrapText: 1,
                            // 居中
                            horizontal: "center",
                            vertical: "center",
                            indent: 0
                        }
                    }
                }

            }
        });
    }
    if (config.myStyle.rowCells) {
        config.myStyle.rowCells.row.forEach((item, index) => {
            Object.keys(ws).forEach((item1, index1) => {
                let num = Number(dislodgeLetter(item1));
                if (num === item) {
                    ws[item1].s = config.myStyle.rowCells.s;
                }
            });
        });

    }

    Object.keys(ws).forEach((item, index) => {  //对所有的空数据进行处理,不让其显示null
        if (ws[item].t === 's' && !ws[item].v) {
            ws[item].v = '-';
        }
    });
复制代码

7. Difficulties in exporting data processing

  • 1. The exported excel pop-up prompts a file error, as shown in the figure below: 

     Don’t panic, there are only two reasons for such a prompt. One is that the data is disordered, and the other is that the information of the merged cell is incorrect. The data disorder is probably due to the wrong data type, while the merged cell information is mostly due to The cell coordinates are greater than the end cell coordinates, or the start coordinates are the same as the end coordinates. In short, it is not a serious problem. You need to carefully check the style configuration information. If the information is correct, it will not prompt again.
  • 2. Merge cells: This is a point I have struggled with for a long time, because some tables are exported without subtotals, and my project exports the member information of each department in the company, that is, it needs to be merged by department. There is a line of classification under each department, yes, it is like the cover picture, so how can it be realized, after thinking twice, in the end I can only do a summary after obtaining the personnel data of the entire department and then add a line to the total data, and at the same time use another The array stores the number of personnel in the department, which is used for subsequent cell merge calculations. Here is my subtotal calculation code:
    const countFormatedData = (formatedData, text) => {
    //formatedData是整个部门的数据,text是新添加的汇总行的名称在当前项目是小计或者合计
        if (formatedData.length != 0) {
            let result1 = {
                '1月': 0,
                '2月': 0,
                '3月': 0,
                '4月': 0,
                '5月': 0,
                '6月': 0,
                '7月': 0,
                '8月': 0,
                '9月': 0,
                '10月': 0,
                '11月': 0,
                '12月': 0,
                [T('上期实绩')]: 0,
                [T('下期实绩')]: 0,
                [T('年度实绩')]: 0,
                [T('上期目标')]: 0,
                [T('下期目标')]: 0,
                [T('年度目标')]: 0,
                ['共同1月']: 0,
                ['共同2月']: 0,
                ['共同3月']: 0,
                ['共同4月']: 0,
                ['共同5月']: 0,
                ['共同6月']: 0,
                ['共同7月']: 0,
                ['共同8月']: 0,
                ['共同9月']: 0,
                ['共同10月']: 0,
                ['共同11月']: 0,
                ['共同12月']: 0,

            }
            let result = formatedData.reduce((init, next) => {
                if (next['部署'] === T('小计')) {
                    return init;
                } else {
                    return {
                        '1月': init['1月'] + next['1月'],
                        '2月': init['2月'] + next['2月'],
                        '3月': init['3月'] + next['3月'],
                        '4月': init['4月'] + next['4月'],
                        '5月': init['5月'] + next['5月'],
                        '6月': init['6月'] + next['6月'],
                        '7月': init['7月'] + next['7月'],
                        '8月': init['8月'] + next['8月'],
                        '9月': init['9月'] + next['9月'],
                        '10月': init['10月'] + next['10月'],
                        '11月': init['11月'] + next['11月'],
                        '12月': init['12月'] + next['12月'],
                        [T('上期实绩')]: init[T('上期实绩')] + next[T('上期实绩')],
                        [T('下期实绩')]: init[T('下期实绩')] + next[T('下期实绩')],
                        [T('年度实绩')]: init[T('年度实绩')] + next[T('年度实绩')],
                        [T('上期目标')]: init[T('上期目标')] + next[T('上期目标')],
                        [T('下期目标')]: init[T('下期目标')] + next[T('下期目标')],
                        [T('年度目标')]: init[T('年度目标')] + next[T('年度目标')],
                        ['共同1月']: init['共同1月'] + next['共同1月'],
                        ['共同2月']: init['共同2月'] + next['共同2月'],
                        ['共同3月']: init['共同3月'] + next['共同3月'],
                        ['共同4月']: init['共同4月'] + next['共同4月'],
                        ['共同5月']: init['共同5月'] + next['共同5月'],
                        ['共同6月']: init['共同6月'] + next['共同6月'],
                        ['共同7月']: init['共同7月'] + next['共同7月'],
                        ['共同8月']: init['共同8月'] + next['共同8月'],
                        ['共同9月']: init['共同9月'] + next['共同9月'],
                        ['共同10月']: init['共同10月'] + next['共同10月'],
                        ['共同11月']: init['共同11月'] + next['共同11月'],
                        ['共同12月']: init['共同12月'] + next['共同12月'],
                    }
                }

            }, result1);
            result[T('上期达成率')] = result[T('上期目标')] ? `${(result[T('上期实绩')] / result[T('上期目标')] * 100).toFixed(1)}%` : '0%';
            result[T('下期达成率')] = result[T('下期目标')] ? `${(result[T('下期实绩')] / result[T('下期目标')] * 100).toFixed(1)}%` : '0%';
            result[T('年度达成率')] = result[T('年度目标')] ? `${(result[T('年度实绩')] / result[T('年度目标')] * 100).toFixed(1)}%` : '0%';
            result['NO.'] = text;
            result['部署'] = text;
            result[T("姓名")] = text;
            formatedData.push(result)
        }
        return formatedData;
    }

复制代码

Using reduce to calculate and accumulate is the best way I can think of, and it is still the same. Welcome everyone to discuss better writing methods.

The following is the calculation logic code for my merged cell configuration:

exportData.forEach((item, index) => { //遍历找出合并需要的下标
//小计和合计需要合并,原因请看封面图
        if (item[1] === T('总计') || item[1] === T('小计')) {
            merge.push({
                s: {
                    r: index,
                    c: 0
                },
                e: {
                    r: index,
                    c: 2
                }
            });
            if (item[1] === T('小计')) {
            //config是我们之前已经介绍了的配置信息
                config.myStyle.rowCells.row.push(index + 1);
            }

        }
    });
    deptCount.forEach((item, index) => { //这里是重头戏,不进行一定的考量很容易计算错误
        if (index == 0) {
            merge.push({
                s: {
                    r: 2,
                    c: 1
                },
                e: {
                    r: item + 1,
                    c: 1
                }
            });
        } else {
            let count = 0;
            for (let i = 0; i < index; i++) {
                count = count + deptCount[i];
            }
            if (item > 1) {
                merge.push({
                    s: {
                        r: count + index + 2,
                        c: 1
                    },
                    e: {
                        r: count + item + index + 1,
                        c: 1
                    }
                });
            }
        }
    })
复制代码

There is also a table in the project that does not require subtotals, which is naturally simpler than the one that requires subtotals, so I won’t introduce it here.

  • 3. Data deduplication: We all know that once statistics are involved, data deduplication must be considered, because any redundant data will cause errors in statistical summary data. We will not deal with more details here. When the time comes Wrong customer complaints can be criticized by superiors; and deduplication is generally the simplest deduplication of arrays, but it is impossible to be so simple in actual projects, which often involves deduplication of object arrays, and the conditions for deduplication are still It doesn’t have to be completely equal. It may include the previous or the next one if they include each other, or it may not remove one, but remove all the elements between the two including the two. At this time, it will be a bit big. The following are several de-duplication methods I have used. You are welcome to refer to them, and you are more welcome to point out the shortcomings. The first is to cache the push array that needs to be deduplicated, and then use filter to filter out the elements in the array:
export function removeDuplicate(data,field) {
    let newData = data //去重
    let shouldDelete = [];  //将所有需要删除的节点放到数组,根据title判断删除节点
    for (let i = 0; i < newData.length; i++) {
        for (let j = i + 1; j < newData.length; j++) {
            if (newData[i][field] == newData[j][field]) {
                shouldDelete.push({[field]:newData[i][field],index:i});
            }

        }
    }
    console.log(shouldDelete);
    if (shouldDelete.length != 0) {
        newData = newData.filter((item,index) => {
            let result = true;
            shouldDelete.forEach((item1) => {
                if (item[field] === item1[field]&&index==item1.index) {
                    result = false;
                }
            });
            return result;
        });
    }
    return newData;
}

复制代码

The second type, which is different from the first type, is that it needs to determine whether the userName field of the element is included, and if it is included, remove the element between the two elements and the previous element. See the code for details:

export function duplicateRemove(flowData) {
    let newFlowInfo = flowData //去重
    let shouldDelete = [];  //将所有需要删除的节点放到数组,根据title判断删除节点
    for (let i = 0; i < newFlowInfo.length; i++) {
        for (let j = i + 1; j < newFlowInfo.length; j++) {
            let name = newFlowInfo[i].userName.split('、').concat(newFlowInfo[j].userName.split('、'));
            let newName = Array.from(new Set(name));
            if (name.length != newName.length //有重复项
                &&
                newFlowInfo[i].code && newFlowInfo[i].code !== 'Committee' &&
                newFlowInfo[j].code && newFlowInfo[j].code !== 'Committee') {
                for (let k = i; k < j; k++) {
                    shouldDelete.push(newFlowInfo[k]);
                }
            }

        }
    }
    console.log(shouldDelete);
    if (shouldDelete.length != 0) {
        newFlowInfo = newFlowInfo.filter((item) => {
            let result = true;
            shouldDelete.forEach((item1) => {
                if (item.title === item1.title) {
                    result = false;
                }
            });
            return result;
        });
    }
    return newFlowInfo;

}
复制代码

The third type, which removes items with the same UPN, is the most concise:

               //结果去重
                let data = {};
                result.data.data = result.data.data.reduce((cur, next) => {
                    data[next.UPN] ? "" : data[next.UPN] = true && cur.push(next);
                    return cur;
                }, []);
复制代码

The above is the deduplication method I used in the project, of course there are other methods, here are a few to show.

8. Summary

The functions exported by excel are needed by many front-end projects, but there are few tools on the market that can meet most of the needs. , but continue to improve and believe that the things used in this project can be used again in the future. I have to spend more time processing and packaging a more convenient and convenient excel export function module. If you have any suggestions or questions, remember to comment See you later, I will put the excel export code I wrote and the processed xlsx-style on github for everyone to use and improve later, that's all for today ( ^_ ^ )

Project information: github.com/wannigebang…

Update: The improved xlsx-style is published on npm! install commandnpm install xlsx-style-medalsoft

Nine. Update

Recently, the function module code has been sorted out, adding type judgment and multi-table export. However, because of many changes, the code has become a bit complicated. There must be room for optimization in the logic. Let’s do it again in the future. First, the latest put it up first

import XLSX from 'xlsx-style';
// npm install xlsx-style-medalsoft

interface IStyle {
    font?: any;
    fill?: any;
    border?: any;
    alignment?: any;
}
interface ICell {
    r: number;
    c: number;
}
interface IMerge {
    s: ICell;
    e: ICell
}

interface ICol {
    wpx?: number;
    wch?: number;
}

interface ISize {
    cols: ICol[];
    rows: any[];
}
interface ISpecialHeader {
    cells: string[];
    rgb: string;
    color?: string;
}
interface ISpecialCol {
    col: string[];
    rgb: string;
    expect: string[];
    s: IStyle;
    t?: string;
}
interface IRowCells {
    row: string[];
    s: IStyle;
}
interface IMyStyle {
    all?: IStyle;
    headerColor?: string;
    headerFontColor?: string;
    headerLine?: number;
    bottomColor?: string;
    rowCount?: number;
    heightLightColor?: string;
    rowCells?: IRowCells;
    specialHeader?: ISpecialHeader[];
    specialCol?: ISpecialCol[];
    mergeBorder?: any[];
}
interface IConfig {
    merge?: IMerge[];
    size?: ISize;
    myStyle?: IMyStyle;
}
/*
 * @function 导出excel的方法
 * @param data table节点||二维数组 
 * @param type 导出excel文件名
 * @param config 样式配置参数 { all:{all样式的基本格式请参考xlsx-style内xlsx.js文件内的defaultCellStyle}
 *  merge:[s:{r:开始单元格行坐标,c:开始单元格列坐标},e:{r:结束单元格行坐标,c:结束单元格列坐标}]
 *  size:{col:[{wpx:800}],row:[{hpx:800}]},
 *  headerColor: 八位表格头部背景颜色, headerFontColor: 八位表格头部字体颜色,bottomColor:八位表格底部背景色,
 *  rowCount:表格底部行数, specialHeader: [{cells:[特殊单元格坐标],rgb:特殊单元格背景色}],
 *  sepcialCol:[{col:[特殊列的列坐标],rgb:特殊列的单元格背景色,expect:[特殊列中不需要修改的单元格坐标],s:{特殊列的单元格样式}}
 *   }]
 *
 *   导出流程  table节点|二维数组->worksheet工作表对象->workboo工作簿对象->bolb对象->uri资源链接->a标签下载或者调用navigator API下载
 *   每个worksheet都是由!ref、!merge、!col以及属性名为单元格坐标(A1,B2等)值为{v:单元格值,s:单元格样式,t:单元格值类型}的属性组成
 */
export function downLoadExcel(exportElement: [][] | any, fileName: string, config: IConfig|IConfig[], multiSheet?: boolean, sheetNames?: string[]) {
    let ws;
    let wb = [];
    if (multiSheet) {
        exportElement.forEach((item, index) => {
            wb.push(getSheetWithMyStyle(item, config[index]));
        });
    } else {
        if(!Array.isArray(config)){
            ws = getSheetWithMyStyle(exportElement, config);
        }
    }
    console.log(ws, 'worksheet数据');
    if (ws) {
        downLoad([ws], fileName,sheetNames);
    } else {
        downLoad(wb, fileName, sheetNames);
    }

}

export function downLoad(ws, fileName: string, sheetNames?: string[]) {
    var blob = IEsheet2blob(ws, sheetNames);
    if (IEVersion() !== 11) { //判断ie版本
        openDownloadXLSXDialog(blob, `${fileName}.xlsx`);
    } else {
        window.navigator.msSaveOrOpenBlob(blob, `${fileName}.xlsx`);
    }
}

export function getWorkSheetElement(exportElement: [][] | any) {
    let ifIsArray = Array.isArray(exportElement);
    let ws = ifIsArray ? XLSX.utils.aoa_to_sheet(exportElement) : XLSX.utils.table_to_sheet(exportElement);
    return ws;
}

export function getSheetWithMyStyle(exportElement: [][] | any, config: IConfig, callback?: Function) {
    //样式处理函数,返回ws对象,如果要对ws对象进行自定义的修改,可以单独调用此函数获得ws对象进行修改
    try {
        let ws = getWorkSheetElement(exportElement);
        console.log(config, 'excel配置参数');
        //根据data类型选择worksheet对象生成方式
        if (config.merge) {
            ws['!merges'] = config.merge;
        }
        ws['!cols'] = config.size.cols;
        //all样式的基本格式请参考xlsx-style内xlsx.js文件内的defaultCellStyle
        if (config.myStyle) {
            if (config.myStyle.all) { //作用在所有单元格的样式,必须在最顶层,然后某些特殊样式在后面的操作中覆盖基本样式
                Object.keys(ws).forEach((item, index) => {
                    if (ws[item].t) {
                        ws[item].s = config.myStyle.all;
                    }
                });
            }
            if (config.myStyle.headerColor) {
                if (config.myStyle.headerLine) {
                    let line = config.myStyle.headerLine;
                    let p = /^[A-Z]{1}[A-Z]$/;
                    Object.keys(ws).forEach((item, index) => {
                        for (let i = 1; i <= line; i++) {
                            if (item.replace(i.toString(), '').length == 1 || (p.test(item.replace(i.toString(), '')))) {
                                let headerStyle = getDefaultStyle();
                                headerStyle.fill.fgColor.rgb = config.myStyle.headerColor;
                                headerStyle.font.color.rgb = config.myStyle.headerFontColor;
                                ws[item].s = headerStyle;
                            }
                        }
                    });
                }
            }
            if (config.myStyle.specialCol) {
                config.myStyle.specialCol.forEach((item, index) => {
                    item.col.forEach((item1, index1) => {
                        Object.keys(ws).forEach((item2, index2) => {
                            if (item.expect && item.s) {
                                if (item2.includes(item1) && !item.expect.includes(item2)) {
                                    ws[item2].s = item.s;
                                }
                            }
                            if (item.t) {
                                if (item2.includes(item1) && ws[item2].t) {
                                    ws[item2].t = item.t;
                                }
                            }
                        });
                    });
                });
            }

            if (config.myStyle.bottomColor) {
                if (config.myStyle.rowCount) {
                    Object.keys(ws).forEach((item, index) => {
                        if (item.indexOf((config.myStyle.rowCount).toString()) != -1) {
                            let bottomStyle = getDefaultStyle();
                            bottomStyle.fill.fgColor.rgb = config.myStyle.bottomColor;
                            ws[item].s = bottomStyle;
                        }
                    })
                }
            }
            config.myStyle?.specialHeader?.forEach((item, index) => {
                Object.keys(ws).forEach((item1, index1) => {
                    if (item.cells.includes(item1)) {
                        ws[item1].s.fill = {
                            fgColor: {
                                rgb: item.rgb
                            }
                        };
                        if (item.color) {
                            ws[item1].s.font.color = {
                                rgb: item.color
                            };
                        }
                    }
                });
            });
            if (config.myStyle.heightLightColor) {
                Object.keys(ws).forEach((item, index) => {
                    if (ws[item].t === 's' && ws[item].v && ws[item].v.includes('%') && !item.includes((config.myStyle.rowCount).toString())) {
                        if (Number(ws[item].v.replace('%', '')) < 100) {
                            ws[item].s = {
                                fill: {
                                    fgColor: {
                                        rgb: config.myStyle.heightLightColor
                                    }
                                },
                                font: {
                                    name: "Meiryo UI",
                                    sz: 11,
                                    color: {
                                        auto: 1
                                    }
                                },
                                border: {
                                    top: {
                                        style: 'thin',
                                        color: {
                                            auto: 1
                                        }
                                    },
                                    left: {
                                        style: 'thin',
                                        color: {
                                            auto: 1
                                        }
                                    },
                                    right: {
                                        style: 'thin',
                                        color: {
                                            auto: 1
                                        }
                                    },
                                    bottom: {
                                        style: 'thin',
                                        color: {
                                            auto: 1
                                        }
                                    }
                                },
                                alignment: {
                                    /// 自动换行
                                    wrapText: 1,
                                    // 居中
                                    horizontal: "center",
                                    vertical: "center",
                                    indent: 0
                                }
                            }
                        }
                    }
                });
            }

            config.myStyle?.rowCells?.row.forEach((item, index) => {
                Object.keys(ws).forEach((item1, index1) => {
                    let num = Number(dislodgeLetter(item1));
                    if (num == Number(item)) {
                        ws[item1].s = config.myStyle.rowCells.s;
                    }
                });
            });
            if (!Array.isArray(exportElement) && config.myStyle.mergeBorder) { //对导出合并单元格无边框的处理,只针对dom导出,因为只有dom导出会出现合并无边框的情况
                let arr = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
                let range = config.myStyle.mergeBorder;
                range.forEach((item, index) => {
                    if (item.s.c == item.e.c) { //行相等,横向合并
                        let star = item.s.r;
                        let end = item.e.r;
                        for (let i = star + 1; i <= end; i++) {
                            ws[arr[i] + (Number(item.s.c) + 1)] = {
                                s: ws[arr[star] + (Number(item.s.c) + 1)].s
                            }
                        }
                    } else { //列相等,纵向合并
                        let star = item.s.c;
                        let end = item.e.c;
                        for (let i = star + 1; i <= end; i++) {
                            ws[arr[item.s.r] + (i + 1)] = {
                                s: ws[arr[item.s.r] + (star + 1)].s
                            }
                        }
                    }
                });
            }
        }

        callback && callback();
        Object.keys(ws).forEach((item, index) => { //空数据处理,单元格值为空时不显示null
            if (ws[item].t === 's' && !ws[item].v) {
                ws[item].v = '-';
            }
        });
        Object.keys(ws).forEach((item, index) => { //空数据处理,单元格值为空时不显示null
            if (ws[item].t === 's' && ws[item].v.includes('%')) {
                ws[item].v = ws[item].v.includes('.') ? (ws[item].v.replace('%', '').split('.')[1] === '0' ? `${ws[item].v.replace('%', '').split('.')[0]}%` : ws[item].v) : ws[item].v;
            }
        });

        return ws;
    } catch (e) {
        throw (e);
    }
}

function transIndexToLetter(num) { //数字转字母坐标,25->Z ,26-> AA
    if (num < 26) {
        return String.fromCharCode(num + 65);
    } else {
        return transIndexToLetter(Math.floor(num / 26) - 1) + transIndexToLetter(num % 26);
    }
}

function dislodgeLetter(str) {  //去掉字符串中的字母
    var result;
    var reg = /[a-zA-Z]+/; //[a-zA-Z]表示bai匹配字母,dug表示全局匹配
    while (result = str.match(reg)) { //判断str.match(reg)是否没有字母了
        str = str.replace(result[0], ''); //替换掉字母  result[0] 是 str.match(reg)匹配到的字母
    }
    return str;
}

export function sheetToJSON(wb,option?){
    return XLSX.utils.sheet_to_json(wb,option);
}


function IEsheet2blob(sheet, sheetName?: string | string[]) {
    console.log(sheet,'sheet');
    console.log(sheetName,'sheetName');
    try {
        new Uint8Array([1, 2]).slice(0, 2);
    } catch (e) {
        //IE或有些浏览器不支持Uint8Array.slice()方法。改成使用Array.slice()方法
        Uint8Array.prototype.slice = Array.prototype.slice;
    }
    sheetName = Array.isArray(sheetName)?(sheetName.length?sheetName:['sheet1']):'sheet1';
    var workbook = {
        SheetNames: Array.isArray(sheetName) ? sheetName : [sheetName],
        Sheets: {}
    };
    if (Array.isArray(sheetName)) {
        sheetName.forEach((item, index) => {
            workbook.Sheets[item] = sheet[index];
        });
    } else { workbook.Sheets[sheetName] = sheet; }
    console.log(workbook,'workbook');
    // 生成excel的配置项
    var wopts = {
        bookType: 'xlsx', // 要生成的文件类型
        bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
        type: 'binary'
    };
    var wbout = XLSX.write(workbook, wopts);
    var blob = new Blob([s2ab(wbout)], {
        type: "application/octet-stream"
    });
    // 字符串转ArrayBuffer
    function s2ab(s) {
        var buf = new ArrayBuffer(s.length);
        var view = new Uint8Array(buf);
        for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
        return buf;
    }
    return blob;
}

export function sheetToWorkBook(sheet, sheetName?: string | string[]){
    console.log(sheet,'sheet');
    console.log(sheetName,'sheetName');
    try {
        new Uint8Array([1, 2]).slice(0, 2);
    } catch (e) {
        //IE或有些浏览器不支持Uint8Array.slice()方法。改成使用Array.slice()方法
        Uint8Array.prototype.slice = Array.prototype.slice;
    }
    sheetName = sheetName || 'sheet1';
    var workbook = {
        SheetNames: Array.isArray(sheetName) ? sheetName : [sheetName],
        Sheets: {}
    };
    if (Array.isArray(sheetName)) {
        sheetName.forEach((item, index) => {
            workbook.Sheets[item] = sheet[index];
        });
    } else { workbook.Sheets[sheetName] = sheet; }
    console.log(workbook,'workbook');
    // 生成excel的配置项
    var wopts = {
        bookType: 'xlsx', // 要生成的文件类型
        bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
        type: 'binary'
    };
    var wbout = XLSX.write(workbook, wopts);
    return wbout;
}

function getDefaultStyle() {
    let defaultStyle = {
        fill: {
            fgColor: {
                rgb: ''
            }
        },
        font: {
            name: "Meiryo UI",
            sz: 11,
            color: {
                rgb: ''
            },
            bold: true
        },
        border: {
            top: {
                style: 'thin',
                color: {
                    auto: 1
                }
            },
            left: {
                style: 'thin',
                color: {
                    auto: 1
                }
            },
            right: {
                style: 'thin',
                color: {
                    auto: 1
                }
            },
            bottom: {
                style: 'thin',
                color: {
                    auto: 1
                }
            }
        },
        alignment: {
            /// 自动换行
            wrapText: 1,
            // 居中
            horizontal: "center",
            vertical: "center",
            indent: 0
        }
    };
    return defaultStyle;
}


function IEVersion() {
    var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串  
    var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; //判断是否IE<11浏览器  
    var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器  
    var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
    if (isIE) {
        var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
        reIE.test(userAgent);
        var fIEVersion = parseFloat(RegExp["$1"]);
        if (fIEVersion == 7) {
            return 7;
        } else if (fIEVersion == 8) {
            return 8;
        } else if (fIEVersion == 9) {
            return 9;
        } else if (fIEVersion == 10) {
            return 10;
        } else {
            return 6; //IE版本<=7
        }
    } else if (isEdge) {
        return 'edge'; //edge
    } else if (isIE11) {
        return 11; //IE11  
    } else {
        return -1; //不是ie浏览器
    }
}

function openDownloadXLSXDialog(url, saveName: string) {
    try {
        if (typeof url == 'object' && url instanceof Blob) {
            url = URL.createObjectURL(url); // 创建blob地址
        }
        var aLink = document.createElement('a');
        aLink.href = url;
        aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
        var event;
        if (window.MouseEvent) event = new MouseEvent('click');
        else {
            event = document.createEvent('MouseEvents');
            event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
        }
        aLink.dispatchEvent(event);
    } catch (e) {
        throw (e);
    }
}


export function uploadExcel(exportElement: [][] | any, fileName: string, config: IConfig) {
    let ws = getSheetWithMyStyle(exportElement, config);
    console.log(ws, 'worksheet数据');
    downLoad(ws, fileName);
}

export default {
    downLoad,
    downLoadExcel,
    getWorkSheetElement,
    getSheetWithMyStyle,
    transIndexToLetter,
    getDefaultStyle,
    sheetToWorkBook,
    sheetToJSON,
};

Guess you like

Origin blog.csdn.net/weixin_51947053/article/details/127370479