admin 管理员组

文章数量: 887017

需求:生成一份领料单,其中包括文本、表格、图片二维码,为了表格内容好看,表格内容每页最多30行。每页都需要基本信息表头。

效果如下:

maven依赖:

            <!-- excel工具 -->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${poi.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml-schemas</artifactId>
                <version>${poi-ooxml-schemas.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>${poi.version}</version>
            </dependency>
            <dependency>
                <groupId>com.deepoove</groupId>
                <artifactId>poi-tl</artifactId>
                <version>${poi-tl.version}</version>
            </dependency>

注意点:poi-tl 1.12.x 要求 POI 版本在 5.2.2+.

模板制作:

说明:

1、为什么要用区块?由于需求是每页30行,每页都要有基本信息表头,所以用区块标签,将表头和表格作为一个区块。区块可循环N次,一个区块就是一页。

2、为什么要分页标记?通过分页标记来插入分页符。

3、为什么底部签名要用整个标签?表格内容动态,会将底部内容挤到下一页,所以通过代码在最后一行插入签名文本。

4、为什么不用excel模板?excel单元格插入图片展示不美观,且不好分页打印。其实也可以用excel操作,参考这篇文章:使用poi操作excel模板记录

完整代码,仅供参考:

    public SysFile materialUseGenWord(MachineMaterialUseResult useResult) {
        try {
            // 从resources下获取模板
            ClassPathResource resource = new ClassPathResource("template/lldmb.docx");
            InputStream inputStream = resource.getInputStream();
            // 添加二维码图片
            BufferedImage qrCodeImage = QrCodeUtil.generate(useResult.getSerialNo(), QrConfig.create().setMargin(1));
            // 计算页数:每页30行,新页都要有表头
            double page = 1;
            if (useResult.getParts().size() > 30) {
                page = Math.ceil((double) useResult.getParts().size() / 30);
            }
            List<Map<String, Object>> foreachList = new ArrayList<>();
            // 区块标签
            Map<String, Object> resMap = new HashMap<>();
            resMap.put("list", foreachList);
            for (int i = 0; i < page; i++) {
                log.info("第{}页表格数据", i + 1);
                List<MachineMaterialUseDetail> parts = new ArrayList<>();
                for (int j = 0; j < 30; j++) {
                    if (i * 30 + j < useResult.getParts().size()) {
                        parts.add(useResult.getParts().get(i * 30 + j));
                    } else {
                        break;
                    }
                }
                Map<String, Object> materialMap = new HashMap<>();
                // 表格行数据循环标签:置于循环行的上一行
                materialMap.put("tables", parts);
                // 其他标签
                materialMap.put("serialNo", useResult.getSerialNo());
                materialMap.put("useDate", useResult.getUseTime());
                materialMap.put("deptName", "测试部门");
                materialMap.put("warehouse", "DJ0001");
                materialMap.put("projectCode", "XM123121");
                materialMap.put("projectName", "项目0001");
                materialMap.put("startTime", "2024-05-06");
                // 插入分页标记,最后一页不用分页
                if (i != page - 1) {
                    materialMap.put("isPageBreak", "分页标记");
                }
                // 底部文字(签名)
                String bottomWord = "\n制单人:" +
                        useResult.getSponsor() +
                        "            发料人:" +
                        useResult.getOperator() +
                        "            领料人:领料人";
                materialMap.put("bottomWord", Texts.of(bottomWord).create());
                materialMap.put("qrCode", Pictures.ofBufferedImage(qrCodeImage, PictureType.PNG).size(120, 120).create());
                foreachList.add(materialMap);
            }

            //渲染表格:将插件应用到表格标签
            LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
            Configure config = Configure.builder().bind("tables", policy).build();
            XWPFTemplate template = XWPFTemplatepile(inputStream, config).render(resMap);
            // 替换分页标记为分页符
            List<XWPFParagraph> paragraphs = template.getXWPFDocument().getParagraphs();
            for (XWPFParagraph p : paragraphs) {
                List<XWPFRun> runs = p.getRuns();
                if (runs != null) {
                    for (XWPFRun r : runs) {
                        String text = r.getText(0);
                        if (text != null && text.contains("分页标记")) {
                            text = text.replace("分页标记", "");
                            r.setText(text, 0);
                            r.addBreak(BreakType.PAGE);
                        }
                    }
                }
            }

            // 1将文档写入到输出流
//            FileOutputStream outStream = new FileOutputStream("D:\\opt\\mes\\领料单.docx");
//            template.write(outStream);
            // 2将文档作为响应返回
//            response.setHeader("Content-Disposition", "attachment; filename=exported-word.docx");
//            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
//            OutputStream outStream = new BufferedOutputStream(response.getOutputStream());
//            template.write(outStream);

            // 将word转为输出流
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            template.write(outStream);
            template.close();
            // 3将输出流转为 multipartFile 并上传
            MockMultipartFile multipartFile = new MockMultipartFile("file", useResult.getSerialNo() + "领料单" + System.currentTimeMillis() + ".docx", null, outStream.toByteArray());
            outStream.close();
            R<SysFile> upload = fileService.upload(multipartFile, 1);
            return upload.getData();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

遇到的问题:

将模板放入resource目录下,无法读取,maven打包时,报错MalformedInputException。

解决方法:解决 `MalformedInputException: Input length = 1` 错误

本文标签: 模板 操作 poi TL Word