Vue使用vue-esign实现在线签名 加入水印

Vue在线签名

  • 一、目的
  • 二、样式
  • 三、代码
    • 1、依赖
    • 2、代码
      • 2.1 在线签名组件
        • 2.1.1 基础的
        • 2.1.2 携带时间水印的
      • 2.2父组件

一、目的

又来了一个问题,直接让我在线签名(还不能存储base64),并且还得上传,我直接***违禁词。

好家伙又回来了,这次增加了一个加入时间水印的要求,我***。
在这里插入图片描述

二、样式

初始样式
在这里插入图片描述
点击前往组件(忽略写的什么样)
在这里插入图片描述
这里可以调节画笔,颜色什么的,也能进行预览,点击保存之后(
1、这里点击保存按钮我也走了一遍预览签名,不走的话这边直接保存了,签名图片还在上传,无法进行回显了;
2、也可以在保存的方法使用延迟调用setTimeout,但是怕无法把握这个时间,所以就用了方法1)
在这里插入图片描述在这里插入图片描述

三、代码

1、依赖

npm install vue-esign --save

2、代码

因为使用的jeecg框架,这里是按照框架进行写的,原生的其他版本,等有时间在更新一下,毕竟cv工程师。在这里插入图片描述
下面的生成图片逻辑和上一篇Vue中使用图片编辑器 tui-image-editor 实现在线编辑保存最后的base转换差不多都是一样的,这里也是使用了组件调用。

2.1 在线签名组件

2.1.1 基础的

在线编辑的组件,名称我这里是Esignature.vue

<template>
  <j-modal
    :title="title"
    :width="width"
    :visible="visible"
    switchFullscreen
    :okButtonProps="{ class:{'jee-hidden': false} }"
    @ok="handleOk"
    okText="保存"
    @cancel="handleCancel"
    cancelText="关闭"

  >
    <a-card :bordered="false">
      <a-col :span="24">
        <a-card :bordered="true" style="width: 100%;">
          <a-row>
            <a-col :span="6">
              <a-form-model-item label="画笔粗细" :labelCol="labelCol" :wrapperCol="wrapperCol">
                <a-select style="width:100px;" v-model="lineWidth" placeholder="请选择">
                  <a-radio :value="1">1</a-radio>
                  <a-radio :value="3">3</a-radio>
                  <a-radio :value="6">6</a-radio>
                  <a-radio :value="9">9</a-radio>
                </a-select>
              </a-form-model-item>
            </a-col>
            <a-col :span="6">
              <a-form-model-item label="画笔颜色" :labelCol="labelCol" :wrapperCol="wrapperCol">
                <!-- input颜色回显必须要六位的颜色值 -->
                <a-input v-model="lineColor" type="color" placeholder="" placeholder-class="input-placeholder" />
              </a-form-model-item>
            </a-col>
            <a-col :span="6">
              <a-form-model-item label="画布背景" :labelCol="labelCol" :wrapperCol="wrapperCol">
                <a-input v-model="bgColor" type="color" placeholder="" placeholder-class="input-placeholder" />
              </a-form-model-item>
            </a-col>
            <a-col :span="6">
                <a-form-model-item label="是否裁剪" :labelCol="labelCol" :wrapperCol="wrapperCol">
                  <j-switch v-model="isCrop" :options="[true,false]" ></j-switch>
                </a-form-model-item>
              </a-col>
            <vue-esign
              style="border: 1px solid #808080;"
              ref="esignRef"
              :width="canWidth"
              :height="canHeight"
              :isCrop="isCrop"
              :lineWidth="lineWidth"
              :lineColor="lineColor"
              :bgColor.sync="bgColor"
              :isClearBgColor="isClearBgColor" />
            <button @click="handleReset">清空画板</button>
            <button @click="handleGenerate(false)">预览图片</button>
            <div>
              <img style="float:left;border: 1px solid #808080" :src="imgBase" alt="">
            </div>
          </a-row>
        </a-card>
      </a-col>
    </a-card>
  </j-modal>
</template>

<script>

  import { getAction, httpAction } from '@api/manage'
  import VueEsign from 'vue-esign'

  export default {
    name: 'Esign',
    components: {
      VueEsign
    },
    data () {
      return {
        canWidth: 800,//画布宽度--是不会超出父元素的宽度的--设置也不行
        canHeight: 300,

        lineWidth: 3,//画笔粗细
        lineColor: '#000000',//画笔颜色
        bgColor: '#ffffff',//画布背景
        isCrop: false,//是否裁剪
        isClearBgColor: true,//是否清空背景色

        imgBase: '',//生成签名图片-base64
        imgUrl: '',//生成签名图片-base64
        labelCol: {
          xs: { span: 24 },
          sm: { span: 8 }
        },
        wrapperCol: {
          xs: { span: 24 },
          sm: { span: 16 }
        },
        title: '',
        width: 1000,
        visible: false,
        disableSubmit: false,
      }
    },
    methods: {
      //调用组件
      handleSign(){
        this.visible = true
        this.$nextTick(()=>{
          // console.log("调用=========>"+this.$refs.esignRef)
          this.handleReset()
        })
      },
      //保存
      handleOk() {
        this.handleGenerate(true)
        // setTimeout(() =>{
        //   this.$emit('getSign',this.imgUrl);
        //   this.close()
        // },100); // 延迟0.1秒
      },
      //关闭
      close() {
        this.$emit('close')
        this.visible = false
      },
      //关闭按钮
      handleCancel() {
        this.close()
      },
      //重置
      handleReset () {
        ////清空画布内容
        this.lineWidth = 3
        this.lineColor = '#000000'
        this.bgColor = '#ffffff'
        this.isCrop = false
        this.imgBase = ''
        this.$refs.esignRef.reset();
      },
      //生成图片
      handleGenerate (flag) {
        // console.log("生成图片=========>"+this.$refs.esignRef)
        this.$refs.esignRef.generate().then(res => {
          // console.log('base64地址', res)
          this.imgBase = res
          if (flag){
            //进行base64转换的操作,因为后台文件都会加上随机后缀,这里使用sign.png了
            const form = this.base64ChangePicForm(res,'sign.png')
            httpAction('/sys/common/upload', form, 'post').then((uploadRes) => {
              // console.log("============>"+JSON.stringify(uploadRes))
              if (uploadRes.success){
                this.imgUrl = uploadRes.message
                this.$emit('getSign',this.imgUrl);
                this.close()
              }
            })
          }
        }).catch(error=> {
          // console.log('错误:', error)
          this.$message.warning('请先签字!');
        })
      }, 
      //转换图片
      base64ChangePicForm(base64String,fileName){
        const data = window.atob(base64String.split(",")[1]);
        const ia = new Uint8Array(data.length);
        for (let i = 0; i < data.length; i++) {
          ia[i] = data.charCodeAt(i);
        }
        const blob = new Blob([ia], { type: "image/png" }); // blob 文件
        const file = new File([blob], fileName, { type: blob.type });
        const form = new FormData();
        form.append("file", file);
        form.append("biz", 'web/sign');
        return form
      },
    }
  }
</script>
2.1.2 携带时间水印的
<template>
  <j-modal
    :title="title"
    :width="width"
    :visible="visible"
    switchFullscreen
    :okButtonProps="{ class:{'jee-hidden': false} }"
    @ok="handleOk"
    okText="保存"
    @cancel="handleCancel"
    cancelText="关闭"

  >
    <a-card :bordered="false">
      <a-col :span="24">
        <a-card :bordered="true" style="width: 100%;">
          <a-row>
            <a-col :span="6">
              <a-form-model-item label="画笔粗细" :labelCol="labelCol" :wrapperCol="wrapperCol">
                <a-select style="width:100px;" v-model="lineWidth" placeholder="请选择">
                  <a-radio :value="1">1</a-radio>
                  <a-radio :value="3">3</a-radio>
                  <a-radio :value="6">6</a-radio>
                  <a-radio :value="9">9</a-radio>
                </a-select>
              </a-form-model-item>
            </a-col>
            <a-col :span="6">
              <a-form-model-item label="画笔颜色" :labelCol="labelCol" :wrapperCol="wrapperCol">
                <!-- input颜色回显必须要六位的颜色值 -->
                <a-input v-model="lineColor" type="color" placeholder="" placeholder-class="input-placeholder" />
              </a-form-model-item>
            </a-col>
            <a-col :span="6">
              <a-form-model-item label="画布背景" :labelCol="labelCol" :wrapperCol="wrapperCol">
                <a-input v-model="bgColor" type="color" placeholder="" placeholder-class="input-placeholder" />
              </a-form-model-item>
            </a-col>
            <a-col :span="6">
                <a-form-model-item label="是否裁剪" :labelCol="labelCol" :wrapperCol="wrapperCol">
                  <j-switch v-model="isCrop" :options="[true,false]" ></j-switch>
                </a-form-model-item>
              </a-col>
            <vue-esign
              style="border: 1px solid #808080;"
              ref="esignRef"
              :width="canWidth"
              :height="canHeight"
              :isCrop="isCrop"
              :lineWidth="lineWidth"
              :lineColor="lineColor"
              :bgColor.sync="bgColor"
              :isClearBgColor="isClearBgColor" />
            <button @click="handleReset">清空签名</button>
            <button @click="handleGenerate(false)">预览签名</button>
            <div>
              <img style="float:left;border: 1px solid #808080" :src="imgBase" alt="">
            </div>
          </a-row>
        </a-card>
      </a-col>
    </a-card>
  </j-modal>
</template>

<script>

  import { getAction, httpAction } from '@api/manage'
  import VueEsign from 'vue-esign'
  import { formatDate } from '@/utils/dateNumber'

  export default {
    name: 'Esign',
    components: {
      VueEsign
    },
    data () {
      return {
        canWidth: 800,//画布宽度--是不会超出父元素的宽度的--设置也不行
        canHeight: 300,

        lineWidth: 3,//画笔粗细
        lineColor: '#000000',//画笔颜色
        bgColor: '#ffffff',//画布背景
        isCrop: false,//是否裁剪
        isClearBgColor: true,//是否清空背景色

        imgBase: '',//生成签名图片-base64
        imgUrl: '',//生成签名图片-base64
        labelCol: {
          xs: { span: 24 },
          sm: { span: 8 }
        },
        wrapperCol: {
          xs: { span: 24 },
          sm: { span: 16 }
        },
        title: '',
        width: 1000,
        visible: false,
        disableSubmit: false,
      }
    },
    methods: {
      //调用组件
      handleSign(){
        this.visible = true
        this.$nextTick(()=>{
          // console.log("调用=========>"+this.$refs.esignRef)
          this.handleReset()
        })
      },
      //保存
      handleOk() {
        this.handleGenerate(true)
        // setTimeout(() =>{
        //   this.$emit('getSign',this.imgUrl);
        //   this.close()
        // },100); // 延迟0.1秒
      },
      //关闭
      close() {
        this.$emit('close')
        this.visible = false
      },
      //关闭按钮
      handleCancel() {
        this.close()
      },
      //重置
      handleReset () {
        ////清空画布内容
        this.lineWidth = 3
        this.lineColor = '#000000'
        this.bgColor = '#ffffff'
        this.isCrop = false
        this.imgBase = ''
        this.$refs.esignRef.reset();
      },
      //生成图片
      handleGenerate (flag) {
        // console.log("生成图片=========>"+this.$refs.esignRef)
        this.$refs.esignRef.generate().then(res => {
          // console.log('base64地址', res)
          // this.imgBase = res
          //加入时间水印
          this.addWatermark(res,formatDate(new Date(),'yyyy-MM-dd hh:mm:ss')).then((rmarkRes)=>{
            this.imgBase = rmarkRes
            //判断是保存还是预览,保存上传,预览不上传
            if (flag){
      		  //进行base64转换的操作,因为后台文件都会加上随机后缀,这里使用sign.png了
              const form = this.base64ChangePicForm(rmarkRes,'sign.png')
              httpAction('/sys/common/upload', form, 'post').then((uploadRes) => {
                // console.log("============>"+JSON.stringify(uploadRes))
                if (uploadRes.success){
                  this.imgUrl = uploadRes.message
                  this.$emit('getSign',this.imgUrl);
                  this.close()
                }else {
                  this.$message.warning(uploadRes.message);
                }
              }).catch((error) => {
                this.$message.warning(error);
              })
            }
          })
        }).catch(error => {
          // console.log('错误:', error)
          this.$message.warning('请先签字!');
        })
      },
      //转换图片
      base64ChangePicForm(base64String,fileName){
        const data = window.atob(base64String.split(",")[1]);
        const ia = new Uint8Array(data.length);
        for (let i = 0; i < data.length; i++) {
          ia[i] = data.charCodeAt(i);
        }
        const blob = new Blob([ia], { type: "image/png" }); // blob 文件
        const file = new File([blob], fileName, { type: blob.type });
        const form = new FormData();
        form.append("file", file);
        form.append("biz", 'web/sign');
        return form
      },
      //加入水印
      addWatermark(base64String, watermarkText) {
        return new Promise((resolve, reject) => {
          const image = new Image();
          image.onload = () => {
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            canvas.width = image.width;
            canvas.height = image.height;

            // 绘制原始图片
            context.drawImage(image, 0, 0);

            // 添加水印
            context.font = '20px Arial';
            context.fillStyle = 'rgba(128,128,128,0.5)';
            context.textAlign = 'center';

            context.fillText(watermarkText, canvas.width / 2, canvas.height);

            // 将 Canvas 转换为 Base64 图片
            const watermarkedImage = canvas.toDataURL('image/png');
            resolve(watermarkedImage);
          };
          image.onerror = (error) => {
            reject(error);
          };
          image.src = base64String;
        });
      }
    }
  }
</script>

<style>
</style>

2.2父组件

这里就简单一写,反正都是差不多的,这里使用button按钮的userSign1方法进行调用在线签名组件,然后使用getSign1方法进行回调,将上传后的图片赋值给本页面的signFiles1进行显示。

<a-col :span="12" :style="formDisabled?(model.signFile1?'':'display: none;'):''">
            <a-form-model-item label="签字"  :labelCol="labelCol" :wrapperCol="wrapperCol" prop="signFiles1">
              <a-button  @click="userSign1" icon="edit">前往签字</a-button>
              <esignature ref="signFormTo1" @getSign="getSign1"/>
              <j-image-upload text="上传签字" bizPath="web/sign" v-model="signFiles1" :is-multiple="false" disabled/>
            </a-form-model-item>
          </a-col>

方法知己简单明了

//签名
userSign1(){
  this.$refs.signFormTo1.handleSign();
},
 
getSign1(res) {
  this.signFiles1 = res
},

111

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/720396.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Python的垃圾分类检测识别系统(Yolo4网络)【W8】

简介&#xff1a; 垃圾分类检测识别系统旨在利用深度学习和计算机视觉技术&#xff0c;实现对不同类别垃圾的自动识别和分类。应用环境包括Python编程语言、主流深度学习框架如TensorFlow或PyTorch&#xff0c;以及图像处理库OpenCV等&#xff0c;通过这些工具集成和优化模型&a…

M41T00串行实时时钟-国产兼容RS4C1339

RS4C1340是一种实时时钟&#xff08;RTC&#xff09;/日历&#xff0c;与ST M41T00引脚兼容&#xff0c;功能等效&#xff0c;包括软件时钟校准。该器件还提供VBAT引脚上的涓流充电能力、较低的计时电压和振荡器STOP标志。寄存器映射的块访问与ST设备相同。涓流充电器和标志需要…

MATLAB 二维平面绘图

x 0:0.01:2pi: 大家还记得这个是什么意思吧 就是0到2π 每次所取的数 是相差0.01进行选取的 ysin&#xff08;x&#xff09;: figure (这个意思就是建立一个幕布) plot&#xff08;x&#xff0c;y&#xff09; 这个主要是绘制当前的二维平面的图 但是大家会发现这张图里没有标…

ArcGIS arcpy代码工具——批量要素裁剪栅格影像

系列文章目录 ArcGIS arcpy代码工具——批量对MXD文件的页面布局设置修改 ArcGIS arcpy代码工具——数据驱动工具批量导出MXD文档并同步导出图片 ArcGIS arcpy代码工具——将要素属性表字段及要素截图插入word模板 ArcGIS arcpy代码工具——定制属性表字段输出表格 ArcGIS arc…

2024最新AI大模型-LLm八股合集(三)

常见的大模型 1.ChatGLM 1.1 背景 主流的预训练框架主要有三种&#xff1a; autoregressive自回归模型&#xff08;AR模型&#xff09; &#xff1a;代表作GPT。本质上是一个left-to-right的语言模型。 通常用于生成式任务 &#xff0c;在长文本生成方面取得了巨大的成功…

每日一练:攻防世界:qr-easy

本题思路与CTFSHOW: 36D杯 misc ez-qrcode思路相同 工具链接&#xff1a;补全二维码QRazyBox - QR Code Analysis and Recovery Toolkit (h3110w0r1d.com) 1.首先&#xff0c;我们需要基于上图的干净图像。 此二维码的大小为 29x29&#xff0c;版本V的大小为N N&#xff0c;…

msvcp100.dll已加载但找不到入口点的处理方法,分析比较靠谱的msvcp100.dll解决方法

用户在日常使用中有时会遇到一个错误提示&#xff1a;“已加载 msvcp100.dll&#xff0c;但找不到入口点”。这一信息不仅引发了使用上的不便&#xff0c;也对软件的稳定性产生了质疑。理解并解决该问题不仅对确保计算机正常运行至关重要&#xff0c;也对维护软件的长期稳定性和…

最新扣子(Coze)实战案例:扣子图像流的创建及使用,完全免费教程

&#x1f9d9;‍♂️ 诸位好&#xff0c;吾乃斜杠君&#xff0c;编程界之翘楚&#xff0c;代码之大师。算法如流水&#xff0c;逻辑如棋局。 &#x1f4dc; 吾之教程&#xff0c;内含诸般技术之秘诀。吾欲以此笔记&#xff0c;传授编程之道&#xff0c;助汝解技术难题。 &#…

电长推荐:手机数据管理软件,免费备份恢复擦除手机数据

在信息时代&#xff0c;手机成为我们生活中不可或缺的工具。然而&#xff0c;管理手机中的海量数据却往往令人头疼。 特别是对于苹果用户&#xff0c;数据管理并不像安卓那样直观方便。 今天为大家推荐一款强大且免费的工具——苹安手机管家&#xff0c;它将为你的数据管理带…

【windows|003】计算机硬件基础及存储单位

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 &#x1f3c5;阿里云ACE认证高级工程师 &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&…

雨量监测预警系统:非接触式测量防汛预警

TH-SW2雨量监测预警系统是一种用于监测降雨量的重要工具&#xff0c;对于防汛预警工作具有重要意义。该系统采用非接触式测量技术&#xff0c;可以实时监测雨量数据&#xff0c;并自动预警&#xff0c;以便及时采取防汛措施&#xff0c;确保人民生命财产安全。 系统组成 1. 雨…

绿色免费离线版JS加密混淆工具 - 支持全景VR加密, 小程序js加密, H5网站加密

自从我们推出在线版的免费JS加密混淆工具以来&#xff0c;受到了广大用户的热烈欢迎。特别是全景开发人员&#xff0c;他们使用该工具加密VR插件的JS代码, 添加域名锁等&#xff0c;都非常有效地保护了插件的代码资源。 最近&#xff0c;我们收到了许多用户的反馈&#xff0c;…

伊拉克目的港清关严控,所有管控范围内的产品务必申请COC证书

伊拉克目的港清关严控&#xff0c;所有管控范围内的产品务必申请COC证书&#xff0c;COC/COI 伊拉克使馆认证&#xff0c;欢迎随时咨询小詹 近期&#xff0c;伊拉克海关扩大了进口产品管控品类&#xff0c;从产品的12大类700多种商品拓宽到800多种商品&#xff0c; 伊拉克海关…

工厂能耗监控与管理

随着工业4.0的浪潮席卷全球&#xff0c;工厂的能耗监控与管理已不再是简单的节能降耗问题&#xff0c;而是关乎企业竞争力、环保责任及可持续发展的核心议题。在这个关键时刻&#xff0c;HiWoo Cloud平台以其独特的视角和强大的功能&#xff0c;为工厂能耗监控与管理领域带来全…

C++项目实战:SPDK文件系统

目录 一、Blobstore设计框架二、Cache机制三、Blob FS I/O操作四、SPDK FUSE (Filesystem in Userspcae) 前言 Blob FS是spdk面向于用户态的轻量级的文件系统 SPDK通过绕过内核(kernel bypass)的方案&#xff0c;构筑了用户态驱动&#xff0c;并利用异步轮询、无锁机制等&a…

Java输入输出语句 和 保留字

目录 键盘输入语句 保留字 键盘输入语句 Input.java , 需要一个 扫描器(对象), 就是Scanner 步骤 &#xff1a; 导入该类的所在包, java.util.*创建该类对象&#xff08;声明变量&#xff09;调用里面的功能 案例要求&#xff1a;可以从控制台接收用户信息&#xff0c;【姓…

天然健康:源自大自然的馈赠

我们的农产品&#xff0c;承载着乡村的淳朴与大自然的馈赠。我们精选每一片土地&#xff0c;用科学种植方法&#xff0c;打造出品质卓越的农产品。无论是色泽鲜艳、口感脆嫩的蔬菜&#xff0c;还是香甜可口、营养丰富的水果&#xff0c;都让您在品尝美味的同时&#xff0c;享受…

记录一个利用winhex进行图片隐写分离的

前提 是一次大比武里面的题目&#xff0c;属实给我开了眼&#xff0c;跟我之前掌握的关于隐写合并的操作都不一样。 它不是直接在文件里面进行输入文件隐写&#xff0c;叫你输入密码&#xff0c;或者更改颜色&#xff0c;或者偏移位置&#xff1b; 它不是单纯几个文件合并&a…

css 三角形

方法一&#xff1a; <div class"triangle"></div>css .triangle{width: 8px;height: 8px;border: 8px solid #3C69EF;/* border-radius: 0px 2px 0 0; */ // 右上角加一点圆角border-block-end: 8px solid transparent;border-inline-start: 8px solid…

Pikachu靶场--文件包含

参考借鉴 Pikachu靶场之文件包含漏洞详解_pikachu文件包含-CSDN博客 文件包含&#xff08;CTF教程&#xff0c;Web安全渗透入门&#xff09;__bilibili File Inclusion(local) 查找废弃隐藏文件 随机选一个然后提交查询 URL中出现filenamefile2.php filename是file2.php&…