Element ui複雜表格(多級表頭、尾行求合、單元格合併)前端匯出
阿新 • • 發佈:2021-10-20
依賴安裝
使用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>