1. 程式人生 > 其它 >Element ui複雜表格(多級表頭、尾行求合、單元格合併)前端匯出

Element ui複雜表格(多級表頭、尾行求合、單元格合併)前端匯出

依賴安裝

使用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\node_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) => {
    for (let i = 0; i < 11; 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)>excleLineNameToLineIndex(maxLineName)){
                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="width: 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);

其他功能

尾部求和

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

同欄位同值單元格合併

處理表格資料

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="width: 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>