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>
原文地址:https://www.cnblogs.com/cyh1282656849/p/15429425.html