可自定义样式的前端导入导出excel库xlsx

由SheetJS出品的js-xlsx是一款非常方便的只需要纯JS即可读取和导出excel的工具库,功能强大,支持格式众多,API十分简洁方便,当前版本0.16.9

安装
$ npm install xlsx --save
导入

导入的时候需要定义一个模板,让用户按照模板填写excel,模板的第一行定义后台数据实体类的属性名,当数据读取成功后会是json格式[{name:‘’,phoneNo:‘’,description:‘’},{}…]

excel格式如下:
name phoneNo description
张三 13788888888 备注
html(这里使用了elementui)
<el-button type="primary" size="small" class="file-wrap"><a class="seat" download="批量导入联系人模板" href="/static/templateExcel/infoPush-batch-import.xlsx"></a>文件模板下载</el-button>

<el-button type="primary" size="small" class="file-wrap"><input class="seat" type="file" @change="excel2Array($event)">选择文件导入</el-button>
js
excel2Array(evt,fn){
    
    
                let _this = this
                console.log('start analysis excel ')
    			// elementui库里的loading
                let loading = this.$loading({
    
    text:'请稍后...'})
                let file;
                let files = evt.target.files;

                if (!files || files.length == 0) return;

                file = files[0];

                let reader = new FileReader();
                reader.onload = function (e) {
    
    
                    // pre-process data
                    let binary = "";
                    let bytes = new Uint8Array(e.target.result);

                    // 这种写法可以省去for循环
                    // let data = new Uint8Array(e.target.result);
                    // let wb = XLSX.read(data, {type: 'array'});

                    let length = bytes.byteLength;
                    for (let i = 0; i < length; i++) {
    
    
                        binary += String.fromCharCode(bytes[i]);
                    }

                    /* read workbook */
                    let wb = XLSX.read(binary, {
    
    type: 'binary'});

                    /* grab first sheet */
                    let wsname = wb.SheetNames[0];
                    let ws = wb.Sheets[wsname];

                    let jsonArray = XLSX.utils.sheet_to_json(ws)
                    console.log('json:',jsonArray)
					// jsonArray是从excel读出的数据
                    fn(jsonArray)
                    loading.close()

                    /* 可以调用这个方法生成HTML预览 */
                    // let HTML = XLSX.utils.sheet_to_html(ws);
                };

                reader.readAsArrayBuffer(file);
            },

API解释:XLSX.utils.sheet_to_json(ws,props),把excel转成一个json对象

props.raw=false表示输出的数据的属性的值全部转成string

props.header有下面4种情况

header Description eg
1 生成一个没有key的二维数组 {header:1}
"A" 生成以ABCDEF…作为key {header:“A”}
array of strings 自定义key的值 {header:[“name”,“phoneNo”,“description”]}
(default) 生成以excel第一行的值作为key

eg: XLSX.utils.sheet_to_json(ws,{header:[“name”,“phoneNo”,“description”,“sex”,“age”],raw:false}),如果这里不设置raw=false,那么mobile的类型为number;一般情况下我们都会指定header为array of strings,这样可以让我们excel的表头不与数据库字段对应,

excel导入字段包含日期格式

默认情况会转成浮点型,比如44199.0571180556,可以使用XLSX.SSF.parse_date_code(44199.0571180556)得到我们想要的时间;当然也可以把excel中的时间设成字符串类型;另一种方法是指定cellDates:true,让工具直接解析成date类型而不是数字XLSX.read(binary, {type: ‘binary’,cellDates:true}),但是经过测试这种方法有一点误差;

css
 .file-wrap{
    
    
        position: relative;
        overflow: hidden;
        .seat{
    
    
            position: absolute;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            opacity: 0;
            cursor: pointer;
        }
    }
导出

导出的时候事先需要把数据格式转换成一个二维数组[[prop,prop,prop],[…]…]

array2Excel(arrayData,excelName){
    
    
                let loading = this.$loading({
    
    text:'请稍后...'})
                let data = [
                    [ "no data","no data","no data" ],
                ]
                if (!arrayData) {
    
    
                    arrayData = data
                }

                /* convert from array of arrays to workbook */
                let worksheet = XLSX.utils.aoa_to_sheet(arrayData);
                // var worksheet = XLSX.utils.json_to_sheet(data.data);

                /* generate workbook and add the worksheet */
                let new_workbook = XLSX.utils.book_new();
                XLSX.utils.book_append_sheet(new_workbook, worksheet, "sheet1");

                /* save to file */
                XLSX.writeFile(new_workbook, excelName+".xlsx");

                loading.close()
            }

还可以直接读取html–table导出

 /* generate workbook object from table */
 var wb = XLSX.utils.table_to_book(document.getElementById('out-table'));
 /* generate file and force a download*/
  XLSX.writeFile(wb, "sheetjs.xlsx");

html

  <div id="out-table">
       <table >
           <tr><th>一列</th><th>二列</th></tr>
            <tr><td>111</td><td>22</td></tr>
        </table>
   </div>
如果需要还可以指定行高、列宽、合并单元格
 //自定义样式:列宽
// worksheet['!cols'] = [{wch: 10},{wch: 10}]
worksheet['!cols'] = Array.from({
    
    length:arrayData[0].length},()=>{
    
    
    return {
    
    wch: 10}
})
//自定义样式:行高
worksheet['!rows'] = [{
    
    hpx: 25},{
    
    hpx: 35}]
// 单元格合并 第一行合并为一个单元格
worksheet['!merges'] = [{
    
    
    // start 起始位置
    s: {
    
    
        // 列 col
        c: 0,
        // 行 row
        r: 0
    },
    // end 结束位置
    e: {
    
    
        c: worksheet['!cols'].length - 1,
        r: 0
    }
}]
自定义样式

社区版的xlsx并没有提供样式修改的能力,但是社区有人提供了这个能力,详情请看xlsx-style。但遗憾的是这个库的代码有点老,有些官方好用的工具都没有同步过来,还好又有人做了跟进,下面根据layui作者修改后的xlx-style为例子抛砖引玉。点击下载

先看结果:

在这里插入图片描述

调用代码:

excelDownload(){
    
    
                let option = {
    
    
                        excelName:'myExcel',
                        sheetNames:['结算表'],
                        columnWidth:[14,14,14,14,14,14,14,14,14],
                        lineHeight:[26,15,20,20],
                        // 单元格合并
                        merges:[{
    
    pos:'A1:I1'}],
                        styles:[
                            {
    
    
                                // 设置整体样式
                                pos:'A1:I6',
                                style:{
    
    
                                    font: {
    
    
                                        name: '宋体',
                                        sz: 12
                                    },
                                }
                            },
                            {
    
    
                                // 设置title
                                pos:'A1:A1',
                                style:{
    
    
                                    font: {
    
    
                                        bold: true,
                                        sz: 16
                                    },
                                    alignment: {
    
     horizontal: "center", vertical: "center", wrap_text: true },
                                }
                            },
                            {
    
    
                                // 设置header
                                pos:'A3:I3',
                                style:{
    
    
                                    font: {
    
    
                                        bold: true
                                    }
                                }
                            },
                            {
    
    
                                // 设置中心区域
                                pos:'A3:I4',
                                style:{
    
    
                                    alignment: {
    
     vertical: "center", wrap_text: true },
                                    border: {
    
    
                                        top: {
    
    
                                            style: "thin"
                                        },
                                        left: {
    
    
                                            style: "thin"
                                        },
                                        right: {
    
    
                                            style: "thin"
                                        },
                                        bottom: {
    
    
                                            style: "thin"
                                        }
                                    }
                                }
                            }
                        ]
                    }
                
                this.$array2Excel([['2021.5.01-5.15无人岛收入结算表']
                        ,[]
                        ,['店铺名称','未核销张数','未核销金额','已核销张数','已核销金额','应支出佣金','店铺收入','旅行社收入','平台服务费']
                        ,['无人岛',1,2,3,4,5,6,7,8]
                        ,[]
                        ,['董事长:','','','总经理:','','','财务:','','技术部:']],
                    option)

            }

这里代码量最大的是设置单元格样式,不过也简单

常用的样式API
1、设置填充色
fill:{
    
    
	fgColor:{
    
    
		rgb:'ff0000'
	}
}
2、设置字体
font: {
    
    
	name: '宋体', //字体默认为Calibri
	sz: 12, //font-size
	color: {
    
    rgb:'00ff00'},
	bold:true,
	underline:true,
	italic:true
}
3、设置边框,默认为黑色
border: {
    
    
	top: {
    
    
		style: "thin"
	},
	left: {
    
    
		style: "thin"
	},
	right: {
    
    
		style: "thin"
	},
	bottom: {
    
    
		style: "thin",
		color: {
    
    rgb:'00ff00'}//指定下边框颜色
	}
}
4、设置对齐
alignment: {
    
     
    horizontal: "center", 
    vertical: "center", 
    wrap_text: true ,
    textRotation:45 // 单元格旋转45度,取值范围0-180
}

在作者提供的修改样式的API基础上,对常用的功能做了封装,支持多sheet、隔行变色、隔列变色、列宽设置、行高设置、单元格合并等,核心代码如下:

import XLSX from '../../static/xlsx.min.js';
....
$array2Excel(data,option= {
     
     }){
    
    

                    option.data = data

                    let defaultOption = {
    
    
                        excelName:'excel',
                        // 表格数据 若是三维数组对应多个sheet
                        data:[[]],
                        sheetNames:[],
                        // 表格行高 若是二维数组对应多个sheet
                        lineHeight:[[]],
                        // 表格列宽 若是二维数组对应多个sheet
                        columnWidth:[[]],
                        // 单元格合并 若是二维数组对应多个sheet
                        merges:[[]],
                        // 单元格样式 若是二维数组对应多个sheet
                        styles:[[]],
                    }

                    option = Object.assign(defaultOption,option)

                    let loading = this.$loading({
    
    text:'请稍后...'})

                    checkOption(option)

                    function checkOption(option){
    
    
                        if (!Array.isArray(option.data[0][0])){
    
    
                            let arr = [];
                            arr.push(option.data)
                            option.data = arr
                        }

                        checkArray(option,'merges')

                        checkArray(option,'styles')

                        checkArray(option,'columnWidth')

                        checkArray(option,'lineHeight')

                        function checkArray(option,key){
    
    
                            if (!Array.isArray(option[key][0])){
    
    
                                let arr = [];
                                arr.push(option[key])
                                option[key] = arr
                            }
                        }

                        let defaultSheetNames = Array.from({
    
    length:option.data.length},(val,index)=>'sheet'+(index+1))
                        option.sheetNames = Object.assign(defaultSheetNames,option.sheetNames)

                        console.log('data:',option.data)
                    }


                    let worksheetArr = []
                    for (let i=0;i<option.data.length;i++) {
    
    

                        /* convert from array of arrays to workbook */
                        let worksheet = XLSX.utils.aoa_to_sheet(option.data[i]);
                        // let worksheet = XLSX.utils.json_to_sheet(data.data);

                        /* --单元格合并 */
                        mergeFun(worksheet,option.merges[i])

                        /* --设置样式 */
                        setStyle(worksheet,option.styles[i])

                        /* --设置列宽和行高 */
                        setWidthAndHeight(worksheet,option.columnWidth[i],option.lineHeight[i])

                        console.log('worksheet:',worksheet)
                        worksheetArr.push(worksheet)
                    }

                    /* generate workbook and add the worksheet */
                    let new_workbook = XLSX.utils.book_new();

                    worksheetArr.forEach((worksheet,i)=>{
    
    
                        XLSX.utils.book_append_sheet(new_workbook, worksheet, option.sheetNames[i]);
                    })

                    /* save to file */
                    XLSX.writeFile(new_workbook, (option.excelName||'excel') + ".xlsx");

                    loading.close()

                    // 设置列宽和行高
                    function setWidthAndHeight(worksheet,colWidthArr=[],lineHeightArr=[]){
    
    
                        if (colWidthArr.length>0){
    
    
                            //自定义样式:列宽 [{wpx: 100},{wpx: 100}]
                            worksheet['!cols'] = colWidthArr.map(val=>{
    
    return {
    
    wch:val}})
                        }

                        if (lineHeightArr.length>0){
    
    
                            //自定义样式:行高 [{hpx: 25},{hpx: 35}]
                            worksheet['!rows'] = lineHeightArr.map(val=>{
    
    return {
    
    hpx:val}})
                        }
                    }


                    // 单元格合并
                    function mergeFun(worksheet,merges){
    
    

                        merges = merges || []
                        let mergesArr = []
                        for (const merge of merges) {
    
    
                            let pos = merge.pos
                            if (pos){
    
    
                                let posObj = decodePos(pos)
                                let p1 = posObj.p1
                                let p2 = posObj.p2
                                mergesArr.push({
    
    s:p1,e:p2})
                            }
                        }
                        if (mergesArr.length>0){
    
    
                            worksheet['!merges'] = mergesArr
                        }
                    }


                    // 设置样式
                    function setStyle(worksheet,styles){
    
    
                        styles = styles || []
                        // let styles = option.styles
                        // !ref: "A1:H2"
                        let region = worksheet['!ref']
                        for (const style of styles) {
    
    

                            // 1、按行设置 2*n  2*n+1
                            let nthRow = style.nthRow
                            // 2、按列设置 2*n  2*n+1
                            let nthCol = style.nthCol
                            if (nthRow||nthCol){
    
    

                                let posObj = decodePos(region)
                                let p1 = posObj.p1
                                let p2 = posObj.p2

                                if (nthRow){
    
    

                                    let rowArr = []
                                    // 行
                                    for (var n=p1.r;n<=p2.r;n++){
    
    

                                        let posRow = eval(nthRow)

                                        rowArr.push(posRow)
                                        if (rowArr.includes(n)){
    
    
                                            // 列
                                            for (let j = p1.c; j <=p2.c ; j++) {
    
    
                                                // 坐标转成字母ABCDEF
                                                let pp = XLSX.utils.encode_cell({
    
    r:n,c:j})
                                                // worksheet[pp].s = style.style
                                                if (worksheet[pp]){
    
    
                                                    
                                                    worksheet[pp].s = assignFun(worksheet[pp].s,style.style)
                                                }
                                            }
                                        }
                                    }
                                    console.log('rowArr:',rowArr)
                                }

                                if (nthCol){
    
    

                                    let colArr = []

                                    // 行
                                    for (let i=p1.r;i<=p2.r;i++){
    
    
                                        // 列
                                        for (var n = p1.c; n <=p2.c; n++) {
    
    

                                            let posCol = eval(nthCol)
                                            colArr.push(posCol)
                                            if (colArr.includes(n)){
    
    
                                                // 坐标转成字母ABCDEF
                                                let pp = XLSX.utils.encode_cell({
    
    r:i,c:n})
                                                if (worksheet[pp]){
    
    
                                                    
                                                    worksheet[pp].s = assignFun(worksheet[pp].s,style.style)
                                                }
                                            }
                                        }
                                    }

                                    console.log('colArr:',colArr)
                                }

                            }

                            // 3、按位置设置
                            let pos = style.pos
                            if (pos){
    
    
                                let posObj = decodePos(pos)
                                let p1 = posObj.p1
                                let p2 = posObj.p2
                                // 行
                                for (let i=p1.r;i<=p2.r;i++){
    
    
                                    // 列
                                    for (let j = p1.c; j <=p2.c ; j++) {
    
    
                                        // 坐标转成字母ABCDEF
                                        let pp = XLSX.utils.encode_cell({
    
    r:i,c:j})
                                        if (worksheet[pp]){
    
    
                                           
                                            worksheet[pp].s = assignFun(worksheet[pp].s,style.style)
                                        }
                                    }
                                }
                            }

                        }
                    }

                    function assignFun(obj1={
     
     },obj2={
     
     }){
    
    
                        Object.keys(obj2).forEach(key=>{
    
    
                            if (typeof obj2[key]==='object'){
    
    
                                if (!obj1[key]){
    
    
                                    obj1[key] = Array.isArray(obj2[key]) ? [] : {
    
    }
                                }
                                obj1[key] = assignFun(obj1[key],obj2[key])
                            }else{
    
    
                                obj1[key]  = obj2[key]
                            }
                        })
                        return obj1;
                    }

                    // 把字母解析成坐标
                    function decodePos(pos){
    
    
                        let posArr = pos.split(':')
                        let p1 = XLSX.utils.decode_cell(posArr[0])
                        let p2 = XLSX.utils.decode_cell(posArr[1])
                        if (p1.r>p2.r||(p1.r==p2.r&&p1.c>p2.c)){
    
    
                            [p1,p2] = [p2,p1]
                        }
                        return {
    
    p1,p2}
                    }

                }
....
实现的接口

columnWidth:[14,14…]指每列的宽度,数组的长度等于列的个数

lineHeight:[20,20…]指每行的行高,数组的长度等于行的个数

merges:[{pos:‘A1:I1’}]指把位置A1到I1的单元格合并,可设置多组

styles:[{pos:‘A1:I6’…}]指设置位置A1:I6的样式,可设置多组

styles:[{ nthRow:‘2*n+1’…}]指设置奇数行样式

styles:[{nthCol:‘2*n’…}]指设置偶数列样式

styles:[{nthCol:‘n+7’…}]指设置第7列之后的样式

styles:[{nthRow:‘n’…}]指设置所有行的样式

修改更复杂xlsx样式参考

更多请看xlsx GitHub

猜你喜欢

转载自blog.csdn.net/dan_seek/article/details/106500048