xlsx.js 表格的导出与导入

1.xlsx简介

通俗的说,xlsx这个插件可以把html中的table元素或者json数据转换成表格后进行导出

<script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.4/xlsx.js"></script>

2.基本概念

一个excel文件就是一个book,一个book中又包含一个或多个sheet,以下方法均由XLSX.utils对象调用

常用方法 参数 说明
book_new - 创建一个空的book
table_to_book table元素(dom) 将table元素转换成 book
table_to_sheet table元素(dom) 将table元素转换成 sheet
json_to_sheet json数据(数组) 将json数据转换成 sheet
book_append_sheet 参数1:book对象,参数2:sheet对象,参数3:sheet名称 将sheet添加到book中
writeFile 参数1:book对象,参数2:导出的文件名称 导出表格文件

3.JSON转表格

  • 基本代码
 exportEvent () {
    // 要打印的数据格式 对象中的key将会作为表头渲染
    var print_data = [
        {'序号':1,'姓名':'张三','年龄':20},
        {'序号':2,'姓名':'李四','年龄':25},
        {'序号':3,'姓名':'王五','年龄':30}
    ]
    // 创建一个新sheet
    const new_sheet = XLSX.utils.json_to_sheet(print_data)
    // 设置每列的列宽(可选),10代表10个字符,注意中文占2个字符
    new_sheet['!cols'] = [
        { wch: 10 },
        { wch: 30 },
        { wch: 25 }
    ]
    // 新建book
    const new_book = XLSX.utils.book_new()
    // 将 sheet 添加到 book 中
    XLSX.utils.book_append_sheet(new_book, new_sheet, '人员名单')
    // 导出excel文件
    XLSX.writeFile(new_book, '数据导出.xlsx')
}   
  • 表头标题替换方法
replaceKeyForTableData(tableData = [],mapData = {}){
        //要返回的最终数据
        var result = []
        //循环数组的每行数据
        tableData.forEach(row=>{
            //新的行数据
            var new_row = {}
            //循环每个键值
            Object.keys(row).forEach(key=>{
                //判断这个可以是否 可以在映色中找到对应的值 有则使用,无则使用原来的key
                var new_key = mapData[key]? mapData[key]:key
                //为新的行数据添加键值对
                new_row[new_key] = row[key]
            })
            //新的行添加到表格数据中
            result.push(new_row)
        })
        return result
    }

4.HTML-table转表格

  • 导出原生表格:将table元素传入即可导出
//获取dom元素
var table_dom = document.querySelector('table')
//将dom转换为book
const new_book = XLSX.utils.table_to_book(table_dom)
// 导出excel文件 如导出后的文件不能打开,请将后缀替换为 .xls
XLSX.writeFile(new_book, '数据导出.xlsx')
  • 导出插件生成的表格:插件生成的表格因为兼容一些功能,最终渲染的时候其实是2个table元素,为了能将表格完整的导出(thead+body),需要手动拼接dom
//这里以vxe-table为例
exportEvent () {
    // 创建新的table元素
    var print_table_dom = document.createElement('table')
    // copy一份thead
    var print_table_dom_thead = this.$refs.p_table.$el.querySelector('.vxe-table--header-wrapper > .vxe-table--header thead').cloneNode(true)
    // copy一份tbody
    var print_table_body = this.$refs.p_table.$el.querySelector('.vxe-table--body-wrapper > .vxe-table--body tbody').cloneNode(true)
    // 将thead和tbody添加到 目标table中
    print_table_dom.appendChild(print_table_dom_thead)
    print_table_dom.appendChild(print_table_body)
    // 生成 book
    const new_sheet = XLSX.utils.table_to_book(print_table_dom)
    // 导出excel
    XLSX.writeFile(new_sheet, '数据导出.xlsx')
},
  • 数字格式的问题:excel时,以0开头的数据,其开头0被忽略,如001234导出到excel后变为了1234,只需要将dom转换为book的时候,传入第二个参数即可
//将dom转换为book,传入第二个参数{raw:true}
const new_book = XLSX.utils.table_to_book(table_dom,{raw:true})

5.jsonToxlsx封装

  • 1.表格转换过程中,表头可能需要替换
  • 2.表格列宽动态设定
  • 3.20230906升级:导出表格列的顺序已表头信息配置为准,自动过滤表头没有的列(表头信息配置为空时,使用原逻辑)
  • 4.代码已上传至gitee
//此插件依赖xlsl.js
//@author zhoulianli 2022-04-15
//此插件主要用来导出表格数据,在xlsl.js基础上封装了列宽自适应,表头标题替换功能

//使用方法 jsonToxlsx(参数)
//参数1: 表格数据 [{},{}]
//参数2:可选,表头标题映射数据 {name:"姓名",age:"年龄"}
//参数3:可选,文件名 例如:"数据导出"
//参数4:可选,sheet名 例如:"能耗比数据"

//闭包  传入当前环境的this
(function (global, factory) {
    //1.先判断当前环境是否支持CommonJS规范(node.js)
    if (typeof exports == 'object' && typeof module !== 'undefined') {
        //console.log('CommonJS规范')
        module.exports = factory()
    } else if (typeof define == 'function' && define.amd) {//2.再判断是否支持AMD规范(require.js)
        //console.log('AMD规范')
        define(factory)
    } else {
        //console.log('script标签引入')
        //接收该对象
        this.jsonToxlsx = factory()
    }
}(this,function(){
    //将表格数组导出的方法
    function jsonToxlsx(tableData = [],tHeadMap = {},fileName = "数据导出",sheetName = "数据导出"){
        //判断依赖是否存在
        if(typeof XLSX == undefined){
            console.log('请先引入XLSX.js')
            return
        }
        
        //判断是否有表头信息
        if(tHeadMap && Object.keys(tHeadMap).length > 0){
            //转换数据格式2
            var print_data = transFormTableData2(tableData,tHeadMap)
        }else{
            //转换数据格式1
            var print_data = transFormTableData(tableData,tHeadMap)
        }

        // 创建一个新sheet
        const new_sheet = XLSX.utils.json_to_sheet(print_data)

        // 根据每列数据智能设定列宽
        var two_dimensional_arr = transformToTwoDimensional(print_data)
        var width_arr = two_dimensional_arr.map(arr=>{
            return {
                wch: getMaxLenByArr(arr) + 2
            }
        })
        new_sheet['!cols'] = width_arr

        // 新建book
        const new_book = XLSX.utils.book_new()
        // 将 sheet 添加到 book 中
        XLSX.utils.book_append_sheet(new_book, new_sheet, sheetName)
        // 导出excel文件
        XLSX.writeFile(new_book, `${fileName}${Moment().format('YYYYMMDDHHmmss')}.xlsx`)
    }

    //转换表格数据
    function transFormTableData(tableData = [],mapData = {}){
        //要返回的最终数据
        var result = []
        //循环数组的每行数据
        tableData.forEach(row=>{
            //新的行数据
            var new_row = {}
            //循环每个键值
            Object.keys(row).forEach(key=>{
                //判断这个可以是否 可以在映色中找到对应的值 有则使用,无则使用原来的key
                var new_key = mapData[key]? mapData[key]:key
                //为新的行数据添加键值对
                new_row[new_key] = row[key]
            })
            //新的行添加到表格数据中
            result.push(new_row)
        })
        return result
    }

    //转换表格数据2
    //表格行的属性顺序和数量由表头配置来决定
    function transFormTableData2(tableData = [],mapData = {}){
        //要返回的最终数据
        var result = []
        //循环数组的每行数据
        tableData.forEach(row=>{
            //新的行数据
            var new_row = {}
            //循环表头每个键值
            for(var key in mapData){
                var target_key = mapData[key]
                //给表格行添加字段
                new_row[target_key] = row[key]
            }
            //新的行添加到表格数据中
            result.push(new_row)
        })
        return result
    }

    //将表格数组转换为二维数组 [[col1-1,col1-2],[col2-1,col2-2]]
    function transformToTwoDimensional(tableData){
        //如果数据源为空 则返回空
        if (tableData == 0) {
            return []
        }
        //对象有多少个key,就有多少列,每列数据是一个数组

        //先填充标题
        var result = []
        for (key in tableData[0]) {
            result.push([key])
        }

        //接着填充数据
        tableData.forEach(row => {
            var index = 0
            for (key in row) {
                result[index].push(row[key])
                index++
            }
        })

        return result
    }


    //从一维数组中提取出 最大的字节占位
    function getMaxLenByArr(strArr){
        var max = 0
        strArr.forEach(item => {
            //转为字符串
            var str = item + ''
            var strlen = 0;
            for (var i = 0; i < str.length; i++) {
                if (str.charCodeAt(i) > 255) //如果是汉字,则字符串长度加2
                    strlen += 2;
                else
                    strlen++;
            }
            if( strlen > max){
                max = strlen
            }
        })
        return max
    }
    return jsonToxlsx;
}))

6.表格的导入

表格的导入一般经过几个步骤:
1.文件读取:借助文件域+H5的FileReader对文件进行读取
2.一次转码:使用XLSX对FileReader读取到的数据转换成bookData
3.二次转码:再借助vxe-table中的方法将上个步骤的数据转换成csvData
4.三次转码:将csvData转换为json数据

值得说明的是,前面三个步骤调用的都是浏览器或者插件现成的接口,而且也不会涉及业务层面的逻辑,所以我们不去动它。而第四步将csvData转换为json数据,目标json的数据格式需要根据业务来决定,所以第四步就值得探讨一番

我先说结论,csvData转换为json数据只能通过下标进行匹配,这也就意味着excel文件的列需要一个固定的格式,列的顺序一旦出错,导入就会有问题

这是excel表格数据:

这是转换后的csvData:

以下代码作为参考:

 data: {
            tableColumn:[
                {field:'id',title:'id'},
                {field:'name',title:'姓名'},
                {field:'role',title:'角色'},
                {field:'sex',title:'性别'},
                {field:'age',title:'年龄'},
                {field:'address',title:'地址'}
            ],
            tableData: [
                { id: 10001, name: 'Test1', role: 'Develop', sex: 'Man', age: 28, address: 'test abc' },
                { id: 10002, name: 'Test2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' },
                { id: 10003, name: 'Test3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' },
                { id: 10004, name: 'Test4', role: 'Designer', sex: 'Women', age: 24, address: 'Shanghai' }
            ]
},
methods:{
//选择文件
            fileChange(){
                var file = document.querySelector('#file').files[0]
                //如果文件存在 则读取
                if(file){
                    //创建reader对象
                    var reader = new FileReader()
                    //读取选择的文件
                    reader.readAsBinaryString(file)

                    //监听读取事件
                    var that = this
                    reader.onload = function(ev){
                        const data = ev.target.result
                        //转换book对象
                        const workbook = XLSX.read(data, { type: 'binary' })
                        //将某个sheet转换成csvData 注意 sheet 的名称
                        const csvData = XLSX.utils.sheet_to_csv(workbook.Sheets['Sheet1'])
                        that.tableData = that.csvToJson(csvData,that.tableColumn)
                    }
                }
            },
            //将csv数据转换为json数据
            csvToJson(csvData,tableColumn=[]){
                //1.一般情况下,表格数据与tableColumn根据下标来进行匹配
                //1.如果导入的表格有表头,则与表头与tableColumn进行匹配
                const tableData = []
                // 解析数据
                csvData.split('\n').forEach((vRow) => {
                    //遍历行
                    if (vRow) {
                        const vCols = vRow.split(',')
                        const item = {}
                        //遍历列
                        vCols.forEach((val, cIndex) => {
                            //根据列的下标找到对应的 field
                            const column = tableColumn[cIndex]
                            if (column.field) {
                                //添加字段
                                item[column.field] = val
                            }
                        })
                        tableData.push(item)
                    }
                })
                return tableData
            }
}
posted @ 2022-01-10 16:55  ---空白---  阅读(6181)  评论(1编辑  收藏  举报