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

news2024/11/27 4:04:27

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.coloradmin.cn/o/1825876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】——常见排序

文章目录 一、 冒泡排序二、 选择排序三、插入排序四、 快速排序1. hoare版本2. 优化版本3. 前后指针法4. 非递归版本 五、 堆排序六、 希尔排序七、 归并排序1. 递归版本2. 非递归版本 八、 计数排序 在开始之前先准备一个交换数据的函数&#xff0c;排序会经常用到 //交换位置…

【设计模式深度剖析】【8】【行为型】【备忘录模式】| 以后悔药为例加深理解

&#x1f448;️上一篇:观察者模式 设计模式-专栏&#x1f448;️ 文章目录 备忘录模式定义英文原话直译如何理解呢&#xff1f; 3个角色1. Memento&#xff08;备忘录&#xff09;2. Originator&#xff08;原发器&#xff09;3. Caretaker&#xff08;负责人&#xff09;类…

FPGA - 数 - 加减乘除

一&#xff0c;数的表示 首先&#xff0c;将二进制做如下解释&#xff1a; 2的0次方1 2的1次方2 2的2次方4 2的3次方8 ..... 以此类推&#xff0c;那么任何整数&#xff0c;或者说任意一个自然数均可以采用这种方式来表示。 例如&#xff0c;序列10101001&#xff0c;根据上述…

【数据挖掘】机器学习中相似性度量方法-欧式距离

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 路虽远&#xff0c;行则将至&#…

三运放仪表放大器通过设置单个电阻器的值来调整增益

从公式 1 中可以看出&#xff0c;我们可以通过调整单个电阻器 R G的值来调整仪表放大器的差分增益。这很重要&#xff0c;因为与电路中的其他电阻器不同&#xff0c; RG的值不需要与任何其他电阻器匹配。 例如&#xff0c;如果我们尝试通过更改 R 5的值来设置增益&#xff0c;…

PHP杂货铺家庭在线记账理财管理系统源码

家庭在线记帐理财系统&#xff0c;让你对自己的开支了如指掌&#xff0c;图形化界面操作更简单&#xff0c;非常适合家庭理财、记账&#xff0c;系统界面简洁优美&#xff0c;操作直观简单&#xff0c;非常容易上手。 安装说明&#xff1a; 1、上传到网站根目录 2、用phpMyad…

Linux文本处理三剑客+正则表达式

Linux文本处理常用的3个命令&#xff0c;脚本或者文本处理任务中会用到。这里做个整理。 三者的功能都是处理文本&#xff0c;但侧重点各不相同&#xff0c;grep更适合单纯的查找或匹配文本&#xff0c;sed更适合编辑匹配到的文本&#xff0c;awk更适合格式化文本&#xff0c;对…

牛客热题:兑换零钱(一)

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;兑换零钱(一)题目链接方法一&am…

学习资料分析

学习资料分析 速算运算 √截位直除分数比较等比修正其他速算方法基期与现期基本概念求基期求现期增长率与增长量增长相关统计术语求一般增长率比较一般增长率增长量比重比重相关公式求比重平均数倍数间隔增长乘积增长率年增长率混合增长率资料分析:主要测查报考者对文字、数字…

windows环境如何运行python/java后台服务器进程而不显示控制台窗口

1.通常我们在windows环境下使用Java或Python语言编写服务器程序&#xff0c;都希望他在后台运行&#xff0c;不要显示黑乎乎的控制台窗口&#xff1a; 2.有人写了一个bat文件: cd /d D:\lottery\server && python .\main.py 放到了开机自启动里&#xff0c;可是开机的…

“土猪拱白菜” 的学霸张锡峰,如今也苦于卷后端

大家好&#xff0c;我是程序员鱼皮&#xff0c;前几天在网上刷到了一个视频&#xff0c;是对几年前高考励志演讲的学霸张锡峰的采访。 不知道大家有没有看过他的演讲视频。在演讲中&#xff0c;衡水中学的学霸张锡峰表达了城乡孩子差距大、穷人家的孩子只想要努力成为父母的骄…

港理工最新综述:基于LLM的text-to-SQL调查(方法实验数据全面梳理)1

【摘要】文本到SQL旨在将自然语言问题转换为可执行的SQL语句,这对用户提问理解、数据库模式理解和SQL生成都是一个长期存在的挑战。传统的文本到SQL系统包括人工工程和深度神经网络。随后,预训练语言模型(PLMs)被开发并用于文本到SQL任务,取得了可喜的成绩。随着现代数据库变得…

02_01_SpringMVC初识

一、回顾MVC三层架构 1、什么是MVC三层 MVC是 模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;、控制器&#xff08;Controller&#xff09;的简写&#xff0c;是一种软件设计规范。主要作用是降低视图与业务逻辑之间的双向耦合&#xff0c;它不是一种…

如何轻松利用人工智能深度学习,提升半导体制造过程中的良率预测?

背景 这个项目涉及半导体制造过程的监测领域。在半导体制造中&#xff0c;不断收集来自传感器或过程测量点的信号是常态。然而&#xff0c;并非所有这些信号在特定的监测系统中都同等重要。这些信号包括了有用的信息、无关的信息以及噪声。通常情况下&#xff0c;工程师获得的…

MySQL-----排序 GROUP BY

在我们对数据进行分析的时候&#xff0c;通常会根据一个或多个列对结果集进行分组&#xff0c;从而得到我们想要的结果。例如&#xff1a;统计考某一门课程的学生信息等。 而MySQL的GROUP BY 语句根据一个或多个列对结果集进行分组。同时&#xff0c;我们也可以使用 COUNT, SUM…

【实战进阶】用Zig高效引入外部库,打造专属命令行神器!

经过几周的密集练习&#xff0c;我们开始对 Zig 有了感觉了吧。我反正已经开始能熟练的安装 Zig 并搭建项目了。本篇将把这些核心的技能加以混合使用&#xff0c;拿编写命令行程序为目的&#xff0c;把整个流程给大家跑一遍&#xff0c;手把手教导一遍&#xff0c;和大家一起梳…

Vscode中使用make命令

前言 需要注意&#xff0c;如下操作需要进行网络代理&#xff0c;否则会出现安装失败的情况 安装 第一步 — 安装MingGW &#xff08;1&#xff09;进入官网下载 &#xff08;2&#xff09;下载完成之后&#xff0c;双击exe文件 &#xff08;3&#xff09;点击Install &#x…

day09--151.翻转字符串里的单词+ 右旋字符串

一、151.翻转字符串里的单词 题目链接&#xff1a;https://leetcode.cn/problems/reverse-words-in-a-string/ 文章讲解&#xff1a;https://programmercarl.com/0151.%E7%BF%BB%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%87%8C%E7%9A%84%E5%8D%95%E8%AF%8D.html#%E7%AE%97%E6%…

PROSAIL模型前向模拟与植被参数遥感

原文链接&#xff1a;PROSAIL模型前向模拟与植被参数遥感 “绿水青山就是金山银山”的生态文明理念现已深入人心&#xff0c;从顶层设计到全面部署&#xff0c;生态文明建设进入举措最实、推进最快、力度最大、成效最好的时期。生态文明评价必须将生态系统健康作为基本内容&am…

什么是深拷贝;深拷贝和浅拷贝有什么区别;深拷贝和浅拷贝有哪些方法(详解)

目录 一、为什么要区别深拷贝和浅拷贝 二、浅拷贝 2.1、什么是浅拷贝 2.2、浅拷贝的方法 使用Object.assign() 使用展开运算符(...) 使用数组的slice()方法&#xff08;仅适用于数组&#xff09; 2.3、关于赋值运算符&#xff08;&#xff09; 三、深拷贝 3.1、什么是…