实现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
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. 样式不是我们理想的,并且尝试了好多种方式均未生效,只有行内样式可以。但一共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

每一个模板都需要以上的样式解决方案,需将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;
}
`

模板页

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
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
// 打印事件最终代码
// 打印方法拿到导出的动态模板
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. 为何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

在开发环境中,模板组件内容在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.
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值