vue使用Fabric和pdfjs完成合同签章及批注

发布于:2025-05-17 ⋅ 阅读:(19) ⋅ 点赞:(0)

合同签章及批注模块涉及以下核心难点和复杂点,按功能模块归类如下:

效果展示
使用拖拽的方式将签名放入pdf文件,再点击文字批注对文件进行添加批注
在这里插入图片描述

难点及功能点描述

一、PDF渲染与交互

多页动态渲染

使用pdfjs-dist库实现PDF分页渲染

动态创建Canvas元素并根据页面尺寸自动调整布局

处理PDF缩放时的全量重新渲染(sliderChange方法)

滚动定位

通过canvasLayoutTopList记录每页位置

滚动事件监听实现精准页码定位(outViewRun方法)

滚动节流处理防止性能问题

二、电子签章系统

图形交互

使用Fabric.js实现可拖拽签章

自定义控制点图标(删除/缩放按钮)

边界检测防止拖出画布(object:moving事件处理)

状态同步

通过coordinateList维护签章坐标/尺寸/角度等状态

实时同步画布操作与数据模型(getSignatureJson方法)

唯一标识符cacheKey管理对象身份

缩放控制

自定义缩放算法(handleScaling方法)

最小缩放限制防止过度缩小

等比缩放保持图形比例

三、文字批注系统

富文本交互

动态创建可编辑文本框(fabric.IText)

字体缩放时自动调整字号(baseFontSize跟踪)

文本框边界检测与自动换行

复合对象管理

每页仅允许单个批注的限制逻辑

文字与签章的状态混合存储(通过name字段区分)

四、文件处理
PDF文件下载

Blob对象处理二进制流

文件名编码解码处理(content-disposition解析)

内存管理(revokeObjectURL)

签章图片上传

自定义上传组件(el-upload封装)

服务端响应处理(二进制流/JSON判断)

Token鉴权处理

五、性能优化

画布渲染优化

局部渲染(canvas.requestRenderAll())

对象缓存(cacheKey机制)

事件委托减少重复绑定

内存管理

Canvas对象销毁逻辑

Blob URL及时释放

六、业务逻辑复杂性

状态验证

签章必填校验(提交前coordinateList检查)

每页签章/批注数量限制

数据转换

坐标系统转换(屏幕坐标与PDF坐标)

服务端数据结构适配(processSignsData方法)

七、浏览器兼容性

Blob API兼容

多浏览器下载兼容处理(Chrome/Firefox/Edge)

大文件下载内存管理

触摸事件支持

移动端拖拽/缩放适配(未完全实现)

前期准备

引入插件

在文件public下增加一个pdfjs文件夹,且再增加一个build文件夹,其中放pdf.js和pdf.worker.js文件

文末放有pdf.js和pdf.worker.js文件源码
在这里插入图片描述

// pdf插件
import { fabric } from "fabric";
import * as pdfjsLib from "pdfjs-dist/build/pdf";
import workerSrc from "pdfjs-dist/build/pdf.worker.entry";
import * as pdfjsViewer from "pdfjs-dist/web/pdf_viewer";

主要代码,

这是大佬的地址 vue里面使用pdfjs-dist+fabric实现pdf电子签章!!!

在借鉴了另外一篇文章后进行修改的如下,就是做了点修改(ps:目前印章的位置和坐标保存,使用的得本地缓存,便于调试,后期会保存到接口里面!)

<!-- //?模块说明 =>  合同签章模块 addToTab-->
<template>
  <div class="contract-signature-view">
    <div class="section-box">
      <!-- 签章图片 -->

      <!-- <aside class="signature-img">
        <div class="info">
          <h3 class="name">印章</h3>
          <p class="text">将示例印章标识拖到文件相应区域即可获取签章位置</p>
        </div>
      </aside> -->
      <!-- 主体区域 -->
      <div class="main-layout" :class="{ 'is-first': isFirst }">
        <!-- 操作 -->
        <div class="title-operation">
          <div class="operation">
            <el-button
              v-if="$route.query.formst==31"
              class="searchbutton"
              type="primary"
              @click="optionSignature('addSubSign')"
            >
              <svg-icon icon-class="check" />呈签</el-button
            >
            <el-button
              v-else
              class="searchbutton"
              type="primary"
              @click="optionSignature('agree')"
            >
              <svg-icon icon-class="check" />同意</el-button
            >
           
            <!-- <el-button
              class="searchbutton"
              icon="el-icon-close"
              @click="optionSignature('return')"
              >回退</el-button
            > -->

            <!-- <el-button class="searchbutton" @click="download()"
              ><svg-icon icon-class="download" />下载签核文件</el-button
            > -->
            <el-button class="searchbutton" @click="returnSignature"
              ><svg-icon icon-class="return" />返回</el-button
            >
            <el-popover placement="bottom" trigger="click"
              ><template #default>
                <div class="signatureimg" v-if="vissign == true">
                  <!-- 拖拽 -->
                  <draggable
                    v-model="mainImagelist"
                    :group="{ name: 'itext', pull: 'clone' }"
                    :sort="false"
                    @end="end"
                    @move="onMove"
                  >
                    <transition-group type="transition">
                      <li
                        v-for="item in mainImagelist"
                        :key="item.img"
                        class="item"
                        style="text-align: center"
                      >
                        <authImg
                          :authSrc="item.img"
                          alt=""
                          class="img"
                        ></authImg>
                        <!-- <img ref="img" :src="item.img" width="100%;" height="100%" class="img" /> -->
                      </li>
                    </transition-group>
                  </draggable>
                  <div class="opera">
                    <span>请拖拽签名至目标位置</span>
                    <span>
                      <el-button
                        class="delbut"
                        icon="el-icon-delete"
                        @click="deleteSignature"
                        >删除</el-button
                      ></span
                    >
                  </div>
                </div>
                <div v-else class="upimg">
                  <!-- <div style="color: #3f7afa;font-size: 14px;"><svg-icon icon-class="plus" />添加签名</div> -->
                  <el-upload
                    class="upload-demo"
                    action="#"
                    :http-request="customRequest"
                    :on-preview="handlePreview"
                    :on-remove="handleRemove"
                    :before-remove="beforeRemove"
                    :limit="1"
                    accept=".png, .jpg"
                    :on-exceed="handleExceed"
                  >
                    <el-button size="small" class="upload-button">
                      <svg-icon icon-class="plus" /> 添加签名
                    </el-button>
                    <div
                      slot="tip"
                      style="font-size: 10px;padding-top: 10px;color:#00000066;"
                    >
                      支持上传png图片,大小不超过500kb
                    </div>
                  </el-upload>
                </div>
              </template>
              <el-button slot="reference" class="button">
                <svg-icon icon-class="Esign" /> 电子签名</el-button
              >
            </el-popover>

            <!-- <el-button  class="button" @click="addTextobjectHandle($event)" > <svg-icon icon-class="Esign" /> 电子签名</el-button> -->

            <!-- <el-button type="danger" @click="removeSignature">删除签章</el-button>-->

            <!-- <el-button type="primary" @click="submitSignature">提交签章</el-button> -->
            <el-button class="button" @click="addTextobjectHandle($event)">
              <svg-icon icon-class="font-size" /> 文字批注</el-button
            >
            <!-- <el-button type="danger" @click="clearSignature">清空签章</el-button> -->
          </div>
        </div>
        <div class="operate-box">
          <div class="pageNo-change">
            <i class="icon el-icon-arrow-left" @click="prevPage" />
            <el-input
              class="input-box"
              v-model.number="pageNum"
              :max="defaultNumPages"
              @change="cutover"
            />
            <span class="default-text">/{{ defaultNumPages }}</span>
            <i class="icon el-icon-arrow-right" @click="nextPage" />
          </div>
        </div>
        <!-- <div class="container_cont" style="margin-top:1%;position: relative;display: flex;align-items: center;justify-content: center;">
            <button id="prevpage" style=" width: 120px;height: 30px;background: none;border: 1px solid #b1afaf;border-radius: 5px;font-size: 12px;font-weight: 1000;color: #384240;cursor: pointer;outline: none;margin: 0 0.5%">上一页</button>
            <button id="nextpage" style=" width: 120px;height: 30px;background: none;border: 1px solid #b1afaf;border-radius: 5px;font-size: 12px;font-weight: 1000;color: #384240;cursor: pointer;outline: none; margin: 0 0.5%">下一页</button>
        </div> -->

        <!-- 画图 -->
        <div class="out-view" :class="{ 'is-show': isShowPdf }">
          <div class="canvas-layout" v-for="item in numPages" :key="item">
            <!-- pdf部分 -->
            <canvas class="the-canvas" id="box" />
            <!-- 签名部分 -->
            <canvas class="ele-canvas" id="canvas"></canvas>
            <!-- <div id="menu" class="menu-x">
                <div class="menu-li"  @click="removeSignature">删除</div>
            </div> -->

            <!-- <canvas class="text-canvas" id="canvas" ></canvas> -->
          </div>
        </div>
        <i class="loading" v-loading="!isShowPdf" />
      </div>
      <!-- 位置信息 -->
    </div>
    <Dialog
      :isVisible="isVisible"
      :id="id"
      :scale="scale"
      :title="title"
      @closeVisible="closeVisible"
      :signlist="signlist"
    >
    </Dialog>
  </div>
</template>

<script>
import { getToken } from "@/utils/auth";
import { uploadSignture, removeSignture } from "@/api/create/index";
import userApi from "@/api/user";
import Dialog from "./components/Dialog.vue";
import authImg from "@/components/authImg.vue";
import { downloadsignoffApi } from "@/api/common";
// 拖拽插件
import draggable from "vuedraggable";
// pdf插件
import { fabric } from "fabric";
import * as pdfjsLib from "pdfjs-dist/build/pdf";
import workerSrc from "pdfjs-dist/build/pdf.worker.entry";
import * as pdfjsViewer from "pdfjs-dist/web/pdf_viewer";
import { fa, it } from "element-plus/es/locales.mjs";
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;

export default {
  components: { draggable, Dialog, authImg },

  data() {
    return {
      // pdf地址
      pdfUrl: "",
      // 左侧签章列表
      mainImagelist: [],
      // 右侧坐标数据
      coordinateList: [],
      // 总页数
      numPages: 1,
      defaultNumPages: 1,
      // 当前页
      pageNum: 1,
      // 缩放比例
      scale: 1,
      // pdf是否显示
      isFirst: true,
      isShowPdf: false,
      // pdf最外层的out-view
      outViewDom: null,
      // 各页pdf的canvas-layout
      canvasLayoutTopList: [],
      currentDragPosition: {
        x: "",
        y: ""
      },
      // 用来签章的canvas数组
      canvasEle: [],
      // 绘图区域的宽高
      whDatas: null,
      whlist: [],
      // pdf渲染的canvas数组
      canvas: [],
      // pdf渲染的canvas的ctx数组
      ctx: [],
      // pdf渲染的canvas的宽高
      pdfDoc: null,
      // 隐藏的input,用来提交数据
      shadowInputValue: "",
      points: [],
      activeEl: null,
      activeobjectData: {
        type: "textbox",
        text: "文字批注示例",
        fontSize: 20,
        left: 200,
        top: 400,
        width: 100,
        fill: "#000000",
        textBackgroundcolor: "rgba(0,0,0,0)",
        opacity: 1,
        stroke: "#ffffff",
        strokeWidth: 0,
        background: "#7ED321",
        scaleX: 1,
        scaleY: 1,
        fontFamily: 'Microsoft YaHei',
        underline: false,
        linethrough: false,
        overline: false,
        textAlign: "left",
        lineHeight: 1,
        charSpacing: 1,
        cornerColor: "#3B82F6",
        cornerStyle: "circle",
        borderScaleFactor: 2,
        transparentCorners: false,
        rotate: 0,
        selectable: true
      },
      isVisible: false,
      title: "",
      deleteIcon:"data:image/png;base64,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",
      samllIcon:"data:image/png;base64,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",
      largeIcon:"data:image/png;base64,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",
      fileList: [],
      vissign: false,
      signlist: [],
      id: "",
      sts: [],
      baseIp: "",
      userInfo: {}
    };
  },
  created() {
    this.getsignimg();
    this.setPdfArea();
  },

  watch: {
    coordinateList: {
      handler(val) {},
      deep: true
    },
    vissign: {
      handler(val) {},
      deep: true
    }
  },
  mounted() {},
  methods: {
    getsignimg() {
    //初始化判断是否个人信息有绑定自己的签名章,
      userApi.getInfo().then(res => {
        this.userInfo = res.data.data;
        //如果没有则上传后获取图片地址,如果有就直接获取
        if (this.userInfo.signture != null) {
          this.vissign = true;
          const hostname = window.location.hostname;
          this.baseIp =
            "https://xxx.com/mis/api-docs/chairmansoffice/download";
          // /2025/2/F1339892_212757768635974367.png
          if (hostname != "localhost") {
            var origin = window.location.origin;
            this.baseIp = origin + "/mis/api-docs/chairmansoffice/download";
          }

          this.mainImagelist = [
            { name: "印章", img: this.baseIp + this.userInfo.signture }
          ];
        } else {
          this.vissign = false;
        }
      });
    },
    /**
     * pdf相关部分
     */
    // 设置PDF地址
    setPdfArea() {
      //  1. 获取地址栏
      
      //z这里的地址根据用户需求自己更改,不是真实可用地址
      // 获取pdf地址,这里区分是否开发环境和测试环境
      var fileurl = this.$route.query.fileurl;
      
      const hostname = window.location.hostname;
      var baseIp =
        "https://test-xxxx.com/mis/download";
      if (hostname != "localhost") {
        var origin = window.location.origin;
        baseIp = origin + "/mis/download";
      }
      this.baseIp = baseIp;
      this.pdfUrl = baseIp + fileurl;
      // this.pdfUrl ="https://test-xxxx.com/mis/download/2025/2/430956676170020900__2674826190452557870.pdf"
      //可以先定一个固定pdf地址进行测试
      this.showpdf(this.pdfUrl); // 接口返回的应该还有签名信息,不只是pdf

    },
    showsts() {
      if (this.$route.query.sts?.length > 0) {
        this.sts = this.$route.query.sts;
        this.sts.forEach(element => {
          const targetCanvas = this.canvasEle[element.pageNo - 1];
          if (!targetCanvas) return;

          // 创建文本的逻辑封装
          const createAndAddText = () => {
            if (!element.opinion) return;
            
            const scale = element.opscale || 1;
            const fontSize = element.fontsize || 14;
            const textConfig = {
              left: element.yopinion,
              top: element.xopinion,
              width: element.wopinion / scale,
              fontSize: fontSize,
              fill: "#000000",
              scaleX: scale,
              scaleY: scale,
              selectable: false,
              hasControls: false,
              splitByGrapheme: true,
              textBackgroundColor: "rgba(0,0,0,0)",
              fontWeight: (element.fontweight || "normal").toLowerCase()
            };

            const text = new fabric.Textbox(element.opinion, textConfig);
            targetCanvas.add(text);
            targetCanvas.renderAll();
            console.log("Created text:", text, "Config:", textConfig);
          };

          // 创建图片的公共逻辑
          const createAndAddImage = () => {
            fabric.Image.fromURL(this.baseIp + element.fn, img => {
              img.set({
                left: element.ysign,
                top: element.xsign,
                scaleX: element.scale || 1,
                scaleY: element.scale || 1,
                selectable: false
              });
              targetCanvas.add(img);
            });
          };

          // 执行创建操作
          createAndAddText();  // 有opinion才创建
          createAndAddImage(); // 无论有无opinion都创建
        });
      }
    },
    showpdf(pdfUrl) {
      this.canvas = document.querySelectorAll(".the-canvas");
      let currentPage = 1;
      // console.log(getToken(), "-------------");
      pdfjsLib
        .getDocument({
          url: pdfUrl,
          httpHeaders: { Token: getToken() },
          rangeChunkSize: 65536,
          disableAutoFetch: false
        })

        .promise.then(pdfDoc_ => {
          this.pdfDoc = pdfDoc_;
          this.numPages = this.pdfDoc.numPages;
          this.defaultNumPages = this.pdfDoc.numPages;
          this.$nextTick(() => {
            this.canvas = document.querySelectorAll(".the-canvas");
            this.canvas.forEach(item => {
              this.ctx.push(item.getContext("2d"));
            });

            // 循环渲染pdf
            for (let i = 1; i <= this.numPages; i++) {
              this.renderPage(i).then(() => {
                this.renderPdf({
                  width: this.canvas[i - 1].width,
                  height: this.canvas[i - 1].height
                });
              });
            }

            setTimeout(() => {
              this.renderFabric();
              this.canvasEvents();
              
            }, 1000);
          });
        });
    },
    // 设置pdf宽高,缩放比例,渲染pdf
    renderPage(num) {
      return this.pdfDoc.getPage(num).then(pageNo => {
        const viewport = pageNo.getViewport({ scale: this.scale }); // 设置视口大小
        this.canvas[num - 1].height = viewport.height;
        this.canvas[num - 1].width = viewport.width;
        // Render PDF pageNo into canvas context
        const renderContext = {
          canvasContext: this.ctx[num - 1],
          viewport: viewport
        };
        pageNo.render(renderContext);
      });
    },
    // 设置绘图区域宽高
    renderPdf(data) {
      this.whlist.push(data);
      this.whDatas = data;
      // document.querySelector(".elesign").style.width = data.width + "px";
    },
    // 生成绘图区域
    renderFabric() {
      // 1. 拿到全部的canvas-layout
      const canvasLayoutDom = document.querySelectorAll(".canvas-layout");
      // 2. 循环遍历
      canvasLayoutDom.forEach((item, index) => {
        this.canvasLayoutTopList.push({ obj: item, top: item.offsetTop });
        // 3. 设置宽高和居中,根据存放绘制宽高遍历获取
        // item.style.width = this.whDatas.width + "px";
        // item.style.height = this.whDatas.height + "px";
        item.style.width = this.whlist[index].width + "px";
        item.style.height = this.whlist[index].height + "px";
        item.style.margin = "0 auto 18px";
        item.style.boxShadow = "4px 4px 4px #e9e9e9";

        // 4. 拿到签名canvas
        const canvasEle = item.querySelector(".ele-canvas");
        // 5. 拿到pdf的canvas
        const pCenter = item.querySelector(".the-canvas");
        // 6. 设置签名canvas的宽高
        canvasEle.width = pCenter.clientWidth;
        canvasEle.height = this.whlist[index].height;
        // 7. 创建fabric对象并存储
        const canvas = new fabric.Canvas(canvasEle);

        // canvas.on('mouse:up', (e) => {
        //   // 画布鼠标按下事件
        //   this.getSignatureJson();
        // })s
        // .on('object:scaling', (e) => {
        //     // 图形缩放时触发;
        //     console.log(e.transform);
        //   })
        this.canvasEle.push(canvas);

        // 按下鼠标
        const containers = item.querySelectorAll(".canvas-container");
        // 8. 设置签名和text文本输入的canvas的样式
        // containers.forEach(div =>  {
        //     div.style.position = "absolute";
        //     div.style.left = "50%";
        //     div.style.transform = "translateX(-50%)";
        //     div.style.top = "0px";
        //     // console.dir(div);// 找到和class相关的属性
        // })
        const container = item.querySelector(".canvas-container");
        container.style.position = "absolute";
        container.style.left = "50%";
        container.style.transform = "translateX(-50%)";
        container.style.top = "0px";
      });

      // 现形
      this.isFirst = false;
      this.isShowPdf = true;
      this.outViewDom = document.querySelector(".out-view");
      // 开启监听窗口滚动
      this.outViewScroll();
      this.showsts();
    },

    // 开启监听窗口滚动
    outViewScroll() {
      this.outViewDom.addEventListener("scroll", this.outViewRun);
    },
    // 关闭监听窗口滚动
    outViewScrollClose() {
      this.outViewDom.removeEventListener("scroll", this.outViewRun);
    },
    // 窗口滚动
    outViewRun() {
      const scrollTop = this.outViewDom.scrollTop;
      const topList = this.canvasLayoutTopList.map(item => item.top);
      // 增加一个最大值
      topList.push(Number.MAX_SAFE_INTEGER);
      for (let index = 0; index < topList.length; index++) {
        const element = topList[index];
        if (element <= scrollTop && scrollTop < topList[index + 1]) {
          this.pageNum = index + 1;
          break;
        }
      }
    },
    // scale滑块,重新渲染整个pdf
    sliderChange() {
      this.pageNum = 1;
      this.numPages = 0;
      this.canvasLayoutTopList = [];
      this.canvasEle = [];
      this.ctx = [];
      this.canvas = [];
      this.isShowPdf = false;
      // this.outViewScrollClose();
      this.whDatas = null;
      this.coordinateList = [];
      this.getSignatureJson();
      setTimeout(() => {
        this.numPages = this.pdfDoc.numPages;
        this.$nextTick(() => {
          this.canvas = document.querySelectorAll(".the-canvas");
          this.canvas.forEach(item => {
            this.ctx.push(item.getContext("2d"));
          });
          // 循环渲染pdf
          for (let i = 1; i <= this.numPages; i++) {
            this.renderPage(i).then(() => {
              this.renderPdf({
                width: this.canvas[i - 1].width,
                height: this.canvas[i - 1].height
              });
            });
          }
          setTimeout(() => {
            this.renderFabric();
            this.canvasEvents();
            this.showsts();
          }, 1000);
        });
      }, 1000);
    },
    /**
     * 签章相关部分
     */
    // 签章拖拽边界处理,不能将图片拖拽到绘图区域外
    // canvasEvents() {
    //   this.canvasEle.forEach(item => {
    //     item.on("object:moving", e => {
    //       const obj = e.target;
    //       if(e.target!=null){
    //       const top = obj.top;
    //       const left = obj.left;
    //       // if object is too big ignore
    //       if (
    //         obj.currentHeight > obj.canvas.height ||
    //         obj.currentWidth > obj.canvas.width
    //       ) {
    //         return;
    //       }
    //       obj.setCoords();
    //       // top-left  corner
    //       if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
    //         obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top);
    //         obj.left = Math.max(
    //           obj.left,
    //           obj.left - obj.getBoundingRect().left
    //         );
    //       }
    //       // bot-right corner
    //       if (
    //         obj.getBoundingRect().top + obj.getBoundingRect().height >
    //         obj.canvas.height ||
    //         obj.getBoundingRect().left + obj.getBoundingRect().width >
    //         obj.canvas.width
    //       ) {
    //         obj.top = Math.min(
    //           obj.top,
    //           obj.canvas.height -
    //           obj.getBoundingRect().height +
    //           obj.top -
    //           obj.getBoundingRect().top
    //         );
    //         obj.left = Math.min(
    //           obj.left,
    //           obj.canvas.width -
    //           obj.getBoundingRect().width +
    //           obj.left -
    //           obj.getBoundingRect().left
    //         );
    //       }
    //       var findIndex= 0
    //       this.coordinateList.forEach((element,index) => {
    //         if(element.cacheKey == e.target.cacheKey){
    //           findIndex  = index
    //         }
    //       });
    //       console.log(   this.coordinateList[findIndex ],'------------------   this.coordinateList[findIndex + 1]')
    //       // const findIndex = this.coordinateList
    //       //   .slice(1)
    //       //   .findIndex(coord => coord.cacheKey == obj.cacheKey);
    //       const keys = [
    //         "width",
    //         "height",
    //         "top",
    //         "left",
    //         "angle",
    //         "scaleX",
    //         "scaleY"
    //       ];
    //       keys.forEach(item => {
    //         this.coordinateList[findIndex ][item] = Math.ceil(
    //           obj[item] * obj["scaleX"]
    //         );
    //       });
    //       if(this.coordinateList[findIndex + 1].name == '批注'){
    //         this.coordinateList[findIndex + 1].xopinion= JSON.parse(JSON.stringify(top))
    //         this.coordinateList[findIndex + 1].opinion= JSON.parse(JSON.stringify(obj.text))
    //         this.coordinateList[findIndex + 1].fontsize= JSON.parse(JSON.stringify(obj.fontsize))
    //         this.coordinateList[findIndex + 1].yopinion= JSON.parse(JSON.stringify(left))
    //         this.coordinateList[findIndex + 1].wopinion= JSON.parse(JSON.stringify(obj.width))
    //         this.coordinateList[findIndex + 1].hopinion= JSON.parse(JSON.stringify(obj.height))
    //         this.coordinateList[findIndex + 1].opscale= JSON.parse(JSON.stringify(obj.scaleX))
    //       }else{
    //         this.coordinateList[findIndex + 1].name = '印章'
    //         this.coordinateList[findIndex + 1].xsign= JSON.parse(JSON.stringify(top))
    //         this.coordinateList[findIndex + 1].ysign= JSON.parse(JSON.stringify(left))
    //         this.coordinateList[findIndex + 1].wsign= JSON.parse(JSON.stringify(obj.width))
    //         this.coordinateList[findIndex + 1].hsign= JSON.parse(JSON.stringify(obj.height))
    //         this.coordinateList[findIndex + 1].scale= JSON.parse(JSON.stringify(obj.scaleX))
    //       }

    //       this.coordinateList[findIndex + 1].scaleX= JSON.parse(JSON.stringify(obj.scaleX))
    //       this.coordinateList[findIndex + 1].scaleY= JSON.parse(JSON.stringify(obj.scaleY))

    //       // console.log( this.coordinateList[findIndex + 1].top, this.coordinateList[findIndex + 1].left,'----------item')
    //       this.getSignatureJson();
    //     }
    //     });
    //     // item.on('mouse:down', e => {
    //     item.on("mouse:move", e => {
    //       if(e.target!=null){
    //       const obj =JSON.parse(JSON.stringify( e.target));
    //       const top = obj.top;
    //       const left = obj.left;
    //       var findIndex= 0
    //       this.coordinateList.forEach((element,index) => {
    //         if(element.cacheKey == e.target.cacheKey){
    //           findIndex  = index
    //         }
    //       });
    //       console.log(   findIndex,this.coordinateList,e,'------------------   this.coordinateList[findIndex + 1]')
    //       const keys = [
    //         "width",
    //         "height",
    //         "top",
    //         "left",
    //         "angle",
    //         "scaleX",
    //         "scaleY"
    //       ];

    //       keys.forEach(item => {
    //         // console.log(item,  this.coordinateList[findIndex + 1][item],'-------------item')
    //         this.coordinateList[findIndex][item] = Math.ceil(
    //           obj[item] * obj["scaleX"]
    //         );
    //       });
    //       // this.coordinateList[findIndex + 1].width= JSON.parse(JSON.stringify(obj.width))
    //       // this.coordinateList[findIndex + 1].height= JSON.parse(JSON.stringify(obj.height))

    //       if(this.coordinateList[findIndex].name == '批注'){
    //         this.coordinateList[findIndex].xopinion= JSON.parse(JSON.stringify(top))
    //         this.coordinateList[findIndex].yopinion= JSON.parse(JSON.stringify(left))
    //         this.coordinateList[findIndex].fontsize= JSON.parse(JSON.stringify(obj.fontsize))
    //         this.coordinateList[findIndex].opinion= JSON.parse(JSON.stringify(obj.text))
    //         this.coordinateList[findIndex].wopinion= JSON.parse(JSON.stringify(obj.width))
    //         this.coordinateList[findIndex].hopinion= JSON.parse(JSON.stringify(obj.height))
    //         this.coordinateList[findIndex].opscale= JSON.parse(JSON.stringify(obj.scaleX))

    //       }else{
    //         this.coordinateList[findIndex].name = '印章'
    //         this.coordinateList[findIndex].xsign= JSON.parse(JSON.stringify(top))
    //         this.coordinateList[findIndex].ysign= JSON.parse(JSON.stringify(left))
    //         this.coordinateList[findIndex].wsign= JSON.parse(JSON.stringify(obj.width))
    //         this.coordinateList[findIndex].hsign= JSON.parse(JSON.stringify(obj.height))
    //         this.coordinateList[findIndex].scale= JSON.parse(JSON.stringify(obj.scaleX))
    //       }
    //       this.coordinateList[findIndex ].scaleX= JSON.parse(JSON.stringify(obj.scaleX))
    //       this.coordinateList[findIndex].scaleY= JSON.parse(JSON.stringify(obj.scaleY))
    //       // console.log(obj.fontSize, top,left,'--obj.fontsize--------item')
    //       console.log( this.coordinateList[findIndex ],'----------coordinate33List')

    //       this.getSignatureJson();
    //     }
    //     });

    //   });
    // },
    canvasEvents() {
      this.canvasEle.forEach(canvas => {
        canvas.on("object:moving", e => {
          const obj = e.target;
          if (!obj) return;

          // 边界处理
          obj.setCoords(); // 更新控制点坐标
          const canvasWidth = canvas.getWidth();
          const canvasHeight = canvas.getHeight();

          // 限制移动范围(考虑缩放)
          const boundingRect = obj.getBoundingRect();
          const scale = this.scale;

          // 计算有效移动范围
          const minX = 0 - (boundingRect.width * (1 - scale)) / 2;
          const minY = 0 - (boundingRect.height * (1 - scale)) / 2;
          const maxX = canvasWidth - (boundingRect.width * (1 + scale)) / 2;
          const maxY = canvasHeight - (boundingRect.height * (1 + scale)) / 2;

          // 应用位置限制
          obj.left = Math.min(Math.max(obj.left, minX), maxX);
          obj.top = Math.min(Math.max(obj.top, minY), maxY);

          // 更新坐标数据
          const findIndex = this.coordinateList.findIndex(
            item => item.cacheKey === obj.cacheKey
          );
          if (findIndex === -1) return;

          // 计算实际字体大小(核心修改部分)
          const baseFontSize = obj.baseFontSize || obj.fontSize; // 获取基准字号
          const scaleFactor = Math.max(obj.scaleX, obj.scaleY); // 取最大缩放值
          const actualFontSize = Math.round(baseFontSize * scaleFactor);
          // 通用属性更新
          const coordinate = this.coordinateList[findIndex];
          const updateProperty = (key, value) => {
            coordinate[key] = Math.round(value / this.scale);
          };

          updateProperty("left", obj.left);
          updateProperty("top", obj.top);
          updateProperty("width", obj.getScaledWidth());
          updateProperty("height", obj.getScaledHeight());
          coordinate.angle = Math.round(obj.angle);
          coordinate.scaleX = obj.scaleX;
          coordinate.scaleY = obj.scaleY;
          // 特定类型处理
          if (coordinate.name === "批注") {
            coordinate.xopinion = coordinate.top;
            coordinate.yopinion = coordinate.left;
            coordinate.opinion = obj.text;
            coordinate.fontsize = obj.fontSize;
            coordinate.wopinion = coordinate.width;
            coordinate.hopinion = coordinate.height;
            coordinate.opscale = obj.scaleX;
            coordinate.fontsize = actualFontSize;
            obj.set("fontSize", actualFontSize); // 更新画布显示字号
            // 重置缩放比例避免双重缩放
            obj.set({
              scaleX: 1,
              scaleY: 1,
              baseFontSize: actualFontSize // 更新基准字号
            });
          } else {
            coordinate.xsign = coordinate.top;
            coordinate.ysign = coordinate.left;
            coordinate.wsign = coordinate.width;
            coordinate.hsign = coordinate.height;
            coordinate.scale = obj.scaleX;
          }
          canvas.requestRenderAll();
          this.getSignatureJson();
        });
        canvas.on("mouse:move", e => {
          const obj = e.target;
          if (!obj) return;

          // 边界处理
          obj.setCoords(); // 更新控制点坐标
          const canvasWidth = canvas.getWidth();
          const canvasHeight = canvas.getHeight();

          // 限制移动范围(考虑缩放)
          const boundingRect = obj.getBoundingRect();
          const scale = this.scale;

          // 计算有效移动范围
          const minX = 0 - (boundingRect.width * (1 - scale)) / 2;
          const minY = 0 - (boundingRect.height * (1 - scale)) / 2;
          const maxX = canvasWidth - (boundingRect.width * (1 + scale)) / 2;
          const maxY = canvasHeight - (boundingRect.height * (1 + scale)) / 2;

          // 应用位置限制
          obj.left = Math.min(Math.max(obj.left, minX), maxX);
          obj.top = Math.min(Math.max(obj.top, minY), maxY);

          // 更新坐标数据
          const findIndex = this.coordinateList.findIndex(
            item => item.cacheKey === obj.cacheKey
          );
          if (findIndex === -1) return;

          // 计算实际字体大小(核心修改部分)
          const baseFontSize = obj.baseFontSize || obj.fontSize; // 获取基准字号
          const scaleFactor = Math.max(obj.scaleX, obj.scaleY); // 取最大缩放值
          const actualFontSize = Math.round(baseFontSize * scaleFactor);
          // 通用属性更新
          const coordinate = this.coordinateList[findIndex];
          const updateProperty = (key, value) => {
            coordinate[key] = Math.round(value / this.scale);
          };

          updateProperty("left", obj.left);
          updateProperty("top", obj.top);
          updateProperty("width", obj.getScaledWidth());
          updateProperty("height", obj.getScaledHeight());
          coordinate.angle = Math.round(obj.angle);
          coordinate.scaleX = obj.scaleX;
          coordinate.scaleY = obj.scaleY;
          // 特定类型处理
          if (coordinate.name === "批注") {
            coordinate.xopinion = coordinate.top;
            coordinate.yopinion = coordinate.left;
            coordinate.opinion = obj.text;
            coordinate.fontsize = obj.fontSize;
            coordinate.wopinion = coordinate.width;
            coordinate.hopinion = coordinate.height;
            coordinate.opscale = obj.scaleX;
            coordinate.fontsize = actualFontSize;
            obj.set("fontSize", actualFontSize); // 更新画布显示字号
            // 重置缩放比例避免双重缩放
            obj.set({
              scaleX: 1,
              scaleY: 1,
              baseFontSize: actualFontSize // 更新基准字号
            });
          } else {
            coordinate.xsign = coordinate.top;
            coordinate.ysign = coordinate.left;
            coordinate.wsign = coordinate.width;
            coordinate.hsign = coordinate.height;
            coordinate.scale = obj.scaleX;
          }
          canvas.requestRenderAll();
          this.getSignatureJson();
        });
      });
    },
    onMove(evt) {
      // 记录当前拖拽位置
      this.currentDragPosition = {
        x: evt.draggedContext.futureIndex,
        y: evt.draggedContext.index
      };
      console.log(
        this.currentDragPosition,
        "----------currentDragPosition----"
      );
    },
    // 拖拽结束
    end(e) {
      // 找到当前拖拽到哪一个canvas-layout上
      const currentCanvasLayout =
        e.originalEvent.target.parentElement.parentElement;
      const findIndex = this.canvasLayoutTopList.findIndex(
        item => item.obj == currentCanvasLayout
      );
      if (findIndex == -1) return false;
      // 取整
      // console.log("e", e, findIndex);
      const left =
        e.originalEvent.offsetX < 0
          ? 0
          : Math.ceil(e.originalEvent.offsetX / this.scale);
      const top =
        e.originalEvent.offsetY < 0
          ? 0
          : Math.ceil(e.originalEvent.offsetY / this.scale);
      // console.log('e', e, findIndex,left,top,this.scale,);
      this.addSeal({
        sealUrl: this.mainImagelist[e.newDraggableIndex].img,
        left,
        top,
        index: e.newDraggableIndex,
        pageNum: findIndex
      });
    },
    // 注意图片的,onload是异步的,如果要封装成工具函数,需要用promise包装一下

    // 引入项目中的图片

    // 添加公章
    addSeal({ sealUrl, left, top, index, pageNum }) {
      const hasDuplicate = this.coordinateList.some(
        item => item.sealUrl === sealUrl && item.pageNo === pageNum + 1
      );
      if (hasDuplicate) {
        return this.$message.error("每页只能添加一个电子签名");
      }

      // 生成唯一 cacheKey
      const cacheKey = `seal_${Date.now()}_${pageNum}_${index}`;
  
      // const deleteIcon = "@assets/icon/del.svg";
      var deleteImg = document.createElement("img");
      deleteImg.src = this.deleteIcon;
      var samllImg = document.createElement("img");
      samllImg.src = this.samllIcon;
      var largeImg = document.createElement("img");
      largeImg.src = this.largeIcon;
      fabric.Image.fromURL(sealUrl, oImg => {
        oImg.set({
          left: left,
          top: top,
          cacheKey: cacheKey,
          cornerColor: "#3B82F6",
          cornerStyle: "circle",
          borderScaleFactor: 2,
          transparentCorners: false,
          // 角度
          // angle: 10,
          // 缩放比例,需要乘以scale
          scaleX: 1 * this.scale,
          scaleY: 1 * this.scale,
          // 设置当点击了该控制点,鼠标弹起是执行的动作处理方法
          index
          // 禁止缩放
          // lockScalingX: true,
          // lockScalingY: true,
          // 禁止旋转
          // lockRotation: true,
        });
        oImg.setControlsVisibility({
          mtr: false,
          mt: false,
          ml: false,
          mb: false,
          mr: false
        });
        oImg.controls.mtControl = new fabric.Control({
          visible: true, // 控制角的显隐
          x: -0.5,
          y: -0.5,
          offsetY: -16,
          offsetX: 30,
          cursorStyle: "pointer",
          //removeSignature
          // mouseDownHandler: (eventData, transform) => createLineDown(eventData, transform),
          mouseUpHandler: (eventData, transform) =>
            this.largeObject(eventData, transform),
          render: function(ctx, left, top, styleOverride, fabricObject) {
            // 渲染一个粉红色的正方形
            var size = this.cornerSize;
            ctx.save();
            ctx.translate(left, top);
            ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
            ctx.drawImage(largeImg, -size / 2, -size / 2, size, size);
            ctx.restore();
          },
          cornerSize: 26
        });
        //  // 左上
        oImg.controls.tlControl = new fabric.Control({
          visible: true, // 控制角的显隐
          x: -0.5,
          y: -0.5,
          offsetY: -16,
          offsetX: 0,
          cursorStyle: "pointer",
          //removeSignature
          // mouseDownHandler: (eventData, transform) => createLineDown(eventData, transform),
          mouseUpHandler: (eventData, transform) =>
            this.smallObject(eventData, transform),
          render: function(ctx, left, top, styleOverride, fabricObject) {
            // 渲染一个粉红色的正方形
            var size = this.cornerSize;
            ctx.save();
            ctx.translate(left, top);
            ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
            ctx.drawImage(samllImg, -size / 2, -size / 2, size, size);
            ctx.restore();
          },
          cornerSize: 26
        });
        // 左上 删除
        oImg.controls.trControl = new fabric.Control({
          visible: true, // 控制角的显隐
          x: -0.5,
          y: -0.5,
          offsetY: -16,
          offsetX: 60,
          cursorStyle: "pointer",
          //removeSignature
          // mouseDownHandler: (eventData, transform) => createLineDown(eventData, transform),
          mouseUpHandler: (eventData, transform) =>
            this.deleteObject(eventData, transform),
          render: function(ctx, left, top, styleOverride, fabricObject) {
            // 渲染一个粉红色的正方形
            var size = this.cornerSize;
            ctx.save();
            ctx.translate(left, top);
            ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
            ctx.drawImage(deleteImg, -size / 2, -size / 2, size, size);
            ctx.restore();
          },
          cornerSize: 26
        });
        this.canvasEle[pageNum].add(oImg);
        // 保存签章信息
        this.saveSignature({ pageNum, index, sealUrl,cacheKey  });
      });
      this.removeActive();
    },
    //控制器删除对象
    deleteObject(eventData, transform) {
      let target = transform.target;
      let canvas = target.canvas;
      // 拿到选中的文本的
      var activeObj = canvas.getActiveObject();
      const findIndex = this.coordinateList.findIndex(
        item =>
          item.cacheKey == activeObj.cacheKey && item.pageNo == activeObj.pageNo
      );
      // console.log(findIndex,this.coordinateList,activeObj,'-----------activeObjS')
      //  删除选中的文本
      canvas.remove(target); // 删除元素
      //  删除选中的文本的信息
      this.coordinateList.splice(findIndex, 1);
      this.getSignatureJson();
    },

    // 放大操作处理
    largeObject(eventData, transform) {
      this.handleScaling(transform, 0.1); // +10%缩放
    },

    // 缩小操作处理
    smallObject(eventData, transform) {
      this.handleScaling(transform, -0.1); // -10%缩放
    },

    // 统一缩放处理方法
    handleScaling(transform, scaleStep) {
      const target = transform.target;
      const canvas = target.canvas;
      const activeObj = canvas.getActiveObject();

      // 校验有效性
      if (!activeObj || !activeObj.cacheKey) {
        console.error("操作对象无效或缺少cacheKey");
        return;
      }
      console.log(this.coordinateList,'--------')
      // 精确查找坐标项
      const findIndex = this.coordinateList.findIndex(
        item => item.cacheKey === activeObj.cacheKey
      );

      if (findIndex === -1) {
        console.warn("未找到对应坐标项,cacheKey:", activeObj.cacheKey);
        return;
      }

      // 计算新缩放比例(带最小限制)
      const minScale = 0.2;
      const newScaleX = Math.max(minScale, activeObj.scaleX + scaleStep);
      const newScaleY = Math.max(minScale, activeObj.scaleY + scaleStep);

      // 更新对象属性
      activeObj
        .set({
          scaleX: newScaleX,
          scaleY: newScaleY
        })
        .setCoords();

      // 同步到数据存储
      this.coordinateList[findIndex] = {
        ...this.coordinateList[findIndex],
        scaleX: newScaleX,
        scaleY: newScaleY,
        width: activeObj.width * newScaleX,
        height: activeObj.height * newScaleY
      };

      // 渲染优化(避免全局重绘)
      canvas.renderAll();
      console.log("缩放操作完成:", this.coordinateList[findIndex]);
    },

    // 保存签章
    saveSignature({ pageNum, index, sealUrl ,cacheKey}) {
      // 1. 拿到当前签章的信息
      let length = 0;
      let pageConfig = this.coordinateList.filter(
        item => item.pageNo - 1 == pageNum
      );
      if (pageConfig) length = pageConfig.length;
      const currentSignInfo = this.canvasEle[pageNum].getObjects()[length];
      // 2. 拼接数据
      const keys = [
        "width",
        "height",
        "top",
        "left",
        "angle",
        "scaleX",
        "scaleY",
        "xsign",
        'cacheKey',
        "ysign"
      ];
      const obj = {};
      keys.forEach(item => {
        obj[item] = Math.ceil(currentSignInfo[item] / this.scale);
      });
      obj.cacheKey = cacheKey;
      obj.sealUrl = sealUrl;
      obj.index = index;
      obj.xsign = obj.top;
      obj.ysign = obj.left;
      obj.scale = obj.scaleX;
      obj.wsign = obj.width;
      obj.hsign = obj.height;
      obj.name = "印章";
      obj.pageNo = pageNum + 1;
      // currentSignInfo.set("cacheKey", obj.cacheKey); // 关键:将 cacheKey 存入 Fabric 对象
      // currentSignInfo.set("name", obj.name); // 关键:将 cacheKey 存入 Fabric 对象
      this.coordinateList.push(obj);
      console.log(obj,'-------cacheKey')
      this.getSignatureJson();
    },
    // 签章生成json字符串
    getSignatureJson() {
      // 1. 判断是否有签章
      if (this.coordinateList.length <= 1) return (this.shadowInputValue = "");
      // 2. 拿到签章的信息,去除第一条
      const signatureList = this.coordinateList;
      // 3. 拼接数据,只要left和top和page
      const keys = [
        "pageNo",
        "left",
        "top",
        "width",
        "height",
        "xsign",
        "ysign",
        "xopinion",
        "text",
        "yopinion"
      ];
      const arr = [];
      signatureList.forEach(item => {
        const obj = {};
        keys.forEach(key => {
          obj[key] = item[key];
        });
        arr.push(obj);
      });
      // 4. 转成json字符串
      this.shadowInputValue = JSON.stringify(arr);
    },
    /**
     * 操作相关部分
     */
    // 上一页
    prevPage() {
      if (this.pageNum <= 1) return;
      this.pageNum--;
      // 滚动到指定位置
      this.outViewDom.scrollTop = this.canvasLayoutTopList[
        this.pageNum - 1
      ].top;
    },
    // 下一页
    nextPage() {
      if (this.pageNum >= this.numPages) return;
      this.pageNum++;
      // 滚动到指定位置
      this.outViewDom.scrollTop = this.canvasLayoutTopList[
        this.pageNum - 1
      ].top;
    },
    // 切换页码
    cutover() {
      this.outViewScrollClose();
      if (this.pageNum < 1) {
        this.pageNum = 1;
      } else if (this.pageNum > this.numPages) {
        this.pageNum = this.numPages;
      }
      // 滚动到指定位置
      this.outViewDom.scrollTop = this.canvasLayoutTopList[
        this.pageNum - 1
      ].top;
      setTimeout(() => {
        this.outViewScroll();
      }, 500);
    },
    // 删除所有的签章选中状态
    removeActive() {
      this.canvasEle.forEach(item => {
        item.discardActiveObject().renderAll();
      });
    },
    // 删除签章
    removeSignature() {
      // 1. 判断是否有选中的签章
      const findItem = this.canvasEle.filter(item => item.getActiveObject());
      // 2. 判断选中签章的个数
      if (findItem.length == 0)
        return this.$message.error("请选择要删除的签章");
      // 3. 判断选中签章的个数是否大于1
      if (findItem.length > 1) {
        this.removeActive();
        return this.$message.error("只能选择删除一个签章,请重新选择");
      }
      // 4. 拿到选中的签章的cacheKey
      const activeObj = findItem[0].getActiveObject();
      const findIndex = this.coordinateList.findIndex(
        item =>
          item.cacheKey == activeObj.cacheKey && item.pageNo == activeObj.pageNo
      );
      // 5. 删除选中的签章
      findItem[0].remove(activeObj);
      // 6. 删除选中的签章的信息
      this.coordinateList.splice(findIndex, 1);
      this.getSignatureJson();
    },
    // 清空签章
    clearSignature() {
      this.canvasEle.forEach(item => {
        item.clear();
      });
      this.coordinateList = [];
      this.getSignatureJson();
    },
    processSignsData(originalData) {
      // 按pageNo分组
      const grouped = originalData.reduce((acc, item) => {
        const pageNo = item.pageNo;
        if (!acc[pageNo]) acc[pageNo] = [];
        acc[pageNo].push(item);
        return acc;
      }, {});

      // 生成目标数据格式
      const signs = Object.keys(grouped)
        .map(Number)
        .sort((a, b) => a - b)
        .map(pageNo => {
          const items = grouped[pageNo];
          const signItem = {
            xsign: "",
            ysign: "",
            wsign: "",
            hsign: "",
            xopinion: "",
            yopinion: "",
            wopinion: "",
            hopinion: "",
            scale: "1",
            opscale: "1",
            sealUrl: "",
            opinion: "",
            pageNo: "",
            fontsize: ""
          };
          items.forEach(item => {
            if (item.name === "印章") {
              signItem.xsign = String(item.xsign ?? "");
              signItem.ysign = String(item.ysign ?? "");
              signItem.wsign = String(item.wsign ?? "");
              signItem.hsign = String(item.hsign ?? "");
              signItem.scale = String(item.scaleX ?? "");
              signItem.sealUrl = String(item.sealUrl ?? "");
            } else if (item.name === "批注") {
              signItem.xopinion = String(item.xopinion ?? "");
              signItem.yopinion = String(item.yopinion ?? "");
              signItem.wopinion = String(item.wopinion ?? "");
              signItem.hopinion = String(item.hopinion ?? "");
              signItem.opscale = String(item.scaleX ?? "");
              signItem.opinion = String(item.opinion ?? "");
              signItem.fontsize = String(item.fontsize ?? "");
            }
            signItem.pageNo = String(item.pageNo ?? "");
          });

          return signItem;
        });

      return { signs };
    },
    //操作单据
    optionSignature(val) {
      this.id = this.$route.query.id;
      var includesign = this.coordinateList.some(item =>
        item.sealUrl?.includes("/chairmansoffice/download/")
      );
      console.log(
        this.coordinateList,
        "------------------------this.coordinateList "
      );
      if (val == "agree" ||val == "addSubSign") {
        if (
          includesign &&
          this.coordinateList != [] &&
          this.coordinateList.length > 0
        ) {
          this.isVisible = true;
          this.signlist = this.processSignsData(this.coordinateList)["signs"];
          // console.log(
          //   this.signlist,
          //   "------------------------this.coordinateList "
          // );
        } else {
          this.$message.warning(`请先对文件进行签章后再提交`);
        }
        this.title = val;
      } else {
        this.title = "return";
        this.isVisible = true;
      }
      // console.log("this.coordinateList", this.coordinateList);
    },

    closeVisible(value) {
      this.isVisible = value;
    },
    //返回
    returnSignature() {
      this.$router.go(-1);
    },
    // 提交数据
    submitSignature() {
      console.log("this.coordinateList", this.coordinateList);
    },

    //添加文字
    addTextobjectHandle(e) {
      var deleteImg = document.createElement("img");
      deleteImg.src = this.deleteIcon;
      var samllImg = document.createElement("img");
      samllImg.src = this.samllIcon;
      var largeImg = document.createElement("img");
      largeImg.src = this.largeIcon;
      let Shape;
      let currentoptionCss;
      var findIndex = this.pageNum - 1;
      const cacheKey = `text_${Date.now()}_${findIndex}`;
      currentoptionCss = this.activeobjectData;
      //通过最大行高计算高度,并删除多余文字,多出文字..表示,三个会换行
      Shape = new fabric.IText(currentoptionCss.text || "", currentoptionCss);
      Shape.set({
        cacheKey: cacheKey  // 为文本对象设置唯一标识
      });
      Shape.setControlVisible("mtr", false);
      Shape.setControlVisible("mt", false);
      Shape.setControlVisible("ml", false);
      Shape.setControlVisible("mb", false);
      Shape.setControlVisible("mr", false);
      Shape.controls.mtControl = new fabric.Control({
        visible: true, // 控制角的显隐
        x: -0.5,
        y: -0.5,
        offsetY: -16,
        offsetX: 30,
        cursorStyle: "pointer",
        //removeSignature
        // mouseDownHandler: (eventData, transform) => createLineDown(eventData, transform),
        mouseUpHandler: (eventData, transform) =>
          this.largeObject(eventData, transform),
        render: function(ctx, left, top, styleOverride, fabricObject) {
          var size = this.cornerSize;
          ctx.save();
          ctx.translate(left, top);
          ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
          ctx.drawImage(largeImg, -size / 2, -size / 2, size, size);
          ctx.restore();
        },
        cornerSize: 26
      });
      Shape.controls.tlControl = new fabric.Control({
        visible: true, // 控制角的显隐
        x: -0.5,
        y: -0.5,
        offsetY: -16,
        offsetX: 0,
        cursorStyle: "pointer",
        //removeSignature
        // mouseDownHandler: (eventData, transform) => createLineDown(eventData, transform),
        mouseUpHandler: (eventData, transform) =>
          this.smallObject(eventData, transform),
        render: function(ctx, left, top, styleOverride, fabricObject) {
          var size = this.cornerSize;
          ctx.save();
          ctx.translate(left, top);
          ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
          ctx.drawImage(samllImg, -size / 2, -size / 2, size, size);
          ctx.restore();
        },
        cornerSize: 26
      });
      // 左上 删除
      Shape.controls.trControl = new fabric.Control({
        visible: true, // 控制角的显隐
        x: -0.5,
        y: -0.5,
        offsetY: -16,
        offsetX: 60,
        cursorStyle: "pointer",
        //removeSignature
        // mouseDownHandler: (eventData, transform) => createLineDown(eventData, transform),
        mouseUpHandler: (eventData, transform) =>
          this.deleteObject(eventData, transform),
        render: function(ctx, left, top, styleOverride, fabricObject) {
          // 渲染一个粉红色的正方形
          var size = this.cornerSize;
          ctx.save();
          ctx.translate(left, top);
          ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
          ctx.drawImage(deleteImg, -size / 2, -size / 2, size, size);
          ctx.restore();
        },
        cornerSize: 26
      });
      Shape.splitByGrapheme = true;
      var index = 0,
        index = index + 1;
      var pageNum = findIndex;
      var sealUrl = this.activeobjectData.text;

      // 获取当前页面信息
      const currentPage = this.pageNum;
      const canvasIndex = currentPage - 1;

      // 验证当前页是否已有批注
      const hasExistingAnnotation = this.coordinateList.some(
        item => item.pageNo === currentPage && item.name === "批注"
      );

      if (hasExistingAnnotation) {
        this.$message.error("每页只能添加一个文字批注");
        return;
      }
      this.canvasEle[findIndex].add(Shape).setActiveObject(Shape);
      this.saveText({ pageNum, index, sealUrl,cacheKey });
    },

    saveText({ pageNum, index, sealUrl,cacheKey }) {
      // 1. 拿到当前批注的信息
      let length = 0;
      let pageConfig = this.coordinateList.filter(
        item => item.pageNo - 1 == pageNum
      );
      if (pageConfig) length = pageConfig.length;
      const currentSignInfo = this.canvasEle[pageNum].getObjects()[length];
      const keys = [
        "width",
        "height",
        "cacheKey",
        "top",
        "left",
        "angle",
        "scaleX",
        "scaleY"
      ];

      const obj = {};
      keys.forEach(item => {
        obj[item] = Math.ceil(currentSignInfo[item] / this.scale);
      });
      obj.cacheKey = cacheKey;
      obj.opinion = obj.text;
      obj.fontsize = obj.fontsize;
      obj.xopinion = obj.top;
      obj.yopinion = obj.left;
      obj.wopinion = obj.width;
      obj.hopinion = obj.height;
      obj.opscale = obj.scaleX;
      obj.index = index;
      obj.name = "批注";
      obj.pageNo = pageNum + 1;
      // currentSignInfo.set("cacheKey", obj.cacheKey); // 关键:将 cacheKey 存入 Fabric 对象
      // currentSignInfo.set("name", obj.name); // 关键:将 cacheKey 存入 Fabric 对象
      this.coordinateList.push(obj);
    },
    //下载文件
    download() {
      // downloadApi(this.id)
      //   .then(res => {
      //     const fileNameEncode = res.headers["content-disposition"]
      //       .split(";")[1]
      //       .split("filename=")[1];
      //     let contentDisposition = decodeURIComponent(fileNameEncode);
      //     this.downloadBinaryFile(res.data, contentDisposition);
      //     this.loading = false;
      //   })
      //   .catch();
    },
    async downloadBinaryFile(
      binFile,
      fileName,
      blobType = "application/octet-stream"
    ) {
      // 处理二进制数据并创建 Blob 对象
      const blobObj = new Blob([binFile], { type: blobType });
      // 创建一个链接并设置下载属性
      const downloadLink = document.createElement("a");
      let url = window.URL || window.webkitURL || window.moxURL; // 兼容不同浏览器的 URL 对象
      url = url.createObjectURL(blobObj);
      downloadLink.href = url;
      downloadLink.download = fileName; // 设置下载的文件名
      // 将链接添加到 DOM 中,模拟点击
      document.body.appendChild(downloadLink);
      downloadLink.click();
      // 移除创建的链接和释放 URL 对象
      document.body.removeChild(downloadLink);
      window.URL.revokeObjectURL(url);
    },
    customRequest(param) {
      this.uploadAction(param.file);
    },
    //上传文件
    async uploadAction(file) {
      this.dayLoading = true;
      const param = new FormData();
      param.append("file", file);
      uploadSignture(param)
        .then(res => {
          if (res.data.type == "application/json") {
            //转换步骤
            const file = new FileReader();
            file.readAsText(res.data, "utf-8");
            file.onload = e => {
              const obj = JSON.parse(file.result);
              res.data = obj;
              if (obj.code == "100") {
                this.$message({ message: "上传成功", type: "success" });
                this.userInfo.signture = obj.data.fn;
                // this.form.fn = obj.data[0].fn;
                this.getsignimg();
                // this.form.sourceName = obj.data[0].sourceName;
                // console.log(this.userInfo.signture);
              } else {
                this.$message({ message: obj.message, type: "error" });
              }
            };
          } else {
            this.$message.warning(`上传失败`);
          }
        })
        .catch(err => {
          this.$message.error(res.$message);
        });
      // this.dialogVisible = false;
      // this.$emit("dialogVisible", this.dialogVisible);
    },
    // 超出上传文件个数的钩子
    handleExceed(file, fileList) {
      this.$message.warning(`只能上传一个文件`);
    },
    beforeRemove(file, fileList) {
      return this.$confirm(`确定移除 ${file.name}`);
    },
    handleRemove(file, fileList) {
      console.log(file, fileList);
    },
    handlePreview(file) {
      console.log(file);
    },
    //删除绑定个人的签章文件
    deleteSignature() {
      removeSignture().then(res => {
        if (res.data.code == 100) {
          this.$message({ message: res.data.message, type: "success" });
          this.getsignimg();
          this.vissign = false;
        } else {
          this.$message({ message: res.data.message, type: "error" });
        }
      });
    }
    // imgsrc(val){
    //   let token = getToken()
    //   Object.defineProperty(Image.prototype, 'authsrc', {
    //     writable: true,
    //     enumerable: true,
    //     configurable: true
    //   })
    //   let img = val
    //   let request = new XMLHttpRequest();
    //   request.responseType = 'blob';
    //   request.open('get', this.authSrc, true);
    //   request.setRequestHeader('token', token);
    //   request.onreadystatechange = e => {
    //     if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
    //       img.src = URL.createObjectURL(request.response);
    //       img.onload = () => {
    //         URL.revokeObjectURL(img.src);
    //       }
    //     }
    //   };
    //   request.send(null);
    // },
  }
};
</script>
<style lang="scss" scoped>
.contract-signature-view {
  /*pdf部分*/
  .ele-canvas {
    overflow: hidden;
  }

  .text-canvas {
    overflow: hidden;
  }

  .title-operation {
    background: rgba(255, 255, 255, 1);
    height: 80px;
    padding: 20px 40px;
    display: flex;
    align-items: center;
    justify-content: space-between;

    .operation {
      .searchbutton {
        margin-right: 10px;
        min-width: 96px;
        height: 40px;
        border: 1px 0px 0px 0px;
        gap: 8px;
        border-radius: 4px;
        padding-top: 8px;
        padding-right: 20px;
        padding-bottom: 8px;
        padding-left: 18px;
        font-size: 12px;
      }

      .button {
        margin-right: 10px;
        min-width: 96px;
        height: 40px;
        border: none;
        gap: 8px;
        border-radius: 4px;
        padding-top: 8px;
        padding-right: 20px;
        padding-bottom: 8px;
        padding-left: 18px;
        font-size: 12px;
      }
    }

    .title {
      font-size: 20px;
      font-weight: 600;
    }

    border-bottom: 1px solid #e4e4e4;
  }

  .section-box {
    position: relative;
    display: flex;
    height: calc(100vh - 60px);

    .main-layout {
      // flex: 1;
      width: 100%;
      background-color: #f7f8fa;
      position: relative;

      &.is-first {
        .operate-box {
          opacity: 0;
        }
      }

      .operate-box {
        opacity: 1;
        position: absolute;
        top: 80px;
        left: 0;
        width: 100%;
        height: 40px;
        background-color: #fff;
        border-bottom: 1px solid #e4e4e4;
        display: flex;
        justify-content: center;
        align-items: center;

        .slider-box {
          width: 230px;
          display: flex;
          justify-content: center;
          align-items: center;
          border-left: 1px solid #e4e4e4;
          border-right: 1px solid #e4e4e4;

          .slider {
            width: 120px;
          }

          .scale-value {
            margin-left: 24px;
            font-size: 16px;
            color: #000000;
            line-height: 22px;
          }
        }

        .pageNo-change {
          display: flex;
          align-items: center;
          margin-left: 30px;

          .icon {
            cursor: pointer;
            padding: 0 5px;
            color: #c1c1c1;
          }

          .input-box {
            border: none;

            .el-input__inner {
              width: 34px;
              height: 20px;
              border: none;
              padding: 0;
              text-align: center;
              border-bottom: 1px solid #e4e4e4;
            }
          }

          .default-text {
            display: flex;
            line-height: 22px;
            margin-right: 5px;
          }
        }
      }

      .out-view {
        height: calc(100vh - 185px);
        margin: 40px auto;
        overflow-x: auto;
        overflow-y: auto;
        padding-top: 20px;
        text-align: center;
        opacity: 0;
        transition: all 0.5s;

        &.is-show {
          opacity: 1;
        }

        .canvas-layout {
          position: relative;
          text-align: center;
          margin: 0 auto 18px;

          #box {
            position: relative;
          }

          .menu-x {
            visibility: hidden;
            z-index: -100;
            position: absolute;
            top: 0;
            left: 0;
            box-sizing: border-box;
            border-radius: 4px;
            box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
            background-color: #fff;
          }

          .menu-li {
            box-sizing: border-box;
            padding: 4px 8px;
            border-bottom: 1px solid #ccc;
            cursor: pointer;
          }

          .menu-li:hover {
            background-color: antiquewhite;
          }

          .menu-li:first-child {
            border-top-left-radius: 4px;
            border-top-right-radius: 4px;
          }

          .menu-li:last-child {
            border-bottom: none;
            border-bottom-left-radius: 4px;
            border-bottom-right-radius: 4px;
          }
        }
      }

      .loading {
        width: 20px;
        height: 20px;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: 999;

        .el-loading-mask {
          background-color: transparent;
        }
      }
    }

    .position-info {
      width: 355px;
      min-width: 355px;
      border-left: 1px solid #e4e4e4;
      background-color: #fff;
      padding: 14px 15px;

      .title {
        font-size: 14px;
        font-weight: 400;
        color: #000000;
        line-height: 20px;
        padding-bottom: 18px;
      }

      .nav {
        display: flex;
        flex-direction: column;

        .item {
          display: flex;
          justify-content: space-between;
          padding: 10px 0;
          border-bottom: 1px solid #eee;

          &:first-child {
            background-color: #f7f8fa;
          }

          span {
            flex: 1;
            text-align: center;
            font-size: 12px;
            color: #000000;
            line-height: 20px;
          }
        }
      }
    }
  }
}

.signatureimg {
  min-width: 240px !important;
  background-color: #fff;
  //
  padding-bottom: 20px;

  .name {
    font-size: 18px;
    font-weight: 600;
    color: #000000;
    line-height: 25px;
    margin-bottom: 20px;
  }

  .text {
    font-size: 14px;
    color: #000000;
    line-height: 20px;
  }

  .item {
    // margin: 10p;
    // padding: 10px;
    border: 1px dashed rgba(0, 0, 0, 0.3);

    &:not(:last-child) {
      margin-bottom: 10px;
    }

    .img {
      vertical-align: middle;
      background-repeat: no-repeat;
    }
  }

  .opera {
    bottom: 0;
    position: absolute;
    justify-content: space-between;
    background: rgba(243, 243, 243, 1);
    left: 0;
    width: 100%;
    display: flex;
    align-items: center;

    .delbut {
      cursor: pointer;
      background: rgba(243, 243, 243, 1);
      border: none;
      color: #000000;
    }

    :hover {
      color: #409eff;
      border: #c6e2ff;
      // background-color: #ecf5ff;
    }
  }
}

.upimg {
  .upload-demo {
    padding: 10px 20px;
    text-align: center;

    .upload-button {
      padding: 30px 0 0 0;
      color: #3f7afa;
      font-size: 14px;
      border: none;

      &:hover,
      &.is-active {
        background: transparent;
      }
    }
  }
}
</style>


网站公告

今日签到

点亮在社区的每一天
去签到