使用针式打印机打印多种出库单遇到问题及思路

559 阅读8分钟

背景

多种出库单/入库单打印,之前是购买(按年)专业团队的成型程序去做这个打印,现在雇佣前公司(已跑路)去一次性做这个事情。

在项目初期,后端已经做了一版,后续已经准备上线,验收工作中,甲方发现有些字段信息展示不完整,不会换行,现在在要求前端去做这个事情。就到我手里了,这个文章就是想记录一下当时踩的坑,以及后续解决方案。共做两版。


实现方案

方法一:使用html2Dcanvas+jspdf

为啥用这个方案,不直接用window.print()呢?这里是开始的时候把表头表尾定义为了页眉页脚 window.print() 不太好定义,就选这个了,而且之前也使用过一次了,更熟悉一点,就直接用了。

遇到问题:

分页不完整,使用offsetHight获取高度有问题,

最后解决方案:

分一页,手动加1px

相关代码:
import jsPDF from 'jspdf'
import html2canvas from 'html2canvas'

const A4_WIDTH = (241 * 72) / 25.4
const A4_HEIGHT = (140 * 72) / 25.4
const newHeader = null
// const A4_WIDTH = 592.28
// const A4_HEIGHT = 841.89
jsPDF.API.output2 = function(outputType = 'save', filename = 'document.pdf') {
  let result = null
  switch (outputType) {
    case 'file':
      result = new File([this.output('blob')], filename, {
        type: 'application/pdf',
        lastModified: Date.now()
      })
      break
    case 'save':
      result = this.save(filename)
      break
    default:
      result = this.output(outputType)
  }
  return result
}

jsPDF.API.addBlank = function(x, y, width, height) {
  this.setFillColor(255, 255, 255)
  this.rect(x, y, Math.ceil(width), Math.ceil(height), 'F')
}

jsPDF.API.toCanvas = async function(element, width) {
  const canvas = await html2canvas(element, {
    width: element.scrollWidth,
    height: element.scrollHeight,
    allowTaint: true,
    useCORS: true,
    scale: 5,
    dpi: 3000
  })

  const canvasWidth = canvas.width
  const canvasHeight = canvas.height
  const height = (width / canvasWidth) * canvasHeight
  const canvasData = canvas.toDataURL('image/jpeg', 1.0)
  // // // 创建一个新的图像标签,并设置其源为data URL
  // var img = new Image();
  // img.src = canvasData;

  // // 将图像添加到文档中
  // document.body.appendChild(img);
  return { width, height, data: canvasData }
}
jsPDF.API.addHeader = async function(x, width, header) {
  if (!(header instanceof HTMLElement)) return
  let __header
  if (this.__header) {
    __header = this.__header
  } else {
    __header = await this.toCanvas(header, width)
    this.__header = __header
  }
  const { height, data } = __header

  this.addImage(data, 'JPEG', x, 0, width, height)
}

jsPDF.API.addFooter = async function(x, width, footer, flag, contentHeight) {
  if (!(footer instanceof HTMLElement)) return
  let __footer
  if (flag) {
    // 是否需要每次都重新生成页脚插入
    __footer = await this.toCanvas(footer, width)
  } else {
    if (this.__footer) {
      __footer = this.__footer
    } else {
      __footer = await this.toCanvas(footer, width)
      this.__footer = __footer
    }
  }

  const { height, data } = __footer
  this.addImage(data, 'JPEG', x, contentHeight, width, height)
}

/**
 * 生成pdf(处理多页pdf截断问题)
 * @param {Object} param
 * @param {HTMLElement} param.element - 需要转换的dom根节点
 * @param {number} [param.contentWidth=550] - 一页pdf的内容宽度,0-592.28
 * @param {number} [param.contentHeight=800] - 一页pdf的内容高度,0-841.89
 * @param {string} [param.outputType='save'] - 生成pdf的数据类型,添加了'file'类型,其他支持的类型见http://raw.githack.com/MrRio/jsPDF/master/docs/jsPDF.html#output
 * @param {string} [param.filename='document.pdf'] - pdf文件名
 * @param {number} param.x - pdf页内容距页面左边的高度,默认居中显示,为(A4宽度 - contentWidth) / 2)
 * @param {number} param.y - pdf页内容距页面上边的高度,默认居中显示,为(A4高度 - contentHeight) / 2)
 * @param {HTMLElement} param.header - 页眉dom元素
 * @param {HTMLElement} param.footer - 页脚dom元素
 * @param {boolean} [param.headerOnlyFirst=true] - 是否只在第一页添加页眉
 * @param {boolean} [param.footerOnlyLast=true] - 是否只在最后一页添加页脚
 * @param {string} [param.mode='adaptive'] - 生成pdf的模式,支持'adaptive'、'fixed','adaptive'需给dom添加标识,'fixed'需固定布局。
 * @param {string} [param.itemName='item'] - 给dom添加元素标识的名字,'adaptive'模式需在dom中设置
 * @param {string} [param.groupName='group'] - 给dom添加组标识的名字,'adaptive'模式需在dom中设置
 * @returns {Promise} 根据outputType返回不同的数据类型
 */
async function outputPdf({
  element,
  contentWidth = 550,
  contentHeight = 800,
  outputType = 'save',
  filename = 'document.pdf',
  x,
  y,
  header,
  footer,
  headerOnlyFirst = true,
  footerOnlyLast = true,
  mode = 'adaptive',
  itemName = 'item',
  groupName = 'group'
}) {
  if (!(element instanceof HTMLElement)) {
    throw new Error('The root element must be HTMLElement.')
  }

  const pdf = new jsPDF({
    unit: 'pt',
    format: [(241 * 72) / 25.4, (140 * 72) / 25.4],
    orientation: 'landscape' // 或者 'landscape'
  })
  // 获取页脚的高度
  const { height: footerHeight } = await pdf.toCanvas(footer, contentWidth)
  // 获取页头的高度
  const { height: headerHeight } = await pdf.toCanvas(header, contentWidth)
  // 出去页头页尾后每页的高度
  const originalPageHeight = contentHeight - footerHeight - headerHeight
  // 记录每一页的截取位置
  const { width, height, data } = await pdf.toCanvas(element, contentWidth)
  const rate = accDiv(contentWidth, element.offsetWidth)
  const baseX = x == null ? accDiv(subtr(A4_WIDTH, contentWidth), 2) : accMul(x, rate)
  const baseY = y == null ? accDiv(subtr(A4_HEIGHT, contentHeight), 2) : accMul(y, rate)
  async function addHeader(isFirst) {
    if (isFirst || !headerOnlyFirst) {
      await pdf.addHeader(baseX, contentWidth, header)
    }
  }
  async function addFooter(isLast, pageNum, now) {
    if (isLast || !footerOnlyLast) {
      if (footer.querySelector('.pageSize')) {
        footer.querySelector('.pageSize').innerText = `第${now}/${pageNum}页`
      }

      await pdf.addFooter(
        baseX,
        contentWidth,
        footer,
        footer.querySelector('.pageSize') ? true : false,
        contentHeight
      )
      //document.doc
    }
  }
  function addImage(_x, _y) {
    pdf.addImage(data, 'JPEG', _x, _y, width, height)
  }

  const params = {
    element,
    contentWidth,
    contentHeight,
    itemName,
    groupName,
    pdf,
    baseX,
    baseY,
    width,
    height,
    addImage,
    addHeader,
    addFooter,
    originalPageHeight
  }
  switch (mode) {
    case 'adaptive':
      await outputWithAdaptive(params)
      break
    case 'fixed':
    default:
      await outputWithFixedSize(params)
  }
  //

  return pdf.output2(outputType, filename)
}

async function outputWithFixedSize({
  pdf,
  baseX,
  baseY,
  height,
  addImage,
  addHeader,
  addFooter,
  contentHeight
}) {
  const pageNum = Math.ceil(height / contentHeight) // 总页数
  const arr = Array.from({ length: pageNum }).map((_, i) => i)
  for await (const i of arr) {
    addImage(baseX, baseY - i * contentHeight)
    const isFirst = i === 0
    const isLast = i === arr.length - 1
    if (!isFirst) {
      // 用空白遮挡顶部需要隐藏的部分
      pdf.addBlank(0, 0, A4_WIDTH, baseY)
    }
    if (!isLast) {
      // 用空白遮挡底部需要隐藏的部分
      pdf.addBlank(0, baseY + contentHeight, A4_WIDTH, A4_HEIGHT - (baseY + contentHeight))
    }
    await addHeader(isFirst)

    await addFooter(isLast)
    if (!isLast) {
      pdf.addPage()
    }
  }
}

async function outputWithAdaptive({
  element,
  contentWidth,
  itemName,
  groupName,
  pdf,
  baseX,
  baseY,
  addImage,
  addHeader,
  addFooter,
  contentHeight,
  height,
  originalPageHeight,
  width
}) {
  let canvasHight = height
  let canvasWeight = width
  // 从根节点遍历dom,计算出每页应放置的内容高度以保证不被截断
  const splitElement = () => {
    const res = []
    let pos = 0 // 一页分割页内容占据高度
    let totalHight = 0 // 当前已已放置的全部高度
    const elementWidth = element.offsetWidth
    const rate = accDiv(contentWidth, elementWidth)

    function getRate1(sum) {
      let data = 0

      if (sum <= 15) {
        data = 1
      } else if (sum > 15 && sum <= 20) {
        data = 2
      } else if (sum > 20 && sum <= 40) {
        data = Math.floor(res.length / 20) * 1
      } else {
        data = 2 + Math.floor(res.length / 50) * 1
      }
      return data
    }

    function updatePos(height, one) {
      // 当前行 的offsetTop 的转为pdf对应大小后减去 之前以放置高度
      // const top = Math.ceil((contentWidth / elementWidth) * one.offsetTop) - totalHight
      const top = subtr(accMul(rate, one.offsetTop + getRate1(res.length)), totalHight)
      const lastTop = Number(top)
      // if(numAdd(lastTop, height)>originalPageHeight){
      //   // h 是o的几倍数
      //   const length = Math.floor(numAdd(lastTop, height)/originalPageHeight)
      //   for(let i=0;i<length;i++){
      //     res.push(originalPageHeight)
      //     totalHight+=originalPageHeight
      //   }
      //   pos = height-originalPageHeight*length
      //   totalHight+=pos
      //   return
      // }
      if (numAdd(pos, numAdd(lastTop, height)) < originalPageHeight) {
        pos = numAdd(pos, numAdd(lastTop, height))
        // pos += lastTop + height
        // totalHight += lastTop + height
        totalHight = numAdd(totalHight, numAdd(lastTop, height))
        return
      }
      // console.log('pos',pos)
      res.push(pos)
      pos = numAdd(lastTop, height)
      totalHight = numAdd(totalHight, numAdd(lastTop, height))
      console.log('totalHight:', totalHight)
    }
    function traversingNodes(nodes) {
      if (nodes.length === 0) return
      nodes.forEach((one) => {
        if (one.nodeType !== 1) return
        const { [itemName]: item, [groupName]: group } = one.dataset
        if (item != null) {
          const { offsetHeight } = one
          // dom高度转换成生成pdf的实际高度
          // 代码不考虑dom定位、边距、边框等因素,需在dom里自行考虑,如将box-sizing设置为border-box
          // console.log('traversingNodes-hight',offsetHeight)
          updatePos(accMul(accDiv(contentWidth, elementWidth), offsetHeight), one)
        } else if (group != null) {
          traversingNodes(one.childNodes)
        }
      })
    }
    traversingNodes(element.childNodes)
    res.push(pos)
    return res
  }

  const elements = splitElement()
  let accumulationHeight = 0
  let currentPage = 0
  for await (const elementHeight of elements) {
    addImage(baseX, baseY - accumulationHeight)
    // accumulationHeight += elementHeight
    accumulationHeight = numAdd(accumulationHeight, elementHeight)
    const isFirst = currentPage === 0
    const isLast = currentPage === elements.length - 1
    if (!isFirst) {
      pdf.addBlank(0, 0, A4_WIDTH, baseY)
    }
    if (!isLast) {
      pdf.addBlank(0, baseY + elementHeight, A4_WIDTH, A4_HEIGHT - (baseY + elementHeight))
    }
    await addHeader(isFirst)
    await addFooter(isLast, elements.length, currentPage + 1)
    if (!isLast) {
      pdf.addPage()
    }
    currentPage++
  }
}

/**
 * 加法运算,避免数据相加小数点后产生多位数和计算精度损失
 * @param num1加数1 | num2加数2
 */
function numAdd(num1, num2) {
  let baseNum, baseNum1, baseNum2
  try {
    baseNum1 = num1.toString().split('.')[1].length
  } catch (e) {
    baseNum1 = 0
  }
  try {
    baseNum2 = num2.toString().split('.')[1].length
  } catch (e) {
    baseNum2 = 0
  }
  baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
  return Number(((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(2))
}

// 减
function subtr(arg1, arg2) {
  let r1, r2, m, n
  try {
    r1 = arg1.toString().split('.')[1].length
  } catch (e) {
    r1 = 0
  }
  try {
    r2 = arg2.toString().split('.')[1].length
  } catch (e) {
    r2 = 0
  }
  m = Math.pow(10, Math.max(r1, r2))
  // last modify by deeka
  // 动态控制精度长度
  n = r1 >= r2 ? r1 : r2
  return ((arg1 * m - arg2 * m) / m).toFixed(n)
}

// 除
function accDiv(arg1, arg2) {
  let t1 = 0,
    t2 = 0,
    r1,
    r2
  try {
    t1 = arg1.toString().split('.')[1].length
  } catch (e) {}
  try {
    t2 = arg2.toString().split('.')[1].length
  } catch (e) {}
  r1 = Number(arg1.toString().replace('.', ''))
  r2 = Number(arg2.toString().replace('.', ''))
  return (r1 / r2) * Math.pow(10, t2 - t1)
}

// 乘
function accMul(arg1, arg2) {
  let m = 0
  const s1 = arg1.toString(),
    s2 = arg2.toString()
  try {
    m += s1.split('.')[1].length
  } catch (e) {}
  try {
    m += s2.split('.')[1].length
  } catch (e) {}
  return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m)
}
export default outputPdf

放弃原因:

针式打印机,打印后出现点状文字,不完整,不符合用户要求。这里是因为开始的时候没有调研清楚,就直接开始开发了,针式打印机是一针一针的打,而该方法是将这个生成图片的pdf去打印,有漏针的情况。

方法二: window.print()

遇到问题:
  1. 自动分页,表头,表尾不能超过页面的25%,现在表头超过了25%,需要手动分页,

  2. 特殊纸张大小,打印分页不明确,给打印机设置自定义纸张

  3. 火狐兼容性问题

    1. 分页失效
    2. 打卡空白
 printClick() {
      this.loadingPrintBtn = true

      const hasPageSize = [
        'incomingWarehouseReceipt',
        'interOfficeTransferOrders',
        'officeDeliveryOrderPrintPdf241X140',
        'officeDeliveryOrderPrintPdf241X140New',
        'officeDeliveryOrderPrintPdfOne',
        'officeDeliveryOrderPrintPdfOneNew',
        'officeDeliveryOrderPrintPdfTwo'
      ] // 有页码组件
      const noHasPageSize = [
        'outboundWarehouseReceiptsOne',
        'outboundWarehouseReceiptsTwo',
        'simLanOutputRepositoryOrderOne'
      ] // 没有页码组件

      // 针对于火狐浏览器单独区分是否需要手动分页
      const noNeedPaging = [
        'incomingWarehouseReceipt',
        'officeDeliveryOrderPrintPdf241X140',
        'officeDeliveryOrderPrintPdf241X140New',
        'officeDeliveryOrderPrintPdfOne',
        'officeDeliveryOrderPrintPdfOneNew',
        'officeDeliveryOrderPrintPdfTwo'
      ] // 不需要手动分页
      const needPaging = [
        'interOfficeTransferOrders',
        'outboundWarehouseReceiptsOne',
        'outboundWarehouseReceiptsTwo',
        'simLanOutputRepositoryOrderOne'
      ] // 需要手动分页
      // 创建一个 iframe 元素
      const iframe = document.createElement('iframe')
      iframe.style.display = 'none'

      if (navigator.userAgent.match('Firefox')) {
        iframe.src = 'javascript:'
      }
      document.body.appendChild(iframe)
      // 获取 iframe 的 document 对象
      const iframeDoc = iframe.contentDocument || iframe.contentWindow.document
      if (navigator.userAgent.match('Firefox')) {
        if (needPaging.some((item) => item === this.printComponent))
          iframeDoc.body.innerHTML = this.isFirefoxPaging()
        if (noNeedPaging.some((item) => item === this.printComponent))
          iframeDoc.body.innerHTML = this.isFirefox()
      } else {
        // 将需要打印的内容插入到 iframe 中
        iframeDoc.body.innerHTML = this.selfPaging()
      }

      setTimeout(() => {
        this.loadingPrintBtn = false
        iframe.contentWindow.print()
        // 移除 iframe 元素
        document.body.removeChild(iframe)
      }, 100)
    },
   
    selfPaging() {
      let {
        thead,
        tbody,
        tfoot,
        theadHeight,
        contentHeight,
        currentPageHeight,
        html,
        hasTfoot
      } = this.getElement()
      function insertPageBreak() {
        if (hasTfoot) {
          html += `<tr><td style="height: 0; page-break-before: always;"></td></tr>`
        }
        html += `<thead style="width: 100%; border-top: none;">${thead.innerHTML}</thead>`
        html += `<tbody style="width: 100%;">`
        currentPageHeight = theadHeight
      }

      // 在第一页开始时插入 thead
      insertPageBreak()

      const rows = tbody.querySelectorAll('tr')
      rows.forEach((row) => {
        const isBreakCells = row.querySelectorAll('.is-break')
        const maxCellHeight = Math.max(...Array.from(isBreakCells, (cell) => cell.offsetHeight))
        const height = row.offsetHeight

        if (currentPageHeight + maxCellHeight > contentHeight) {
          // 如果当前行中包含 .is-break 的最高单元格高度超过一页的高度限制,插入分页符
          if (!hasTfoot) {
            html += `</tbody><tfoot style="width: 100%;">${tfoot.innerHTML}</tfoot>`
            hasTfoot = true
          }
          insertPageBreak()
        }

        currentPageHeight += height
        html += `<tr>${row.innerHTML}</tr>`
      })

      // 在最后一页插入 tfoot
      if (!hasTfoot) {
        html += `</tbody><tfoot style="width: 100%;">${tfoot.innerHTML}</tfoot>`
      }

      return `<div style="width: 241mm !important;">
    <div 
    style="
    margin: 0 auto; 
    max-width: 230mm !important; 
    color: #000;">
    <table 
    border="0" 
    width="100%" 
    cellpadding="2" 
    cellspacing="0" style="table-layout: fixed; border: none !important; border-collapse: collapse;">
      ${html}
      </table></div></div>`
    },
    getTotalPage() {
      let { tbody, theadHeight, contentHeight, currentPageHeight } = this.getElement()
      let pageTotal = 1 // 总页数
      const rows = tbody.querySelectorAll('tr')
      rows.forEach((row) => {
        const isBreakCells = row.querySelectorAll('.is-break')
        const maxCellHeight = Math.max(...Array.from(isBreakCells, (cell) => cell.offsetHeight))
        const height = row.offsetHeight
        if (currentPageHeight + maxCellHeight > contentHeight) {
          currentPageHeight = theadHeight
          pageTotal += 1
        }
        currentPageHeight += height
      })

      return pageTotal
    },
    hasPageSize() {
      const pageSize = this.getTotalPage()
      let currentPageSize = 0
      let {
        thead,
        tbody,
        tfoot,
        theadHeight,
        contentHeight,
        currentPageHeight,
        html,
        hasTfoot
      } = this.getElement()

      function insertPageBreak() {
        if (hasTfoot) {
          html += `<tr><td style="height: 0; page-break-before: always;"></td></tr>`
        }
        html += `<thead style="width: 100%; border-top: none;">${thead.innerHTML}</thead>`
        html += `<tbody style="width: 100%;">`
        currentPageHeight = theadHeight
      }

      // 在第一页开始时插入 thead
      if (!hasTfoot) {
        insertPageBreak()
        hasTfoot = true
      }
      const cloneTfoot = tfoot.cloneNode(true)
      const pageLine = cloneTfoot.querySelector('.pageSize')
      // originalElement.cloneNode(true)
      const rows = tbody.querySelectorAll('tr')

      rows.forEach((row) => {
        const isBreakCells = row.querySelectorAll('.is-break')
        const maxCellHeight = Math.max(...Array.from(isBreakCells, (cell) => cell.offsetHeight))
        const height = row.offsetHeight

        if (currentPageHeight + maxCellHeight > contentHeight) {
          // 如果当前行中包含 .is-break 的最高单元格高度超过一页的高度限制,插入分页符
          currentPageSize += 1
          if (pageLine) {
            pageLine.innerHTML = `第${currentPageSize}/${pageSize}页`
          }
          if (hasTfoot) {
            html += `${cloneTfoot.innerHTML}</tbody>`
            hasTfoot = true
            insertPageBreak()
          }
        }
        currentPageHeight += height
        html += `<tr>${row.innerHTML}</tr>`
      })

      currentPageSize += 1
      if (pageLine) {
        pageLine.innerHTML = `第${currentPageSize}/${pageSize}页`
      }
      if (hasTfoot) {
        html += `${cloneTfoot.innerHTML}</tbody>`
      }

      return `<div style="width: 241mm !important;">
    <div 
    style="
    margin: 0 auto; 
    max-width: 230mm !important; 
    color: #000;">
    <table 
    border="0" 
    width="100%" 
    cellpadding="2" 
    cellspacing="0" style="table-layout: fixed; border: none !important; border-collapse: collapse;">
      ${html}
      </table></div></div>`
    },
    getElement() {
      const className = this.printComponent
        .replace(/([a-z])([A-Z])/g, '$1-$2')
        .replace(/([A-Z]+)([A-Z])([a-z])/g, '$1-$2-$3')
        .toLowerCase()
      const printBody = document.querySelector(`#${className}`)
      let example = null
      if (navigator.userAgent.match('Firefox')) {
        example = document.querySelector('.exampleFirefox')
      } else {
        example = document.querySelector('.example')
      }
      const thead = printBody.querySelector('thead')
      const tbody = printBody.querySelector('tbody')
      const tfoot = printBody.querySelector('tfoot')
      const exampleHeight = example.offsetHeight // 每一页的高度
      const theadHeight = thead.offsetHeight
      const tfootHeight = tfoot.offsetHeight
      const contentHeight = exampleHeight - tfootHeight // 每一页去掉表头表尾的高度,即内容高度

      let html = ''
      let currentPageHeight = 0 // 当前页的高度
      let hasTfoot = false // 是否已经插入过 tfoot

      return {
        className,
        printBody,
        example,
        thead,
        tbody,
        tfoot,
        exampleHeight,
        theadHeight,
        tfootHeight,
        contentHeight,
        currentPageHeight,
        html,
        hasTfoot
      }
    },
    isFirefox() {
      let { thead, tbody, tfoot, html } = this.getElement()

      html += `<thead style="width: 100%; border-top: none;">${thead.innerHTML}</thead>`
      html += `<tbody style="width: 100%;">${tbody.innerHTML}</tbody>`
      html += `<tfoot style="width: 100%;">${tfoot.innerHTML}</tfoot>`

      return `<div style="width: 241mm !important;">
    <div 
    style="
    margin: 0 auto; 
    max-width: 230mm !important; 
    color: #000;">
    <table 
    border="0" 
    width="100%" 
    cellpadding="2" 
    cellspacing="0" style="table-layout: fixed; border: none !important;">
      ${html}
      </table></div></div>`
    },

    /**
     *  火狐
     */
    isFirefoxPaging() {
      const pageSize = this.getTotalPage()
      let currentPageSize = 0
      let {
        thead,
        tbody,
        tfoot,
        theadHeight,
        contentHeight,
        currentPageHeight,
        html,
        hasTfoot
      } = this.getElement()

      function insertPageBreak() {
        html += `<div class="page-break" style="page-break-after: always;
    page-break-inside: avoid;"><table 
    border="0" 
    width="100%" 
    cellpadding="2" 
    cellspacing="0" style="table-layout: fixed;">
      <thead style="width: 100%; border-top: none;">${thead.innerHTML}</thead>`
        html += `<tbody style="width: 100%;">`
        currentPageHeight = theadHeight
      }

      // 在第一页开始时插入 thead
      if (!hasTfoot) {
        insertPageBreak()
        hasTfoot = true
      }
      const cloneTfoot = tfoot.cloneNode(true)
      const pageLine = cloneTfoot.querySelector('.pageSize')
      const rows = tbody.querySelectorAll('tr')

      rows.forEach((row) => {
        const isBreakCells = row.querySelectorAll('.is-break')
        const maxCellHeight = Math.max(...Array.from(isBreakCells, (cell) => cell.offsetHeight))
        const height = row.offsetHeight

        if (currentPageHeight + maxCellHeight > contentHeight) {
          // 如果当前行中包含 .is-break 的最高单元格高度超过一页的高度限制,插入分页符
          currentPageSize += 1
          if (pageLine) {
            pageLine.innerHTML = `第${currentPageSize}/${pageSize}页`
          }

          if (hasTfoot) {
            html += `${cloneTfoot.innerHTML}</tbody></table></div>`
            hasTfoot = true
            insertPageBreak()
          }
        }
        currentPageHeight += height
        html += `<tr>${row.innerHTML}</tr>`
      })

      currentPageSize += 1
      if (pageLine) {
        pageLine.innerHTML = `第${currentPageSize}/${pageSize}页`
      }
      if (hasTfoot) {
        html += `${cloneTfoot.innerHTML}</tbody></table></div>`
      }

      return `<div style="width: 241mm !important;">
    <div 
    style="
    margin: 0 auto; 
    max-width: 240mm !important; 
    color: #000;">
      ${html}
      </div></div>`
    }