java使用XDocReport导出word

news2024/11/23 4:23:31

使用XDocReport 导出word

    • 效果
    • word编辑器
    • 案例word模板
    • 模板制作
    • 解决图片不存在时, "现场照片" 列被隐藏问题
    • 依赖
    • 返回数据对象 DailyRecord
    • ReportOpinionVO 审核记录对象
    • PicVo 图片对象
    • 导出接口

效果

在这里插入图片描述
说明: “现场图片” 为动态图片列表 , “专业负责人审核意见” 和 “项目负责人审核意见” 两行是对象列表数据

word编辑器

使用的WPS

案例word模板

在这里插入图片描述

模板制作

  1. 在需要插入动态数据的位置,按键盘 Ctrl + F9 生成域
    在这里插入图片描述

  2. 右键点击上面的域,选择“编辑域”

  3. 选择“邮件合并” ,再在域代码处 输入对应字段 ${contractName}
    在这里插入图片描述

  4. 对象列表数据的处理: ${对象.属性} ,如 ${opinions.opinion}
    在这里插入图片描述

  5. 图片数据处理: 先 插入 任意图片,作为模板图片,再点击书签 ,输入书签名,并点击 “添加” 按钮
    在这里插入图片描述
    在这里插入图片描述

  6. 由于我的图片列表数据,在图片所在行依次添加 如下两个域

"@before-row[#list picList as item]"

说明: picList as item ,其中 picList 对应代码中的列表数据, item为每项数据
在这里插入图片描述

"@after-row[/#list]"

在这里插入图片描述

  1. 效果如下
    在这里插入图片描述

解决图片不存在时, “现场照片” 列被隐藏问题

  1. 在对应的单元格中再加一个单元格, 将图片和对应遍历用的两个域放在其中, 并编辑图片布局,如下
    在这里插入图片描述

依赖

<!-- 导出word相关 start-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.1</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.1</version>
</dependency>
<dependency>
    <groupId>org.jxls</groupId>
    <artifactId>jxls</artifactId>
    <version>2.6.0</version>
    <exclusions>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.jxls</groupId>
    <artifactId>jxls-poi</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.xdocreport.core</artifactId>
    <version>2.0.2</version>
</dependency>
<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.xdocreport.document</artifactId>
    <version>2.0.2</version>
</dependency>
<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.xdocreport.template</artifactId>
    <version>2.0.2</version>
</dependency>
<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
    <version>2.0.2</version>
</dependency>
<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
    <version>2.0.2</version>
</dependency>
<!-- 导出word相关 end-->

返回数据对象 DailyRecord

package com.fillke.biz.domain;

import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.baomidou.mybatisplus.annotation.*;
import com.fillke.biz.vo.ReportOpinionVO;
import com.fillke.common.annotation.Excel;
import com.fillke.common.core.domain.VerifyBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
import java.util.Map;

/**
 * 跟审日志对象 c_daily_record
 *
 * @author qts
 * @date 2022-11-29
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("c_daily_record")
public class DailyRecord extends VerifyBaseEntity implements Serializable{
    private static final long serialVersionUID = 1L;
    public static final String FlowKey = "gsrbsp";

    /** 日志ID */
    @TableId(value = "record_id" ,type = IdType.AUTO)
    private Long recordId;
    /** 动态投资ID(对应工程名称) */
    private Long investId;
    /** 日期 */
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd" )
    @Excel(name = "日期", width = 30, dateFormat = "yyyy-MM-dd",sort = 1)
    private Date recordDate;
    /** 天气 */
    @Excel(name = "天气",sort = 2)
    private String weather;
    /** 当日驻场人员,多选逗号分割 */
    @Excel(name = "当日驻场人员",sort = 3)
    private String personnels;
    /** 施工内容 */
    //@Excel(name = "施工内容")
    private String constructionContent;
    /** 主要会议 */
    //@Excel(name = "主要会议")
    private String majorMeeting;
    /** 现场跟踪情况 */
    //@Excel(name = "现场跟踪情况")
    private String siteCase;
    /** 跟审过程问题及追踪 */
    //@Excel(name = "跟审过程问题及追踪")
    private String issueTrace;
    /** 备注 */
    private String remarks;
    /** 附件 */
    //@Excel(name = "附件")
    private String files;
    /** 删除标志(0代表存在 2代表删除) */
    @TableLogic
    private String delFlag;
    /** 项目ID */
    private Long projectId;
    /**
     * 图片 json数据
     */
    private String pictures;
    /**
     * 其他
     */
    private String other;

    /**
    * 主键
    */
    public Long getPk() {
        return recordId;
    }
	/**
     * 工程名称(合同名称)
     * select = false 解决查询详情时,自动拼接该字段,而当前数据库没有此字段,从而报错
     */
    @TableField(insertStrategy = FieldStrategy.NEVER,updateStrategy = FieldStrategy.NEVER,select = false)
    @Excel(name = "工程名称",sort = 0, needMerge = true)
    private String contractName;

	/**
     * 创建者
     */
    @Excel(name = "创建人",sort = 101, needMerge = true)
    @TableField(fill = FieldFill.INSERT)
    private String createBy;

    /**
     * 创建时间
     */
    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss",sort = 102, needMerge = true)
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    /**
     * 更新者
     */
    @Excel(name = "更新人",sort = 103, needMerge = true)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;

    /**
     * 更新时间
     */
    @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss",sort = 104, needMerge = true)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

    /**
     * 创建人id
     */
    @TableField(fill = FieldFill.INSERT)
    private String createId;
    /**
     * 更新人id
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateId;
    // =============== 非映射字段 start ========================
    /**
     * 查询状态
     */
    @TableField(exist = false)
    private List<Integer> queryStatus;

    /**
     * 项目负责人
     */
    @TableField(exist = false)
    private String projectLeaderName;

    /**
     * 审核人意见列表
     */
    @TableField(exist = false)
    private List<ReportOpinionVO> opinions;

    // =============== 非映射字段 end ========================
}

ReportOpinionVO 审核记录对象

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * desc:
 *
 * @author qts
 * @date 2023/1/11 0011
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ReportOpinionVO implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 节点名称
     */
    private String nodeName;
    /**
     * 审核意见
     */
    private String opinion;
}

PicVo 图片对象

import fr.opensagres.xdocreport.document.images.ByteArrayImageProvider;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * desc:
 *
 * @author qts
 * @date 2023/1/11 0011
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PicVo implements Serializable {
    private static final long serialVersionUID = 1L;
    private ByteArrayImageProvider pic;
}

导出接口

/**
* 导出word
 */
@Log(title = "跟审日志", businessType = BusinessType.EXPORT)
@PostMapping("/exportWord")
public void exportWord(HttpServletResponse response, Long recordId) {
    InputStream in = null;
    FileOutputStream out =null;
    try {
        //获取本地目录的word模板
        in = new FileInputStream("D:\\跟审导出模板\\跟审日志模板.docx");
        //注册xdocreport实例并加载FreeMarker模板引擎
        IXDocReport report = XDocReportRegistry.getRegistry().loadReport(in, TemplateEngineKind.Freemarker);
        //创建xdocreport上下文对象
        IContext context = report.createContext();
		
		// 封装返回需要数据 start
        DailyRecord dailyRecord = dailyRecordService.getById(recordId);
        DynamicInvest dynamicInvest = dynamicInvestService.getById(dailyRecord.getInvestId());
        // 设置工程名称
        dailyRecord.setContractName(dynamicInvest.getContractName());
        TProject project = projectService.getById(dailyRecord.getProjectId());
        // 设置项目负责人名称
        dailyRecord.setProjectLeaderName(Optional.ofNullable(project).map(BaseEntity::getCreateBy).orElse(""));

        List<ReportOpinionVO> opinionList = CollUtil.newArrayList();
        // 根据流程key获取所有节点名称
        List<ProcessNode> nodes = bpmDefinitionService.getNodes(DailyRecord.FlowKey);
        // 根据实例ID查询历史审核记录
        List<TaskOpinion> opinions = CollUtil.newArrayList();
        if (StrUtil.isNotBlank(dailyRecord.getInstId())) {
            opinions = instanceService.getOpinions(dailyRecord.getInstId());
        }
        // 处理审批意见列表
        for (int i = 0; i < nodes.size(); i++) {
            if (i < 2 || i >= nodes.size() - 1) {
                continue;
            }
            // 节点名称
            String nodeName = nodes.get(i).getNodeName();
            // 节点审核意见
            List<String> opinionStrs = opinions.stream().filter(e -> nodeName.equals(e.getTaskName())).map(TaskOpinion::getOpinion).collect(Collectors.toList());
            String opinion = opinionStrs.size() > 0 ? opinionStrs.get(opinionStrs.size() - 1) : "";

            ReportOpinionVO opinionMap = new ReportOpinionVO(nodeName + "意见",Optional.ofNullable(opinion).orElse(""));

            opinionList.add(opinionMap);
        }
        // 设置审核意见列表
        dailyRecord.setOpinions(opinionList);
		// 封装返回需要数据 end
        //将需要替换的数据数据添加到上下文中
        //其中key为word模板中的域名,value是需要替换的值
        context.put(ColumnUtil.getName(DailyRecord::getContractName), Optional.ofNullable(dailyRecord.getContractName()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getRecordDate), Optional.ofNullable(dailyRecord.getRecordDate()).map(DateUtil::formatDate).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getWeather), Optional.ofNullable(dailyRecord.getWeather()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getPersonnels), Optional.ofNullable(dailyRecord.getPersonnels()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getConstructionContent), Optional.ofNullable(dailyRecord.getConstructionContent()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getMajorMeeting), Optional.ofNullable(dailyRecord.getMajorMeeting()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getSiteCase), Optional.ofNullable(dailyRecord.getSiteCase()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getIssueTrace), Optional.ofNullable(dailyRecord.getIssueTrace()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getOther), Optional.ofNullable(dailyRecord.getOther()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getOpinions),dailyRecord.getOpinions());
        context.put(ColumnUtil.getName(DailyRecord::getCreateBy), Optional.ofNullable(dailyRecord.getCreateBy()).orElse(""));
        context.put(ColumnUtil.getName(DailyRecord::getProjectLeaderName), Optional.ofNullable(dailyRecord.getProjectLeaderName()).orElse(""));

        //创建字段元数据,需要表格才加下面这两行,否则不用
        FieldsMetadata fm = report.createFieldsMetadata();
        //Word模板中的表格数据对应的集合类型
        fm.load(ColumnUtil.getName(DailyRecord::getOpinions), ReportOpinionVO.class, true);
        // 设置特殊字段: 图片 ,pic对应书签  item.pic对应每一项PicVo中的pic字段
        fm.addFieldAsImage("pic", "item.pic", NullImageBehaviour.RemoveImageTemplate);
        
        // 图片列表
        String pictures = dailyRecord.getPictures();
        List<PicVo> picList = CollUtil.newArrayList();
        if (StrUtil.isNotBlank(pictures)) {
            JSONArray picJsonArray = JSONArray.parse(pictures);
            if (CollUtil.isNotEmpty(picJsonArray)) {
                for (int i = 0; i < picJsonArray.size(); i++) {
                    JSONObject picJsonObject = picJsonArray.getJSONObject(i);
                    String url = picJsonObject.get("url").toString();
                    // 本地资源路径
                    String localPath = FillkeConfig.getProfile();
                    // 数据库资源地址
                    String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX);
                    //fm.addFieldAsImage("pic"+num);
                    PicVo picVo = new PicVo(new ByteArrayImageProvider(new FileInputStream(downloadPath)));
                    picList.add(picVo);
                }
            }
        }
        context.put("picList", picList); // 图片数据存入上下文
        //输出到本地目录
        //out = new FileOutputStream(new File("D://用户模板导出.docx"));
        //处理word文档并输出
        //report.process(context, out);

        //浏览器端下载
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/msword");
        String fileName = "跟审日志.docx";
        fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");// 谷歌
        response.setHeader("Content-Disposition", "attachment;filename="+fileName);
        report.process(context, response.getOutputStream());

    } catch (IOException e) {
        logger.error("读取Word模板异常",e);
    } catch (XDocReportException e) {
        logger.error("word模板生成失败",e);
    } finally {
        if(in!=null){
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(out!=null){
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

参考:https://www.cnblogs.com/huigee/p/16588247.html
https://blog.csdn.net/weixin_45433031/article/details/125874897

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

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

相关文章

freeswitch的多租户模式

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 现在的VOIP服务越来越倾向于云端服务&#xff0c;包括呼叫中心云服务&#xff0c;线路云平台。 而云平台对多个客户的服务就需要做好隔离&#xff0c;包括数据隔离、线路隔离、服务隔离等。 freeswitch内部的多租户模…

如何给视频批量添加背景图的实例操作教程

如何给视频添加上背景图片呢&#xff1f;有需要的宝宝跟着小编一起来看看怎么操作的吧。 先运行【云炫AI智剪】&#xff0c;然后再选择画中画&#xff0c;切换相对应的界面当中。 接下来把底画背景图导入到列表中&#xff0c;可以选中文件直接拖动到软件中&#xff0c;或者…

多次执行相同的push、replace问题(重写push、replace)

1.多次执行相同的push、replace问题编程式导航路由跳转到当前路由(参数不变), 多次执行会抛出NavigationDuplicated的警告错误注意:编程式导航&#xff08;push|replace&#xff09;才会有这种情况的异常&#xff0c;声明式导航是没有这种问题&#xff0c;因为声明式导航内部已…

你是真的“C”——详解C语言函数模块知识(上篇)

详解C语言函数模块知识(上篇&#xff09;&#x1f60e;前言&#x1f64c;1. 函数是什么⁉️2、C语言中函数的分类&#x1f64c;库函数&#xff1a;&#x1f603;自定义函数&#x1f603;**这里通过几个例子来辅助大家更好的理解函数如何用** ⁉️1、实现一个函数来交换两个整数…

C语言实现静态通讯录

专栏&#xff1a;C语言 每日一句&#xff1a;这几年可能会有点累&#xff0c;但要相信你的人生不可能就止于此地了&#xff0c;你要有你的梦想&#xff0c;所以你要努力&#xff0c;只有坚持这阵子&#xff0c;才不会辛苦一辈子&#xff0c;努力会让自己过得很好&#xff0c; 静…

Qt扫盲-QAction理论总结

QAction理论总结一、概述二、使用一、概述 在应用程序中&#xff0c;许多常用命令可以通过 菜单、工具栏按钮 和 键盘快捷键 调用。由于用户希望以相同的方式执行每个命令&#xff0c;而不管使用什么用户界面&#xff0c;因此将每个命令表示为一个Action操作是有效的。可以将A…

23年 车辆检测+车距检测+行人检测+车辆识别+车距预测(附yolo v5最新版源码)

我们先看一下效果2023年最新版 yolo车距行人识别yolov5和v7对比yolo车距其他步骤参考另外一篇文章&#xff1a; yolo 车辆测距车辆识别单目测距&#xff08;双目测距&#xff09;_SYBH.的博客-CSDN博客_yolo测距基于yolo目标检测算法实现的车前道路中的车辆和行人检测&#xf…

opener 值得注意的安全问题

前言 最近在学习浏览器知识的时候&#xff0c;讲到了浏览器之间的渲染进程共用的问题。其中 opener 能被引用到的两个页面&#xff0c;会在同一个渲染进程中。而这两个页面&#xff0c;可以称为&#xff1a;浏览上下文组。但在测试的时候&#xff0c;反倒是发现了一个opener 注…

JavaEE进阶第一课:Spring核心与设计思想

目录1.Spring是什么1.1什么是容器1.2什么是IoC1.3什么是DISpring的核心功能1.Spring是什么 用官方的话来说&#xff1a;Spring是包含众多工具方法的IoC容器 但是仅仅这样一句话&#xff0c;就会让大家有许多不解&#xff1f;什么是IoC&#xff1f;什么是容器&#xff1f;接下来…

python基础篇之字符串类型

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a;lqj_本人的博客_CSDN博客-微信小程序,前端,vue领域博主lqj_本人擅长微信小程序,前端,vue,等方面的知识https://blog.csdn.net/lbcyllqj?spm1000.2115.3001.5343 哔哩哔哩欢迎关注&…

Java字节流基础详解(InputStream/OutputStream)

文章目录概念InputStream字节输入流FileInputStream文件字节输入流FileOutputStream字节输出流相关的方法&#xff08;write&#xff09;和构造器构造器注意事项文件拷贝概念 在Java中&#xff0c;字节流一般适用于处理字节数据&#xff08;诸如图片、视频&#xff09;&#x…

Netty入门笔记(一)BIO、NIO、AIO

一.Netty简介 Netty是由JBOSS提供的一个java开源框架Netty是一个异步的&#xff0c;基于事件驱动的网络应用框架&#xff0c;用以快速开发高性能&#xff0c;高可靠性的网络IO程序Netty主要针对在TCP协议下&#xff0c;面向Client端的高并发应用&#xff0c;或者peer-to-peer场…

裁剪图片原理

FileReader HTML5定义了FileReader作为文件API的重要成员用于读取文件&#xff0c;根据W3C的定义&#xff0c;FileReader接口提供了读取文件的方法和包含读取结果的事件模型。 创建实例 const reader new FileReader(); 方法 事件 Blod Blob是用来支持文件操作的。简单的…

牛客每日一题(1/12)

233的字符串题目描述 读入一个正整数n&#xff0c;代表将字符串"abc"重复n次&#xff0c;形成一个长度为3n的字符串。例如n3时&#xff0c;形成的字符串为"abcabcabc"。请你计算该字符串中有多少个"acb"子序列。答案对10^97取模。输入描述:一个正…

【计算机网络-数据链路层】局域网(LAN)

文章目录1 局域网的概念1.1 局域网的拓扑结构1.2 局域网的传输介质1.3 局域网的介质访问控制方式&#xff08;MAC&#xff09;1.4 局域网的分类2 以太网&#xff08;Ethernet&#xff0c;IEEE 802.3 标准&#xff09;2.1 以太网的传输介质2.2 以太网的网卡2.3 以太网的 MAC 地址…

flutter apk 加固引发的问题

背景&#xff1a;Apk 加固&#xff0c;防止动态调试啥的&#xff0c;用的是 腾讯家的加固方案:应用加固&#xff0c;这个加固完之后 因为破壳了&#xff0c;所以需要重写签名&#xff1b;今天发现一个这样的问题&#xff1a;最早加固完毕安装不了提示&#xff1a;Failure IINST…

C语言从入门到放弃——静态通讯录实现

目录 一.功能实现 1.打印开始菜单 2.实现选择 3.初始化通讯录 4.添加、删除等功能实现 &#xff08;1&#xff09;添加联系人 &#xff08;2&#xff09;删除联系人 &#xff08;3&#xff09;查找联系人 &#xff08;4&#xff09;修改联系人 &#xff08;5&#xff…

【C++】基于EasyX库的2048小游戏

文章目录0 前言1 先看一下最终的效果图2 2048核心2 EasyX库2.1 配色2.2 文字2.3 填充3 总结0 前言 最近比较迷2048小游戏&#xff0c;于是想自己写代码实现出来&#xff0c;恰好也在网上找到一个现成的2048的VS工程&#xff0c;但是界面做得很难看&#xff0c;且运行逻辑存在一…

研究发现,大多数长期 COVID 影响在感染后一年内消退

英国医学杂志今天发表的一项来自以色列的大型研究发现&#xff0c;轻度 COVID-19 感染后出现的大多数症状或病症会持续数月&#xff0c;但在一年内恢复正常。 特别是接种过疫苗的人&#xff0c;呼吸困难的风险较低。这通常也是轻度感染后最常见的影响。相比之下&#xff0c;比未…

【Day2】977有序数组的平方、209长度最小的子数组、59螺旋矩阵Ⅱ

【Day2】977有序数组的平方、209长度最小的子数组、59螺旋矩阵Ⅱ977有序数组的平方暴力排序双指针法209长度最小的子数组暴力解法滑动窗口法59螺旋矩阵Ⅱ977有序数组的平方 题目链接&#xff1a;977 题目&#xff1a;给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返…