• 实现React模板打印


    需求描述

    综合会议模块下,所有类型的会议记录都需要添加一个打印功能。每种会议均对应两种模板(如下图),打印相应的内容。
    模板选择截图

    实现思路

    将模板当作react组件进行存放。打印时,实时创建iframe作为打印区域,根据参数将对应的模板组件动态引入iframe,实现模板打印。先看下最终效果:
    在这里插入图片描述

    功能实现

    1. 按规则对模板命名

    我的命名规则:template_模板类型_会议类型
    这里我放在同级文件夹下,若模板较多,也可按模板类型分别存放
    在这里插入图片描述

    2. 实现模板内容,并将模板作为react组件导出
    // 最终模板内容+++++++
    import React from "react";
    const Template1_1 = ({record}) => {
    // table内容需嵌套一层tbody,否则会报警告
    // 此处为部分代码,替换成自己的模板即可
      return (
        <div>
          <table className={'printContent'} border="" cellSpacing="" cellPadding="10">
            <tbody>
              <tr className={'height50'}>
                <th className={'tableName'} colSpan="4">支部党员大会</th>
              </tr>
              <tr className={'height50'}>
                <th className={'tableName'}>会议主题:</th>
                <th colSpan="3">{record.theme}</th>
              </tr>
            </tbody>
          </table>
          <div className={'qianzi'}>
            <div>党支部书记签字:</div>
            <div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
          </div>
        </div>
      )
    }
    export default Template1_1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    3. 借助ReactDOM渲染模板
    // 引入模板
    import TemplateTest from '../component/PrintTemplates/plan/template1_1'
    // 此方法是选择模板类型后调用
    const print = () => {
         const iframe = document.createElement('IFRAME');
         iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:500px;top:500px;');
         document.body.appendChild(iframe);
         let doc = iframe.contentWindow.document;
         ReactDOM.render(<TemplateTest record={{...record.info, ...record.record}} />, doc)
         doc.close();
         iframe.contentWindow.focus();
         iframe.contentWindow.print();
         if (navigator.userAgent.indexOf("MSIE") > 0)
         {
           document.body.removeChild(iframe);
         }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    至此,打印功能基本实现。
    在这里插入图片描述
    但是有两个问题:

    1. 样式不是我们理想的,并且尝试了好多种方式均未生效,只有行内样式可以。但一共18个模板,样式大差不差,每一个模板去添加行内样式不可行。页眉和页脚也是不需要的,希望默认取消。
    2. 以上内容是固定引入了一个模板,但需求要根据会议类型和模板类型动态去引入对应的模板。模板全部引入,用条件判断来区分固然可以,但不利于后期模板的改动

    问题解决

    1. 样式处理

    尝试了多种引入的方式都不生效。尝试将css和模板内容一起返回,果然生效了。

    import React from "react";
    const Template1_1 = ({record}) => {
      return (
      <html>
        <head>
          <style>
            .printContent {
    		  width: 100%;
    		  border-collapse:collapse;
    		  font-size: 14px !important;
    		}
    		
    		.height50 {
    		  height: 80px;
    		}
    		
    		.height200{
    		  height: 200px;
    		}
    		
    		.height300 {
    		  height: 300px;
    		}
    		
    		.tableName {
    		  width: 15%;
    		  text-align: center;
    		}
    		
    		.width12 {
    		  width: 12%;
    		  text-align: center;
    		}
    		.qianzi{
    		  text-align: right;
    		  margin-top: 20px;
    		  margin-right: 100px;
    		}
          </style>
        </head>
        <body>
        <div>
          <table className={'printContent'} border="" cellSpacing="" cellPadding="10">
            <tbody>
              <tr className={'height50'}>
                <th className={'tableName'} colSpan="4">支部党员大会</th>
              </tr>
              <tr className={'height50'}>
                <th className={'tableName'}>会议主题:</th>
                <th colSpan="3">{record.theme}</th>
              </tr>
            </tbody>
          </table>
          <div className={'qianzi'}>
            <div>党支部书记签字:</div>
            <div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
          </div>
        </div>
        </body>
        </html>
      )
    }
    export default Template1_1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    每一个模板都需要以上的样式解决方案,需将css抽离再引入。但用link引入css文件发现样式又失效了。调试发现,用js的方式引入可行。
    样式页

    // style.js
    // 将样式作为字符串导出
    export const styleTemplate = `
    .printContent {
      width: 100%;
      border-collapse:collapse;
      font-size: 14px !important;
    }
    // ... 此处省略,与上方样式相同
    // 去除页眉页脚
    @page {
        margin: 0;
    }
    // 页面四周留出1cm的空白边
    body {
      margin: 1cm;
    }
    `
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    模板页

    import React from "react";
    import {styleTemplate} from "@/pages/branch/OrganizationLife/component/PrintTemplates/styleCss"
    const Template1_1 = ({record}) => {
      return (
      <html>
        <head>
          <style>
           {styleTemplate}
          </style>
        </head>
        <body>
        <div>
          <table className={'printContent'} border="" cellSpacing="" cellPadding="10">
            // 模板表格内容
          </table>
          <div className={'qianzi'}>
            <div>党支部书记签字:</div>
            <div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
          </div>
        </div>
        </body>
        </html>
      )
    }
    export default Template1_1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    2. 动态引入模板

    每一个模板都需要添加相同的样式,所以需要给所有模板套一个公共的外壳。
    所有模板单个引入总觉得有点不太聪明的样子,也考虑到后期的维护,尝试用nodejs的require.context来实现批量引入模板,并动态传入

    // 动态模板最终代码
    // 将公共的外壳当作一个方法导出
    import React from "react";
    import {styleTemplate} from "@/pages/branch/OrganizationLife/component/PrintTemplates/styleCss";
    
    const TemplatePrint = async ({meetingType, planType, record}) => {
      const templates = require.context('./plan', true, /\.tsx$/)
      let components = {};
      templates.keys().forEach(fileName => {
        let names = fileName.split("/").pop().replace(/\.\w+$/, "");
        const componentConfig = templates(fileName);
        components[names] = componentConfig.default || componentConfig;
      })
      const CurTem = await components[`template${planType}_${meetingType}`]
      const CurTe = CurTem.default || CurTem
      return (
        <html>
        <head>
          <style>
            {styleTemplate}
          </style>
        </head>
        <body>
        <CurTe record={{...record.info, ...record.record}} />
        </body>
        </html>
      )
    }
    export default TemplatePrint
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    // 打印事件最终代码
    // 打印方法拿到导出的动态模板
    const print = async () => {
         const Res11 = await printFun({meetingType, planType: value, record})
         const iframe = document.createElement('IFRAME');
         iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:500px;top:500px;');
         document.body.appendChild(iframe);
         let doc = iframe.contentWindow.document;
         ReactDOM.render(Res11, doc)
         doc.close();
         iframe.contentWindow.focus();
         iframe.contentWindow.print();
         if (navigator.userAgent.indexOf("MSIE") > 0)
         {
           document.body.removeChild(iframe);
         }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    错误总结

    以上就是全部内容了,但是在开发过程中,还遇到了很多问题。这里总结一下 ,希望能有帮助。

    1. 为何TemplatePrint作为方法返回?
      根据TemplatePrint的内容应该能看出来,我一开始是想以组件的形式导出,在ReactDOM.render方法中直接渲染组件。
      打印components发现,require.context拿到的数据整理后的数据是一个值为promise的集合,要想同步的使用模板里的内容,需要添加async-await。添加之后还用组建形式去渲染,会出现以下报错:
      error Image
      所以这里当作普通方法,将组建内容返回。
      需要注意的是,作为普通方法的动态模板,返回值也是一个promise对象,调用时也需要async-await来获取到内容,打印输出内容为动态模板的虚拟dom就成功了
      在这里插入图片描述
    2. 开发环境与生产环境的差异
    const CurTem = await components[`template${planType}_${meetingType}`]
    const CurTe = CurTem.default || CurTem
    
    • 1
    • 2

    在开发环境中,模板组件内容在await拿到的promise返回值default中,
    在生产环境中,模板组件内容就是await拿到的promise的返回值。若使用default,会出现以下报错信息:

    Uncaught Error: Minified React error #130; visit http://reactjs.org/docs/error-decoder.html?invariant=130&args[]=object&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
    
    • 1
  • 相关阅读:
    浅尝KBQA中使用语义角色标注进行约束挂载
    若依集成minio实现分布式文件存储
    记一次用arthas排查jvm中CPU占用过高问题
    Springboot分片下载实现
    【学习Docker(一)】Docker Jenkins的安装与卸载
    2021年InfoWorld 精选最佳开源软件
    [中秋特别定制版本]绝美登录页面搭配[登录数据存储到服务器](服务器宝塔数据库开通+短信服务开通+后端redis验证码缓存)
    技术分享| 融合调度系统中的电子围栏功能说明
    Unity-UML类图讲解
    编写驱动代码实现LED灯点亮
  • 原文地址:https://blog.csdn.net/Li_na_na01/article/details/126378964