uniapp:手写签名,多张图合成一张图
要实现的内容:手写签名,协议内容。点击提交后:生成1张图片,有协议内容和签署日期和签署人。
实现的效果图如下:


1、签名页面
<template><view class="index"><u-navbar title="电子协议" :is-back="false" :border-bottom="false" title-color="#333" :background="{background:''}"><view class="page_navbar_warp"><image src="../../static/icon/0.png" mode="" class="page_navbar_commonImg" @click="$go(1,1)"></image></view></u-navbar><image src="https://www.*****/xieyi.png" mode="" class="banner"></image><view class="signBox"><view class="title">签名区</view><view style="width: 700rpx;height: 450rpx;"><l-signature disableScroll backgroundColor="rgba(255, 249, 238, .0)" ref="signatureRef" penColor="#333" :penSize="5" :openSmooth="true" ></l-signature></view></view><view class="footer"><view class="btn1 t-c" @click="onClick('undo')">撤消</view><u-button class="btn2 t-c" @click="onClick('save')" :loading="loading">提交</u-button></view></view>
</template><script>export default {data() {return {loading:false,}},methods:{onClick(type) {if(type == 'openSmooth') {this.openSmooth = !this.openSmoothreturn}if (type == 'save') {this.$refs.signatureRef.canvasToTempFilePath({success: (res) => {// 是否为空画板 无签名// 生成图片的临时路径// H5 生成的是base64let url = res.tempFilePath;console.log(res);if(res.isEmpty){this.$toast('请签名')}else{this.loading = true;this.$uploadImage('common/upload', url).then(res => {this.loading = false;if(res.code == 1){this.$go(2,'/pages/mine/canvas?signImg='+res.data.fullurl)}})}}})return}if (this.$refs.signatureRef) this.$refs.signatureRef[type]()}}}
</script><style scoped lang="scss">.index{min-height: 100vh;position: relative;.banner{display: block;width: 585rpx;height: 416rpx;margin: auto;}.signBox{border: 1rpx dashed #BF9350;width: 700rpx;height: 500rpx;margin: 32rpx auto;.title{padding-top: 32rpx;font-size: 40rpx;color: #BF9350;padding-left: 32rpx;}}.footer{position: fixed;left: 0;bottom: 0;width: 750rpx;height: 98rpx;background: #fff;box-shadow: 0rpx 3rpx 6rpx 1rpx rgba(0,0,0,0.32);padding: 0 50rpx;display: flex;align-items: center;justify-content: space-between;.btn1{width: 300rpx;height: 81rpx;border-radius: 41rpx 41rpx 41rpx 41rpx;border: 1rpx solid #BF9350;font-size: 32rpx;color: #BF9350;}.btn2{width: 300rpx;height: 81rpx;background: #BF9350;border-radius: 41rpx 41rpx 41rpx 41rpx;font-size: 32rpx;color: #fff;}}}
</style>
2、canvas页面,用来合成1张图
<template><view class="demo"><u-navbar title="电子协议" :is-back="false" :border-bottom="false" title-color="#333" :background="{background:'#FFFAF3'}"><view class="page_navbar_warp"><image src="../../static/icon/0.png" mode="" class="page_navbar_commonImg" @click="$go(1,1)"></image></view></u-navbar><canvas :style="{ width: canvasW + 'px', height: canvasH + 'px' }" canvas-id="myCanvas" id="myCanvas"></canvas><view class="footer"><view class="btn1 t-c" @click="$go(1,1)">取消</view><u-button class="btn2 t-c" @click="submit" :loading="loading" shape="circle" :ripple="true">提交</u-button></view></view>
</template>
<script>export default {components: {},data() {return {loading:false,canvasW:0, // 画布宽canvasH:0, // 画布高SystemInfo:{}, // 设备信息goodsImg: {}, // 协议图片signImg:{}, // 签名图片signW:120, // 签名图片大小bgImg:{},year:'',mon:'',date:'',tempFilePath:'',}},async onLoad(option) {var start = new Date();this.year = start.getFullYear();this.mon = start.getMonth() + 1;this.date = start.getDate();// 获取设备信息,主要获取宽度,赋值给canvasW 也就是宽度:100%this.SystemInfo = await this.getSystemInfo();// 获取协议图片,签名二维码图片信息,APP端会返回图片的本地路径(H5端只能返回原路径)this.bgImg = await this.getImageInfo('https://www.*******/xieyi.png');this.goodsImg = await this.getImageInfo('https://www.*******/bg.png');this.signImg = await this.getImageInfo(option.signImg);this.canvasW = this.SystemInfo.windowWidth; // 画布宽度// #ifdef APP-PLUSthis.canvasH = this.SystemInfo.windowHeight-94-uni.getSystemInfoSync().statusBarHeight; // 画布高度 = 页面高度-(导航栏固定44px+footer的50px+APP内手机双跳栏的高度)// #endif// #ifdef H5this.canvasH = this.SystemInfo.windowHeight-94; // #endif// 如果主图,二维码图片,设备信息都获取成功,开始绘制海报,这里需要用setTimeout延时绘制,否则可能会出现图片不显示。if(this.goodsImg.errMsg == 'getImageInfo:ok' && this.signImg.errMsg == 'getImageInfo:ok' && this.SystemInfo.errMsg == 'getSystemInfo:ok'){uni.showToast({icon:'loading',mask:true,duration:10000,title: '加载中,请稍后',});setTimeout(()=>{var ctx = uni.createCanvasContext('myCanvas', this);// 填充背景ctx.drawImage(this.bgImg.path, 0, 0, this.canvasW, this.canvasH) // drawImage(图片路径,x,y,绘制图像的宽度,绘制图像的高度)// 绘制协议主图ctx.drawImage(this.goodsImg.path, 50, 60, this.canvasW-100, this.canvasW-180) // drawImage(图片路径,x,y,绘制图像的宽度,绘制图像的高度)// 签署日期ctx.setFontSize(16)ctx.setFillStyle('#333')ctx.fillText(`签署日期:${this.year}年${this.mon}月${this.date}日`, 50, this.canvasH -this.signW-80);// 签署人ctx.setFontSize(14)ctx.setFillStyle('#333')ctx.fillText('签署人:', 50, this.canvasH -this.signW-40);// 签署人ctx.drawImage(this.signImg.path, 90, this.canvasH-this.signW-80, this.signW, this.signW) // drawImage(图片路径,x,y,绘制图像的宽度,绘制图像的高度,二维码的宽,高)ctx.draw(true,(ret)=>{ // draw方法 把以上内容画到 canvas 中。console.log(ret) uni.showToast({icon:'success',mask:true,title: '绘制完成',});uni.canvasToTempFilePath({ // 保存canvas为图片canvasId: 'myCanvas',quality: 1,complete: (res)=> {console.log(res)// 在H5平台下,tempFilePath 为 base64, // 图片提示跨域 H5保存base64失败,APP端正常输出临时路径if(res.tempFilePath){this.tempFilePath = res.tempFilePath;}},})});},1500)}else{console.log('err')}},methods: {submit(){this.loading = true;console.log('需要提交给后台的图片',this.tempFilePath)},// 获取图片信息getImageInfo(image) {return new Promise((req, rej) => {uni.getImageInfo({src: image,success: function(res) {req(res)},});})},// 获取设备信息getSystemInfo(){return new Promise((req, rej) => {uni.getSystemInfo({success: function (res) {req(res)}});})},},}
</script><style scoped lang="scss">.footer{position: fixed;left: 0;bottom: 0;width: 750rpx;height: 50px;box-shadow: 0rpx 3rpx 6rpx 1rpx rgba(0,0,0,0.32);padding: 0 50rpx;display: flex;align-items: center;justify-content: space-between;background: #fff;.btn1{width: 300rpx;height: 40px;border-radius: 41rpx 41rpx 41rpx 41rpx;border: 1rpx solid #BF9350;font-size: 32rpx;color: #BF9350;}.btn2{width: 300rpx;height: 40px;background: #BF9350;border-radius: 41rpx 41rpx 41rpx 41rpx;font-size: 32rpx;color: #fff;}}
</style>
备注:
1、协议页面内用的l-signature来自于uniapp插件市场
2、canvas页面灵感来自于之前写过的一篇绘制海报文章
3、页面中用到的 xieyi.png(协议内容)、bg.png(底图)、以及签名后的option.signImg(签名图),都需要后台设置允许跨域。否则H5就会报错画布污染无法生成base64。
这个问题在APP内不存在,只有H5会出现。
相关文章:
uniapp:手写签名,多张图合成一张图
要实现的内容:手写签名,协议内容。点击提交后:生成1张图片,有协议内容和签署日期和签署人。 实现的效果图如下: 1、签名页面 <template><view class"index"><u-navbar title"电子协议…...
DevExpress WPF Tree List组件,让数据可视化程度更高!(一)
DevExpress WPF Tree List组件是一个功能齐全、数据感知的TreeView-ListView混合体,可以把数据信息显示为REE、GRID或两者的组合,在数据绑定或非绑定模式下,具有完整的数据编辑支持。 DevExpress WPF 拥有120个控件和库,将帮助您…...
Linux操作系统下安装python环境
参考:Linux操作系统下安装python环境_linux如何下载python_秃头小猿-F的博客-CSDN博客 注意 切换用户 二、切换root用户 1.给root用户设置密码:命令:sudo passwd root输入密码,并确认密码。2.重新输入命令:su root …...
JavaScript的宏任务和微任务
宏任务和微任务 JS为微任务和宏任务简单介绍任务执行顺序例子任务执行顺序简单例子 关于new Promise实例化过程的例子 JS为微任务和宏任务简单介绍 js是单线程的,但是分同步异步微任务和宏任务皆为异步任务,它们都属于一个队列宏任务一般是:…...
java的空引用null和空字符串““
java中如果字符串变量指向null,表示空引用,此时对字符串求长度会抛出异常。 而""表示一个空字符串,对字符串求长度是可以的,求出来的字符串长度为0。 举例: package com.thb;public class Test6 {public s…...
Python+OpenCV实现自动扫雷,挑战扫雷世界记录!
目录 准备 - 扫雷软件 实现思路 - 01 窗体截取 - 02 雷块分割 - 03 雷块识别 - 04 扫雷算法实现 福利:文末有Python全套资料哦 我们一起来玩扫雷吧。用PythonOpenCV实现了自动扫雷,突破世界记录,我们先来看一下效果吧。 中级 - 0.74秒 …...
XtarBackup 8.0.33-28 prepare 速度提升 20 倍!
在这篇博文中,我们将描述 Percona XtraBackup 8.0.33-28 的改进,这显著减少了备份准备所需的时间,以便进行恢复操作。 Percona XtraBackup 中的这一改进显着缩短了新节点加入 Percona XtraDB 集群(PXC) 所需的时间。 …...
Blazor前后端框架Known-V1.2.8
V1.2.8 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行。 Gitee: https://gitee.com/known/KnownGithub:https://github.com/known/Known 概述 基于C#和Blazor…...
python模拟加密爬取诸葛
用python模拟代码加密逻辑 获取arg1def get_arg1(arg):_0x4b082b [0xf, 0x23, 0x1d, 0x18, 0x21, 0x10, 0x1, 0x26, 0xa, 0x9, 0x13, 0x1f, 0x28, 0x1b, 0x16, 0x17, 0x19, 0xd,0x6, 0xb, 0x27, 0x12, 0x14, 0x8, 0xe, 0x15, 0x20, 0x1a, 0x2, 0x1e, 0x7, 0x4, 0x11, 0x5, 0x3…...
安全学习DAY13_WEB应用源码获取
信息打点-WEB应用-源码获取 文章目录 信息打点-WEB应用-源码获取小节概述-思维导图资产架构-源码获取(后端)后端-开源后端-闭源-源码泄露源码泄露原因源码泄露方式集合网站备份压缩包git,svn源码泄露DS_Store文件泄露composer.json 泄露资源搜…...
Selenium+Java环境搭建(测试系列6)
目录 前言: 1.浏览器 1.1下载Chrome浏览器 1.2查看Chrome浏览器版本 1.3下载Chrome浏览器的驱动 2.配置系统环境变量path 3.验证是否成功 4.出现的问题 结束语: 前言: 这节中小编给大家讲解一下有关于Selenium Java环境的搭建&…...
Shell编程学习-If条件语句
示例1:使用传参的方式实现两个整数的比较: #!/bin/bash # read -p "Please input second number: " num1 num2if [ $num1 -lt $num2 ]thenecho "$num1 is less than $num2."exit fiif [ $num1 -eq $num2 ]thenecho "$num1 is …...
Android getDrawable()和getColor()
Android getDrawable() 1.过时代码 虽然过时,但是不妨碍使用 context.getResources().getDrawable(R.drawable.xxx) 2.建议代码 context.getDrawable(R.drawable.xxx) 有API限制 3.最新代码 ContextCompat.getDrawable(getContext(), R.drawable.xxx); 有A…...
Android Calendar
1.字符串日期比较大小 public static boolean compareDate(String pre, String last) {SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd");try {Date lastDate sdf.parse(last);Calendar lastCal Calendar.getInstance();lastCal.setTime(lastDate);Date …...
C# PaddleDetection 目标检测 ( yolov3_darknet)
效果 项目 VS2022.net4.8OpenCvSharp4Sdcb.PaddleDetection 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using Sdcb.PaddleDetection; using Sdcb.PaddleInference; using System; using System.Drawing; using System.Windows.Forms; using YamlDotNet;namespa…...
matlab多线程,parfor循环进度,matlab互斥锁
一. 内容简介 matlab多线程,parfor循环进度,matlab互斥锁 二. 软件环境 2.1 matlab 2022b 2.2代码链接 https://gitee.com/JJW_1601897441/csdn 三.主要流程 3.1 matlab多线程 有好几种,最简单的,最好理解的就是parfor&am…...
建木使用进阶-创建密钥管理
阿丹: 第一次我们进入建木,第一件事情就是配置我们相关的密钥。 解读: 在建木中我们可以进行创建密钥来对我们服务器等密码进行方便的管理。 注意: 登录的时候账号为:admin 密码为:123456 这是初始…...
多模态第2篇:MMGCN代码配置
一、Windows环境 1.创建并激活虚拟环境 #创建虚拟环境命名为mmgcn,指定python版本为3.8 conda create -n mmgcn python3.8 #激活虚拟环境 conda activate mmgcn2.安装pytorch #torch2.0.0 cu118 pip install torch2.0.0cu118 torchvision0.15.1cu118 torchaudio…...
DHCP部署与安全详解
文章目录 一、DHCP是什么?二、DHCP相关概念三、DHCP优点四、DHCP原理1. 客户机发送DHCP Discovery广播包(发现谁是DHCP服务器)2. 服务器响应DHCP Offer广播包3. 客户机发送DHCP Request广播包4. 服务器发送DHCP ACK广播包 五、DHCP续约六、部…...
华为数通HCIP-PIM原理与配置
组播网络概念 组播网络由组播源,组播组成员与组播路由器组成。 组播源的主要作用是发送组播数据。 组播组成员的主要作用是接收组播数据,因此需要通过IGMP让组播网络感知组成员位置与加组信息。 组播路由器的主要作用是将数据从组播源发送到组播组成员。…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...
