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让组播网络感知组成员位置与加组信息。 组播路由器的主要作用是将数据从组播源发送到组播组成员。…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
人工智能 - 在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型
在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型。这些平台各有侧重,适用场景差异显著。下面我将从核心功能定位、典型应用场景、真实体验痛点、选型决策关键点进行拆解,并提供具体场景下的推荐方案。 一、核心功能定位速览 平台核心定位技术栈亮…...
加密通信 + 行为分析:运营商行业安全防御体系重构
在数字经济蓬勃发展的时代,运营商作为信息通信网络的核心枢纽,承载着海量用户数据与关键业务传输,其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级,传统安全防护体系逐渐暴露出局限性&a…...
TCP/IP 网络编程 | 服务端 客户端的封装
设计模式 文章目录 设计模式一、socket.h 接口(interface)二、socket.cpp 实现(implementation)三、server.cpp 使用封装(main 函数)四、client.cpp 使用封装(main 函数)五、退出方法…...
