html实现打印,word文档模板数据渲染下载上传

        这周好艰难,项目经理提了一个包含在线打印,word下载及数据渲染的需求,这个可我难坏了,文件一直是我的噩梦(os:我好想写CRUD啊)。唉,本想着去看看技术经理写没写这个demo,结果发现,他们竟然没有做这个功能,这我真的是匪夷所思,都做了3,4个月了,竟然没有写打印,和模板文档数据渲染的功能(这个项目是一个很大的管理后台,老系统jsp翻新做boot),唉,要做第一个吃蘑菇的人了吗,不到要踩多少坑。

  • HTML实现打印功能
    • 首先下载了word看了一下,要素很多,问了下其他同事,他们都是在前端打印的(好像也只能在前端),这就简单了,我先下载了一个word,然后转成了html(注意:一定要是html网页文件,而不是上面那个单一网页文件:单一网页文件的内容太臃肿看着非常乱不好修改)。转换完成后复制到VUE项目中,数据渲染了一下页面就做好了。
    • 然后调用了一下工具类,前端的在线打印就完成了。(os:说着简单也耗费了我好久,找了很多转换方法,其实写样式也能搞定但是我不怎末会(福昕))

(前端不是专业的。代码原作者——李鼎元)这个Print类有三个元素

  1. iframe内联框架这个里面放的就是我面页面上要打印的html节点(VUE项目中的REF即可)
  2. printStyle用来设置打印要调整的样式
  3. option好像也是用来设置样式,不过应该设置的是打印纸上的样式

        主要看print方法,第一行为js原生方法,在文档的body中添加了一个iframe子元素;第二行是将这个创建的元素取了出来;第三行设置打印样式(这个打印样式的写法好像word转html-时样式的写法:styleEl.setAttribute('media', 'print'););第四行是设置html的header部分;第五行是设置html的body部分;第六行为js原生操作打印的方法打印html页面(window.print());第七行清除了iframe内的html代码,第八行又删除了这个iframe元素。

/* eslint-disable @typescript-eslint/no-explicit-any */
export default class Print {
  iframe: HTMLIFrameElement;
  printStyle!: HTMLStyleElement;
  globalOption;

  constructor(option = { margin: 10 }) {
    this.iframe = document.createElement('iframe');
    this.iframe.style.display = 'none';
    this.globalOption = option;
  }

  /**
   *  打印区域内容
   * @param {*} el HTMLElement
   * @example
      vue组件中使用 this.$print(this.$refs.test.$el);
   */
  print(el: HTMLElement, option = this.globalOption) {
    document.body.appendChild(this.iframe);
    const printWindow = window.frames[window.frames.length - 1];
    this.setPrintStyle(option);
    this.appendHead(printWindow);
    this.appendBody(el, printWindow);
    printWindow.print();
    this.clear(printWindow);
    document.body.removeChild(this.iframe);
  }

  setPrintStyle(style: Record<string, any> = {}) {
    let { margin } = style;
    if (!Array.isArray(margin)) {
      margin = [margin, margin, margin, margin];
    } else if (margin.length === 1) {
      margin = [margin[0], margin[0], margin[0], margin[0]];
    } else if (margin.length === 2) {
      margin = [margin[0], margin[1], margin[0], margin[1]];
    }
    const styleEl = document.createElement('style');
    styleEl.setAttribute('type', 'text/css');
    styleEl.setAttribute('media', 'print');
    styleEl.innerHTML = `
    @page{size: auto;}
    html{margin: ${margin[0]}mm ${margin[1]}mm ${margin[2]}mm ${margin[3]}mm;}
    body{
      -webkit-print-color-adjust:exact;
      -moz-print-color-adjust:exact;
      -ms-print-color-adjust:exact;
      print-color-adjust:exact;
      margin: 0;
    }
    `;
    this.printStyle = styleEl;
  }

  appendHead(printWindow: Window) {
    document.head.cloneNode(true).childNodes.forEach((node) => {
      if (['STYLE', 'link'].includes(node.nodeName)) {
        printWindow.document.head.appendChild(node);
      }
    });
    printWindow.document.head.appendChild(this.printStyle);
  }

  appendBody(el: HTMLElement, printWindow: Window) {
    const copyEl = el.cloneNode(true);
    printWindow.document.body.appendChild(copyEl);
  }

  clear(printWindow: Window) {
    printWindow.document.head.innerHTML = '';
    printWindow.document.body.innerHTML = '';
  }
}

       总结:打印功能的实现主要是使用window.print()方法,这个方法打印document文档内的html元素。至于为什么要创建个iframe再进行打印我是这莫理解的,也简单实践过,就是window.print(),会打印页面中的所有东西包括在常规系统中的菜单栏等,这并不能满足我们的使用需求,我们只想打印文档中的元素。html网页其实也就是一种文档,和word,excel没什么区别,都由内容(数据)和样式(展示的样式)组成。

        疑问:后端能否直接实现在线打印?(在网上看了一些文章,好像不行,可用的方法就是再java后台‘画’一个页面出来[awt包],如果有其他方法,还请各位大神多多指教我)

  • Word模板渲染数据并下载

        word模板渲染数据这个我也找了好久的框架,aspose-words要付费就算了给老板省钱,后来找到了apacha下的poi-tl还挺好用的

先添加下依赖

<dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.10.5</version>
            <exclusions>
                <exclusion>
                    <artifactId>poi-ooxml-schemas</artifactId>
                    <groupId>org.apache.poi</groupId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.poi</groupId>
                    <artifactId>poi-ooxml</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

 然后看了看poi的官方文档Poi-tl Documentation (deepoove.com),文档比较容易看懂,里面写的也比较清楚,常规的取值比较简单{{}}双大括号就可以取值,表格的话要先在表格行上面把集合的key写好,然后再在行上使用[]方括号取出集合中每个元素的属性值。(其他的有时间在研究,目前只用到这俩个),渲染表格一定要使用LoopRowTableRenderPolicy绑定好你表格集合的key

@Override
    public void downloadContractWord(@RequestBody Map<String, Object> printMap, HttpServletResponse httpServletResponse) {
        System.out.println(printMap);
        //List<ContractDTO> contractWords = contractService.findDetailByContractCodesWithFilePath(contractCodes);
        //TODO XWPFTemplate  aliOssFileStorage.getFile()
        LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
        Configure config = Configure.builder()
                .bind("brand_list", policy).build();
        XWPFTemplate xwpfTemplate = XWPFTemplate.compile(new File("C:\\Users\\17674\\Downloads\\模板.docx"), config)
                .render(printMap);
        httpServletResponse.setContentType("application/octet-stream");
        httpServletResponse.setHeader("Content-disposition", "attachment;filename=\"" + "out_template.docx" + "\"");

        try {
            OutputStream out = httpServletResponse.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(out);
            xwpfTemplate.write(bos);
            bos.flush();
            out.flush();
            PoitlIOUtils.closeQuietlyMulti(xwpfTemplate, bos, out);
        } catch (Exception e) {
            log.warn("文件下载失败:{}", e.getMessage());
        }
    }

        代码在这里,我是从前端传过来的值,因为我这个渲染其实就是和上面哪个打印出的文档是一个东西,而且我们的表设计的比较不好,查询真的好费劲,项目组内的也大部分都在前端使用linq处理出数据,所以我就在前端把数据处理好,然后在后端用map接了一下比较方便也方便后面抽取其他打印 。(linq和其他的前端缓存等等有时间再写一个文章总结下)文件还没存储到线上,就是个简单的demo吧,实现了项目经理的需求。debug跑了几遍发现该去的属性都取到了就没问题了,然后返回了2进制流给前端。

        

        前端一定要使用blob接啊,否则乱码会伴随你左右!!,

printList.value.forEach((item) => {
    ContractApi.handleDownloadContract(item).then((res) => {
      console.log(res);
      const blob = new Blob([res.data]);
      const downloadElement = document.createElement('a');
      const href = window.URL.createObjectURL(blob); // 创建下载的链接
      downloadElement.href = href;
      downloadElement.download = '合.docx'; // 下载后文件名
      document.body.appendChild(downloadElement);
      downloadElement.click(); // 点击下载
      document.body.removeChild(downloadElement); // 下载完成移除元素
      window.URL.revokeObjectURL(href); // 释放blob对象
    });
  });

         前端代码也贴一下,防止我以后找不到了,这个和上面打打印比较像,js原生代码创建a链接和点击后下载然后删除。(Blob主要就是接收2进制流的)

  • 文件上传(下周写完再回来补充!!!! TODO)

        心得:其实无论什么语言,什么代码,都是在不同的地方取数据,在不同的地方输出数据,只是在调用不同方法罢了,以后提升的方向应该就是不同业务需求下的架构能力,不同业务需求下的‘好’代码的编写能力。

  • 40
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以使用POI库中的XWPFRun类在Word文档中添加文字。以下是一个简单的示例代码,该代码打开一个Word文档,将一张图片插入到文档中,并在图片上添加文字。 ```java import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.util.IOUtils; import org.apache.poi.xwpf.usermodel.*; public class WordTemplateRenderer { public static void main(String[] args) throws IOException, InvalidFormatException { // Open the template file XWPFDocument doc = new XWPFDocument( WordTemplateRenderer.class.getResourceAsStream("template.docx")); // Get the first paragraph XWPFParagraph para = doc.getParagraphs().get(0); // Add a new run to the paragraph XWPFRun run = para.createRun(); // Load the image from file InputStream imageStream = WordTemplateRenderer.class.getResourceAsStream("image.png"); byte[] imageBytes = IOUtils.toByteArray(imageStream); // Insert the image into the document int pictureIdx = doc.addPicture(imageBytes, XWPFDocument.PICTURE_TYPE_PNG); XWPFPicture picture = run.addPicture(imageBytes, XWPFDocument.PICTURE_TYPE_PNG, "image.png", Units.toEMU(300), Units.toEMU(200)); // Add text to the picture String text = "Hello World"; picture.getParagraphArray(0).createRun().setText(text); // Save the document FileOutputStream out = new FileOutputStream("output.docx"); doc.write(out); doc.close(); out.close(); } } ``` 在上面的代码中,我们首先打开一个Word文档,并获取第一个段落。然后,我们创建一个新的运行,并将一张图片插入到文档中。接着,我们从图片中获取第一个段落,并在其中创建一个新的运行,将文本添加到该运行中。最后,我们将文档保存到磁盘上。 请注意,上面的代码中使用的图片和模板文件都需要在classpath下可用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值