vue导出pdf

DomPDF导出

项目使用传入dom结构导出文件为PDF格式

demo查看

使用技术栈版本

vue@3

dompdf.js@1.3

vxetable@4.19
中文默认乱码 需要加载字体

html 复制代码
<template>
  <div>
    <button @click="handlerPrint">导出</button>
    <!-- 动态绑定pdf-mode,仅导出时添加 -->
    <div
      ref="printDom"
      :class="['print-container', printFlag ? 'pdf-mode' : '']"
    >
      <div class="header-info">
        <div class="info-row" v-for="(item, index) in list" :key="index">
          <div class="info-title">{{ item.title }}</div>
          <div class="info-item">{{ index }}</div>
        </div>
      </div>
      <!-- [table] 不支持的参数 "show-overflow=tooltip",可能为 "show-overflow=title" -->
      <div style="width: 100%; height: 800px">
        <vxe-grid
          class="h5-vxe-grid"
          v-if="gridColumns.length"
          size="mini"
          ref="xTable"
          border="none"
          align="center"
          stripe
          resizable
          auto-resize
          height="auto"
          show-overflow="title"
          :show-header-overflow="false"
          :scroll-y="printFlag ? { enabled: false } : { enabled: true }"
          :columns="gridColumns"
          :data="dataSource"
          :span-method="colspanMethod"
          :cell-style="getCellStyle"
        >
          <template v-slot:evalResult="{ row }">
            <span class="text-result"> {{ row.evalResult }} </span>
          </template>
        </vxe-grid>
      </div>
    </div>
  </div>
</template>

<script>
import dompdf from 'dompdf.js';

import '@assets/font/domPdf/SourceHanSansSC-Normal-Min-normal.js';

export default {
  name: 'tableLog',

  props: {},

  data() {
    return {
      printFlag: false, // 新增打印标识
      domHeight: 400,
      otherHeight: 80,
      column: 2,
      list: [
        {
          title: 'label1',
        },
        {
          title: 'label1',
        },
        {
          title: 'label1',
        },
        {
          title: 'label1',
        },
      ],

      gridColumns: [
        {
          title: '名称',
          field: 'mainItem',
          // width: 160,
          // 合并单元格渲染主分类
          // rowspanMethod: ({ row }) => row.rowSpan
        },
        {
          title: '数据',
          field: 'subItem',
          width: 160,
        },
        {
          title: '值',
          field: 'limitVal',
          // width: 140
        },
        {
          title: '合格率',
          field: 'passRate',
          width: 80,
        },
        {
          title: '结论',
          field: 'evalResult',
          slots: {
            default: 'evalResult',
          },
        },
        {
          title: '数据分析',
          field: 'dataAnalyse',
          //slots: {
          //  default: 'action',
          // },
        },
      ],
      dataSource: [
        {
          mainItem: 'label1',
          rowSpan: 1,
          subItem: '------',
          limitVal: '-0.2Hz~0.2Hz',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        // 不平衡
        {
          mainItem: 'label2',
          rowSpan: 2,
          subItem: '电流',
          limitVal: '-2%~2%',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label2',
          rowSpan: 0,
          subItem: '电压',
          limitVal: '<2%',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },

        {
          mainItem: 'label3',
          rowSpan: 3,
          subItem: 'A相',
          limitVal: '-7%~7%',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label3',
          rowSpan: 0,
          subItem: 'B相',
          limitVal: '-7%~7%',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label3',
          rowSpan: 0,
          subItem: 'C相',
          limitVal: '-7%~7%',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },

        {
          mainItem: 'label4',
          rowSpan: 1,
          subItem: '3',
          limitVal: '0.9~1.0',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },

        {
          mainItem: 'label5',
          rowSpan: 12,
          subItem: 'A',
          limitVal: '173.1',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label5',
          rowSpan: 0,
          subItem: 'A',
          limitVal: '173.1',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label5',
          rowSpan: 0,
          subItem: 'A',
          limitVal: '173.1',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label5',
          rowSpan: 0,
          subItem: '1',
          limitVal: '173.1',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label5',
          rowSpan: 0,
          subItem: '3',
          limitVal: '173.1',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label5',
          rowSpan: 0,
          subItem: '5',
          limitVal: '173.1',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '---',
          hasLookBtn: false,
        },
        {
          mainItem: 'label5',
          rowSpan: 0,
          subItem: '7',
          limitVal: '173.1',
          passRate: '98.2%',
          evalResult: '合格',
          dataAnalyse: '',
          hasLookBtn: true,
        },
        {
          mainItem: 'label5',
          rowSpan: 0,
          subItem: '9',
          limitVal: '173.1',
          passRate: '78.2%',
          evalResult: '不合格',
          dataAnalyse: '',
          hasLookBtn: true,
        },
      ],
    };
  },

  methods: {
    async handlerPrint() {
      const originDom = this.$refs.printDom;
      // 1. 深度克隆整个打印区域DOM(包含所有子元素、表格、插槽)
      const cloneDom = originDom.cloneNode(true);

      // 2. 给克隆副本添加打印专属class,只修改副本,原页面不动
      cloneDom.classList.add('pdf-mode');

      // 3. 创建临时隐藏容器,把克隆体丢进去,脱离页面正常流
      const tempWrap = document.createElement('div');
      tempWrap.style.position = 'absolute';
      tempWrap.style.left = '-9999px'; // 移出可视区域,看不见
      tempWrap.style.width = '210mm';
      tempWrap.appendChild(cloneDom);
      document.body.appendChild(tempWrap);

      try {
        // 等待克隆DOM样式、表格渲染完成
        await new Promise((resolve) => setTimeout(resolve, 150));

        // 直接传入克隆后的dom导出PDF
        const blob = await dompdf(cloneDom, {
          pagination: true,
          format: 'a4',
          scale: 1.1,
          pageConfig: {
            header: { content: '评估报告', height: 15 },
            footer: {
              content: '第 ${currentPage} 页',
              height: 15,
              contentPosition: 'center',
            },
          },
          fontConfig: {
            fontFamily: 'SourceHanSansSC-Normal-Min',
            fontBase64: window.fontBase64,
            fontStyle: 'normal',
            fontWeight: 400,
          },
        });

        // 下载逻辑不变
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = '评估报告.pdf';
        document.body.appendChild(a);
        a.click();
        setTimeout(() => {
          document.body.removeChild(a);
          URL.revokeObjectURL(url);
        }, 100);
      } catch (err) {
        console.error('导出失败', err);
      } finally {
        // 导出完成销毁临时DOM,释放内存
        document.body.removeChild(tempWrap);
      }
    },

    async handlerPrint2() {
      const element = this.$refs.printDom;

      // 开启打印模式,加载pdf专属样式
      this.printFlag = true;
      // 等待dom更新完成再导出
      await this.$nextTick();

      dompdf(element, {
        pagination: true,
        format: 'a4',
        scale: 1.2, // 缩小缩放,避免内容横向溢出
        pageConfig: {
          header: { content: '评估报告', height: 20 },
          footer: {
            content: '第 ${currentPage} 页',
            height: 20,
            contentPosition: 'center',
          },
        },
        fontConfig: {
          fontFamily: 'SourceHanSansSC-Normal-Min',
          fontBase64: window.fontBase64,
          fontStyle: 'normal',
          fontWeight: 400,
        },
      }).then((blob) => {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = '评估报告.pdf';
        document.body.appendChild(a);
        a.click();
        setTimeout(() => {
          document.body.removeChild(a);
          URL.revokeObjectURL(url);
          // 打印完成关闭打印模式,恢复页面原有样式
          this.printFlag = false;
        }, 100);
      });
    },

    getCellStyle({ row, rowIndex, column, columnIndex }) {
      if (columnIndex === 0) {
        return {
          background: 'rgba(201, 236, 255, 0.45)',
        };
      }
    },
    handleLookDetail(row) {
      console.log('查看分项详情', row);
      // 可弹窗/跳转查看对应谐波、暂态事件详情
    },

    getRef() {
      return this.$refs.xTable;
    },

    colspanMethod({ row, $rowIndex, column, data }) {
      let fields = ['mainItem']; // 需要合并的列
      if (fields.length === 0) {
        return;
      }
      let cellValue = row[column.property];

      if (cellValue && fields.includes(column.property)) {
        let prevRow = data[$rowIndex - 1];
        let nextRow = data[$rowIndex + 1];
        if (prevRow && prevRow[column.property] === cellValue) {
          return { rowspan: 0, colspan: 0 };
        } else {
          let countRowspan = 1;
          while (nextRow && nextRow[column.property] === cellValue) {
            nextRow = data[++countRowspan + $rowIndex];
          }
          if (countRowspan > 1) {
            return { rowspan: countRowspan, colspan: 1 };
          }
        }
      }
    },
  },
};
</script>

<style scoped lang="less">
.header-info {
  display: flex;
  gap: 10px;
  box-sizing: border-box;
  padding: 10px 0;
  margin-bottom: 8px;

  .info-row {
    flex: 1;
    background: linear-gradient(180deg, #ffffff 0%, #ebebeb 98.57%);
    border: 0.5px solid #9f9e9e;
    border-radius: 4px;
    padding: 10px 25px;
    text-align: center;

    .info-title {
      font-family: PingFang SC;
      color: #333333;
      font-size: 12px;
      padding-bottom: 5px;
      margin-bottom: 5px;
      border-bottom: 0.5px solid #dddddd;
    }

    .info-item {
      font-family: PingFang SC;
      font-weight: 600;
      color: #0093ff;
      font-size: 14px;
    }
  }

  .info-row:nth-child(2) .info-item {
    color: #333333;
  }

  .info-row:nth-child(4) .info-item {
    width: 60px;
    text-align: center;
    color: #fc5454;
    border: 1px solid currentColor;
    margin: 0 auto;
  }
}

.h5-vxe-grid.vxe-grid {
  :deep(.vxe-header--row) {
    background: #4e82bb;
    font-family: SF Pro Display;
    font-weight: 500;
    color: #ffffff;
    font-size: 14px;

    .vxe-resizable {
      opacity: 0;
    }

    .vxe-body--row.row--stripe {
      background-color: #f4f4f4;
    }

    /* 页面预览:文字超出省略 */

    .vxe-cell {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }
}

.text-result {
  display: inline-block;
  width: 60px;
  box-sizing: border-box;
  border: 1px solid;
}

.text-red {
  color: #23d09a;
  border-color: currentColor;
  font-weight: bold;
}

.text-blue {
  color: #fc5454;
  border-color: currentColor;
  font-weight: bold;
}

.look-btn {
  cursor: pointer;
}

/* 页面预览模式,不限制宽度、不强制换行 */
.print-container {
  width: 100%;
  background: transparent;
  padding: 0;
}
</style>
<style lang="less">
/* ========== PDF导出专属样式,页面正常浏览完全不生效 ========== */
.print-container.pdf-mode {
  min-height: auto !important; /* 删掉固定297mm,不要强制A4高度 */
  height: auto !important;
  margin: 0 auto !important;
  margin-bottom: 0 !important;
  padding: 0 10mm !important;
  background: #fff !important;
  box-sizing: border-box !important;
  max-width: 100% !important;
  overflow: visible !important;
  /* 清除底部多余留白 */
  margin-block-end: 0 !important;
}

/* PDF全局强制换行,解决文字截断 */
.print-container.pdf-mode * {
  word-break: break-all !important;
  overflow-wrap: break-word !important;
  white-space: normal !important;
}

/* PDF表格自适应,铺满宽度,取消滚动 */
.print-container.pdf-mode table,
.print-container.pdf-mode .vxe-table {
  table-layout: fixed !important;
  width: 100% !important;
}

.print-container.pdf-mode .vxe-table .vxe-cell {
  white-space: normal !important;
  overflow: visible !important;
  text-overflow: unset !important;
}

/* PDF模式取消表格固定高度,自动撑开,解决底部空白 */
.print-container.pdf-mode [style*='height'] {
  height: auto !important;
}

/* 打印隐藏操作按钮 */
.print-container.pdf-mode .look-btn {
  display: none !important;
}

/* 打印背景色渲染 */
@media print {
  body {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
  }
}
</style>
相关推荐
Sour1 小时前
产品说明书翻译成英文怎么保留排版?PDF、Word、外贸资料处理清单
pdf·word
liuniansilence1 小时前
一次讲透 H5 唤起原生 App:Scheme 协议、平台限制与生产级封装
前端·javascript
heiyehk1 小时前
我用AI不断迭代到7W行代码的项目-像素画编辑器
前端·架构·设计
weedsfly1 小时前
前端懒加载全家桶:图片、路由、组件的按需加载
前端·javascript·面试
千帆_Evan1 小时前
🤖 AI 写代码总在重复造轮子?我们把 Utils 复用门禁做进了 Agent 运行时(已开源)
前端·agent
Kel1 小时前
多 Tab 聊天不翻车:纯前端实现跨标签页消息同步的三层协同架构
前端·javascript·人工智能
半个落月1 小时前
Promise 入门:理解 async/await 与 Promise.all
前端·javascript
廖菌1 小时前
DATASPACE3D 产品介绍:面向数字孪生与 Web3D 场景的一体化可视化搭建平台
前端·webgl
敖行客 Allthinker1 小时前
前端调试实战:DevTools调试工具 + 二分定位Bug法全总结
前端·typescript·chrome devtools