前端复杂 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.rhkb.cn/news/252072.html

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

相关文章

css3 属性 backface-visibility 的实践应用

backface-visibility 是一个用于控制元素在面对屏幕不同方向时的可见性的CSS3特性。它有两个可能的值&#xff1a; visible&#xff1a;当元素不面向屏幕&#xff08;即背面朝向用户&#xff09;时&#xff0c;元素的内容是可以被看到的。hidden&#xff1a;当元素不面向屏幕…

day32 买卖股票的最佳时机Ⅱ 跳跃游戏 跳跃游戏Ⅱ

题目1&#xff1a;122 买卖股票的最佳时机Ⅱ 题目链接&#xff1a;122 买卖股票的最大时机Ⅱ 题意 整数数组prices[i]表示某股票的第i天的价格&#xff0c;每天可买卖股票且最多持有1股股票&#xff0c;返回最大利润 利润拆分&#xff0c;拆分为每天的利润 每天的正利…

基于若依的ruoyi-nbcio流程管理系统自定义业务回写状态的一种新方法(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/n…

【八大排序】选择排序 | 堆排序 + 图文详解!!

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅C语言进阶之路 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 一、选择排序1.1 基本思想1.2 算法步骤 动图演示1.3 代码实现1.4 选择排序特性总结 二…

Vue3项目封装一个Element-plus Pagination分页

前言:后台系统分页肯定是离不开的,但是ui框架都很多,我们可以定义封装一种格式,所有项目按到这个结构来做. 实例: 第一步:在项目components组件新建一个分页组件,用来进行封装组件. 第二步:根据官方的进行定义,官方提供的这些,需要我们封装成动态模式 第三步:代码改造 <!-…

aspose-words基础功能演示

我们在Aspose.Words中使用术语“渲染”来描述将文档转换为文件格式或分页或具有页面概念的介质的过程。我们正在讨论将文档呈现为页面。下图显示了 Aspose.Words 中的渲染情况。 Aspose.Words 的渲染功能使您能够执行以下操作: 将文档或选定页面转换为 PDF、XPS、HTML、XAML、…

MySQL操作问题汇总

MySQL操作问题汇总 1.无法远程连接Ubuntu的MySQL2.ubuntu忘记mysql的root密码时的操作 1.无法远程连接Ubuntu的MySQL (1) 需要检查防火墙状态 > sudo ufw status #如果防火墙开启的情况&#xff0c;添加规则&#xff1a;允许3306端口开启 > sudo ufw allow 3306 (2) 需要…

计算机网络_1.6.3 计算机网络体系结构分层思想举例

1.6.3 计算机网络体系结构分层思想举例 1、实例引入&#xff08;用户在主机中使用浏览器访问web服务器&#xff09;2、从五层原理体系结构的角度研究该实例3、练习题 笔记来源&#xff1a; B站 《深入浅出计算机网络》课程 本节通过一个常见的网络应用实例&#xff0c;来介绍计…

【论文阅读笔记】Advances in 3D Generation: A Survey

Advances in 3D Generation: A Survey 挖个坑&#xff0c;近期填完摘要 time&#xff1a;2024年1月31日 paper&#xff1a;arxiv 机构&#xff1a;腾讯 挖个坑&#xff0c;近期填完 摘要 生成 3D 模型位于计算机图形学的核心&#xff0c;一直是几十年研究的重点。随着高级神经…

计算机设计大赛 深度学习+opencv+python实现昆虫识别 -图像识别 昆虫识别

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数&#xff1a;2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 4 MobileNetV2网络5 损失函数softmax 交叉熵5.1 softmax函数5.2 交叉熵损失函数 6 优化器SGD7 学…

H5 加密(MD5 Base64 sha1)

1. 说明 很多的时候是避免不了注册登录这一关的&#xff0c;但是一般的注册是没有任何的难度的&#xff0c;无非就是一些简单的获取用户输入的数据&#xff0c;然后进行简单的校验以后调用接口&#xff0c;将数据发送到后端&#xff0c;完成一个简单的注册的流程&#xff0c;那…

python的进程,线程、协程

python进程的实现 #coding:utf-8 from multiprocessing import Process import timedef run(name):print(%s is running % name)time.sleep(3)print(%s finished his run % name)if __name__ __main__:p Process(targetrun, args(XWenXiang,)) # 创建一个进程对象p.start()…

Jvm FullGC 如何排查?

使用场景 我们在使用系统时&#xff0c;有时请求和响应会变得特别慢&#xff0c;系统也变得很卡。 有可能是FullGC的问题&#xff0c;可以逐步地进行排查。 使用jps和top确定进程号pid jps可以列出正在运行的jvm进程&#xff0c;并显示jvm执行主类名称( main()函数所在的类…

Android Button background 失效

问题 Android Button background 失效 详细问题 笔者开发Android项目&#xff0c;期望按照 android:background中所要求的颜色展示。 实际显示按照Android 默认颜色展示 解决方案 将xml的Button 组件修改为<android.widget.Button> 即将代码 <Buttonandroid:l…

oracle数据库慢查询SQL

目录 场景&#xff1a; 环境&#xff1a; 慢SQL查询一&#xff1a; 问题一&#xff1a;办件列表查询慢 分析&#xff1a; 解决方法&#xff1a; 问题二&#xff1a;系统性卡顿 分析&#xff1a; 解决方法&#xff1a; 慢SQL查询二 扩展&#xff1a; 场景&#xff1a; 线…

用Audio2Face导出Unity面部动画

开始之前说句话&#xff0c;新年前最后一篇文章了 一定别轻易保存任何内容&#xff0c;尤其是程序员不要轻易Ctrl S 在A2F去往Unity的路上&#xff0c;还要经历特殊Blender&#xff0c;自己电脑中已下载好的可能不是很好使。 如果想查看UE相关的可以跳转到下边这两篇链接 1. …

【51单片机】LED的三个基本项目(LED点亮&LED闪烁&LED流水灯)(3)

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

瑞_23种设计模式_工厂模式

文章目录 1 什么是工厂模式案例案例代码 2 简单工厂模式&#xff08;Simple Factory&#xff09;2.1 简单工厂模式的结构2.2 案例改进——简单工厂模式2.3 案例改进代码实现2.4 简单工厂模式优缺点2.5 拓展——静态工厂 3 工厂方法模式&#xff08;Factory Method&#xff09;★…

两个重要极限【高数笔记】

【第一个&#xff1a;lim &#xff08;sinx / x&#xff09; 1, x -- > 0】 1.本质&#xff1a; lim &#xff08;sin‘&#xff1f;’ / ‘&#xff1f;’&#xff09; 1, ‘&#xff1f;’ -- > 0&#xff1b;保证‘&#xff1f;’ -- > 0,与趋向无关 2.例题&#x…

gtkmm xml ui 例子(from string)

文章目录 前言来看一个从字符串中生成UI的例子 前言 glade生成的xml格式不被gtkmm4支持, 需要作修改 来看一个从字符串中生成UI的例子 #include <gtkmm/application.h> #include <gtkmm.h> #include <iostream> using namespace std;class ExampleWindow :…