EasyExcel模板导出与公式计算(下)

news2024/12/27 11:10:09

目录

环境要求

功能预览

需求分析

导入依赖

制作模板

编写代码

格式优化

最终效果

总结


在上一篇 EasyExcel模板导出与公式计算(上)-CSDN博客 文章中我们知道了在若依中使用自带的@Excel注解来实现表格数据的导出,并且通过重写相关接口来支持导出公式的自动计算功能,而在本文中,我们将探究如何在带有自定义表头的定制化场景下,如何实现表格数据的导出和公式的自动计算。

环境要求

若依Cloud 3.6.3

EasyExcel  3.3.2

功能预览

与上一篇文章相同,这里继续以若依用户管理的导出功能为示例,做一个简单的公式计算的验证,大家可根据自己需求修改相应代码。

首先本地启动若依微服务版本,登录后台,依次选择系统管理 —> 用户管理菜单,进入如下页面

 点击该页面的导出按钮

获取如下导出文件

现在如果我们想要在第四行计算第二和第三行用户编号的和,可以直接在B4单元格输入"=A2+A3"这样的公式来实现,可以注意到此时B4单元格中的公式颜色已经发生变化,并且自动选中了A2和A3单元格,但实际上我们的需求为从数据库表中导出的单元格实现自动计算结果,接下来将通过若依前端页面来实现该功能验证。

现在需求变为除了能够正确计算单元格数据外,还要变成像如下图的导出样式:

不难看出,我们需要导出的结果有自定义的表头,以及时间,并且需要特别注意的是,原本的计算公式由=A2+A3,变为了=A5+A6,这是因为新增了表头导致行号发生了变化,接下来将针对这种需求进行实现。

需求分析

由于有了自定义标头的要求,显然我们不能按照上一篇文章中直进行数据的导出,因此我们将使用EasyExcel实现该功能

导入依赖

首先在ruoyi-common-core的pom.xml添加EaysExcel的依赖,之所以添加到这里是因为该模块为通用核心模块,会在项目中被其他的模块引用,可以实现“一处添加,多处使用”的效果。

<!-- easyexcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.3.2</version>
        </dependency>

制作模板

为了实现上述中的导出效果,我们需要使用Excel制作一个用于导出的模板,最终效果如下所示:

可以看到图中红色框所标识的是新增的表头内容,其中时间后面的{dataDate} 则为填充对象,而第5行对应的内容中除了花括号外,还有一个点,这其实是代表的是将使用对象集合填充,与时间的dataDate区别为, dataDate仅为一个对象。

编写代码

由于我们使用了EasyExcel之后,原有的导出功能需要做调整, @Excel注解将被替换为@ExcelProperty,如下图所示,其中value属性对应了我们的模板中的表头

对应Controller层的代码也需要做如下调整:

@Log(title = "用户管理", businessType = BusinessType.EXPORT)
    @RequiresPermissions("system:user:export")
    @PostMapping("/export")
    public void export(HttpServletResponse response, SysUser user)
    {
        List<SysUser> list = userService.selectUserList(user);
        //ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
        //util.exportExcel(response, list, "用户数据");

        try(InputStream resourceAsStream =  SysUserController.class.getClassLoader().getResourceAsStream("UserExportTemplate.xlsx");
            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
                    .withTemplate(resourceAsStream)
                    .inMemory(true).build()) {

            response.setContentType("application/vnd.ms-excel");
            String filename=  URLEncoder.encode("用户管理数据导出","UTF-8");
            response.setHeader("Content-disposition","attachment;filename="+filename+".xlsx");

            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(list, fillConfig, writeSheet);
            Map<String, Object> map = MapUtils.newHashMap();
            // 自定义日期
            map.put("dataDate", new Date());
            excelWriter.fill(map, writeSheet);

            // 拿到原始的Workbook对象
            Workbook workbook = excelWriter.writeContext().writeWorkbookHolder().getWorkbook();
            // 获取已经填充的sheet对象
            Sheet userSheet = workbook.getSheetAt(0);
            for(Row row : userSheet){
                for (Cell cell : row){
                    if(cell.getCellType() == CellType.STRING &&
                            StringUtils.isNotBlank(cell.getStringCellValue())
                            && cell.getStringCellValue().contains("=")){
                        cell.setCellFormula(cell.getStringCellValue().substring(1));
                    }
                }
            }

            // 关闭
            excelWriter.finish();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 我们首先获取到了用户信息的list对象, 随后加载自定义的模板 UserExportTemplate.xlsx ,使用EasyExcel 构建一个 WriteSheet 对象,其作用为填充自定义的日期对象以及我们的查询的list对象,待填充完成后。通过excelWriter再次获取到初始的WorkBook对象,在这里就是如何解决公式自动计算的关键,通过workBookt获取到Sheet对象后对其进行遍历,依次获取到Row和Cell对象, 这里的Cell对象就是我们的单元格,而我们填值的单元格为String类型,因此需要进行过滤,并且此处需要特别注意,去掉公式中原有的 = 符号,否则会引发异常。最后需要使用finish方法关闭流。该方案来源于官方文档easyexcel模板填充后,有公式的单元格不会自动计算,而需要我手动算,如何处理? | Easy Excel 官网

以及github上的 issue https://github.com/alibaba/easyexcel/issues/3242 

格式优化

虽然上述内容已经解决了公式导出后无法自动计算的问题,但是如果仔细观察就会发现有个问题可以进一步优化,由于最开始填入的单元格公式为A2+A3,而当增加了自定义表头后,此处内容应变为A5+A6,在Excel中这种情况会自动处理,但在当前场景下,只能通过手动方式去修正最终的单元格,而如果带有公式的单元格非常多时就会变得非常麻烦,因此可以通过如下代码进行调整:

 @Log(title = "用户管理", businessType = BusinessType.EXPORT)
    @RequiresPermissions("system:user:export")
    @PostMapping("/export")
    public void export(HttpServletResponse response, SysUser user)
    {
        List<SysUser> list = userService.selectUserList(user);
        //ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
        //util.exportExcel(response, list, "用户数据");

        try(InputStream resourceAsStream =  SysUserController.class.getClassLoader().getResourceAsStream("UserExportTemplate.xlsx");
            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
                    .withTemplate(resourceAsStream)
                    .inMemory(true).build()) {

            response.setContentType("application/vnd.ms-excel");
            String filename=  URLEncoder.encode("用户管理数据导出","UTF-8");
            response.setHeader("Content-disposition","attachment;filename="+filename+".xlsx");

            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(list, fillConfig, writeSheet);
            Map<String, Object> map = MapUtils.newHashMap();
            // 自定义日期
            map.put("dataDate", new Date());
            excelWriter.fill(map, writeSheet);

            // 根据自定义表头调整公式
            int rowOffset = 3;

            // 拿到原始的Workbook对象
            Workbook workbook = excelWriter.writeContext().writeWorkbookHolder().getWorkbook();
            // 获取已经填充的sheet对象
            Sheet userSheet = workbook.getSheetAt(0);
            for(Row row : userSheet){
                for (Cell cell : row){
                    if(cell.getCellType() == CellType.STRING &&
                            StringUtils.isNotBlank(cell.getStringCellValue())
                            && cell.getStringCellValue().contains("=")){
                        // 获取当前单元格内容
                        String formula = cell.getStringCellValue();
                        // 正则表达式匹配单元格引用 (例如 A1, B2 等)
                        Pattern pattern = Pattern.compile("([A-Z]+)(\\d+)");
                        Matcher matcher = pattern.matcher(formula);

                        StringBuffer adjustedFormula = new StringBuffer();
                        while(matcher.find()) {
                            // 列名 (A, B, ...)
                            String column = matcher.group(1);
                            // 行号 (1, 2, ...)
                            int rowNum = Integer.parseInt(matcher.group(2));

                            // 调整行号
                            int adjustedRow = rowNum + rowOffset;

                            // 替换原始引用为调整后的引用
                            matcher.appendReplacement(adjustedFormula, column + adjustedRow);
                        }
                        matcher.appendTail(adjustedFormula);
                        // 重新设置公式
                        cell.setCellFormula(adjustedFormula.substring(1));
                    }
                }
            }
            // 关闭
            excelWriter.finish();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

此处通过在验证公式时使用正则匹配原有的公式,并根据设置的 rowOffset 动态进行调整(rowOffset为3是因为当前填入的公式从A2为基准开始,因此偏移量为3而并非4),使其能够正确计算结果。(此处代码也可抽取为方法在查询时list对象时直接使用)

最终效果

可以看到前端页面中填入的内容为=A2+A3,而导出文件内容为=A5+A6,并且打开导出的文件后自动计算了结果。

总结

对于具有自定义表头内容的导出需求,需要使用EasyExcel工具实现功能增强,并且需要先将内容写入到内容中之后,再读取将公式进行进一步过滤转换(去除等号,调整行号),进而实现导出文文件的计算功能,根据官方文档所述,该方案适用于文件小的场景,当文件过大时将可能造成内存溢出的问题,因此官方建议在Java层实现对公式的计算后再进行导出,如果各位读者有更好的想法欢迎评论区留言探讨。

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

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

相关文章

C++复习day07

一、继承 1.什么是继承&#xff1f;继承的意义是什么&#xff1f; 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c…

C++ STL 适配器

系列文章目录 模板特例化&#xff0c;偏特化&#xff0c;左右值引用 https://blog.csdn.net/surfaceyan/article/details/126794013 C STL 关联容器 https://blog.csdn.net/surfaceyan/article/details/127414434 C STL 序列式容器(二) https://blog.csdn.net/surfaceyan/arti…

项目实战系列三: 家居购项目 第四部分

购物车 &#x1f333;购物车&#x1f346;显示购物车&#x1f346;更改商品数量&#x1f346;清空购物车&&删除商品 &#x1f333;生成订单 &#x1f333;购物车 需求分析 1.会员登陆后, 可以添加家居到购物车 2.完成购物车的设计和实现 3.每添加一个家居,购物车的数量…

比较顺序3s1,3s2,4s1之间的关系

(A,B)---6*30*2---(0,1)(1,0) 分类A和B&#xff0c;让B全是0。当收敛误差为7e-4&#xff0c;收敛199次取迭代次数平均值&#xff0c;3s1为 3s2为 4s1为 3s1&#xff0c;3s2&#xff0c;4s1这3个顺序之间是否有什么联系 &#xff0c; 因为4s1可以按照结构加法 变换成与4s1内在…

Linux相关概念和重要知识点(2)(用户、文件和目录、inode、权限)

1.root和普通用户 在Windows里面&#xff0c;管理员Administrator是所有用户里面权限最高的&#xff0c;很多文件都会提示请使用管理员打开等。但在整个Windows系统中&#xff0c;管理员的权限并不是最大的&#xff0c;System优先级更高&#xff0c;因此我们系统中的某些文件是…

谈谈ES搜索引擎

一 ES的定义 ES 它的全称是 Elasticsearch&#xff0c;是一个建立在全文搜索引擎库Lucene基础上的一个开源搜索和分析引擎。ES 它本身具备分布式存储&#xff0c;检索速度快的特性&#xff0c;所以我们经常用它来实现全文检索功能。目前在 Elastic 官网对 ES 的定义&#xff0c…

模拟实现vector中的常见接口

insert void insert(iterator pos, const T& x) {if (_finish _endofstorage){int n pos - _start;size_t newcapacity capacity() 0 ? 2 : capacity() * 2;reserve(newcapacity);pos _start n;//防止迭代器失效}int end _finish-1;while (end > pos){*(end 1…

PMBOK® 第六版 规划进度管理

目录 读后感—PMBOK第六版 目录 规划进度管理主要关注为整个项目期间的进度管理提供指南和方向。以下是两个案例&#xff0c;展示了进度管理中的复杂性和潜在的冲突&#xff1a; 案例一&#xff1a;近期&#xff0c;一个长期合作的客户因政策要求&#xff0c;急需我们为多家医…

SQL的增删改查CRUD练习知识点(day27)

1 学习目标 重点掌握插入单条记录的语法了解全表插入记录的语法重点掌握修改记录的语法重点掌握删除记录的语法重点掌握主键约束、外键约束了解检查约束、非空约束、唯一约束 2 数据类型 MySQL支持多种数据类型&#xff0c;大致可以分类三类&#xff1a;数值、日期和字符串。…

【JavaEE初阶】多线程(3)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 线程状态 线程安全 代码示例 解释 总结原因 解决方案-->加锁 t1和t2都加锁 且 同一个锁对象 t1和t2中只有一个加锁了 t1和t2都加锁,但锁对象不同 加锁 与线程等待…

我给孩子请了个AI老师,省掉了1999元的报名费

大家好&#xff0c;我是凡人。 最近老婆想给儿子在线报个书法班&#xff0c;要价1999元&#xff0c;本来是个好事情&#xff0c;但一向勤俭持家的我&#xff0c;怎能让她花这个冤枉钱&#xff0c;经过我三七二十一个小时的上网&#xff0c;还真让我找出一套利用AI学习的万能命…

图片无损放大编辑PhotoZoom Pro 9.0.4多版本软件2024年最新安装包下载含安装教程

PhotoZoom Pro 9.0.4是一款非常流行的图像放大软件&#xff0c;它可以让你将低分辨率的图像放大到高分辨率的尺寸&#xff0c;同时保持高质量的图像细节和清晰度。 PhotoZoom Pro 9.0.4采用了一种称为S-Spline技术的算法&#xff0c;这是一种能够保持图像细节的高级插值算法。…

Web3 详解

1. 使用 Web3 库 Web3 是一个 JavaScript 库&#xff0c;可用于通过 RPC 通信与以太坊节点通信。 Web3 的工作方式是&#xff0c;公开已通过 RPC 启用的方法&#xff0c;这允许开发利用 Web3 库的用户界面&#xff0c;以便与部署在区块链上的合约进行交互。 一旦 Geth JavaScri…

25届计算机专业选题推荐-基于python的线上拍卖会管理系统【python-爬虫-大数据定制】

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于python的线上拍卖会管理…

Window下编译OpenJDK17

本文详细介绍Window下如何编译OpenJDK17&#xff0c;包含源码路径&#xff0c;各工具下载地址&#xff0c;严格按照文章中的步骤来操作&#xff0c;你将获得一个由自己亲手编译出的jdk。 一、下载OpenJDK17源码 下载地址&#xff1a;GitHub - openjdk/jdk at jdk-1735 说明&a…

碰撞检测 | 详解矩形AABB与OBB碰撞检测算法(附ROS C++可视化)

引言 在复杂的人工智能系统和机器人应用中,碰撞检测(Collision Detection)作为一项基础技术,扮演着至关重要的角色。无论是在自动驾驶车辆中防止车祸的发生,还是在机器人导航中避免障碍物,碰撞检测的精度和效率都直接决定了系统的可靠性和安全性。在游戏开发、虚拟现实、…

USART—串口通讯

USART—串口通讯 大纲 串口通讯协议简介STM32 的 USART 简介USART 功能框图USART 初始化结构体详解 具体案例 串口通讯协议简介 物理层 串口通讯的物理层有很多标准及变种&#xff0c;我们主要讲解 RS-232 标准&#xff0c;RS-232 标准主要规定了信号的用途、通讯接口以及…

SpringCache之本地缓存

针对不同的缓存技术&#xff0c;需要实现不同的cacheManager&#xff0c;Spring定义了如下的cacheManger实现。 CacheManger 描述 SimpleCacheManager 使用简单的Collection来存储缓存&#xff0c;主要用于测试 ConcurrentMapCacheManager 使用ConcurrentMap作为缓存技术&…

spring揭秘20-spring事务02-编程式事务与声明式事务管理

文章目录 【README】【1】编程式事务管理【1.1】使用PlatformTransactionManager进行编程式事务管理【1.2】使用TransactionTemplate进行编程式事务管理【1.3】基于Savepoint的嵌套事务 【2】声明式事务管理【2.1】基于xml的声明式事务【2.1.1】使用ProxyFactory&#xff08;Pr…

【基础篇】应届毕业生必备:机器学习面试题指南【1】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发…