vue-前端匯出 pdf 並且壓縮 zip

少俠發表於2021-02-08

需求

1、前端自定義 html 模板,匯出 pdf
2、多個 pdf 匯出壓縮到一個檔案 zip 裡面

原理

1、html2Canvas 將 html 模板轉為 canvas
2、canvas 轉為 img 圖片
3、生成的圖片新增到 pdf 裡面

用到的外掛

html2canvas
jspdf
jszip
file-saver

模板

<template>
  <div class="table-class">
    <div class="name-style">{{ row.meetingName }}會議紀要</div>
    <table border="0" class="table-style">
      <tbody>
        <tr>
          <td>會議主題</td>
          <td colspan="5">{{ row.meetingTheme }}</td>
        </tr>
        <tr>
          <td>會議時間</td>
          <td>{{ meetingTime || '-' }}</td>
          <td>會議地點</td>
          <td>{{ row.meetingPlace }}</td>
          <td>會議級別</td>
          <td>{{ matterLevelObj[row.meetingLevel]||"-" }}</td>
        </tr>
        <tr>
          <td>主持人</td>
          <td>{{ row.host }}</td>
          <td>記錄人</td>
          <td>{{ row.recorder }}</td>
          <td>抄報</td>
          <td>{{ row.ccleader }}</td>
        </tr>
        <tr>
          <td>審批人</td>
          <td colspan="5">{{ row.approverName || '-' }}</td>
        </tr>
        <tr>
          <td>參與人</td>
          <td colspan="5">{{ row.participant }}</td>
        </tr>
        <tr>
          <td>備註</td>
          <td colspan="5">{{ row.remark }}</td>
        </tr>
        <tr>
          <td colspan="6" class="td-bg">會議內容</td>
        </tr>
        <tr>
          <td colspan="6">{{ row.remark }}</td>
        </tr>
        <tr>
          <td colspan="6" class="td-bg">會議事項</td>
        </tr>
        <tr>
          <td>序號</td>
          <td>型別</td>
          <td>工作事項</td>
          <td>負責人</td>
          <td>預計完成時間</td>
          <td>工作計劃</td>
        </tr>
        <tr v-for="(item,i) of row.meetMatters" :key="i">
          <td>{{ i+1 }}</td>
          <td>{{ item.type }}</td>
          <td>{{ item.workItem }}</td>
          <td>{{ item.principalName }}</td>
          <td>{{ item.planEndtime }}</td>
          <td>{{ item.workPlan }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
<script>
  import mixin from '../mixin';
  export default {
    name: 'ExportPdf',
    mixins: [mixin],
    props: {
      row: {
        type: Object,
        default: () => {},
      },
    },
    computed: {
      meetingTime() {
        const start = this.row?.meetingStartTime;
        const end = this.row?.meetingEndTime;
        if (start && end) {
          return start.substr(0, start.length - 3) + '~' + end.substr(0, end.length - 3);
        } else {
          return '';
        }
      },
    },
  };
</script>
<style lang="scss" scoped>
  .table-class {
    background-color: #fff;
    width: 1000px;
    margin: auto;
    padding: 40px;
    box-sizing: border-box;
    .name-style {
      text-align: center;
      font-size: 20px;
      font-weight: bold;
      margin-bottom: 20px;
    }
  }
  .table-style {
    border-collapse: collapse;
    width: 100%;
    text-align: center;
    td,
    th {
      padding: 10px;
      font-size: 15px;
      border: 1px solid black;
    }
    .td-bg {
      background: #ccc;
    }
  }
</style>

非同步匯出函式

async exportMeeting (type) {
      // type:'1' 選擇匯出 'all':匯出所有
      try {
        this.allLoading = true
        let selectedData = []
        if (type === '1') {
          const ids = this.multipleSelection.map(el => el.id)
          selectedData = await this.getMeetingAll(ids)
        } else if (type === 'all') {
          selectedData = await this.getMeetingAll()
        }
        this.loadingText = '正在拼命匯出...'

        const zip = new JSZip()
        const promises = []
        this.isShowPdf = true

        for (let i = 0; i < selectedData.length; i++) {
          const element = selectedData[i]
          this.data = element
          // 等待每一個轉為pdf
          const p = await htmlToPdf.getPdf(document.getElementById('pdf'), element.meetingName)
          promises.push(p)
        }
        // 等到所有的promise執行完成依次壓縮到zip中
        Promise.all(promises).then(async (pdfs) => {
          for (let i = 0; i < pdfs.length; i++) {
            const { PDF, name } = pdfs[i]
            // 如果只是匯出一個pdf,則匯出pdf格式
            if (pdfs.length === 1) {
              PDF.save(`${name}-${new Date().getTime()}.pdf`)
              this.allLoading = false
              this.loadingText = '正在請求資料'
            } else {
              // 否則新增到壓縮包裡面
              await zip.file(`${name}-${new Date().getTime()}.pdf`, PDF.output('blob'))
            }
          }
          if (pdfs.length > 1) {
            zip.generateAsync({ type: 'blob' }).then(content => {
              FileSaver.saveAs(content, '銷項管理平臺會議紀要.zip')
            })
          }
        }).finally(() => {
          this.allLoading = false
          this.loadingText = '正在請求資料'
        })
      } catch (e) {
        this.allLoading = false
        this.loadingText = '正在請求資料'
        throw new Error(e)
      }
    },

獲取 pdf 的公共封裝函式

// 匯出pdf
import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';

export default {
  getPdf: (el, pdfName) => {
    // 滾輪滑動造成的,主要是html2canvas是根據body進行截圖,若內容高度高於body時,就會出現這樣的問題
    // 解決方案:(在生成截圖前,先把捲軸置頂)
    window.pageYoffset = 0;
    document.documentElement.scrollTop = 0;
    document.body.scrollTop = 0;

    return new Promise((resolve, reject) => {
      // 在點選儲存圖片時,此時要儲存的資源較多,造成模組並沒有完全載入完畢,就已經生成了截圖。
      // 解決方案:(延遲)
      setTimeout(() => {
        // 這句挺重要
        html2Canvas(el, {
          scale: 4,
          dpi: 300,
          useCORS: true,
          // allowTaint: true
        })
          .then((canvas) => {
            const contentWidth = canvas.width;
            const contentHeight = canvas.height;
            const pageHeight = (contentWidth / 592.28) * 841.89;
            let leftHeight = contentHeight; //
            let position = 0;
            const imgWidth = 595.28;
            const imgHeight = (592.28 / contentWidth) * contentHeight;
            const pageData = canvas.toDataURL('image/jpeg', 1.0);
            const PDF = new JsPDF('', 'pt', 'a4');
            if (leftHeight < pageHeight) {
              // 在pdf.addImage(pageData, 'JPEG', 左,上,寬度,高度)設定在pdf中顯示;
              PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
              // pdf.addImage(pageData, 'JPEG', 20, 40, imgWidth, imgHeight);
            } else {
              // 分頁
              while (leftHeight > 0) {
                PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
                leftHeight -= pageHeight;
                position -= 841.89;
                // 避免新增空白頁
                if (leftHeight > 0) {
                  PDF.addPage();
                }
              }
            }
            resolve({ PDF, name: pdfName });
          })
          .catch((e) => {
            reject(e);
          });
      }, 500);
    });
  },
};

遇到的問題

  • 新增到 zip 時,獲取 pdf 的內容
zip.file(`${name}-${new Date().getTime()}.pdf`, PDF.output('blob')); //第二個引數PDF.output('blob')
  • 新增到 zip 時,同名檔案會被覆蓋
// 透過給檔名新增一個時間戳解決
zip.file(`${name}-${new Date().getTime()}.pdf`, PDF.output('blob')); //`${name}-${new Date().getTime()}.pdf`

相關文章