前端复杂 table 渲染及 excel.js 导出

转载请注明出处,点击此处 查看更多精彩内容

现在我们有一个如图(甚至更复杂)的表格需要展示到页面上,并提供下载为 excel 文件的功能。

效果图.png

前端表格渲染我们一般会使用 element-ui 等组件库提供的 table 组件,这些组件一般都是以列的维度进行渲染,而我们使用的 excel 生成工具(如 exceljs)却是以行的维度进行生成,这就导致页面渲染和 excel 生成的数据结构无法匹配。

为了解决这个问题,达到使用一套代码兼容页面渲染和 excel 生成的目的,我们需要统一使以行的维度进行数据的组织,然后分别使用原生 table 元素和 exceljs 进行页面渲染和 excel 文件生成。

功能列表

  • 单元格展示文字
  • 单元格文字尺寸
  • 单元格文字是否加粗
  • 单元格文字颜色
  • 单元格水平对齐方式
  • 单元格自定义展示内容(复杂样式、图片等)
  • 单元格合并
  • 指定行高
  • 单元格背景色
  • 是否展示单元格对角线
  • 是否展示边框

定义单元格数据结构

首先我们需要定义单元格和表格行的数据结构。

/*** 表格单元格配置*/
export interface TableCell {/** 展示文案 */text?: string;/** 文字尺寸,默认 14 */fontSize?: number;/** 文字是否加粗 */bold?: boolean;/** 文字颜色,默认 #000000 */color?: string;/** 水平对齐方式,默认 center */align?: "left" | "center" | "right";/** 所占行数,默认 1 */rowspan?: number;/** 所占列数,默认 1 */colspan?: number;/** 高度,若一行中有多个单元格设置高度,将使用其中的最大值 */height?: number;/** 背景颜色 */bgColor?: string;/** 是否绘制对角线 */diagonal?: boolean;/** 是否绘制边框,默认 true */border?: ("top" | "right" | "bottom" | "left")[];/** 动态属性 */[key: string]: any;
}/*** 表格行。undefined 标识被合并的单元格*/
export type TableRow = (TableCell | undefined)[];

TableCell 表示一个单元格,定义了单元格的基本配置,如展示文案、对齐方式、单元格合并、颜色、字体大小、边框等,可根据实际需求进行扩展。

TableRow 是由多个单元格组成的表格行,undefined 用于标识被合并的单元格。

表格渲染

基于如上表格单元格和行的定义,我们可以编写一个组件用于渲染表格。

<template><div class="custom_table"><table><colgroup><colv-for="(width, index) in colWidthList":key="index":style="{ width: `${width}px` }"/></colgroup><trv-for="(row, rowIndex) in data":key="rowIndex":style="{ height: calcRowHeight(row) }"><tdv-for="(cell, colIndex) in row.filter((item) => !!item)":key="colIndex":class="['table-cell',...getCellBorderClass(cell),{ 'table-cell--diagonal': cell?.diagonal },]":style="{fontSize: `${cell?.fontSize || 14}px`,fontWeight: cell?.bold ? 'bold' : 'initial',color: cell?.color || '#000000',textAlign: cell?.align || 'center',background: cell?.bgColor || '#ffffff',...cellStyle?.(cell),}":rowspan="cell?.rowspan":colspan="cell?.colspan"><slot name="cell" :cell="cell">{{ cell?.text }}</slot></td></tr></table></div>
</template><script setup lang="ts">
import { computed, CSSProperties } from "vue";
import { TableCell, TableRow } from "@/utils/excel-helper";export interface Props {/** 表格数据 */data: TableRow[];/** 表格列宽。number[] 精确指定每列的宽度;number 表示所有列统一使用指定宽度 */colWidth?: number | number[];/** 自定义指定单元格的样式 */cellStyle?: (cell?: TableCell) => CSSProperties;
}const props = withDefaults(defineProps<Props>(), {});export interface Slots {cell?: (props: { cell?: TableCell }) => void;
}defineSlots<Slots>();// 列宽
const colWidthList = computed(() => {if (!props.colWidth) {return [];}if (Array.isArray(props.colWidth)) {return props.colWidth;}return new Array(props.data[0]?.length).fill(props.colWidth);
});// 计算行高
const calcRowHeight = (row: TableRow) => {const heightList = row.map((item) => item?.height || 0);return `${Math.max(25, ...heightList)}px`;
};// 获取边框样式
const getCellBorderClass = (cell?: TableCell) => {const border = cell?.border || ["top", "right", "bottom", "left"];return border.map((item) => `table-cell--border-${item}`);
};
</script><style lang="scss" scoped>
.custom_table {display: flex;width: fit-content;max-width: -webkit-fill-available;font-size: 14px;overflow: auto;table {flex-shrink: 0;border-collapse: collapse;}td {height: 20px;line-height: 20px;padding: 8px 6px 6px;text-align: center;white-space: break-spaces;word-break: break-all;}.table-cell {&--border-top {border-top: 1px solid #606266;}&--border-right {border-right: 1px solid #606266;}&--border-bottom {border-bottom: 1px solid #606266;}&--border-left {border-left: 1px solid #606266;}&--diagonal {position: relative;&::before {content: "";position: absolute;inset: 0;background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxsaW5lIHgxPSIwIiB5MT0iMCIgeDI9IjEwMCUiIHkyPSIxMDAlIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjEiLz48L3N2Zz4=)no-repeat 100% center !important;}}}
}
</style>

该组件接收表格数据(data)、表格列宽(colWidth)、自定义指定单元格样式的回调函数(cellStyle)等参数。

该组件对外公开名为 cell 的插槽,可自定义单元格的渲染内容。

生成 excel 文件

我们通过 exceljs 完成 excel 文件的生成。

安装 exceljs

npm install exceljs

根据表格配置生成 excel 文件

import ExcelJS, { Workbook, Worksheet } from "exceljs";/*** 生成 excel 文件*/
export async function generateExcel(rowList: TableRow[],colWidth: number | number[] = []
): Promise<ExcelJS.Workbook> {// 创建表const workbook = new ExcelJS.Workbook();const worksheet = workbook.addWorksheet("Sheet1");// 插入表头和数据rowList.forEach((row) =>worksheet.addRow(row.map((cell) => cell?.text || "")));// 合并单元格rowList.forEach((rowItem, rowIndex) => {rowItem.forEach((cellItem, colIndex) => {if (!cellItem) {return;}const colNoStart = convertColumnNo(colIndex);const colNoEnd = convertColumnNo(colIndex + (cellItem.colspan || 1) - 1);const rowNoStart = rowIndex + 1;const rowNoEnd = rowNoStart + (cellItem.rowspan || 1) - 1;worksheet.mergeCells(`${colNoStart}${rowNoStart}:${colNoEnd}${rowNoEnd}`);});});// 设置列宽let colWidthList: number[];if (Array.isArray(colWidth)) {colWidthList = colWidth;} else {colWidthList = new Array(rowList[0].length).fill(colWidth);}colWidthList.forEach((width, index) => {worksheet.getColumn(index + 1).width = width / 7.8;});// 设置默认行高worksheet.properties.defaultRowHeight = 28;// 设置单元格样式rowList.forEach((rowItem, rowIndex) => {const row = worksheet.getRow(rowIndex + 1);let maxHeight = worksheet.properties.defaultRowHeight;rowItem.forEach((cellItem, colIndex) => {if (!cellItem) {return;}const cell = row.getCell(colIndex + 1);maxHeight = Math.max(maxHeight, cellItem.height || 0);// 文字样式cell.font = {name: "等线",size: ((cellItem.fontSize || 14) * 11) / 14, // Excel 字体大小为 11bold: cellItem.bold,color: { argb: (cellItem.color || "#000000").slice(1) },};const border = cellItem?.border || ["top", "right", "bottom", "left"];// 设置边框cell.border = {top: border.includes("top") ? { style: "thin" } : undefined,right: border.includes("right") ? { style: "thin" } : undefined,bottom: border.includes("bottom") ? { style: "thin" } : undefined,left: border.includes("left") ? { style: "thin" } : undefined,diagonal: { up: false, down: cellItem?.diagonal, style: "thin" },};// 设置居中&自动换行cell.alignment = {horizontal: cellItem.align || "center",vertical: "middle",wrapText: true,};// 设置背景if (cellItem.bgColor) {cell.fill = {type: "pattern",pattern: "solid",fgColor: { argb: cellItem.bgColor.slice(1) },};}});row.height = maxHeight;});return workbook;
}/*** 转换数字列号为字母列号* @param num*/
function convertColumnNo(num: number) {const codeA = "A".charCodeAt(0);const codeZ = "Z".charCodeAt(0);const length = codeZ - codeA + 1;let result = "";while (num >= 0) {result = String.fromCharCode((num % length) + codeA) + result;num = Math.floor(num / length) - 1;}return result;
}

调用 generateExcel 函数传入表格配置即可生成一个 excel 工作簿对象 ExcelJS.Workbook

下载 excel 文件

/*** 下载为 excel 文件* @param workbook excel 工作簿对象* @param fileName 文件名*/
export async function downloadExcel(workbook: ExcelJS.Workbook, fileName: string) {const buffer = await workbook.xlsx.writeBuffer();const blob = new Blob([buffer], { type: "arraybuffer" });const link = document.createElement("a");link.href = URL.createObjectURL(blob);link.download = fileName;link.click();
}

调用 downloadExcel 函数传入 ExcelJS.Workbook 对象和文件名即可下载为 excel 文件。

图片等内容处理

当前 generateExcel 函数并未处理图片等复杂内容。

由于这些内容具有不确定性,因此,我们定义一个专门处理这些内容的回调函数。

函数声明

/*** 渲染图片等非普通文本的数据*/
export type RenderAdditionalData = (/** 行号 */rowIndex: number,/** 列号 */colIndex: number,/** excel 工作簿对象 */workbook: ExcelJS.Workbook,/** excel 工作表对象 */worksheet: ExcelJS.Worksheet
) => Promise<void> | void;

将图片等内容的处理插入到 generateExcel 函数:

async function generateExcel(rowList: TableRow[],colWidth: number | number[] = [],renderAdditionalData?: RenderAdditionalData
): Promise<ExcelJS.Workbook> {...// 合并单元格rowList.forEach((rowItem, rowIndex) => {...});// 渲染图片等非普通文本的数据if(renderAdditionalData) {for (let rowIndex = 0; rowIndex < rowList.length; rowIndex++) {const rowItem = rowList[rowIndex];for (let colIndex = 0; colIndex < rowItem.length; colIndex++) {if (!rowItem[colIndex]) {continue;}await renderAdditionalData(rowIndex, colIndex, workbook, worksheet);}}}// 设置默认行高worksheet.properties.defaultRowHeight = 28;...
}

exceljs 对图片的渲染请查询官方文档。

至此,即可完成复杂 excel 表格的渲染和导出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/662642.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Node系列】EventEmitter详解

文章目录 一、EventEmitter介绍二、EventEmitter方法三、EventEmitter类方法四、EventEmitter事件五、EventEmitter的error 事件六、node介绍七、相关链接 一、EventEmitter介绍 Node.js 的 EventEmitter 是一个核心模块&#xff0c;用于处理事件驱动的编程。它提供了一个事件…

CSS常用属性

CSS常用属性 1. 像素的概念 概念&#xff1a;我们的电脑屏幕是&#xff0c;是由一个一个“小点”组成的&#xff0c;每个“小点”&#xff0c;就是一个像素&#xff08;px&#xff09;。规律&#xff1a;像素点越小&#xff0c;呈现的内容就越清晰、越细腻。 注意点&#xff…

记一次logtail锁死/tmp目录

1.现象 线上一台备用节点logtail 100%&#xff0c;这台机器是8核&#xff0c;部署的是线上服务的备用节点&#xff0c;平时都没什么负载&#xff0c;现在负载居然到了500多 这时想到的最直接的操作就是kill -9&#xff0c;居然干不掉 2.追查过程 1&#xff09;看最近系统有什…

maven打包spring项目

常用的Maven命令如下 命令 说明mvn clean 清理Maven 项目。会删除目标路径(一般是target目录)Maven生成的打包文件,编译文件。mvn package 打包Maven项目,会生成jar 或者war文件。mvn test 执行test目录下的测试用例。mvn deploy 发布依赖到远端mvn site 生成…

2024年超声波清洗机排行榜,实测五款超声波清洗机,哪款比较强?

相信大家在选购超声波清洗机时&#xff0c;会发现市面上有非常多的超声波清洗机品牌&#xff0c;会非常纠结到底哪款比较好用&#xff0c;而小编选购超声波清洗机时更多时候会比较在意的是便捷度、清水槽容量、噪音等方便的一个参数值&#xff0c;了解清楚各个参数后再选择超声…

cmd: Failure calling service package: Failed transaction(2147483646)

在终端Termux中执行pm命令&#xff0c;但是出现报错&#xff1a; cmd: Failure calling service package: Failed transaction(2147483646) 解决办法&#xff1a;您必须将selinux 更改为permissive。 # setenforce 0 需要ROOT权限才能执行成功。。。。 本文参考&#xff1a; T…

postgresql lc_ctype不同值之间的转换

LC_CTYPE 用于决定字元是否为数字,字母,空格,标点符号,及大小写等[1]。将 LC_CTYPE 设为「C」表示 isupper(c) 或 tolower(c) 等 C 语言函数[2]仅针对 US-ASCII 范围内的字元给出预期结果。因为像 upper()、lower() 或 initcap 这类型的Postgres SQL 语句是在libc 函数上实…

私域流量:用户增长与运营的艺术

在当今数字化的商业环境中&#xff0c;私域流量已成为企业发展的重要资源。私域流量指的是企业在自己的平台上积累的用户资源&#xff0c;这些用户通常具有较高的忠诚度&#xff0c;为企业带来稳定的收益。本文将探讨私域流量用户增长的关键策略与实践。 一、建立强大的品牌形…

02.PostgreSQL运算符

1. 算术运算符 算术运算符 描述 示例 + 加法运算符 SELECT A+B - 减法运算符 SELECT A-B * 乘法运算符 SELECT A*B / 除法运算符 SELECT A/B % 取余运算符 SELECT A%B 1.1 加法与减法操作符 SELECT 100,100+11,100-11,100+23.0,100-23.0 运算结果 由此得出结论: 一个整数加上…

2024美赛数学建模C题思路源码——网球选手的动量

这题挺有意思,没具体看比赛情况,打过比赛的人应该都知道险胜局(第二局、第五局逆转局)最影响心态的,导致第3、5局输了 模型结果需要证明这样的现象 赛题目的 赛题目的:分析网球球员的表现 问题一.球员在比赛特定时间表现力 问题分析 excel数据:每个时间段有16场比赛,…

测试ASP.NET Core项目调用EasyCaching的基本用法(Redis)

EasyCaching中的包EasyCaching.Redis和EasyCaching.CSRedis都支持集成Redis实现缓存&#xff0c;前者基于StackExchange.Redis&#xff0c;而后者基于CSRedisCore&#xff0c;本文学习使用EasyCaching.Redis包连接redis服务实现缓存的基本用法。   新建WebApi项目&#xff0c…

体育馆运动场地预定小程序的独特优势与用户体验

随着人们健康意识的提高&#xff0c;体育馆成为了大家进行锻炼和运动的重要场所。为了更好地满足用户的需求&#xff0c;体育馆需要开发一款预定场地的小程序&#xff0c;为用户提供便捷、高效的预定服务。本文将介绍如何使用乔拓云平台开发体育馆运动场地预定小程序&#xff0…

#10外部网页跳转vue3+SpringMVC解码GBK编码的参数

目录 1、背景 2、失败尝试之iconv-lite 2.1、安装和使用 2.2、遇到的问题 2.3、解决方案(vite-plugin-node-polyfills) 2.4、测试 3、成功尝试 3.1、前端参数读取方式 3.2、后端解码 1、背景 外部jsp页面中编码方式为GBK&#xff0c;跳转到vue页面时如果使用decodeURI…

vue不同环境配置不同打包命令

这个需求非常普遍&#xff0c;通常情况我们在开发的时候一般会有三个环境&#xff1a;开发环境、测试环境、生产环境&#xff0c;我们一步步来看下。 vue环境变量是什么&#xff1f; 指的是在不同地方&#xff08;开发环境、测试环境、生产环境&#xff09;&#xff0c;变量就…

【Node系列】REPL详解

文章目录 一、REPL介绍二、REPL案例三、REPL命令四、node介绍五、相关链接 一、REPL介绍 Node.js REPL&#xff08;Read-Eval-Print Loop&#xff09;是一个交互式环境&#xff0c;允许用户在命令行中直接输入JavaScript代码并立即看到结果。REPL是Node.js的一个重要组成部分&…

代码随想录算法训练营Day45|70. 爬楼梯(进阶版)、322. 零钱兑换、279.完全平方数

目录 70. 爬楼梯&#xff08;进阶版&#xff09; 前言 思路 算法实现 322. 零钱兑换 前言 思路 279.完全平方数 前言 思路 算法实现 总结 70. 爬楼梯&#xff08;进阶版&#xff09; 题目链接 文章链接 前言 本题是70. 爬楼梯问题的进阶版&#xff0c;每次可以跳跃的…

混合攻击流量对系统安全性的综合评估

很多针对安全设备的测试仅仅针对安全设备本身的防护&#xff0c;比如防御的漏洞攻击行为、恶意代码是否足够多&#xff0c;能否抵御大流量的L23层DDoS或者应用层的DDoS攻击&#xff0c;却没有考虑是否防御攻击时&#xff0c;一并阻止了正常的业务流量。以下图为例&#xff0c;当…

Spring-mvc、Spring-boot中如何在调用同类方法时触发AOP

1. 问题描述 Spring-mvc和Spring-boot中aop可以实现代理的功能&#xff0c;我们可以借此实现事务和日志记录或者限流等多种操作。但是&#xff0c;如果你在一个方法中调用其同类下的其他方法的时候不会触发AOP。本文主要说明其原因及解决办法和实现原理。 2. 原因 AIOP的本质是…

从零开始学Linux之gcc链接

目录 创建静态库并使用 创建动态库(共享库)并使用 链接&#xff1a;将.o目标文件链接起来生成一个可执行程序文件&#xff0c;可分为静态链接和动态链接 静态链接&#xff1a;链接器会找出程序所需的函数&#xff0c;然后将它们拷贝到执行文件&#xff0c;由于这种拷贝是完整…

apt 指定版本

apt 指定版本 https://linuxcpp.0voice.com/?id117477 在使用apt命令安装软件时&#xff0c;可以通过指定版本来选择要安装的软件版本。具体步骤如下&#xff1a; 首先&#xff0c;确保你的系统已经添加了相应的软件源。 使用apt-cache policy命令查看可用版本列表&#xf…