Vue2 / Vue3 使用自定义配置,实现打印+生成PDF的带水印文件

文章目录

  • 1. 自定义配置
    • 1.1 注意事项
    • 1.2 去除默认带入的页眉页脚 / 禁止跨行分页
    • 1.3 源代码
  • 2 效果图
  • 3. Vue2 示例(包含源码)
    • 3.1 展示页面(需被打印 / 生成PDF的页码)
    • 3.2 被打印/生成PDF页面
  • 4. Vue3 示例(包含源码)
    • 4.1 展示页面(需被打印 / 生成PDF的页面)
    • 4.2 被打印/生成PDF页面

1. 自定义配置

1.1 注意事项

  • 此方法是调用打印机方法,需要自己选择导出为PDF。
  • 被导出的PDF转为Docx后,内容支持修改。
  • 被打印页面最外层容器禁止使用 overflow 属性。
  • 涉及 echarts 时,使用 getDataURL() 方法将 echarts 转换为图片展示。
  • 使用此方法需要额外写一份被打印页面的内容。
  • 水印的文字长度需满足 can.width = ??; 的长度,比如水印长度文字长度为 320,则can.width = 320;,如果文字长度未占满水印容器长度的话,导出的PDF转成 Docx 后,水印会被转换未图片,可以被删除。
  • 如果使用了 el-table,打印/导出时需使用 table 写,否则会出现样式错乱、不显示 el-table 等问题
  • 为强制分页

1.2 去除默认带入的页眉页脚 / 禁止跨行分页


@media print {
  tr {
    page-break-inside: avoid; /* 禁止跨行分页 */
  }
}

1.3 源代码

usePrint.js

// 打印类属性、方法定义
// import FileSaver from "file-saver";
// import htmlDocx from "html-docx-fixed/dist/html-docx";
const Print = function (dom, options) {
  options = options || {};
  if (!(this instanceof Print)) return new Print(dom, options);
  this.conf = {
    isAllStyle: false, // 是否加载当前页面所有样式
    styleStr: "", // 设置样式字符串
    setDomHeightArr: [], // 需要动态获取并设置高度的 元素
    echartDomArr: [], // echart dom
    donotPrint: false, // 仅获取到dom
    donotPrintCallback: null, // 仅返回dom
    fileName: "", // 导出文件名称
    documentTitle: "", // 浏览器页签标题(与 fileName 属性配合使用)
    printBeforeFn: null, // 打印前回调
    printDoneCallBack: null, // 打印后回调
  };
  for (const key in this.conf) {
    if (key && options.hasOwnProperty(key)) {
      this.conf[key] = options[key];
    }
  }
  if (typeof dom === "string") {
    this.dom = document.querySelector(dom);
  } else {
    this.dom = this.isDOM(dom) ? dom : dom.$el;
  }
  if (this.conf.setDomHeightArr && this.conf.setDomHeightArr.length) {
    this.setDomHeight(this.conf.setDomHeightArr);
  }
  this.init();
};

Print.prototype = {
  /**
   * 初始化
   */
  init: function () {
    var content = this.getStyle() + this.getHtml();
    this.writeIframe(content);
  },
  /**
   * 配置属性扩展
   * @param {Object} obj
   * @param {Object} obj2
   */
  extendOptions: function (obj, obj2) {
    for (var k in obj2) {
      obj[k] = obj2[k];
    }
    return obj;
  },
  /**
     复制原网页所有的样式
     */
  getStyle: function () {
    var str = "";
    if (this.conf.isAllStyle) {
      let styles = document.querySelectorAll("style,link");
      for (var i = 0; i < styles.length; i++) {
        str += styles[i].outerHTML;
      }
    }
    // .no-print 选定不显示的区域
    // .containBg 主要配合实现打印水印,能让背景正常显示
    str += ``;
    return str;
  },
  // 表单赋值
  getHtml: function () {
    var inputs = document.querySelectorAll("input");
    var textareas = document.querySelectorAll("textarea");
    var selects = document.querySelectorAll("select");
    // debugger
    for (var k = 0; k < inputs.length; k++) {
      if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
        if (inputs[k].checked == true) {
          inputs[k].setAttribute("checked", "checked");
        } else {
          inputs[k].removeAttribute("checked");
        }
      } else if (inputs[k].type == "text") {
        inputs[k].setAttribute("value", inputs[k].value);
      } else {
        inputs[k].setAttribute("value", inputs[k].value);
      }
    }

    for (var k2 = 0; k2 < textareas.length; k2++) {
      if (textareas[k2].type == "textarea") {
        textareas[k2].innerHTML = textareas[k2].value;
      }
    }

    for (var k3 = 0; k3 < selects.length; k3++) {
      if (selects[k3].type == "select-one") {
        var child = selects[k3].children;
        for (var i in child) {
          if (child[i].tagName == "OPTION") {
            if (child[i].selected == true) {
              child[i].setAttribute("selected", "selected");
            } else {
              child[i].removeAttribute("selected");
            }
          }
        }
      }
    }
    /** 分页符,导出为word时可以在需要分页的地方加入 
分页符
* 来进行分页操作 【page-break-after是无效的】,由于浏览器引擎会自动转成 break-(before|after): page所以获取到后得转换回来 * */
let resDOM = this.dom.innerHTML; resDOM = resDOM.replace( /break-(before|after):\s*page/g, `page-break-$1: always` ); return resDOM; }, /** 创建iframe */ writeIframe: function (content) { // 方法二: var _this = this; var w, doc, iframe = document.createElement("iframe"), f = document.body.appendChild(iframe); if (_this.conf.fileName) { document.title = _this.conf.fileName; } iframe.id = "myIframe"; iframe.setAttribute( "style", "position:absolute;width:0;height:0;top:-10px;left:-10px;" ); w = f.contentWindow || f.contentDocument; doc = f.contentDocument || f.contentWindow.document; doc.open(); doc.write(`
${content}
`
); doc.close(); iframe.onload = function () { if (_this.conf.donotPrint) { _this.conf.donotPrintCallback && _this.conf.donotPrintCallback({ doc }); // let converted = htmlDocx.asBlob(doc.documentElement.outerHTML); // FileSaver.saveAs(converted, `${_this.conf.fileName || "test"}.doc`); } else { // 弹出前,回调 if (_this.conf.printBeforeFn) { _this.conf.printBeforeFn({ doc }); } _this.drawEchartImg(doc).then(() => { _this.toPrint(w); setTimeout(function () { document.body.removeChild(iframe); document.title = _this.conf.documentTitle // 弹出后,回调 if (_this.conf.printDoneCallBack) { _this.conf.printDoneCallBack(); } }, 100); }); } }; }, /** * 项目用到echarts,需要获取图片,来打印 * @param {Object} doc iframe window */ drawEchartImg (doc) { return new Promise((resolve, reject) => { if (this.conf.echartDomArr && this.conf.echartDomArr.length > 0) { this.conf.echartDomArr.forEach((e) => { const dom = doc.querySelector("#" + e.$el.id); const img = new Image(); const w = dom.offsetWidth + "px"; const H = dom.offsetHeight + "px"; img.style.width = w; img.style.height = H; img.src = e.imgSrc; dom.innerHTML = ""; dom.appendChild(img); }); } resolve(); }); }, /** 打印 */ toPrint: function (frameWindow) { try { setTimeout(function () { frameWindow.focus(); try { if (!frameWindow.document.execCommand("print", false, null)) { frameWindow.print(); } } catch (e) { frameWindow.print(); } frameWindow.close(); }, 10); } catch (err) { console.log("err", err); } }, isDOM: typeof HTMLElement === "object" ? function (obj) { return obj instanceof HTMLElement; } : function (obj) { return ( obj && typeof obj === "object" && obj.nodeType === 1 && typeof obj.nodeName === "string" ); }, /** * 设置指定dom元素高度,通过获取该dom元素现有高度,并设置 * @param {Array} arr */ setDomHeight (arr) { if (arr && arr.length) { arr.forEach((name) => { const domArr = document.querySelectorAll(name); // debugger domArr.forEach((dom) => { dom.style.height = dom.offsetHeight + "px"; }); }); } }, }; export function usePrint (dom, options) { Print(dom, options); }

2 效果图

  • Vue2 / Vue3 效果图都是这个,代码也是一样的,只是代码写法不一样。
    Vue2 / Vue3 使用自定义配置,实现打印+生成PDF的带水印文件_第1张图片
  • *点击 打印 / 导出PDF 按钮后的效果图
    Vue2 / Vue3 使用自定义配置,实现打印+生成PDF的带水印文件_第2张图片

3. Vue2 示例(包含源码)

3.1 展示页面(需被打印 / 生成PDF的页码)

<template>
  <div class="container">
    <div class="print-btn-class">
      <el-button type="primary" @click="printPDF">打印/导出PDF</el-button>
    </div>
    <div class="echarts_class" id="echarts_line"></div>
    <el-form ref="form" :model="ruleForm" label-width="80px">
      <el-form-item label="活动名称">
        <el-input v-model="ruleForm.name"></el-input>
      </el-form-item>
      <el-form-item label="活动区域">
        <el-select v-model="ruleForm.region" placeholder="请选择活动区域">
          <el-option label="区域一" value="shanghai"></el-option>
          <el-option label="区域二" value="beijing"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="活动时间">
        <el-col :span="11">
          <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm.date1" style="width: 100%;"></el-date-picker>
        </el-col>
        <el-col class="line" :span="2">-</el-col>
        <el-col :span="11">
          <el-time-picker placeholder="选择时间" v-model="ruleForm.date2" style="width: 100%;"></el-time-picker>
        </el-col>
      </el-form-item>
      <el-form-item label="即时配送">
        <el-switch v-model="ruleForm.delivery"></el-switch>
      </el-form-item>
    </el-form>
    <div class="echarts_class" id="echarts_bar"></div>
    <el-form ref="form" :model="ruleForm1" label-width="80px">
      <el-form-item label="活动名称">
        <el-input v-model="ruleForm1.name"></el-input>
      </el-form-item>
      <el-form-item label="活动区域">
        <el-select v-model="ruleForm1.region" placeholder="请选择活动区域">
          <el-option label="区域一" value="shanghai"></el-option>
          <el-option label="区域二" value="beijing"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="活动时间">
        <el-col :span="11">
          <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm1.date1" style="width: 100%;"></el-date-picker>
        </el-col>
        <el-col class="line" :span="2">-</el-col>
        <el-col :span="11">
          <el-time-picker placeholder="选择时间" v-model="ruleForm1.date2" style="width: 100%;"></el-time-picker>
        </el-col>
      </el-form-item>
      <el-form-item label="即时配送">
        <el-switch v-model="ruleForm1.delivery"></el-switch>
      </el-form-item>
    </el-form>
    <el-table :data="tableData" border style="width: 100%">
      <el-table-column prop="date" label="日期" width="180">
      </el-table-column>
      <el-table-column prop="name" label="姓名" width="180">
      </el-table-column>
      <el-table-column prop="address" label="地址">
      </el-table-column>
    </el-table>
    <!-- 打印/导出 -->
    <printEchartsFomItem style="display: none;" ref="print_echarts_fomItemRef" :echarts_line_image="echarts_line_image" :echarts_bar_image="echarts_bar_image">
    </printEchartsFomItem>
  </div>
</template>

<script>
import * as echarts from 'echarts';
import { usePrint } from '@/utils/usePrint'
import printEchartsFomItem from './components/print_echarts_fomItem.vue'
export default {

  name: '',

  props: {},

  components: {
    printEchartsFomItem
  },

  data () {
    return {
      ruleForm: {
        name: '王小虎',
        region: '',
        date1: '2016-05-02',
        date2: '',
        delivery: false,
        type: [],
        resource: '',
        desc: ''
      },
      ruleForm1: {
        name: '',
        region: '',
        date1: '',
        date2: '',
        delivery: false,
        type: [],
        resource: '',
        desc: ''
      },
      echartsLine: null,
      echartsBar: null,
      echarts_line_image: null,
      echarts_bar_image: null,
      tableData: [{
        date: '2016-05-04',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1517 弄'
      }, {
        date: '2016-05-01',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1519 弄'
      }, {
        date: '2016-05-03',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1516 弄'
      }]
    }
  },

  computed: {},

  watch: {},

  created () { },

  mounted () {
    this.echartsLine = echarts.init(document.getElementById('echarts_line'));

    let optionLine = {
      xAxis: {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          data: [150, 230, 224, 218, 135, 147, 260],
          type: 'line'
        }
      ]
    };

    optionLine && this.echartsLine.setOption(optionLine);

    // ------------------------------------------------------------------

    this.echartsBar = echarts.init(document.getElementById('echarts_bar'));

    let optionBar = {
      xAxis: {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          data: [150, 230, 224, 218, 135, 147, 260],
          type: 'bar'
        }
      ]
    };

    optionBar && this.echartsBar.setOption(optionBar);
  },

  methods: {
    printPDF () {
      // pixelRatio 放大倍数
      this.echarts_line_image = this.echartsLine.getDataURL({ type: "png", pixelRatio: 1, backgroundColor: "#fff" });
      this.echarts_bar_image = this.echartsBar.getDataURL({ type: "png", pixelRatio: 1, backgroundColor: "#fff" });
      this.$nextTick(() => {
        usePrint(this.$refs.print_echarts_fomItemRef, { isAllStyle: true, fileName: '打印_导出表', documentTitle: document.title, printDoneCallBack: this.printCompleteCallBack() })
      })
    },
    printCompleteCallBack () {
      console.log('打印完成/取消');
    }
  },
}
</script>

<style scoped lang="less">
.container {
  height: 100%;
  overflow: auto;
  .echarts_class {
    width: 100%;
    height: 300px;
  }
  .print-btn-class {
    width: 100%;
    text-align: center;
  }
}
</style>

3.2 被打印/生成PDF页面

print_echarts_fomItem.vue

<template>
  <div class="container reportPage-content" id="containerId">
    <div v-waterMarker="{ text:'水印水印水印水印水印' }" class="containBg waterBox">
      <img class="echarts_img_class" :src="echarts_line_image" alt="">
      <el-form ref="form" :model="ruleForm" label-width="80px">
        <el-form-item label="活动名称">
          <el-input v-model="ruleForm.name"></el-input>
        </el-form-item>
        <el-form-item label="活动区域">
          <el-select v-model="ruleForm.region" placeholder="请选择活动区域">
            <el-option label="区域一" value="shanghai"></el-option>
            <el-option label="区域二" value="beijing"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="活动时间">
          <el-col :span="11">
            <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm.date1" style="width: 100%;"></el-date-picker>
          </el-col>
          <el-col class="line" :span="2">-</el-col>
          <el-col :span="11">
            <el-time-picker placeholder="选择时间" v-model="ruleForm.date2" style="width: 100%;"></el-time-picker>
          </el-col>
        </el-form-item>
        <el-form-item label="即时配送">
          <el-switch v-model="ruleForm.delivery"></el-switch>
        </el-form-item>
        <el-form-item label="活动性质">
          <el-checkbox-group v-model="ruleForm.type">
            <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
            <el-checkbox label="地推活动" name="type"></el-checkbox>
            <el-checkbox label="线下主题活动" name="type"></el-checkbox>
            <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>
      
      <!-- 分页符 -->
      <!-- <div style="page-break-before:always;"></div> -->

      <img class="echarts_img_class" :src="echarts_bar_image" alt="">
      <el-form ref="form" :model="ruleForm1" label-width="80px">
        <el-form-item label="活动名称">
          <el-input v-model="ruleForm1.name"></el-input>
        </el-form-item>
        <el-form-item label="活动区域">
          <el-select v-model="ruleForm1.region" placeholder="请选择活动区域">
            <el-option label="区域一" value="shanghai"></el-option>
            <el-option label="区域二" value="beijing"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="活动时间">
          <el-col :span="11">
            <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm1.date1" style="width: 100%;"></el-date-picker>
          </el-col>
          <el-col class="line" :span="2">-</el-col>
          <el-col :span="11">
            <el-time-picker placeholder="选择时间" v-model="ruleForm1.date2" style="width: 100%;"></el-time-picker>
          </el-col>
        </el-form-item>
        <el-form-item label="即时配送">
          <el-switch v-model="ruleForm1.delivery"></el-switch>
        </el-form-item>
        <el-form-item label="活动性质">
          <el-checkbox-group v-model="ruleForm1.type">
            <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
            <el-checkbox label="地推活动" name="type"></el-checkbox>
            <el-checkbox label="线下主题活动" name="type"></el-checkbox>
            <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>
      <div v-for="(item, index) in 1" :key="index">
        <table id="data-table">
          <!-- <colgroup>
          <col v-for="(th, indexH) in tableColumn" :key="indexH" style="width: 10%;">
        </colgroup> -->
          <thead>
            <tr>
              <th :style="indexH < 2 ? 'width:180px' : ''" v-for="(th, indexH) in tableColumn" :key="indexH">{{ th.label }}</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(tr, indexR) in tableData" :key="indexR">
              <td v-for="(th, indexH) in tableColumn" :key="indexH">{{ tr[th.key] || '/' }}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

<script>
export default {

  name: 'print_echarts_fomItem',

  props: {
    echarts_line_image: {},
    echarts_bar_image: {},
  },

  components: {},

  data () {
    return {
      ruleForm: {
        name: '王小虎',
        region: '',
        date1: '2016-05-02',
        date2: '',
        delivery: false,
        type: [],
        resource: '',
        desc: ''
      },
      ruleForm1: {
        name: '',
        region: '',
        date1: '',
        date2: '',
        delivery: false,
        type: [],
        resource: '',
        desc: ''
      },
      tableColumn: [
        { label: '日期', key: 'date', },
        { label: '姓名', key: 'name', },
        { label: '地址', key: 'address', },
      ],
      tableData: [
        {
          date: '2016-05-04',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1517 弄'
        }, {
          date: '2016-05-01',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1519 弄'
        }, {
          date: '2016-05-03',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1516 弄'
        }, {
          date: '2016-05-03',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1516 弄'
        }
      ]
    }
  },

  computed: {},

  watch: {},

  directives: {
    waterMarker: (el, binding, vnode) => {
      vnode.context.addWaterMarker(
        binding.value.text,
        el,
        binding.value.font,
        binding.value.textColor)
    }
  },

  created () { },

  mounted () {
    this.preventWatermarkRemoval()
  },

  methods: {
    addWaterMarker (str, parentNode, font, textColor) {
      // 水印文字,父元素,字体,文字颜色
      let can = document.createElement("canvas");
      parentNode.appendChild(can);
      can.width = 320;
      can.height = 250;
      can.style.display = "none";
      let cans = can.getContext("2d");
      cans.rotate((-20 * Math.PI) / 180);
      cans.font = font || "24px Microsoft";
      cans.fillStyle = textColor || "rgba(180, 180, 180, 0.7)";
      cans.textAlign = "left";
      cans.textBaseline = "Middle";
      cans.fillText(str, can.width / 10, can.height / 2);
      parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";
    },

    // 阻止通过控制台去除水印
    preventWatermarkRemoval () {
      // 获取需要观察的节点
      const target = document.querySelector('.container');
      // 观察器配置
      const config = {
        attributes: true, // 监听目标元素属性的变化
        childList: true, // 监听目标原型子节点的变化
        subtree: false // 是否观察后代的变化。默认false
      };
      // 创建观察器
      const observer = new MutationObserver((abc) => {
        // console.log(abc);
        // 获取背景图片
        const bgi = target?.style?.backgroundImage;
        if (!bgi) {
          // 当背景被取消后,重新添加
          this.initWatermark();
        }
      });
      // 开始观察
      observer.observe(target, config);
      // 停止观察
      observer.disconnect()
    }
  },
}
</script>

<style scoped lang="less">
.waterBox {
  min-height: 100%;
  padding: 12px;
}
.echarts_img_class {
  width: 100%;
  height: 300px;
  display: inline-block;
}
table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse !important;
  th,
  td {
    height: 48px;
    min-height: 48px;
    box-sizing: border-box;
    padding: 8px;
    text-align: center;
    border: 1px solid rgba(128, 128, 128, 0.8);
  }
  &:not(:first-of-type) {
    border-top: none;
  }
}
</style>

4. Vue3 示例(包含源码)

4.1 展示页面(需被打印 / 生成PDF的页面)

<template>
  <div class="container">
    <el-button type="primary" @click="printPDF">打印/导出PDF</el-button>
    <div class="echarts_class" id="echarts_line"></div>
    <el-form ref="ruleFormRef" :model="ruleForm" label-width="80px">
      <el-form-item label="活动名称">
        <el-input v-model="ruleForm.name"></el-input>
      </el-form-item>
      <el-form-item label="活动区域">
        <el-select v-model="ruleForm.region" placeholder="请选择活动区域">
          <el-option label="区域一" value="shanghai"></el-option>
          <el-option label="区域二" value="beijing"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="活动时间">
        <el-col :span="11">
          <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm.date1" style="width: 100%;"></el-date-picker>
        </el-col>
        <el-col class="line" :span="2">-</el-col>
        <el-col :span="11">
          <el-time-picker placeholder="选择时间" v-model="ruleForm.date2" style="width: 100%;"></el-time-picker>
        </el-col>
      </el-form-item>
      <el-form-item label="即时配送">
        <el-switch v-model="ruleForm.delivery"></el-switch>
      </el-form-item>
      <el-form-item label="活动性质">
        <el-checkbox-group v-model="ruleForm.type">
          <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
          <el-checkbox label="地推活动" name="type"></el-checkbox>
          <el-checkbox label="线下主题活动" name="type"></el-checkbox>
          <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
        </el-checkbox-group>
      </el-form-item>
    </el-form>
    <div class="echarts_class" id="echarts_bar"></div>
    <el-form ref="ruleForm1Ref" :model="ruleForm1" label-width="80px">
      <el-form-item label="活动名称">
        <el-input v-model="ruleForm1.name"></el-input>
      </el-form-item>
      <el-form-item label="活动区域">
        <el-select v-model="ruleForm1.region" placeholder="请选择活动区域">
          <el-option label="区域一" value="shanghai"></el-option>
          <el-option label="区域二" value="beijing"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="活动时间">
        <el-col :span="11">
          <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm1.date1" style="width: 100%;"></el-date-picker>
        </el-col>
        <el-col class="line" :span="2">-</el-col>
        <el-col :span="11">
          <el-time-picker placeholder="选择时间" v-model="ruleForm1.date2" style="width: 100%;"></el-time-picker>
        </el-col>
      </el-form-item>
      <el-form-item label="即时配送">
        <el-switch v-model="ruleForm1.delivery"></el-switch>
      </el-form-item>
      <el-form-item label="活动性质">
        <el-checkbox-group v-model="ruleForm1.type">
          <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
          <el-checkbox label="地推活动" name="type"></el-checkbox>
          <el-checkbox label="线下主题活动" name="type"></el-checkbox>
          <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
        </el-checkbox-group>
      </el-form-item>
    </el-form>
    <el-table :data="tableData" border style="width: 100%">
      <el-table-column prop="date" label="日期" width="180">
      </el-table-column>
      <el-table-column prop="name" label="姓名" width="180">
      </el-table-column>
      <el-table-column prop="address" label="地址">
      </el-table-column>
    </el-table>
    <!-- 打印/导出 -->
    <printEchartsFomItem style="display: none;" ref="print_echarts_fomItemRef" id="printBox" :echarts_line_image="echarts_line_image"
      :echarts_bar_image="echarts_bar_image"></printEchartsFomItem>
  </div>
</template>

<script setup name="setup">
import { nextTick, onMounted, ref } from 'vue'
import * as echarts from 'echarts';
import printEchartsFomItem from './components/printEchartsFomItem.vue'
import { usePrint } from '@/utils/usePrint'

const ruleForm = ref(
  {
    name: '活动名称',
    region: '',
    date1: '2016-05-02',
    date2: '',
    delivery: false,
    type: [],
    resource: '',
    desc: ''
  }
)
const ruleForm1 = ref(
  {
    name: '',
    region: '',
    date1: '',
    date2: '',
    delivery: false,
    type: [],
    resource: '',
    desc: ''
  }
)
const echartsLine = ref()
const echartsBar = ref()
const echarts_line_image = ref()
const echarts_bar_image = ref()

const tableData = ref([{
  date: '2016-05-04',
  name: '王小虎',
  address: '上海市普陀区金沙江路 1517 弄'
}, {
  date: '2016-05-01',
  name: '王小虎',
  address: '上海市普陀区金沙江路 1519 弄'
}, {
  date: '2016-05-03',
  name: '王小虎',
  address: '上海市普陀区金沙江路 1516 弄'
}])

onMounted(() => {
  echartsLine.value = echarts.init(document.getElementById('echarts_line'));

  let optionLine = {
    xAxis: {
      type: 'category',
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    yAxis: {
      type: 'value'
    },
    series: [
      {
        data: [150, 230, 224, 218, 135, 147, 260],
        type: 'line'
      }
    ]
  };

  optionLine && echartsLine.value.setOption(optionLine);

  // ------------------------------------------------------------------

  echartsBar.value = echarts.init(document.getElementById('echarts_bar'));

  let optionBar = {
    xAxis: {
      type: 'category',
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    yAxis: {
      type: 'value'
    },
    series: [
      {
        data: [150, 230, 224, 218, 135, 147, 260],
        type: 'bar'
      }
    ]
  };

  optionBar && echartsBar.value.setOption(optionBar);
})

// 打印
const print_echarts_fomItemRef = ref()
const printPDF = () => {
  // pixelRatio 放大倍数
  echarts_line_image.value = echartsLine.value.getDataURL({ type: "png", pixelRatio: 1, backgroundColor: "#fff" });
  echarts_bar_image.value = echartsBar.value.getDataURL({ type: "png", pixelRatio: 1, backgroundColor: "#fff" });
  nextTick(() => {
    usePrint(print_echarts_fomItemRef.value, { isAllStyle: true, fileName:'打印_导出表', documentTitle: document.title, printDoneCallBack: printCompleteCallBack })
  })
}
const printCompleteCallBack = () =>{
  console.log('打印完成/取消');
}
</script>

<style scoped lang="less">
.container {
  height: 100%;
  overflow: auto;
  .echarts_class {
    width: 100%;
    height: 300px;
  }
  .print-btn-class {
    width: 100%;
    text-align: center;
  }
}
</style>

4.2 被打印/生成PDF页面

print_echarts_fomItem.vue

<template>
  <div class="reportPage-content">
    <div v-waterMarker="{ text:'水印水印水印水印水印' }" class="containBg waterBox">
      <img class="echarts_img_class" :src="echarts_line_image" alt="">
      <el-form ref="ruleFormRef" :model="ruleForm" label-width="80px">
        <el-form-item label="活动名称">
          <el-input v-model="ruleForm.name"></el-input>
        </el-form-item>
        <el-form-item label="活动区域">
          <el-select v-model="ruleForm.region" placeholder="请选择活动区域">
            <el-option label="区域一" value="shanghai"></el-option>
            <el-option label="区域二" value="beijing"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="活动时间">
          <el-col :span="11">
            <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm.date1" style="width: 100%;"></el-date-picker>
          </el-col>
          <el-col class="line" :span="2">-</el-col>
          <el-col :span="11">
            <el-time-picker placeholder="选择时间" v-model="ruleForm.date2" style="width: 100%;"></el-time-picker>
          </el-col>
        </el-form-item>
        <el-form-item label="即时配送">
          <el-switch v-model="ruleForm.delivery"></el-switch>
        </el-form-item>
        <el-form-item label="活动性质">
          <el-checkbox-group v-model="ruleForm.type">
            <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
            <el-checkbox label="地推活动" name="type"></el-checkbox>
            <el-checkbox label="线下主题活动" name="type"></el-checkbox>
            <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>

      <!-- 分页符 -->
      <!-- <div style="page-break-before:always;"></div> -->

      <img class="echarts_img_class" :src="echarts_bar_image" alt="">
      <el-form ref="ruleForm1Ref" :model="ruleForm1" label-width="80px">
        <el-form-item label="活动名称">
          <el-input v-model="ruleForm1.name"></el-input>
        </el-form-item>
        <el-form-item label="活动区域">
          <el-select v-model="ruleForm1.region" placeholder="请选择活动区域">
            <el-option label="区域一" value="shanghai"></el-option>
            <el-option label="区域二" value="beijing"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="活动时间">
          <el-col :span="11">
            <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm1.date1" style="width: 100%;"></el-date-picker>
          </el-col>
          <el-col class="line" :span="2">-</el-col>
          <el-col :span="11">
            <el-time-picker placeholder="选择时间" v-model="ruleForm1.date2" style="width: 100%;"></el-time-picker>
          </el-col>
        </el-form-item>
        <el-form-item label="即时配送">
          <el-switch v-model="ruleForm1.delivery"></el-switch>
        </el-form-item>
        <el-form-item label="活动性质">
          <el-checkbox-group v-model="ruleForm1.type">
            <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
            <el-checkbox label="地推活动" name="type"></el-checkbox>
            <el-checkbox label="线下主题活动" name="type"></el-checkbox>
            <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>
      <div v-for="(item, index) in 1" :key="index">
        <table id="data-table">
          <!-- <colgroup>
          <col v-for="(th, indexH) in tableColumn" :key="indexH" style="width: 10%;">
        </colgroup> -->
          <thead>
            <tr>
              <th :style="indexH < 2 ? 'width:180px' : ''" v-for="(th, indexH) in tableColumn" :key="indexH">{{ th.label }}</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(tr, indexR) in tableData" :key="indexR">
              <td v-for="(th, indexH) in tableColumn" :key="indexH">{{ tr[th.key] || '/' }}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

<script setup name="setup">
import { onMounted, ref } from 'vue'

const props = defineProps({
  echarts_line_image: {},
  echarts_bar_image: {},
})

const ruleForm = ref(
  {
    name: '活动名称',
    region: '',
    date1: '',
    date2: '',
    delivery: false,
    type: [],
    resource: '',
    desc: ''
  }
)
const ruleForm1 = ref(
  {
    name: '活动名称',
    region: '',
    date1: '',
    date2: '',
    delivery: false,
    type: [],
    resource: '',
    desc: ''
  }
)

const tableColumn = ref([
  { label: '日期', key: 'date', },
  { label: '姓名', key: 'name', },
  { label: '地址', key: 'address', },
])
const tableData = ref([
  {
    date: '2016-05-04',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1517 弄'
  }, {
    date: '2016-05-01',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1519 弄'
  }, {
    date: '2016-05-03',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1516 弄'
  }, {
    date: '2016-05-03',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1516 弄'
  }
])

onMounted(() => {
  preventWatermarkRemoval()
})

// 自定义指令 -> 添加水印
const vWaterMarker = (el, binding) => {
  // 水印文字,父元素,字体,文字颜色
  let can = document.createElement("canvas");
  el.appendChild(can);
  can.width = 320;
  can.height = 250;
  can.style.display = "none";
  let cans = can.getContext("2d");
  cans.rotate((-20 * Math.PI) / 180);
  cans.font = "24px Microsoft";
  cans.fillStyle = "rgba(180, 180, 180, 0.7)";
  cans.textAlign = "left";
  cans.textBaseline = "Middle";
  cans.fillText(binding.value.text, can.width / 10, can.height / 2);
  el.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";
};

// 阻止通过控制台去除水印
const preventWatermarkRemoval = () => {
  // 获取需要观察的节点
  const target = document.querySelector('.container');
  // 观察器配置
  const config = {
    attributes: true, // 监听目标元素属性的变化
    childList: true, // 监听目标原型子节点的变化
    subtree: false // 是否观察后代的变化。默认false
  };
  // 创建观察器
  const observer = new MutationObserver((e) => {
    // 获取背景图片
    const bgi = target?.style?.backgroundImage;
    if (!bgi) {
      // 当背景被取消后,重新添加
      this.initWatermark();
    }
  });
  // 开始观察
  observer.observe(target, config);
  // 停止观察
  observer.disconnect()
}
</script>

<style scoped lang="less">
.waterBox {
  min-height: 100%;
  padding: 12px;
}
.echarts_img_class {
  width: 100%;
  height: 300px;
  display: inline-block;
}
table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse !important;
  th,
  td {
    height: 48px;
    min-height: 48px;
    box-sizing: border-box;
    padding: 8px;
    text-align: center;
    border: 1px solid rgba(128, 128, 128, 0.8);
  }
  &:not(:first-of-type) {
    border-top: none;
  }
}
@media print {
  @page {
    margin: 0; /* 设置页面边距为 0 */
  }
  body {
    margin: 0; /* 设置 body 边距为 0 */
  }
  /* 隐藏页眉和页脚 */
  header,
  footer {
    display: none;
  }
}
</style>

你可能感兴趣的:(pdf,vue.js,前端)