首先就是看看实现的效果,一个文件中有以下四种表
2.
3.
4.
sheet
这四个表我给分成两种类型,一种是上下结构(1、2、4),一种是半包围结构(3),下面就开始说一说我是怎么做的
首先,我这边需求是点击导出表格按钮,需要填写一个实验地点,填写完成之后导出表格
<n-button type="info" size="tiny" @click="exportData" >
导出数据
</n-button>
// 导出数据返回数据
const excelData: any = ref([])//:any 是应付ts的类型报错
const exportData = () => {
openExport.value = true//这里是我填写地址的弹窗
//调用接口返回数据假设为res
excelData.value = res
}
此时我已经填写完地址并点击确认提交按钮,表格被导出
import {exportDataToExcel} from '@/utils/exportExcel/exportExcel.js'
const onSubmit = () => {
ruleForm.value?.validate((errors) => {
if (errors) {
if (!excelData.value) {
message.error('暂无可导出数据');
return
} else {
let keys = Object.keys(excelData.value)
let tableArr = []
keys.map(key => {
//newExportConfig 是导出表格前处理数据的
tableArr = tableArr.concat(newExportConfig(excelData.value[key], key))
})
exportDataToExcel(tableArr, `${prop.current?.testName}.xlsx`)//这个是导出表格的方法
tableArr = []
}
} else {
message.error('请填写完整信息');
}
})
}
导出表格前处理数据的方法,这里是复制的网上别人分享的,然后在这个基础上进行的改动(这里边的拖拉机可靠性使用试验综合汇总表是半包围式的)
const newExportConfig = (data, key) => {
// 如果导出前要处理数据,需要深克隆一份表格数据,然后进行处理
let config: any = []
if (key === 'maintenanceRecordSummary') {//保养信息
for (let i in data) {
//header就是表头,有几个header就有几个表头
const header1 = ['保养信息'];
let header2: any = [`累计保养次数:${data[i].numberOfMaintenances}`, '', `累计保养时间:${toTimeCycle(data[i].cumulativeMaintenanceTime) || 0}`, ''];
const header3 = ['序号', '保养时间', '保养时长', '保养内容'];
data[i].maintenanceRecordList.forEach(item => {
//toTimeCycle 是个时间转换方法,我别的文章有写,想看可以去扒拉扒拉
item.maintenanceDuration = toTimeCycle(item.maintenanceDuration) || 0
})
let oncItem: any = ({
data: getIndex(data[i].maintenanceRecordList),//内容
fields: ["serialNumber", "maintenanceTime", "maintenanceDuration", 'maintenanceSubstance'],//每列内容对应的字段
headers: [header1, header2, header3],//表头
merges: [],
attrs: [],
view: [],
columnsWidth: [20, 20, 30, 20],//表格宽度
sheetName: `${i}-保养信息`//sheet名(见上图sheet)
});
// 设置全表单元格边框,居中布局
oncItem.attrs.push({
rowStart: 0,
rowEnd: oncItem.data.length + 2,
colStart: 0,
colEnd: oncItem.fields.length - 1,
attr: {
alignment: { vertical: "middle", horizontal: "center" },
border: {
top: { style: "thin" },
left: { style: "thin" },
bottom: { style: "thin" },
right: { style: "thin" }
}
}
});
// 设置表头填充颜色,字体加粗
oncItem.attrs.push({
rowStart: 0,
rowEnd: 0,
colStart: 0,
colEnd: oncItem.fields.length - 1,
attr: {
fill: {
type: "pattern",
pattern: "solid",
// fgColor: { argb: "99CCFF" }
fgColor: {}
},
font: {
bold: true
}
}
});
// row:行, col:列, rowspan: 合并的行数 , colspan: 合并的列数
let tableItem = [
{
row: 0, col: 0, rowspan: 1, colspan: 4
},
{
row: 1, col: 0, rowspan: 1, colspan: 2,
},
{
row: 1, col: 2, rowspan: 1, colspan: 2,
}
]
oncItem.merges = tableItem
config.push(oncItem)
}
}
if (key === 'shiftSummary') { //拖拉机可靠性使用试验班次记录汇总表
let oncItem: any = {}
for (let i in data) {
// 处理表格内容
data[i] = data[i].map(item => {
return {
timeFrame: item.timeFrame,//时间范围
workingHours: timeCycleTo(item.workingHours) || 0,// 工作时长
fuelConsumption: item.fuelConsumption,//燃油消耗量
aveHourlyFuelConsumption: item.aveHourlyFuelConsumption,//平均小时油耗
aveSpeed: item.aveSpeed,//平均速度
area: item.area,//工作量
workStatus0: item.workInformation.workStatus,
stateAveHourlyFuelConsumption0: item.workInformation.stateAveHourlyFuelConsumption,
duration0: timeCycleTo(item.workInformation.duration) || 0,
stateAveSpeed0: item.workInformation.stateAveSpeed,
workStatus1: item.blankLineInformation.workStatus,
stateAveHourlyFuelConsumption1: item.blankLineInformation.stateAveHourlyFuelConsumption,
duration1: timeCycleTo(item.blankLineInformation.duration) || 0,
stateAveSpeed1: item.blankLineInformation.stateAveSpeed,
workStatus2: item.idlingInformation.workStatus,
stateAveHourlyFuelConsumption2: item.idlingInformation.stateAveHourlyFuelConsumption,
duration2: timeCycleTo(item.idlingInformation.duration) || 0,
stateAveSpeed2: item.idlingInformation.stateAveSpeed,
}
})
const header1 = ['拖拉机可靠性使用试验班次记录汇总表']
const header2 = ['序号', '时间', '工作时长', '燃油消耗量(kg)', '平均小时油耗(kg/h)', '平均速度(km/h)', '工作量(ha)', '状态', '时长', '平均小时油耗(kg/h)', '平均速度(km/h)', '状态', '时长', '平均小时油耗(kg/h)', '平均速度(km/h)', '状态', '时长', '平均小时油耗(kg/h)', '平均速度(km/h)']
oncItem = ({
data: getIndex(data[i]),
fields: ['serialNumber', 'timeFrame', 'workingHours', 'fuelConsumption', 'aveHourlyFuelConsumption', 'aveSpeed', 'area', 'workStatus0', 'duration0', 'stateAveHourlyFuelConsumption0', 'aveSpeed0', 'workStatus1', 'duration1', 'stateAveHourlyFuelConsumption1', 'aveSpeed1', 'workStatus2', 'duration2', 'stateAveHourlyFuelConsumption2', 'aveSpeed2'],
headers: [header1, header2],
merges: [],
attrs: [],
view: [],
columnsWidth: Array.from({ length: 20 }, () => 20),
// protect: {},
sheetName: `${i}-拖拉机可靠性使用试验班次记录汇总表`
});
// 设置全表单元格边框,居中布局
oncItem.attrs.push({
rowStart: 0,
rowEnd: oncItem.data.length + 1,
colStart: 0,
colEnd: oncItem.fields.length - 1,
attr: {
alignment: { vertical: "middle", horizontal: "center" },
border: {
top: { style: "thin" },
left: { style: "thin" },
bottom: { style: "thin" },
right: { style: "thin" }
}
}
});
// // 设置表头填充颜色,字体加粗
oncItem.attrs.push({
rowStart: 0,
rowEnd: 0,
colStart: 0,
colEnd: oncItem.fields.length - 1,
attr: {
fill: {
type: "pattern",
pattern: "solid",
fgColor: {}
},
font: {
bold: true
}
}
});
// row:行, col:列, rowspan: 合并的行数 , colspan: 合并的列数
let tableItem = [
{
row: 0, col: 0, rowspan: 1, colspan: 19
},
]
oncItem.merges = tableItem
config.push(oncItem)
}
}
if (key === 'testFailureSummary') {//拖拉机可靠性使用试验故障汇总表
const header1 = ['拖拉机可靠性使用试验故障汇总表'];
let header2: any = [`拖拉机型号:${data.modelName} 样机编号:${data.protoNumber} 试验日期:${data.testTime}`];
const header3 = [`试验起止日期:${data.testStartAndEndTime} 制造厂:${data.manufacturersName}`];
const header4 = [`试验地点:${form.value.position} 样机台数:${data.protoQuantity} 规定试验结尾时间:${data.endTime}h`];
const header5 = ['序号', '试验样机编号', '故障名称', '拖拉机累计工作时间', '故障原因', '故障类别', '修复工作时间', '危害度系数K', '时间系数E']
//处理时间
data.failureSummaryLists.forEach(item => {
item.workingHoursSum = toTimeCycle(item.workingHoursSum) || 0
item.repairWorkingHours = toTimeCycle(item.repairWorkingHours) || 0
})
let oncItem: any = ({
data: getIndex(data.failureSummaryLists),
fields: ['serialNumber', 'protoNumber', 'failureName', 'workingHoursSum', 'failureCause', 'failureType', 'repairWorkingHours', 'hazardFactor', 'timeCoefficient'],
headers: [header1, header2, header3, header4, header5],
merges: [],
attrs: [],
view: [],
columnsWidth: Array.from({ length: 10 }, () => 20),
sheetName: '拖拉机可靠性使用试验故障汇总表'
});
// 设置全表单元格边框,居中布局
oncItem.attrs.push({
rowStart: 0,
rowEnd: oncItem.data.length + 4,
colStart: 0,
colEnd: header5.length - 1,
attr: {
alignment: { vertical: "middle", horizontal: "center" },
border: {
top: { style: "thin" },
left: { style: "thin" },
bottom: { style: "thin" },
right: { style: "thin" }
}
}
});
// 设置表头填充颜色,字体加粗
oncItem.attrs.push({
rowStart: 0,
rowEnd: 0,
colStart: 0,
colEnd: oncItem.fields.length - 1,
attr: {
fill: {
type: "pattern",
pattern: "solid",
// fgColor: { argb: "99CCFF" }
fgColor: {}
},
font: {
bold: true
}
}
});
// row:行, col:列, rowspan: 合并的行数 , colspan: 合并的列数
let tableItem = [
{
row: 0, col: 0, rowspan: 1, colspan: header5.length
},
{
row: 1, col: 0, rowspan: 1, colspan: header5.length,
},
{
row: 2, col: 0, rowspan: 1, colspan: header5.length,
},
{
row: 3, col: 0, rowspan: 1, colspan: header5.length,
},
]
oncItem.merges = tableItem
config.push(oncItem)
}
if (key === 'synthesisSummary') {//拖拉机可靠性使用试验综合汇总表
let newDate: any = []
let header1: any = []
let header2: any = []
let header3: any = []
data.forEach(item => {
header1 = ['拖拉机可靠性使用试验综合汇总表'];
header2 = [`拖拉机型号:${item.modelName} 制造厂:${item.manufacturersName} 试验起止日期:${item.testStartAndEndTime}`];
header3 = [`试验样机编号`, '', '1', '2', '3'];
//处理表格内容
// 侧标表头的对应关系
let labelList = [
{
key: "totalWorkingHours",
label: "累计工作时间/h"
},
{
key: "totalIdleTime",
label: "累计空转时间/h"
},
{
key: "totalMaintainingTime",
label: "累计保养时间/h"
},
{
key: "totalRepairingTime",
label: "累计修复工作时间/h"
},
{
key: "totalFuelConsumption",
label: "累计耗油量/kg|燃油"
},
{
key: "totalWorkLoadField",
label: "累计工作量|田间/ha"
},
{
key: "avgHourlyFuelConsumptionField",
label: "平均小时燃油耗/(kg/h)|田间"
},
{
key: "avgUnitWorkLoadFuelConsumptionField",
label: "平均单位工作量燃油耗|田间/(kg/ha)"
},
{
key: "workingLoadCoefficient",
label: "田间作业平均负荷系数/%"
},
{
key: "workingTimePercentage",
label: "田间作业时间占总时间的百分比/%"
}
]
/**
* 遍历循环 表头对应关系
* 根据后端返回数据的长度进行for in 遍历,得到每一列的数据
* 设置每一列的字段 colItemData加上当前列的索引
* 获取表头需要显示的内容,在 表头对应关系的 label中设置 如果有多个使用 | 进行分开
* 设置每一行的数据, 需要判断 当前这一行是否有值,因为前面遍历了synthesisSummary 会先执行完synthesisSummary的第一项在执行第二项,如果有值就是他本身,如果没有值就是当前行的表头
* 然后对当前行添加数据
*/
labelList.forEach((item, index) => {
for (let i = 0; i < data.length; i++) {
let keys = 'colItemData' + (i + 1)
let labelList = item.label.split('|')
let header = {
col1: labelList[0],
col2: labelList[1] || '',
}
newDate[index] = newDate[index] ? newDate[index] : header
newDate[index][keys] = data[i][item['key']]
}
})
})
let oncItem: any = ({
data: newDate,
fields: ['col1', 'col2', 'colItemData1', 'colItemData2', 'colItemData3'],
headers: [header1, header2, header3],
merges: [],
attrs: [],
view: [],
columnsWidth: Array.from({ length: 10 }, () => 20),
// protect: {},
sheetName: '拖拉机可靠性使用试验综合汇总表'
});
// 设置全表单元格边框,居中布局
oncItem.attrs.push({
rowStart: 0,
rowEnd: oncItem.data.length + 2,
colStart: 0,
colEnd: oncItem.fields.length - 1,
attr: {
alignment: { vertical: "middle", horizontal: "center" },
border: {
top: { style: "thin" },
left: { style: "thin" },
bottom: { style: "thin" },
right: { style: "thin" }
}
}
});
// 设置表头填充颜色,字体加粗
oncItem.attrs.push({
rowStart: 0,
rowEnd: 0,
colStart: 0,
colEnd: oncItem.fields.length - 1,
attr: {
fill: {
type: "pattern",
pattern: "solid",
// fgColor: { argb: "99CCFF" }
fgColor: {}
},
font: {
bold: true
}
}
});
// row:行, col:列, rowspan: 合并的行数 , colspan: 合并的列数
let tableItem = [
{
row: 0, col: 0, rowspan: 1, colspan: oncItem.fields.length
},
{
row: 1, col: 0, rowspan: 1, colspan: oncItem.fields.length,
},
{
row: 2, col: 0, rowspan: 1, colspan: 2,
},
{
row: 3, col: 0, rowspan: 1, colspan: 2,
},
{
row: 4, col: 0, rowspan: 1, colspan: 2,
},
{
row: 5, col: 0, rowspan: 1, colspan: 2,
},
{
row: 6, col: 0, rowspan: 1, colspan: 2,
},
{
row: 11, col: 0, rowspan: 1, colspan: 2,
},
{
row: 12, col: 0, rowspan: 1, colspan: 2,
},
]
oncItem.merges = tableItem
config.push(oncItem)
}
return config;
}
导出表格的方法,是直接复制使用的别人写好的
// 封装exceljs
// const ExcelJS = require('exceljs');
// const FileSaver = require('file-saver');
import ExcelJS from 'exceljs'
import FileSaver from 'file-saver'
/**
* 导出数据到Excel方法
* @param {Array[Object]} config.data 表格数据
* @param {Array[String]} config.fields 字段列表
* @param {Array[String]} config.headers excel表头列表[[]],可以是多级表头[['A1','B1'],['A2','B2']]
* @param {Array[Object]} config.merges 需要合并的单元格,需要考虑表头的行数[{row:1, col:1, rowspan: 1, colspan: 2}]
* @param {Array[Object]} config.attrs 单元格样式配置
* @param {Array[Object]} config.views 工作表视图配置
* @param {Array[Number]} columnsWidth 每个字段列对应的宽度
* @param {Object} config.protect 工作表保护【此配置会保护全表,一般推荐只针对单元格进行保护配置】
* @param {String} sheetName 工作表名称,默认从sheet1开始
* @param {String} fileName excel文件名称
*/
export function exportDataToExcel(config, fileName) {
if (!config) return;
const options = {
fileName: fileName || `导出excel文件【${Date.now()}】.xlsx`,
worksheets: []
}
if (!Array.isArray(config)) {
config = [config]
}
config.forEach((item) => {
// 深拷贝data【JSON.stringify有缺陷,可自行换成_.cloneDeep】
const data = JSON.parse(JSON.stringify(item.data));
const results = data.map(obj => {
return item.fields.map(key => {
return obj[key]
})
})
// 生成完整excel数据
let excelData = [];
excelData = excelData.concat(item.headers).concat(results);
// 单元格合并处理【excel数据的第一行/列是从1开始】
let excelMerges = [];
excelMerges = item.merges.map(m => {
return [m.row + 1, m.col + 1, m.row + m.rowspan, m.col + m.colspan]
})
// 单元格配置处理【excel数据的第一行/列是从1开始】
let excelAttrs = [];
excelAttrs = item.attrs.map(attr => {
attr.rowStart += 1;
attr.rowEnd += 1;
attr.colStart += 1;
attr.colEnd += 1;
return attr
})
options.worksheets.push({
data: excelData,
merges: excelMerges,
attrs: excelAttrs,
views: item.views,
columnsWidth: item.columnsWidth,
protect: item.protect,
sheetName: item.sheetName
})
})
createExcel(options)
}
// 创建Excel文件方法
async function createExcel(options) {
if (!options.worksheets.length) return;
// 创建工作簿
const workbook = new ExcelJS.Workbook();
for (let i = 0; i < options.worksheets.length; i++) {
const sheetOption = options.worksheets[i];
// 创建工作表
const sheet = workbook.addWorksheet(sheetOption.sheetName || 'sheet' + (i + 1));
// 添加数据行
sheet.addRows(sheetOption.data);
// 配置视图
sheet.views = sheetOption.views;
// 单元格合并处理【开始行,开始列,结束行,结束列】
if (sheetOption.merges) {
sheetOption.merges.forEach((item) => {
sheet.mergeCells(item);
});
}
// 工作表保护
if (sheetOption.protect) {
const res = await sheet.protect(sheetOption.protect.password, sheetOption.protect.options);
}
// 单元格样式处理
if (sheetOption.attrs.length) {
sheetOption.attrs.forEach((item) => {
const attr = item.attr || {};
// 获取开始行-结束行; 开始列-结束列
const rowStart = item.rowStart;
const rowEnd = item.rowEnd;
const colStart = item.colStart;
const colEnd = item.colEnd;
if (rowStart) { // 设置行
for (let r = rowStart; r <= rowEnd; r++) {
// 获取当前行
const row = sheet.getRow(r);
if (colStart) { // 列设置
for (let c = colStart; c <= colEnd; c++) {
// 获取当前单元格
const cell = row.getCell(c);
Object.keys(attr).forEach((key) => {
// 给当前单元格设置定义的样式
cell[key] = attr[key];
});
}
} else {
// 未设置列,整行设置【大纲级别】
Object.keys(attr).forEach((key) => {
row[key] = attr[key];
});
}
}
} else if (colStart) { // 未设置行,只设置了列
for (let c = colStart; c <= colEnd; c++) {
// 获取当前列,整列设置【大纲级别】
const column = sheet.getColumn(c);
Object.keys(attr).forEach((key) => {
column[key] = attr[key];
});
}
} else {
// 没有设置具体的行列,则为整表设置
Object.keys(attr).forEach((key) => {
sheet[key] = attr[key];
});
}
})
}
// 列宽设置
if (sheetOption.columnsWidth) {
for (let i = 0; i < sheet.columns.length; i++) {
sheet.columns[i].width = sheetOption.columnsWidth[i]
}
}
}
// 生成excel文件
workbook.xlsx.writeBuffer().then(buffer => {
// application/octet-stream 二进制数据
FileSaver.saveAs(new Blob([buffer], { type: 'application/octet-stream' }), options.fileName)
})
}