当前位置: 首页 > news >正文

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-esignstyle="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: '',//生成签名图片-base64imgUrl: '',//生成签名图片-base64labelCol: {xs: { span: 24 },sm: { span: 8 }},wrapperCol: {xs: { span: 24 },sm: { span: 16 }},title: '',width: 1000,visible: false,disableSubmit: false,}},methods: {//调用组件handleSign(){this.visible = truethis.$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 = 3this.lineColor = '#000000'this.bgColor = '#ffffff'this.isCrop = falsethis.imgBase = ''this.$refs.esignRef.reset();},//生成图片handleGenerate (flag) {// console.log("生成图片=========>"+this.$refs.esignRef)this.$refs.esignRef.generate().then(res => {// console.log('base64地址', res)this.imgBase = resif (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.messagethis.$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-esignstyle="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: '',//生成签名图片-base64imgUrl: '',//生成签名图片-base64labelCol: {xs: { span: 24 },sm: { span: 8 }},wrapperCol: {xs: { span: 24 },sm: { span: 16 }},title: '',width: 1000,visible: false,disableSubmit: false,}},methods: {//调用组件handleSign(){this.visible = truethis.$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 = 3this.lineColor = '#000000'this.bgColor = '#ffffff'this.isCrop = falsethis.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.messagethis.$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

相关文章:

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

Vue在线签名 一、目的二、样式三、代码1、依赖2、代码2.1 在线签名组件2.1.1 基础的2.1.2 携带时间水印的 2.2父组件 一、目的 又来了一个问题&#xff0c;直接让我在线签名&#xff08;还不能存储base64&#xff09;&#xff0c;并且还得上传&#xff0c;我直接***违禁词。 好…...

与码无关:分数限制下,选好专业还是选好学校?

本文的目标读者&#xff1a;24届的高考生和家长。 写这篇非技术性文章&#xff0c;是因为我看到了24届考生和21年的我同样迷茫。 事先声明&#xff0c;本文带有强烈的个人思考色彩&#xff0c;可能会引起不适&#xff0c;如有不同观点&#xff0c;欢迎在评论区讨论。 一、前言…...

什么是负载均衡技术?

随着网络技术的快速发展&#xff0c;互联网行业也越来越广泛&#xff0c;人们的日常生活中也离不开网络技术&#xff0c;大量的用户进行浏览访问网站时&#xff0c;企业会使用负载均衡技术&#xff0c;降低当前网站的负载&#xff0c;以此来提高网站的访问速度。 今天小编就来给…...

存在重复元素Ⅱ python3

存在重复元素Ⅱ 问题描述解题思路代码实现复杂度 问题描述 给你一个整数数组 nums 和一个整数 k &#xff0c;判断数组中是否存在两个 不同的索引 i 和 j &#xff0c;满足 nums[i] nums[j] 且 abs(i - j) < k 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff…...

【CV炼丹师勇闯力扣训练营 Day13:§6二叉树1】

CV炼丹师勇闯力扣训练营 代码随想录算法训练营第13天 二叉树的递归遍历 二叉树的迭代遍历、统一迭代 二叉树的层序遍历 一、二叉树的递归遍历&#xff08;深度优先搜索&#xff09; 【递归步骤】 1.确定递归函数的参数和返回值&#xff1a;确定哪些参数是递归的过程中需要处理…...

代码随想录算法训练营第46天 [ 121. 买卖股票的最佳时机 122.买卖股票的最佳时机II 123.买卖股票的最佳时机III ]

代码随想录算法训练营第46天 [ 121. 买卖股票的最佳时机 122.买卖股票的最佳时机II 123.买卖股票的最佳时机III ] 一、121. 买卖股票的最佳时机 链接: 代码随想录. 思路&#xff1a;dp[i][0] 第i天持有股票的最大利润 dp[i][1] 第i天不持有股票的最大利润 做题状态&#xff1a;…...

基于IDEA的Maven简单工程创建及结构分析

目录 一、用 mvn 命令创建项目 二、用 IDEA 的方式来创建 Maven 项目。 &#xff08;1&#xff09;首先在 IDEA 下的 Maven 配置要已经确保完成。 &#xff08;2&#xff09;第二步去 new 一个 project &#xff08;创建一个新工程&#xff09; &#xff08;3&#xff09;…...

解锁空间数据奥秘:ArcGIS Pro与Python双剑合璧,处理表格数据、矢量数据、栅格数据、点云数据、GPS数据、多维数据以及遥感云平台数据等

ArcGISPro提供了用户友好的图形界面&#xff0c;适合初学者快速上手进行数据处理和分析。它拥有丰富的工具和功能&#xff0c;支持各种数据格式的处理和分析&#xff0c;适用于各种规模的数据处理任务。ArcGISPro在地理信息系统&#xff08;GIS&#xff09;领域拥有广泛的应用&…...

后端路线指导(4):后端春招秋招经验分享

后端春招&秋招经验分享 春招(暑期实习) /秋招是应届生非常重要的应聘时间,每一个想就业的同学一定要有所了解! 本篇内容&#xff0c;老白将与大家分享暑期实习和秋招如何应对招聘的个人经验&#xff0c;希望每个同学看完都能有所收获! 首先说明一下老白对于面试核心竞争力的…...

面完小红书算法岗,心态崩了。。。

暑期实习基本结束了&#xff0c;校招即将开启。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。提前准备才是完全之策。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c…...

Android 断点续传进阶之多线程下载

今天继续下载的风骚走位内容—多线程多文件断点续传 Android 断点续传基础之单线程下载&#xff1a;http://blog.csdn.net/qq_27489007/article/details/53897653 效果图&#xff1a; 文件关系&#xff1a; 所需内容 多文件下载列表的显示 启动多个线程分段下载 使用通知栏…...

Python爬虫学习 | Scrapy框架详解

一.Scrapy框架简介 何为框架&#xff0c;就相当于一个封装了很多功能的结构体&#xff0c;它帮我们把主要的结构给搭建好了&#xff0c;我们只需往骨架里添加内容就行。scrapy框架是一个为了爬取网站数据&#xff0c;提取数据的框架&#xff0c;我们熟知爬虫总共有四大部分&am…...

用户态协议栈05—架构优化

优化部分 添加了in和out两个环形缓冲区&#xff0c;收到数据包后添加到in队列&#xff1b;经过消费者线程处理之后&#xff0c;将需要发送的数据包添加到out队列。添加数据包解析线程&#xff08;消费者线程&#xff09;&#xff0c;架构分层 #include <rte_eal.h> #inc…...

模拟退火算法

模拟退火算法&#xff08;Simulated Annealing, SA&#xff09;是一种用于全局优化问题的概率搜索算法&#xff0c;其灵感来自于金属退火过程。在金属退火中&#xff0c;材料被加热到高温&#xff0c;然后缓慢冷却&#xff0c;以减少其晶格中的缺陷并达到最小能量状态。模拟退火…...

Java匿名类

Java 匿名类是一种特殊的内部类&#xff0c;它没有名字&#xff0c;并且通常用来简化代码实现&#xff0c;尤其是在实现接口或者抽象类的实例时。匿名类可以在实例化时定义其行为&#xff0c;而不需要创建单独的类文件。 匿名类的特点 没有名字&#xff1a;匿名类是没有名字的…...

G7易流赋能化工物流,实现安全、环保与效率的共赢

近日&#xff0c;中国物流与采购联合会在古都西安举办了备受瞩目的第七届化工物流安全环保发展论坛。以"坚守安全底线&#xff0c;追求绿色发展&#xff0c;智能规划化工物流未来"为主题&#xff0c;该论坛吸引了众多政府部门、行业专家和企业代表的参与。G7易流作为…...

y=sin(2x)

函数 \( y \sin(2x) \) 是一个正弦函数&#xff0c;其中 \( x \) 是自变量&#xff0c;\( y \) 是因变量。这个函数描述了一个周期性波动的波形&#xff0c;其特点是&#xff1a; 1. **振幅**&#xff1a;正弦函数的振幅是 1&#xff0c;这意味着波形在 \( y \) 轴上的最大值…...

快捷方式(lnk)--加载HTA-CS上线

免责声明:本文仅做技术交流与学习... 目录 CS: HTA文档 文件托管 借助mshta.exe突破 本地生成lnk快捷方式: 非系统图标路径不同问题: 关于lnk的上线问题: CS: HTA文档 配置监听器 有效载荷---->HTA文档--->选择监听器--->选择powershell模式----> 默认生成一…...

从同—视角理解扩散模型(Understanding Diffusion Models A Unified Perspective)

从同—视角理解扩散模型 Understanding Diffusion Models A Unified Perspective【全公式推导】【免费视频讲解】 B站视频讲解 视频的论文笔记 从同一视角理解扩散模型【视频讲解笔记】 配合视频讲解的同步笔记。 整个系列完整的论文笔记内容如下&#xff0c;仅为了不用—一回复…...

docker 基本用法及跨平台使用

一、Docker的优点 docker 主要解决的问题就是程序开发过程中编译和部署中遇到的环境配置的问题。 1.1 Docker与其他虚拟机层次结构的区别** 运行程序重点关注点在于环境。 VM虚拟机是基于Hypervisor虚拟化服务运行的。 Docker是基于内核的虚拟化技术实现的。 1.2 Docker的技…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...