前言
平时可能会有导出到 excel
的需求,这里面直接使用 exceljs
仓库来实现导出功能,一般都是table表格导出,这里面主要介绍怎么通过数据 + antd-table column
导出到 excel,图片就先不加入了,自己可以根据需要自己在了解一下即可
exceljs简介
第一步肯定是导入我们的库了
js
const exceljs = require('exceljs');
声明一个 excel 对象,可以理解为 excel 文件本身,添加一个 worksheet,这个应该理解,每一个 excel 会有多个 sheet 表格,我们写入到其中一个就行,名字自己看着定义
创建表格、sheet
js
const workbook = new exceljs.Workbook();
workbook.creator = '帅';
workbook.created = new Date();
// 添加工作表
const worksheet = workbook.addWorksheet('sheet1');
紧接着我们网其中一个 sheet 中添加数据就对了,多个 sheet 逻辑和单个类似,看自己需求了往后写哈,就是多几个 sheet 罢了
创建单元格标题栏(单页表头)
excel 表格样式应该都见过,是一个二维表格,长的跟 table 类似,只不过他们的表头也是单元格的一部分罢了(无论是横向还是纵向,这边以正常表格,也就是表头再上为模版开发,争取跟 table 结构一致)
我们的表头结构为 header、key、width 分别是 名称、键值、宽度,跟 table 可以说很像了
js
const headerColumns: any[] = [];
columns.forEach((item, index) => {
headerColumns.push({
header: item.title,
key: item.dataIndex,
width: item.width ? item.width / 10 : 40, 这个像素个人尝试 / 10 差不多哈
});
});
worksheet.columns = headerColumns;
创建单元格内容
每一行都是一个表格对象,遍历数据,将我们的数据,每组按照 key 分别放到一个对象中,每个对象就是一行,key就是列,就这样生成表格内容了
js
const data: any[] = [];
datasource.forEach((item, index) => {
const obj: any = {};
columns.forEach((e, idx) => {
const info = generateColumnKey(e, idx, item);
obj[item.dataIndex] = item[item.dataIndex]
});
data.push(obj);
});
worksheet.addRows(data);
添加样式
就直说一个,我们的 第一行第一列是是索引行列,第二行是我们的标题,后面的才是我们的数据
这边以一个标题为例,获取其所在的row,然后遍历设置样式即可
js
worksheet
.getRow(1)
.eachCell({ includeEmpty: true }, (cell: any, colNumber: number) => {
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: {
argb: 'FFDEE6F0',
},
};
cell.font = {
size: 14,
color: { argb: 'FF000000' }, // 黑色
bold: true,
};
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
};
cell.alignment = {
horizontal: columns[colNumber - 1].align ?? 'left',
vertical: 'middle',
};
})
实现导出功能
实际上的导出肯定跟我们想象的不一样,肯定要复杂不少,table里面又实用转化的,也就是不直接使用 dataIndex 的,那么我们可以声明一下 transform 方法接受他们的 render、renderText(前提是哪里不写 dom 节点哈,写了就没办法,转化方法只能自己再写了)
涉及到转化(render)的我们的 key 会发生变化,无法获取到内容,可以自己按照 规则声明一个唯一值即可(比如index,同一列所在索引是一样的),对于嵌套的,我们使用 reduce 取值即可,其他的直接获取内容即可,注意需要保证有值
当然最建议的是直接将 dto 转化成 vo,在一个对象,这样就不存在转化了,这里代码也会少一点,不过对于前端来说,一页就那么多数据,什么时候转化都一样,这就不强求了,如果条数非常多,那么就少点样式、提前加工好数据这样能提升一下效率
下面就是实现逻辑,自己尝试了一下,很不错
js
export type ExcelTableColumns = {
title: string;
dataIndex: string | string[];
width?: number; //实际算是文字长度了,英文1个多一点,中文两个多一点最佳,默认按照像素数量 / 10,并预留出部分空间,一般为 12-16号字体之间,这个略长一点
align?: 'left' | 'right' | 'center';
transform?: (value: any, record: any, index: number) => string | number; //render或者renderText,注意返回对象
};
function generateColumnKey(
column: any,
index: number,
data?: any
): { key: string; value: any } {
const info = {
key: '',
value: '',
};
if (column.transform) {
info.key = `___transform__` + index;
return info;
}
if (typeof column.dataIndex === 'string') {
info.key = column.dataIndex;
if (data) {
info.value = data[column.dataIndex];
}
} else if (Array.isArray(column.dataIndex)) {
info.key = column.dataIndex!.join('_');
if (data) {
info.value = column.dataIndex.reduce((pre: any, itm: any) => {
return pre ? pre[itm] : '';
}, data);
}
} else {
info.key = column.dataIndex.toString();
}
return info;
}
//基本导出功能
export const exportExcelByTable = async (
columns: ExcelTableColumns[],
datasource: any[],
filename?: string
) => {
if (!datasource) {
throw new Error('没有数据');
}
columns = columns.filter((e) => e.title && (e.transform || e.dataIndex));
const exceljs = require('exceljs');
const workbook = new exceljs.Workbook();
workbook.creator = '卓朗天工';
workbook.created = new Date();
// 添加工作表
const worksheet = workbook.addWorksheet('sheet1');
const headerColumns: any[] = [];
columns.forEach((item, index) => {
const info = generateColumnKey(item, index);
headerColumns.push({
header: item.title,
key: info.key,
width: item.width ? item.width / 10 : 40,
});
});
worksheet.columns = headerColumns;
const data: any[] = [];
datasource.forEach((item, index) => {
const obj: any = {};
columns.forEach((e, idx) => {
const info = generateColumnKey(e, idx, item);
obj[info.key] = e.transform
? e.transform(info.value, item, index)
: info.value;
});
data.push(obj);
});
worksheet.addRows(data);
worksheet
.getRow(1)
.eachCell({ includeEmpty: true }, (cell: any, colNumber: number) => {
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: {
argb: 'FFDEE6F0',
},
};
cell.font = {
size: 14,
color: { argb: 'FF000000' }, // 黑色
bold: true,
};
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
};
cell.alignment = {
horizontal: columns[colNumber - 1].align ?? 'left',
vertical: 'middle',
};
});
datasource.forEach((item, index) => {
worksheet
.getRow(index + 2)
.eachCell(
{ includeEmpty: true },
(cell: any, colNumber: number) => {
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: {
argb: 'FFFFFFFF',
},
};
cell.font = {
size: 12,
color: { argb: 'FF000000' }, // 黑色
};
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
};
cell.alignment = {
horizontal: columns[colNumber - 1].align ?? 'left',
vertical: 'middle',
};
}
);
});
const buffer = await workbook.xlsx.writeBuffer();
saveFile(buffer, filename);//文件导出
};
导出到文件功能,通过 a 标签实现下载
js
const saveFile = (data: any, filename?: string) => {
const blob = new Blob([data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8',
}) as any;
const url = URL.createObjectURL(blob);
const aLink = document.createElement('a');
aLink.setAttribute(
'download',
filename ? filename : `${new Date().getTime()}.xlsx`
);
aLink.setAttribute('href', url);
document.body.appendChild(aLink);
aLink.click();
document.body.removeChild(aLink);
URL.revokeObjectURL(blob);
};
从我们的 table-column 到 excel 可能还需要一些操作,我们直接在转化一下吧,例如前面加个索引,还有类型不一样,加个类型强转
js
export const exportExcelByTableCustom = async (
columns: Record<string, any>[],
datasource: any[],
filename: string,
no?: boolean | number
) => {
const cols = columns.map((e: any) => {
const transform = e.render
? e.render
: e.renderText
? e.renderText
: undefined;
return {
title: e.title,
dataIndex: e.dataIndex,
width: e.width ?? 300,
align: e.align,
transform,
} as ExcelTableColumns;
});
no &&
cols.unshift({
title: 'No.',
width: no === true ? 60 : no,
align: 'center',
transform: (_, __, index: number) => index + 1,
} as ExcelTableColumns);
await exportExcelByTable(cols, datasource, filename);
};
到上面一步基本可以直接用了
最后
如果有复用要求,可以写一个带接口的,如果接口分页处理,那么一页一页遍历处理的可以直接写好,如果是一次获取所有的,那就更简单了哈