效果展示
前端展示表格
導出表格
依賴安裝
使用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 * as XLSX from "xlsx";
import * as 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);
// 從表生成工作簿對象
console.log(XLSX);
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, mergeFieldArr) => {
// 表格單元格合並多列
let spanObj = [],
pos = [];
for (let index in data) {
let rowObject = data[index];
//循環數據內對象,查看有多少key
for (let rowObjectKey in rowObject) {
//如果只有一條數據時默認為1即可,無需合並
if (parseInt(index) === 0) {
spanObj[rowObjectKey] = [1];
pos[rowObjectKey] = 0;
} else {
let [currentRow, lastRow] = [rowObject, data[index - 1]];
//判斷當前key是否需要判斷合並
let filedArr = dataMerge.getMargeFileldArr(rowObjectKey, mergeFieldArr);
if (lastRow
&& filedArr.length > 0
&& dataMerge.isAllFiledSame(filedArr, currentRow, lastRow)) {
//如果上一級和當前一級相當,數組就加1 數組后面就添加一個0
spanObj[rowObjectKey][pos[rowObjectKey]] += 1;
spanObj[rowObjectKey].push(0)
} else {
spanObj[rowObjectKey].push(1);
pos[rowObjectKey] = index;
}
}
}
}
return spanObj;
},
getMargeFileldArr(key, arr) {
for (let margeFieldStr of arr) {
let margeFieldArr = margeFieldStr.split(",");
if (margeFieldArr.includes(key)) {
return margeFieldArr;
}
}
return [];
},
//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
}
},
isAllFiledSame(filedArr, currentRow, lastRow) {
for (let field of filedArr) {
// let field = filedArr[i];
if (currentRow[field] !== lastRow[field]) {
return false
}
}
return true;
}
};
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);
//自定義樣式處理
//exportExcelUtil.exportExcel(tableId, null, null, fileName, null,handleExcelStyle);
其他功能
尾部求和
參考官方文檔: https://element.eleme.io/#/zh-CN/component/table
同字段同值單元格合並
處理表格數據
其中參數1表示表格數據,參數2表示要合並單元格的字段數組
//如果需要判定在多個字段相同的情況下合並參數二一個元素加逗號隔開多個字段如['A1','F2','C2']
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>