zoukankan      html  css  js  c++  java
  • Element ui复杂表格(多级表头、尾行求合、单元格合并)前端导出excel

    效果展示

    前端展示表格

    image

    导出表格

    image

    依赖安装

    使用nmp安装依赖:xlsx、xlsx-style

     npm install xlsx --save
     npm install xlsx-style --save
    

    安装xlsx-style的坑
    用npm install xlsx-style --save命令可能会安装失败,所以推荐使用cnpm install xlsx-style --save命令进行安装,安装好后不出意外程序会报错Can‘t resolve ‘./cptable‘ in ‘xxx ode_modules_xlsx,解决方法网上搜索即可,如在vue.config.js中添加

    configureWebpack: {
        externals:{
            './cptable': 'var cptable'
        },
    }
    

    工具模块

    exportExcelUtil.js

    点击查看代码
    import XLSX from "xlsx";
    import XLSX_STYLE from "xlsx-style";
    
    const ALL_LETTER = ["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"];
    //默认表头宽度
    const DEFAULT_HEADER_WITH = 210;
    
    /**
     * 去除多余的行数据
     * @param wb
     * @returns {*}
     */
    const removeLastSumRow = (wb) => {
        let arr = wb['!merges'];
        let maxRow = parseInt(wb['!ref'].split(":")[1].replace(/[^0-9]/ig, ""));
        let removeIndex = [];
        for (let i = 0; i < arr.length; i++) {
            let startCell = arr[i].s;
            let endCell = arr[i].e;
            if (startCell.r + 1 >= maxRow || endCell.r + 1 >= maxRow) {
                removeIndex.push(i);
            }
        }
        wb['!merges'] = [];
        for (let i = 0; i < arr.length; i++) {
            if (removeIndex.indexOf(i) === -1) {
                wb['!merges'].push(arr[i]);
            }
        }
        return wb;
    }
    /**
     * 为合并项添加边框
     * @param range
     * @param ws
     * @returns {*}
     */
    const addRangeBorder = (range, ws) => {
        if (range) {
            range.forEach(item => {
                let startColNumber = Number(item.s.r), endColNumber = Number(item.e.r);
                let startRowNumber = Number(item.s.c), endRowNumber = Number(item.e.c);
                const test = ws[ALL_LETTER[startRowNumber] + (startColNumber + 1)];
                for (let col = startColNumber; col <= endColNumber; col++) {
                    for (let row = startRowNumber; row <= endRowNumber; row++) {
                        ws[ALL_LETTER[row] + (col + 1)] = test;
                    }
                }
            })
        }
        return ws;
    }
    /**
     * 将一个sheet转成最终的excel文件的blob对象,然后利用URL.createObjectURL下载
     * @param sheet
     * @param sheetName
     * @returns {Blob}
     */
    const sheet2blob = (sheet, sheetName) => {
        sheetName = sheetName || 'sheet1';
        let workbook = {
            SheetNames: [sheetName],
            Sheets: {}
        };
        workbook.Sheets[sheetName] = sheet; // 生成excel的配置项
        let wopts = {
            bookType: 'xlsx', // 要生成的文件类型
            bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
            type: 'binary'
        };
        let wbout = XLSX_STYLE.write(workbook, wopts);
        let blob = new Blob([s2ab(wbout)], {
            type: "application/octet-stream"
        }); // 字符串转ArrayBuffer
        function s2ab(s) {
            let buf = new ArrayBuffer(s.length);
            let view = new Uint8Array(buf);
            for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
            return buf;
        }
    
        return blob;
    }
    /**
     * 下载
     * @param url
     * @param saveName
     */
    const openDownloadDialog = (url, saveName) => {
        if (typeof url == 'object' && url instanceof Blob) {
            url = URL.createObjectURL(url); // 创建blob地址
        }
        let aLink = document.createElement('a');
        aLink.href = url;
        aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
        let 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);
    }
    /**
     * 处理样式
     * @param wb
     */
    const handleExcelStyleDefault = (wb, cellStyle, headerStyle, maxLineName) => {
        let maxLineIndex = excleLineNameToLineIndex(maxLineName);
        for (let i = 0; i < maxLineIndex; i++) {
            wb["!cols"][i] = headerStyle.default
        }
        for (let specialHeader of headerStyle.specialHeader) {
            wb["!cols"][specialHeader.index] = specialHeader.style;
        }
        for (const key in wb) {
            if (key.indexOf('!') === -1) {
                //列号
                let lineName = key.match(/[a-z,A-Z]/g)[0];
                if (excleLineNameToLineIndex(lineName) > maxLineIndex) {
                    continue;
                }
                if (typeof wb[key].v === 'string' && !!cellStyle.specialCell[wb[key].v]) {
                    wb[key].s = cellStyle.specialCell[wb[key].v];
                } else {
                    wb[key].s = JSON.parse(JSON.stringify(cellStyle.default));
                }
            }
        }
        return wb;
    }
    /**
     * 24进制的表格列头名转数字
     * @param name
     * @returns {number}
     */
    const excleLineNameToLineIndex = (name) => {
        let res = 0;
        for (let i = 0; i < name.length; i++) {
            let letter = name.charAt(i).toUpperCase();
            res += (ALL_LETTER.indexOf(letter)+1) * Math.pow(24, i);
        }
        return res;
    }
    /**
     * 检查参数
     * @param cellStyle
     * @param headerStyle
     */
    const checkExportExcelParam = (cellStyle, headerStyle) => {
        if (!headerStyle) {
            headerStyle = {};
        }
        if (!headerStyle.default) {
            headerStyle.default = [];
        }
        if (!headerStyle.default.with) {
            headerStyle.default.with = DEFAULT_HEADER_WITH;
        }
        if (!cellStyle) {
            cellStyle = {}
        }
        if (!cellStyle.default) {
            cellStyle.default = [];
        }
        if (!cellStyle.default.font) {
            cellStyle.default.font = {
                sz: 13,
                bold: false,
                color: {
                    rgb: '000000'//十六进制,不带#
                }
            }
        }
        if (!cellStyle.default.alignment) {
            cellStyle.default.alignment = {
                horizontal: 'center',
                vertical: 'center',
                wrap_text: true
            }
        }
        if (!cellStyle.default.border) {
            cellStyle.default.border = {
                top: {style: 'thin'},
                bottom: {style: 'thin'},
                left: {style: 'thin'},
                right: {style: 'thin'}
            }
        }
    }
    /**
     *
     * @param tableId  页面指定table的id值
     * @param cellStyle 单元格样式
     * @param headerStyle  表头样式
     *    {
     *        //默认列头
              default: {with:210},
              //特殊列设置
              specialHeader: [{
                      index: 3,
                      with : 300
                  }
              ]
          }
     */
    const exportExcel = (tableId, maxLineName, cellStyle, fileName, headerStyle, handleExcelStyle) => {
        //检查参数传递
        checkExportExcelParam(cellStyle, headerStyle);
        // 从表生成工作簿对象
        let wb = XLSX.utils.table_to_sheet(document.querySelector(`#${tableId}`), {raw: true});
        //处理样式
        if (!!handleExcelStyle) {
            wb = handleExcelStyle(wb);
        } else {
            wb = handleExcelStyleDefault(wb, cellStyle, headerStyle, maxLineName);
        }
        //为合并项添加边框
        wb = addRangeBorder(wb['!merges'], wb)
        //去除最后的行合并
        wb = removeLastSumRow(wb);
        //转换为二进制
        wb = sheet2blob(wb);
        //导出
        openDownloadDialog(wb, fileName);
    }
    /**
     * 表格同类型值合并--表格数据处理
     * @param data
     * @param isH
     * @returns {{}}
     */
    const dataMerge = {
        //数据处理
        dataHandle: (data, isH) => {
            // 表格单元格合并多列
            let spanObj = [],
                pos = [];
            //循环数据
            for (let i in data) {
                let dataI = data[i];
                //循环数据内对象,查看有多少key
                for (let j in dataI) {
                    //如果只有一条数据时默认为1即可,无需合并
                    if (parseInt(i) === 0) {
                        spanObj[j] = [1];
                        pos[j] = 0;
                    } else {
                        let [e, k] = [dataI, data[i - 1]];
                        //判断上一级别是否存在 ,
                        //存在当前的key是否和上级别的key是否一样
                        //判断是否有数组规定只允许那几列需要合并单元格的
                        if (k && e[j] === k[j] && ((!isH || isH.length === 0) || isH.includes(j))) {
                            //如果上一级和当前一级相当,数组就加1 数组后面就添加一个0
                            spanObj[j][pos[j]] += 1;
                            spanObj[j].push(0)
                        } else {
                            spanObj[j].push(1);
                            pos[j] = i;
                        }
                    }
                }
            }
            return spanObj;
        },
        //el-table->span-method
        arraySpanMethod: ({row, column, rowIndex, columnIndex}, spanObj) => {
            // console.log({ row, column, rowIndex, columnIndex },'合并表格')
            //列合并
            let _row = spanObj[column.property] ? spanObj[column.property][rowIndex] : 1;
            let _col = _row > 0 ? 1 : 0;
            return {
                rowspan: _row,
                colspan: _col
            }
        }
    };
    
    
    export default {
        exportExcel,
        dataMerge
    }
    

    工具使用实例

    html代码块

    点击查看代码
      <div :style="staticPageStyle">
        <el-row>
          <el-form :inline="true" :model="condition" size="mini" class="demo-form-inline">
            <el-button size="mini" type="primary" @click="exportExcel()" style="margin-left: 10px">导出excel</el-button>
          </el-form>
        </el-row>
        <el-table
            id="nscjbh-staticTable"
            :data="tableData"
            border
            sum-text="合计"
            show-summary
            :span-method="spanMethod"
            :summary-method="getSummaries"
            v-loading="tableLoading"
            style=" 100%;border: 0">
          <el-table-column
              align="center"
              header-align="center"
              :show-overflow-tooltip=true
              :label="`${condition.year}计划表`">
            <el-table-column
                prop="项目性质"
                width="240"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="项目性质">
            </el-table-column>
            <el-table-column
                prop="区域"
                width="220"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="区域">
            </el-table-column>
            <el-table-column
                prop="项目面积(亩)"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="项目面积(亩)">
            </el-table-column>
            <el-table-column
                prop="项目个数"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="项目个数">
            </el-table-column>
            <el-table-column
                prop="年度资金预算(万元)"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="年度资金预算(万元)">
            </el-table-column>
            <el-table-column
                prop="计划完成拆迁面积"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="计划完成拆迁面积">
            </el-table-column>
            <el-table-column
                prop="计划形成可供经营性用地(亩)"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="计划形成可供经营性用地(亩)">
            </el-table-column>
            <el-table-column
                prop="计划形成可供非经营性用地(亩)"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="计划形成可供非经营性用地(亩)">
            </el-table-column>
          </el-table-column>
        </el-table>
      </div>
    

    导出代码

    点击查看代码
          //导出表id
          let tableId = 'nscjbh-staticTable';
          //单元格样式  样式的文档地址  https://www.npmjs.com/package/xlsx-style
          let cellStyle = {
            default: {
              font: {
                sz: 13,
                bold: false,
                color: {
                  rgb: '000000'//十六进制,不带#
                }
              },
              alignment: {
                horizontal: 'center',
                vertical: 'center',
                wrap_text: true
              },
              border: {
                top: {style: 'thin'},
                bottom: {style: 'thin'},
                left: {style: 'thin'},
                right: {style: 'thin'}
              },
            },
            specialCell: {}
          };
          //导出表名
          let fileName = `计划表(${(new Date()).toDateString()}).xlsx`;
          //头部样式
          let headerStyle = {
            default: {
              wpx: 220
            },
            specialHeader: [{
              index: 2,
              style: {
                wpx: 320
              }
            }]
          };
          //自定义样式处理方法(按需求,可以不传)   
          let handleExcelStyle = (wb, cellStyle, headerStyle) => {
          };
          //列表最大列号 从1开始算
          let maxLineName = 'H';
          exportExcelUtil.exportExcel(tableId, maxLineName, cellStyle, fileName, headerStyle);
    	  //自定义样式处理
    	  //exportExcelUtil.exportExcel(tableId, null, null, fileName, null,handleExcelStyle);
    

    其他功能

    尾部求和

    参考官方文档: https://element.eleme.io/#/zh-CN/component/table

    同字段同值单元格合并

    处理表格数据
    其中参数1表示表格数据,参数2表示要合并单元格的字段数组

    this.spanObj = exportExcelUtil.dataMerge.dataHandle(this.tableData, ['项目性质']);
    

    自定义element-ui合并单元格方法

    <el-table  :span-method="spanMethod">
    

    合并方法

      spanMethod(param) {
          return exportExcelUtil.dataMerge.arraySpanMethod(param, this.spanObj);
        }
    

    完整vue模块实例

    点击查看代码
    <template>
      <div :style="staticPageStyle">
        <el-row>
          <el-form :inline="true" :model="condition" size="mini" class="demo-form-inline">
            <el-button size="mini" type="primary" @click="exportExcel()" style="margin-left: 10px">导出excel</el-button>
          </el-form>
        </el-row>
        <el-table
            id="nscjbh-staticTable"
            :data="tableData"
            border
            sum-text="合计"
            show-summary
            :span-method="spanMethod"
            :summary-method="getSummaries"
            v-loading="tableLoading"
            style=" 100%;border: 0">
          <el-table-column
              align="center"
              header-align="center"
              :show-overflow-tooltip=true
              :label="`${condition.year}计划表`">
            <el-table-column
                prop="项目性质"
                width="240"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="项目性质">
            </el-table-column>
            <el-table-column
                prop="区域"
                width="220"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="区域">
            </el-table-column>
            <el-table-column
                prop="项目面积(亩)"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="项目面积(亩)">
            </el-table-column>
            <el-table-column
                prop="项目个数"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="项目个数">
            </el-table-column>
            <el-table-column
                prop="年度资金预算(万元)"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="年度资金预算(万元)">
            </el-table-column>
            <el-table-column
                prop="计划完成拆迁面积"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="计划完成拆迁面积">
            </el-table-column>
            <el-table-column
                prop="计划形成可供经营性用地(亩)"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="计划形成可供经营性用地(亩)">
            </el-table-column>
            <el-table-column
                prop="计划形成可供非经营性用地(亩)"
                align="center"
                header-align="center"
                :show-overflow-tooltip=true
                label="计划形成可供非经营性用地(亩)">
            </el-table-column>
          </el-table-column>
        </el-table>
      </div>
    </template>
    
    <script scoped>
    
    import exportExcelUtil from "@/global/exportExcelUtil";
    
    export default {
      name: "testTable",
      props: {},
      components: {},
      computed: {},
      data() {
        return {
          staticPageStyle: {
            height: (window.innerHeight - 107) + 'px'
          },
          tableLoading: false,
          tableData: [{
            "SORT": "1",
            "区域": "锦江区",
            "年度资金预算(万元)": "5993.3",
            "计划完成拆迁面积": "0",
            "计划形成可供经营性用地(亩)": "0",
            "计划形成可供非经营性用地(亩)": "0",
            "项目个数": "8",
            "项目性质": "完结项目",
            "项目面积(亩)": "3221.27"
          }, {
            "SORT": "1",
            "区域": "青羊区",
            "年度资金预算(万元)": "1",
            "计划完成拆迁面积": "0",
            "计划形成可供经营性用地(亩)": "0",
            "计划形成可供非经营性用地(亩)": "0",
            "项目个数": "1",
            "项目性质": "完结项目",
            "项目面积(亩)": "13"
          }, {
            "项目性质": "完结项目",
            "区域": "金牛区",
            "计划形成可供经营性用地(亩)": 0,
            "项目面积(亩)": 0,
            "项目个数": 0,
            "计划完成拆迁面积": 0,
            "年度资金预算(万元)": 0,
            "计划形成可供非经营性用地(亩)": 0,
            "SORT": -1
          }, {
            "SORT": "1",
            "区域": "成华区",
            "年度资金预算(万元)": "426",
            "计划完成拆迁面积": "0",
            "计划形成可供经营性用地(亩)": "0",
            "计划形成可供非经营性用地(亩)": "0",
            "项目个数": "1",
            "项目性质": "完结项目",
            "项目面积(亩)": "237"
          }, {
            "项目性质": "完结项目",
            "区域": "合计",
            "计划形成可供经营性用地(亩)": 0,
            "项目面积(亩)": 3301,
            "项目个数": 10,
            "计划完成拆迁面积": 0,
            "年度资金预算(万元)": 1020,
            "计划形成可供非经营性用地(亩)": 0,
            "SORT": -1
          }, {
            "SORT": "2",
            "区域": "锦江区",
            "年度资金预算(万元)": "0",
            "计划完成拆迁面积": "0",
            "计划形成可供经营性用地(亩)": "0",
            "计划形成可供非经营性用地(亩)": "0",
            "项目个数": "1",
            "项目性质": "新增项目",
            "项目面积(亩)": "10"
          }, {
            "项目性质": "新增项目",
            "区域": "青羊区",
            "计划形成可供经营性用地(亩)": 0,
            "项目面积(亩)": 0,
            "项目个数": 0,
            "计划完成拆迁面积": 0,
            "年度资金预算(万元)": 0,
            "计划形成可供非经营性用地(亩)": 0,
            "SORT": -1
          }, {
            "项目性质": "新增项目",
            "区域": "金牛区",
            "计划形成可供经营性用地(亩)": 0,
            "项目面积(亩)": 0,
            "项目个数": 0,
            "计划完成拆迁面积": 0,
            "年度资金预算(万元)": 0,
            "计划形成可供非经营性用地(亩)": 0,
            "SORT": -1
          }, {
            "项目性质": "新增项目",
            "区域": "成华区",
            "计划形成可供经营性用地(亩)": 0,
            "项目面积(亩)": 0,
            "项目个数": 0,
            "计划完成拆迁面积": 0,
            "年度资金预算(万元)": 0,
            "计划形成可供非经营性用地(亩)": 0,
            "SORT": -1
          }, {
            "项目性质": "新增项目",
            "区域": "合计",
            "计划形成可供经营性用地(亩)": 0,
            "项目面积(亩)": 100,
            "项目个数": 1,
            "计划完成拆迁面积": 0,
            "年度资金预算(万元)": 0,
            "计划形成可供非经营性用地(亩)": 0,
            "SORT": -1
          }],
          yearItems: [],
          condition: {
            year: 1996
          },
          spanObj: [],
        }
      },
      methods: {
        spanMethod(param) {
          return exportExcelUtil.dataMerge.arraySpanMethod(param, this.spanObj);
        },
        handleExcelStyle(wb) {
          for (let i = 0; i < 11; i++) {
            wb["!cols"][i] = {wpx: 130}
          }
          //项目性质
          wb["!cols"][0] = {wpx: 220}
          //区域
          wb["!cols"][1] = {wpx: 220}
          //计划形成可供经营性用地(亩)
          wb["!cols"][6] = {wpx: 250}
          //计划形成可供非经营性用地(亩)
          wb["!cols"][7] = {wpx: 260}
          // 样式的文档地址
          // https://www.npmjs.com/package/xlsx-style
          for (const key in wb) {
            if (key.indexOf('!') === -1) {
              //特殊处理 数据有超出列不加边框
              if (key.indexOf("I") >= 0) {
                continue;
              }
              let font = {
                sz: 13,
                bold: false,
                color: {
                  rgb: '000000'//十六进制,不带#
                }
              }
              if (wb[key].v.indexOf('年储备土地拟收储计划表') > 0) {
                font = {
                  sz: 20,
                  bold: false,
                  color: {
                    rgb: '000000'//十六进制,不带#
                  }
                }
              }
              wb[key].s = {
                //字体设置
                font: font,
                alignment: {//文字居中
                  horizontal: 'center',
                  vertical: 'center',
                  wrap_text: true
                },
                border: { // 设置边框
                  top: {style: 'thin'},
                  bottom: {style: 'thin'},
                  left: {style: 'thin'},
                  right: {style: 'thin'}
                }
              }
            }
          }
          return wb;
        },
        exportExcel() {
          //导出表id
          let tableId = 'nscjbh-staticTable';
          //单元格样式
          let cellStyle = {
            default: {
              font: {
                sz: 13,
                bold: false,
                color: {
                  rgb: '000000'//十六进制,不带#
                }
              },
              alignment: {
                horizontal: 'center',
                vertical: 'center',
                wrap_text: true
              },
              border: {
                top: {style: 'thin'},
                bottom: {style: 'thin'},
                left: {style: 'thin'},
                right: {style: 'thin'}
              },
            },
            specialCell: {}
          };
          //导出表名
          let fileName = `计划表(${(new Date()).toDateString()}).xlsx`;
          //头部样式
          let headerStyle = {
            default: {
              wpx: 220
            },
            specialHeader: [{
              index: 2,
              style: {
                wpx: 320
              }
            }]
          };
          //自定义样式处理方法(按需求,可以不传)    样式的文档地址  https://www.npmjs.com/package/xlsx-style
          let handleExcelStyle = (wb, cellStyle, headerStyle) => {
          };
          //列表最大列号 从1开始算
          let maxLineName = 'H';
          exportExcelUtil.exportExcel(tableId, maxLineName, cellStyle, fileName, headerStyle);
        },
        getSummaries(param) {
          const {columns, data} = param;
          const sums = [];
          let ignoreIndesItems = [0, 1];
          columns.forEach((column, index) => {
            if (column.label === '项目性质') {
              sums[index] = '合计';
              return;
            }
            if (ignoreIndesItems.indexOf(index) >= 0) {
              sums[index] = '/';
              return;
            }
            const values = data.map(item => Number(item[column.property]));
            if (!values.every(value => isNaN(value))) {
              sums[index] = values.reduce((prev, curr) => {
                const value = Number(curr);
                if (!isNaN(value)) {
                  return prev + curr;
                } else {
                  return prev;
                }
              }, 0);
            } else {
              sums[index] = 'N/A';
            }
          });
          return sums;
        },
      },
      mounted() {
      },
      created() {
        this.spanObj = exportExcelUtil.dataMerge.dataHandle(this.tableData, ['项目性质']);
        console.log(this.spanObj);
      }
    }
    </script>
    
    <style scoped>
    
    </style>
    
  • 相关阅读:
    C++如何调用父类中的方法
    关于QStandardItemMode的资料
    Qt的信号和槽的使用方法练习
    Redirecting Standard I/O to Windows Console
    C++头文件的工作原理
    《深入浅出MFC》第七章 简单而完整:MFC骨干程序
    《深入浅出MFC》第六章 MFC程序的生死因果
    《深入浅出MFC》第五章 总观Application Framework
    《深入浅出MFC》第四章 Visual C++集成开发环境
    nexus使用记录
  • 原文地址:https://www.cnblogs.com/cyh1282656849/p/15429425.html
Copyright © 2011-2022 走看看