fabric.js实现可视化签章以及遮罩打印的功能__Vue.js
发布于 21 天前 作者 banyungong 107 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

highlight: github theme: github

前言

业务场景

首先,是因为这样一个需求,我开始尝试使用fabric.js

公司有个项目,是可信电子凭证可视化签章

支持在打开的PDF文件上能随意拖动一个图片到PDF文档的任意位置。能获取当前拖动的图片在PDF文档的x,y坐标。

后来,又一次用到了fabric.js是档案管理平台的项目

文件在线预览的页面,需要增加一个遮罩打印的功能,就是在pdf文件上打上马赛克,遮罩一些内容。不通过安装插件,纯前端技术来解决。在PDF文件的预览区域上,按住鼠标左键然后拖着,可生成马赛克的遮罩打印区域。

图片1.png

图片2.png

了解一门新技术,直接看官网和相关文档。

**fabricjs官网在此:**http://fabricjs.com/

官网首页,写在这样一段话:

Fabric.js is a powerful and simple Javascript HTML5 canvas library

Fabric.js是一个强大而简单的Javascript HTML5 Canvas库

然后看了相关文档,了解到我们能通过使用它实现在canvas上创建,填充图形,给图形填充渐变颜色。组合图形(包括组合图形,图形文字,图片等)等一系列功能。

简单来说,我们可以通过使用Fabric从而以较为简单的方式,实现较为复杂的Canvas功能。

知道和做到之间,有一条天然的鸿沟。

有时,人们了解到前人的经验踩过的坑,但是仍然不可避免的掉进这些坑里。自己掉进这些坑里,再爬出来,才最终学习到这些经验,最终避开这些坑。

快速上手

了解到其基础概览,和应用场景之后,准备快速上手。

在vue项目中引入服务

npm install fabric
import { fabric } from 'fabric'

首先做了一个demo用来实现在pdf预览的区域上拖拽图片。

关于pdf文件预览,之前用的pdf.js基于html的pdf阅读器,从官网下载静态资源,放到项目的static静态资源文件夹里面。

使用pdf.js已经写好的viewer.html页面来预览。

static/pdf/web/viewer.html?file=' + encodeURIComponent(pdf)

使用iframe标签去显示。然后,封装成一个公共工作,在需要的地方,直接调用。

组件代码:

<template>
  <div class="pdf">
    <div class="box-card pdf-viewer">
      <iframe
        :src="'static/pdf/web/viewer.html?file=' + encodeURIComponent(pdf)"
        :height="height"
        width="100%"
        frameborder="0"
      ></iframe>
    </div>
  </div>
</template>
<script>
export default {
  name: "PdfDetail",
  components: {},
  props: {
    pdf: {
      type: String,
      default: "",
    },
    height: {
      type: Number,
      default: 560
    }
  },
  data() {
    return {};
  },
  watch: {},
  computed: {},
  methods: {},
  created() {},
  mounted() {},
};
</script>
<style scoped>
.wrapper {
}
</style>

这里只是顺带说了一下pdf.js预览的方法,我并没有采用这种方法去实现pdf预览功能。

因为,不仅要预览,还需要将pdf预览区域转换成canvas画布,然后在画布上实现图片拖拽位置的功能,并获取坐标。

我采用的是vue-pdf组件

GitHub地址:

https://github.com/FranckFreiburger/vue-pdf#readme

npm install --save vue-pdf
<template>
  <pdf src="./static/relativity.pdf"></pdf>
</template>
<script>
import pdf from 'vue-pdf'
export default {
  components: {
    pdf
  }
}

核心代码

以下是demo的核心代码

基础方法

// 初始化画布对象
new fabric.Canvas('canvas')
//赋予一个变量,并且添加了双击事件,通过双击事件删除canvas画布上添加的内容
this.canvas = new fabric.Canvas('canvas')
this.canvas.on('mouse:dblclick', (e) => {
    let items = this.canvas.getObjects()
    items = items.filter((item) => item.width > 1 && item.height > 1)
    let itemIdx = items.indexOf(e.target)
    this.canvas.remove(this.canvas.item(itemIdx));
    this.canvas.renderAll();
})

在created钩子函数中,设置fabric的对象拖拽框

fabric.Object.prototype.setControlsVisibility({
      bl: false, // 左下
      br: false, // 右下
      mb: false, // 下中
      ml: false, // 中左
      mr: false, // 中右
      mt: false, // 上中
      tl: false, // 上左
      tr: false, // 上右
      mtr: false, // 旋转控制键
});

通过图片路径,往画布上添加图片的方法

let imgCoord = fabric.Image.fromURL(imgUrl, (img) => {
    img.scale(1).set({
        crossOrigin: 'anonymous',
        left: 0,
        top: 0,
    })
this.canvas.add(img).setActiveObject(img)
})

通过图片对象,往画布上添加图片的方法

let image= new Image()
image.src = imgUrl
image.crossOrigin = 'Anonymous';
image.onload = () => {
      fabric.Image.fromObject(imgl,(img) => {
          img.scale(1).set({
              crossOrigin: 'anonymous',
              left,
              top,
              width,
              height,
              scaleX,
              scaleY,
          })
        this.canvas.add(img).setActiveObject(img)
          this.canvas.renderAll()
      })
  }

除了添加图片,还可以添加文本框,并且设置文字的颜色字体大小等等。

需要注意的是fontSize参数必须为Number类型。

let attributeObject = {
    fill,
    fontFamily,
    fontWeight,
    textAlign,
    lineHeight,
    width,
    splitByGrapheme:true,
    height,
    fontSize:,
    originX: 'center',
    originY: 'center',
}
var obj = new fabric.Textbox(text, attributeObject)
var group = new fabric.Group([obj], {
    left,
    top,
})
this.canvas.add(group)
this.canvas.renderAll()
  • 为了最终拿到画布上所有对象的属性,以及坐标。我将这些属性和坐标,放到了一个json对象数组里面,保存起来。
  • 图片和文字,除了能够拖拽,文字框还要求,能够改变字体颜色大小等等。
  • 我加了字体颜色大小等属性的选择框,做了数据双向绑定。
  • 每当json对象数组改变的时候,我就清空画布上的所有对象,然后从json对象数组里面拿到保存的属性和坐标,在画布上重新渲染。
//清空画布
this.canvas.clear()

获取画布上所有对象的坐标

getImgPosition() {
    if (!this.canvas) return
    this.imgcoordinate = []
    let items = this.canvas.getObjects()
    items = items.filter((item) => item.width > 1 && item.height > 1)
    items.forEach((item, index) => {
        let itemcoord = {
            floorIndex: index,
            tl: {
                x: item.aCoords.tl.x,
                y: item.aCoords.tl.y,
            },
            tr: {
                x: item.aCoords.tr.x,
                y: item.aCoords.tr.y,
            },
            bl: {
                x: item.aCoords.bl.x,
                y: item.aCoords.bl.y,
            },
            br: {
                x: item.aCoords.br.x,
                y: item.aCoords.br.y,
            },
        }
        this.imgcoordinate.push(itemcoord)
    })
    this.xycoordinate = this.imgcoordinate.map((item) => item.tl)
}

添加马赛克

最后说一下,在canvas画布通过fabric.js添加马赛克的方法

首先,初始化canvas画布对象的时候,增加鼠标事件监听的方法

this.canvas = new fabric.Canvas("canvas");
this.canvas.on("mouse:down", function (e) {
  that.mousedown(e);
});
//鼠标抬起事件
this.canvas.on("mouse:up", function (e) {
  that.mouseup(e);
});
// 移动画布事件
this.canvas.on("mouse:move", function (e) {
  that.mousemove(e);
});
  • 鼠标点击mousedown事件时,记录下画布上点的位置,
  • 鼠标移动后抬起mouseup事件时,记录下画布上点的最终位置,
  • 这样,就可以算出鼠标拖拽的矩形的初始位置x,y坐标,以及矩形的宽高。
let mouse = this.canvas.getPointer(e.e);

接下来是最关键的,实现马赛克的方法,这个花了很长时间。

  • 在初始化canvas画布对象的时候,需要通过getContext() 方法返回一个用于在画布上绘图的环境。
  • 然后传一个图片路径imgUrl,通过drawImage画出底图。
  • 具体生成马赛克的方法,在setColor中,是通过context对象的getImageData方法,获取图片数据。根据设置的马赛克方块大小,通过rgb的颜色设置,模糊掉底图上的图片,实现遮罩的效果。
let Img = new Image();
Img.src = imgUrl;
that.bgImage = Img;
Img.onload = () => {
    that.context.drawImage(Img, 0, 0);
    that.context.save();
};

以下,是画矩形方框,并且填充马赛克,最终实现马赛克的方法

drawMake() {
  if(!this.canvas)return;
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  this.context.drawImage(this.bgImage, 0, 0);
  this.context.save();
  // if (this.canvas) this.canvas.clear();
  this.makeList.forEach((item) => {
    let { beginX, beginY, w, h } = item;
    this.makeGrid(beginX, beginY, w, h);
  });
},
makeGrid(beginX, beginY, rectWidth, rectHight) {
  const row = Math.round(rectWidth / this.squareEdgeLength) + 1;
  const column = Math.round(rectHight / this.squareEdgeLength) + 1;
  for (let i = 0; i < row * column; i++) {
    let x = (i % row) * this.squareEdgeLength + beginX;
    let y = parseInt(i / row) * this.squareEdgeLength + beginY;
    this.setColor(x, y);
  }
},
setColor(x, y) {
  const imgData = this.context.getImageData(
    x,
    y,
    this.squareEdgeLength,
    this.squareEdgeLength
  ).data;
  let r = 0,
    g = 0,
    b = 0;
  for (let i = 0; i < imgData.length; i += 4) {
    r += imgData[i];
    g += imgData[i + 1];
    b += imgData[i + 2];
  }
  r = Math.round(r / (imgData.length / 4));
  g = Math.round(g / (imgData.length / 4));
  b = Math.round(b / (imgData.length / 4));
  this.drawRect(
    x,
    y,
    this.squareEdgeLength,
    this.squareEdgeLength,
    `rgb(${r}, ${g}, ${b})`,
    2,
    `rgb(${r}, ${g}, ${b})`
  );
},
drawRect(
  x,
  y,
  width,
  height,
  fillStyle,
  lineWidth,
  strokeStyle,
  globalAlpha
) {
    this.context.beginPath();
    this.context.rect(x, y, width, height);
    this.context.lineWidth = lineWidth;
    this.context.strokeStyle = strokeStyle;
    fillStyle && (this.context.fillStyle = fillStyle);
    globalAlpha && (this.context.globalAlpha = globalAlpha);
    this.context.fill();
    this.context.stroke();
},

除了fabric.js之外,为了实现遮罩打印的功能。还用到了 html2canvas 和 jsPDF的方法,在此不一一赘述,直接放出遮罩打印的组件完整代码。

实现了基本的业务需求之后,我还做了一些优化,譬如撤销和回退的功能。增加了属性设置弹框,通过拖动滑块选择马赛克方块的大小。通过driver.js实现帮助提示,操作指引。

这里,主要是记录了实现业务需求的解决思路,以及踩坑指南。

参考文章

参考了博客园的两篇文章:

Canvas实用库Fabric.js使用手册

https://www.cnblogs.com/aaron911/p/11949928.html

Vue PDF文件预览vue-pdf

https://www.cnblogs.com/steamed-twisted-roll/p/9648255.html

完整代码

遮罩打印的组件,完整代码

<template>
  <div class="wrapper">
    <div class="web-file">
      <!-- 操作栏 -->
      <div class="operate-box nowrap flex justify-between">
        <div class="flex btn-list">
          <div class="page-btn">
            <el-button
              type="default"
              @click="changePdfPage(0)"
              :disabled="currentPage == 1"
              size="mini"
            >
              上一页
            </el-button>
            <div class="page-count">
              <div v-show="pageCount">{{ currentPage }} / {{ pageCount }}</div>
            </div>
            <el-button
              type="default"
              @click="changePdfPage(1)"
              :disabled="currentPage == pageCount"
              size="mini"
            >
              下一页
            </el-button>
          </div>
          <div id="mask-print">
            <el-button type="default" @click="printPdf" icon="el-icon-printer" size="mini">
              打印
            </el-button>
          </div>
          <div id="mask-setting">
            <el-button
              type="default"
              @click="attributeEdit"
              icon="el-icon-setting"
              size="mini"
            >
              遮罩属性设置
            </el-button>
          </div>
          <div id="mask-add">
            <el-button type="default" @click="addMask" size="mini">添加遮罩</el-button>
          </div>
          <el-button type="text" @click="guide" icon="el-icon-info">
            帮助
          </el-button>
        </div>
        <div class="flex btn-list">
          <div id="mask-back">
            <el-button
              type="default"
              @click="stepBack"
              icon="el-icon-arrow-left"
              size="mini"
            >
              回退
            </el-button>
          </div>
          <div id="mask-clear">
            <el-button
              type="danger"
              @click="clearClean"
              icon="el-icon-refresh-left"
              size="mini"
            >
              撤销
            </el-button>
          </div>
          <el-button @click="goBack" size="mini">返回原文页</el-button>
        </div>
      </div>
      <!-- pdf翻页 -->
      <div v-show="pageCount && pageCount > 0"></div>
      <div class="pdf-box" id="pdf" ref="baseMap" v-if="showPdfBoxFlag">
        <!-- pdf预览 -->
        <pdf
          ref="pdf"
          :src="pdfsrc"
          :page="currentPage"
          @num-pages="pageCount = $event"
          @page-loaded="pageLoaded"
          @loaded="loadPdfHandler"
        ></pdf>
        <div
          class="manager_detail"
          id="manager_detail"
          :class="{ 'accurate-choice': accurateChoiceFlag }"
        >
          <!-- 画布 -->
          <canvas
            id="canvas"
            ref="imgContent"
            :width="canvasObj.width"
            :height="canvasObj.height"
            style="
              position: absolute;
              width: 100%;
              height: 100%;
              left: 0;
              top: 0;
              cursor: crosshair;
            "
          ></canvas>
        </div>
      </div>
    </div>
    <attribute-set
      :title="attribute.title"
      @close="attribute.show = false"
      @ok="setAttribute"
      v-if="attribute.show"
      ref="attribute"
    />
  </div>
</template>
<script>
import { fabric } from "fabric";
import pdf from "vue-pdf";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
import AttributeSet from "./AttributeSet";
// 引导页的功能
import Driver from "driver.js"; // import driver.js
import "driver.js/dist/driver.min.css"; // import driver.js css
import steps from "./steps";
export default {
  components: {
    pdf,
    AttributeSet,
  },
  props: {
    pdfsrc: {
      type: String,
      default: "",
    }
  },
  data() {
    return {
      canvas: null,
      context: "",
      bgImage: null,
      pageCount: 1, // pdf文件总页数
      currentPage: 1,
      clickFlag: false,
      clickTimer: -1,
      canvasObj: {
        width: 0,
        height: 0,
      },
      json: [],
      basecoordinate: [], //基础坐标数组
      xycoordinate: [], // 左上角的坐标数组
      isMasic: true,
      squareEdgeLength: 20, //马赛克大小
      mouse: {
        started: false,
        x: 0,
        y: 0,
      },
      accurateChoiceFlag: false,
      imgUrl: "",
      // maskPic: "/static/img/fabric/mask.png",
      makeGridObject: {
        beginX: 0,
        beginY: 0,
      },
      makeList: [],
      fillColor: "",
      attribute: {
        show: false,
        title: "属性设置",
        loading: false,
        style: "1", //0代表颜色,1代表马赛克
      },
      driver: null,
      showPdfBoxFlag: false,
    };
  },
  computed: {},
  created() {
    fabric.Object.prototype.setControlsVisibility({
      bl: false, // 左下
      br: false, // 右下
      mb: false, // 下中
      ml: false, // 中左
      mr: false, // 中右
      mt: false, // 上中
      tl: false, // 上左
      tr: false, // 上右
      mtr: false, // 旋转控制键
    });
    this.showPdfBoxFlag = true;
  },
  mounted() {
    this.driver = new Driver({
      //此处为api
      animate: true,
      opacity: 0.5,
      allowClose: false,
      doneBtnText: "完成",
      closeBtnText: "关闭",
      nextBtnText: "下一步",
      prevBtnText: "上一步",
      onReset: (Element) => {
        //这里写逻辑回调
      },
    });
  },
  methods: {
    goBack() {
      this.$emit('back');
    },
    guide() {
      this.driver.defineSteps(steps);
      this.driver.start();
    },
    setAttribute(item) {
      this.attribute.style = item.styleValue;
      this.fillColor = item.styleValue == "0" ? "#fff" : "";
      this.squareEdgeLength = item.maskValue;
      this.drawMake();
    },
    attributeEdit() {
      this.attribute.show = true;
      this.$nextTick(() => {
        let item = {
          styleValue: this.attribute.style,
          maskValue: this.squareEdgeLength,
        };
        this.$refs.attribute.initData(item);
      });
    },
    addMask() {
      this.mouse.started = true;
      this.initCanvasObjAndEvent();
      this.accurateChoiceFlag = true;
    },
    // 返回上一步
    stepBack() {
      if (this.makeList.length > 0) {
        this.makeList.splice(this.makeList.length - 1, 1);
        this.drawMake();
      }
    },
    // 初始化画布对象
    initCanvasObjAndEvent() {
      if (!this.canvas) {
        this.canvas = new fabric.Canvas("canvas");
        let imgContent = this.$refs.imgContent;
        this.context = imgContent.getContext("2d");
        let that = this;
        this.pageTransformedIntoCanvas((pageData, PDF) => {
          let Img = new Image();
          Img.src = pageData;
          that.bgImage = Img;
          Img.onload = () => {
            that.context.drawImage(Img, 0, 0);
            that.context.save();
          };
        });
        this.canvas.on("mouse:down", function (e) {
          that.mousedown(e);
        });
        //鼠标抬起事件
        this.canvas.on("mouse:up", function (e) {
          that.mouseup(e);
        });
        // 移动画布事件
        this.canvas.on("mouse:move", function (e) {
          that.mousemove(e);
        });
      }
    },
    pageLoaded(e) {
      this.currentPage = e;
      this.canvasObj.width = document.getElementById("pdf").offsetWidth;
      this.canvasObj.height = document.getElementById("pdf").offsetHeight;
    },
    // pdf加载时
    loadPdfHandler(e) {
      // this.currentPage = 1; // 加载的时候先加载第一页
    },
    // 改变PDF页码,val传过来区分上一页下一页的值,0上一页,1下一页
    changePdfPage(val) {
      if (this.clickFlag) return;
      this.clickFlag = true;
      this.clickTimer = setTimeout(() => {
        this.clickFlag = false;
      }, 500);
      if (val === 0 && this.currentPage > 1) {
        this.currentPage--;
      }
      if (val === 1 && this.currentPage < this.pageCount) {
        this.currentPage++;
      }
      if(this.canvas)this.canvas.clear();
      this.canvas = null;
      this.showPdfBoxFlag = false;
      this.$nextTick(() =>{
        this.showPdfBoxFlag = true;
      })
    },
    // 鼠标事件
    mousedown(e) {
      if (!this.mouse.started) {
        return false;
      }
      let mouse = this.canvas.getPointer(e.e);
      // this.mouse.started = true;
      let x = mouse.x;
      let y = mouse.y;
      this.mouse = {
        ...this.mouse,
        x,
        y,
      };
      this.makeGridObject = {
        beginX: x,
        beginY: y,
      };
    },
    mousemove(e) {
      if (!this.mouse.started) {
        return false;
      }
      var mouse = this.canvas.getPointer(e.e);
      var w = Math.abs(mouse.x - this.mouse.x),
        h = Math.abs(mouse.y - this.mouse.y);
      if (!w || !h) {
        return false;
      }
    },
    mouseup(e) {
      if (!this.mouse.started) {
        return false;
      }
      this.mouse.started = false;
      this.accurateChoiceFlag = false;
      let endX = e.offsetX;
      let endY = e.offsetY;
      // 马赛克遮罩
      let { beginX, beginY } = this.makeGridObject;
      var mouse = this.canvas.getPointer(e.e);
      var w = Math.abs(mouse.x - beginX),
        h = Math.abs(mouse.y - beginY);
      let obj = {
        beginX,
        beginY,
        w,
        h,
      };
      this.makeList.push(obj);
      this.drawMake();
    },
    pageTransformedIntoCanvas(callback) {
      let that = this;
      html2canvas(document.getElementById("pdf"), {
        allowTaint: true,
        useCORS: true,
      }).then(function (canvas) {
        let contentWidth = canvas.width;
        let contentHeight = canvas.height;
        //竖向打印
        let imgWidth = 595.28;
        let imgHeight = (592.28 / contentWidth) * contentHeight;
        let pageHeight = (contentWidth / 592.28) * 841.89;
        let leftHeight = contentHeight;
        let position = 0;
        if (contentWidth > contentHeight) {
          //横向打印
          imgWidth = 841.89;
          imgHeight = (841.89 / contentWidth) * contentHeight;
        }
        let pageData = canvas.toDataURL("image/jpeg", 1.0);
        let PDF;
        if (contentWidth <= contentHeight) {
          //竖向打印
          PDF = new jsPDF("", "pt", "a4");
        } else {
          //    横向打印
          PDF = new jsPDF("l", "pt", "a4");
        }
        if (leftHeight < pageHeight) {
          PDF.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
        } else {
          while (leftHeight > 0) {
            PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
            leftHeight -= pageHeight;
            position -= 841.89;
            if (leftHeight > 0) {
              PDF.addPage();
            }
          }
        }
        let datauri = PDF.output("dataurlstring");
        let base64 = datauri.split("base64,")[1];
        callback(pageData, PDF);
      });
    },
    drawMake() {
      if(!this.canvas)return;
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.context.drawImage(this.bgImage, 0, 0);
      this.context.save();
      // if (this.canvas) this.canvas.clear();
      this.makeList.forEach((item) => {
        let { beginX, beginY, w, h } = item;
        this.makeGrid(beginX, beginY, w, h);
      });
    },
    clearClean() {
      if (this.canvas) this.canvas.clear();
    },
    printPdf() {
      let base64 = "";
      let datauri = "";
      let that = this;
      this.pageTransformedIntoCanvas((pageData, PDF) => {
        let blob = PDF.output("blob");
        that.print(blob);
      });
    },
    print(blob) {
      var date = new Date().getTime();
      var ifr = document.createElement("iframe");
      ifr.style.frameborder = "no";
      ifr.style.display = "none";
      ifr.style.pageBreakBefore = "always";
      ifr.setAttribute("id", "printPdf" + date);
      ifr.setAttribute("name", "printPdf" + date);
      ifr.src = window.URL.createObjectURL(blob);
      document.body.appendChild(ifr);
      this.doPrint("printPdf" + date);
      window.URL.revokeObjectURL(ifr.src); // 释放URL 对象
    },
    doPrint(val) {
      var ordonnance = document.getElementById(val).contentWindow;
      setTimeout(() => {
        ordonnance.print();
      }, 100);
    },
    makeGrid(beginX, beginY, rectWidth, rectHight) {
      const row = Math.round(rectWidth / this.squareEdgeLength) + 1;
      const column = Math.round(rectHight / this.squareEdgeLength) + 1;
      for (let i = 0; i < row * column; i++) {
        let x = (i % row) * this.squareEdgeLength + beginX;
        let y = parseInt(i / row) * this.squareEdgeLength + beginY;
        this.setColor(x, y);
      }
    },
    setColor(x, y) {
      const imgData = this.context.getImageData(
        x,
        y,
        this.squareEdgeLength,
        this.squareEdgeLength
      ).data;
      let r = 0,
        g = 0,
        b = 0;
      for (let i = 0; i < imgData.length; i += 4) {
        r += imgData[i];
        g += imgData[i + 1];
        b += imgData[i + 2];
      }
      r = Math.round(r / (imgData.length / 4));
      g = Math.round(g / (imgData.length / 4));
      b = Math.round(b / (imgData.length / 4));
      this.drawRect(
        x,
        y,
        this.squareEdgeLength,
        this.squareEdgeLength,
        `rgb(${r}, ${g}, ${b})`,
        2,
        `rgb(${r}, ${g}, ${b})`
      );
    },
    drawRect(
      x,
      y,
      width,
      height,
      fillStyle,
      lineWidth,
      strokeStyle,
      globalAlpha
    ) {
      this.context.beginPath();
      this.context.rect(x, y, width, height);
      this.context.lineWidth = lineWidth;
      if (this.fillColor) {
        fillStyle = this.fillColor;
        strokeStyle = this.fillColor;
      }
      this.context.strokeStyle = strokeStyle;
      fillStyle && (this.context.fillStyle = fillStyle);
      globalAlpha && (this.context.globalAlpha = globalAlpha);
      this.context.fill();
      this.context.stroke();
    },
  },
};
</script>
<style lang="scss" scoped>
.btn-list {
  & > div {
    margin-right: 10px;
  }
  & > div:last-child {
    margin-right: 0;
  }
}
.page-btn {
  text-align: center;
  display: flex;
  align-items: center;
  .page-count {
    min-width: 50px;
    margin: 0 10px;
  }
}
.pdf-box {
  position: relative;
}
.manager_detail {
  position: absolute;
  top: 0;
  left: 0;
}
.web-file {
  width: 65%;
  min-width: 900px;
  margin: 0 auto;
  .pdf-box {
    width: 100%; // height: 55vh;
    overflow: hidden;
  }
}
.accurate-choice {
  cursor: crosshair;
}
</style>

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: OrzR3 原文链接:https://juejin.im/post/6943045923085746189

回到顶部