React实现打印功能

1、需求分析:

环境:react,antd,antd-pro
将选中的数据进行打印,能够自定义分页的大小。
因为打印的列等多个因素,致使若是写成组件在使用的时候依旧会改变源码,因此采用了写成页面的方式,css

2、实现需求:

一、数据传值

进行传值的时候,刚开始使用的是在经过this.props.location进行传值,可是这样数据被写死了,致使再次进入页面的时候没法更新打印的值。
最后采用了一个全局的model进行实现的传值。react

二、表格生成

分为四部分进行生成,分别是标题、表头、表格、表尾。其中表头,表尾由于需求的缘由写死。
如下为代码:antd

createTitle = (title)=>(
  <div>
    <h1 style={styleObj.title}>{title}</h1>
  </div>
)

createHeader = (headerData)=>{
  headerData = [
    {
      orderID:'订单编号',
      value:'P201901020002',
    },{
      people:'采购人员',
      value:'xxx',
    },{
      time:'采购时间',
      value:'2019年01月01日',
    }
  ];
  return (
    <table>
      <tbody style={styleObj.header}>
      <tr>
        <th>订单编号:</th>
        <th colSpan="7">
          <input style={styleObj.printInput} value="P201901020002" />
        </th>
      </tr>
      <tr>
        <th>采购员:</th>
        <th colSpan="7">
          <input style={styleObj.printInput} value="xxx" />
        </th>
        <th>采购时间:</th>
        <th colSpan="7">
          <input style={styleObj.printInput} value="2019年01月01日" />
        </th>
      </tr>
      </tbody>
    </table>
  )
}

createForm = (printCol,printData)=>(
  <table style={styleObj.printTable}>
    <tbody>
    {
      (
        <tr style={styleObj.printTableTr}>
          {printCol.map(item=><th style={{...styleObj[item.key],...styleObj.printTableTh}}><div>{item.name}</div></th>)}
        </tr>
      )
    }
    {
      printData.map(item=> (
          <tr style={styleObj.printTableTr}>
            {Object.keys(item).map(i => <th style={styleObj.printTableTh}>{item[i]}</th>)}
          </tr>
        )
      )
    }
    </tbody>
  </table>
)

createFooter = (footerData)=>{
  return (
    <table>
      <tbody style={styleObj.footer}>
      <tr>
        <th>供应商(签字)</th>
        <th>
          <div style={styleObj.footerSpace} />
        </th>
        <th colSpan="4">
          <input style={styleObj.printInputFooter} />
        </th>
        <th>库管员(签字)</th>
        <th>
          <div style={styleObj.footerSpace} />
        </th>
        <th colSpan="4">
          <input style={styleObj.printInputFooter} />
        </th>
        <th>{`第${footerData.current}页`}</th>
        <th>{`共${footerData.total}页`}</th>
      </tr>
      </tbody>
    </table>
  )
}
createPrintArea = (printCol)=>{
  const {printGroupData} = this.state;
  return (
    printGroupData.map((item,index)=>{
      if(item.length){
        return (
          <div style={styleObj.printArea}>
            {this.createTitle('xxxxxxx公司xxx单')}
            {this.createHeader('asd')}
            {this.createForm(printCol,item)}
            {this.createFooter({current:index+1,total:printGroupData.length})}
          </div>
        )
      }
    })
  )
}

最主要的是CSS的调整,由于以后的打印需求将全部的CSS内联:
如下为css:app

export const styleObj = {
  printArea:{
    width: '500px',
    fontSize: '12px',
    align:'center'
  },
  printInput:{
    fontWeight:'bold',
    border: 'none',
  },
  title:{
    textAlign: 'center',
    fontSize: '15px',
    fontWeight: '700',
  },
  header:{
    fontWeight:'bold',
    fontSize: '12px',
  },
  printTable:{
    fontSize: '12px',
    fontWeight: '700',
    color: 'black',
    border: '1px black',
    borderCollapse: 'collapse',
    textAlign: 'center',
  },
  printTableTh:{
    padding: '4px',
    border: '1px solid black',
    textAlign: 'center',
  },
  printTableTr:{
    textAlign:'center',
    padding: '4px',
    border: '1px solid black',
  },
  number:{
    width: '60px',
  },
  goodsName:{
    width: '180px',
  },
  unitName:{
    width: '75px',
  },
  specifications:{
    width: '90px',
  },
  goodsType:{
    width: '90px',
  },
  footer:{
    fontSize:'12px',
  },
  footerSpace:{
    width: '20px',
    display: 'block',
  },
  printInputFooter:{
    fontWeight:'bold',
    border:'none',
    width: '85px',
  },
};

三、实现分页

分页即将数据进行分割,而后每次生成表格的时候将分割后的每一个表格数据依次传入表格生成函数,从而生成所有表格。
分页函数:函数

//传入的数据为:分页的大小,须要分页的数据。
page = (pageNumber,printData)=>{
  const printDataBack = printData.concat();
  const printGroupData = [];
  while(printDataBack.length >= pageNumber){
    let tempGroup = [];
    tempGroup = printDataBack.splice(0,pageNumber);
    printGroupData.push(tempGroup);
  }
  if(printDataBack.length){
    printGroupData.push(printDataBack);
  }
  printGroupData.forEach((item)=>{
    item.forEach((i,index)=>{
      i.number = index+1;
    })
  });
  return printGroupData;
}

注意:解构出来的数据是引用,须要进行备份。
设置一个input框以及一个按钮,input框用于输入分页的数字,再点击按钮以及第一次进入页面的时候进行分页。this

四、实现打印

实现方法一(不推荐):
直接在本页面进行刷新

优势:css不用内嵌。
缺点:致使本页面刷新,某些数据丢失。spa

实现方法:直接获取到须要打印的区域,而后将本页面的innerHTML设置为获取的区域,而后调用系统的print,最后调用reloadcode

代码:component

print = () => {
    window.document.body.innerHTML = window.document.getElementById('billDetails').innerHTML;  
    window.print(); 
    window.location.reload();
}
实现方法二:
打开一个页面进行打印

优势:打印不在关乎本页面的业务
缺点:CSS须要内联
代码:orm

handlePrint = () => {
  const win = window.open('','printwindow');
  win.document.write(window.document.getElementById('printArea').innerHTML);
  win.print();
  win.close();
}

3、完整代码

如下为完整代码:
index.js

import React, { PureComponent } from 'react';
import { connect } from 'dva';
import {
  Row,
  Button,
  Col,
  Card,
  Form,
  message,
  InputNumber,
} from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import {styleObj} from './style';


@Form.create()
@connect(({ print }) => ({
  print,
}))
class PrintTable extends PureComponent {
  state = {
    printData:[],
    printCol:[],
    pageNumber:10,
    printGroupData:[],
  }

  componentDidMount() {
    const printCol = [
      {
        key:'number',
        name:'序号',
      },{
        key:'goodsName',
        name:'商品名称',
      },{
        key:'goodsType',
        name:'商品类型',
      },{
        key:'unitName',
        name:'单位',
      },{
        key:'specifications',
        name:'规格',
      }];
    const printData = [];
    const { pageNumber } = this.state;
    const { print:{ payload:{formPrintData} } } = this.props;
    formPrintData.forEach((i,index)=>{
      const colData = {};
      printCol.forEach(j=>{
        colData[j.key] = i[j.key];
      })
      colData.number = index+1;
      printData.push(colData);
    });
    const printGroupData = this.page(pageNumber,printData);
    this.setState({
      printData,
      printCol,
      printGroupData,
    })
  }

  componentWillReceiveProps(nextProps){
    const printCol = [
      {
        key:'number',
        name:'序号',
      },{
        key:'goodsName',
        name:'商品名称',
      },{
        key:'goodsType',
        name:'商品类型',
      },{
        key:'unitName',
        name:'单位',
      },{
        key:'specifications',
        name:'规格',
      }];
    const printData = [];
    const { pageNumber } = this.state;
    const { print:{ payload:{formPrintData} } } = nextProps;
    formPrintData.forEach((i,index)=>{
      const colData = {};
      printCol.forEach(j=>{
        colData[j.key] = i[j.key];
      })
      colData.number = index+1;
      printData.push(colData);
    });
    const printGroupData = this.page(pageNumber,printData);
    this.setState({
      printData,
      printCol,
      printGroupData,
    })
  }

  createTitle = (title)=>(
    <div>
      <h1 style={styleObj.title}>{title}</h1>
    </div>
    )

  createHeader = (headerData)=>{
    headerData = [
      {
        orderID:'订单编号',
        value:'P201901020002',
      },{
        people:'采购人员',
        value:'xxx',
      },{
        time:'采购时间',
        value:'2019年01月01日',
      }
    ];
    return (
      <table>
        <tbody style={styleObj.header}>
          <tr>
            <th>订单编号:</th>
            <th colSpan="7">
              <input style={styleObj.printInput} value="P201901020002" />
            </th>
          </tr>
          <tr>
            <th>采购员:</th>
            <th colSpan="7">
              <input style={styleObj.printInput} value="xxx" />
            </th>
            <th>采购时间:</th>
            <th colSpan="7">
              <input style={styleObj.printInput} value="2019年01月01日" />
            </th>
          </tr>
        </tbody>
      </table>
    )
  }

  createForm = (printCol,printData)=>(
    <table style={styleObj.printTable}>
      <tbody>
        {
          (
            <tr style={styleObj.printTableTr}>
              {printCol.map(item=><th style={{...styleObj[item.key],...styleObj.printTableTh}}><div>{item.name}</div></th>)}
            </tr>
          )
        }
        {
          printData.map(item=> (
            <tr style={styleObj.printTableTr}>
              {Object.keys(item).map(i => <th style={styleObj.printTableTh}>{item[i]}</th>)}
            </tr>
            )
          )
        }
      </tbody>
    </table>
    )

  createFooter = (footerData)=>{
    return (
      <table>
        <tbody style={styleObj.footer}>
          <tr>
            <th>供应商(签字)</th>
            <th>
              <div style={styleObj.footerSpace} />
            </th>
            <th colSpan="4">
              <input style={styleObj.printInputFooter} />
            </th>
            <th>库管员(签字)</th>
            <th>
              <div style={styleObj.footerSpace} />
            </th>
            <th colSpan="4">
              <input style={styleObj.printInputFooter} />
            </th>
            <th>{`第${footerData.current}页`}</th>
            <th>{`共${footerData.total}页`}</th>
          </tr>
        </tbody>
      </table>
    )
  }

  handlePrint = () => {
    const win = window.open('','printwindow');
    win.document.write(window.document.getElementById('printArea').innerHTML);
    win.print();
    win.close();
  }

  createPrintArea = (printCol)=>{
    const {printGroupData} = this.state;
    return (
      printGroupData.map((item,index)=>{
        if(item.length){
          return (
            <div style={styleObj.printArea}>
              {this.createTitle('xxxxxxx公司xxx单')}
              {this.createHeader('asd')}
              {this.createForm(printCol,item)}
              {this.createFooter({current:index+1,total:printGroupData.length})}
            </div>
          )
        }
      })
    )
  }

  handlePage = ()=>{
    const { pageNumber, printData } = this.state;
    if(pageNumber <= 0){
      message.warning('输出正确的分页');
      return;
    }
    this.setState({
      printGroupData:this.page(pageNumber, printData)
    })
  }

  page = (pageNumber,printData)=>{
    const printDataBack = printData.concat();
    const printGroupData = [];
    while(printDataBack.length >= pageNumber){
      let tempGroup = [];
      tempGroup = printDataBack.splice(0,pageNumber);
      printGroupData.push(tempGroup);
    }
    if(printDataBack.length){
      printGroupData.push(printDataBack);
    }
    printGroupData.forEach((item)=>{
      item.forEach((i,index)=>{
        i.number = index+1;
      })
    });
    return printGroupData;
  }

  onChange = (value)=>{
    this.setState({
      pageNumber:value,
    })
  }

  render() {
    const { printCol, printData } = this.state;
    return (
      <PageHeaderWrapper title="查询表格">
        <Card>
          <Row>
            <Col span={6}>
              <Row>
                <Col span={12}>
                  <InputNumber onChange={this.onChange} placeholder='输入自定义分页数量' style={{width:'100%'}}/>
                </Col>
                <Button onClick={this.handlePage}>确认分页</Button>
              </Row>
              <Row>
                <Button onClick={this.handlePrint} type='primary'>打印</Button>
              </Row>
            </Col>
            <Col span={12}>
              <div id='printArea'>
                <div style={styleObj.printArea}>
                  {printCol.length&&printData.length? this.createPrintArea(printCol):null}
                </div>
              </div>
            </Col>
          </Row>
        </Card>
      </PageHeaderWrapper>
    );
  }
}

export default PrintTable;

style.js

export const styleObj = {
  printArea:{
    width: '500px',
    fontSize: '12px',
    align:'center'
  },
  printInput:{
    fontWeight:'bold',
    border: 'none',
  },
  title:{
    textAlign: 'center',
    fontSize: '15px',
    fontWeight: '700',
  },
  header:{
    fontWeight:'bold',
    fontSize: '12px',
  },
  printTable:{
    fontSize: '12px',
    fontWeight: '700',
    color: 'black',
    border: '1px black',
    borderCollapse: 'collapse',
    textAlign: 'center',
  },
  printTableTh:{
    padding: '4px',
    border: '1px solid black',
    textAlign: 'center',
  },
  printTableTr:{
    textAlign:'center',
    padding: '4px',
    border: '1px solid black',
  },
  number:{
    width: '60px',
  },
  goodsName:{
    width: '180px',
  },
  unitName:{
    width: '75px',
  },
  specifications:{
    width: '90px',
  },
  goodsType:{
    width: '90px',
  },
  footer:{
    fontSize:'12px',
  },
  footerSpace:{
    width: '20px',
    display: 'block',
  },
  printInputFooter:{
    fontWeight:'bold',
    border:'none',
    width: '85px',
  },
};