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

uniapp canvas实现手写签字功能(包括重签,撤回等按钮)

实现效果图:

实现代码,复制即用:

<template>
<view class="grouped-list">
<view class="sign-content">
<view class="signature-container">
<view class="canvas-wrapper">
<canvas id="signatureCanvas" canvas-id="signatureCanvas" :disable-scroll="true"
@touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"
class="signature-canvas">
</canvas>
<view v-if="!hasSignatureText" class="placeholder-text">请在该区域签字</view>
</view>
<view class="btn-arr">
<button class="common-btn" @click="clearSignature">重签</button>
<button class="common-btn" @click="undoSignature">撤回</button>
<button class="button-c common-btn" @click="completeSignature">
完成
</button>
</view>
</view>
</view>
</view>
</template>

相关js代码:

<script setup>import {onReady} from "@dcloudio/uni-app";import {ref,getCurrentInstance} from "vue";// import {// 	uploadToOss// } from "@/utils/functions/oss.js";// 签名相关变量const instance = getCurrentInstance().proxy;const signatureCtx = ref(null);//是否有实际签字const hasSignature = ref(false);//是否显示签字文本const hasSignatureText = ref(false);const isDrawing = ref(false);const lastPoint = ref({x: 0,y: 0});const signaturePaths = ref([]);const currentPath = ref([]);onReady(() => {initSignatureCanvas();});// 初始化签名canvasconst initSignatureCanvas = () => {// 获取canvas实际尺寸const query = uni.createSelectorQuery().in(instance);query.select(".signature-canvas").boundingClientRect((data) => {if (data) {// 初始化canvas上下文signatureCtx.value = uni.createCanvasContext("signatureCanvas",instance);// 设置白色背景signatureCtx.value.setFillStyle("#ffffff");signatureCtx.value.fillRect(0, 0, data.width, data.height);// 设置线条样式 - 确保是实线signatureCtx.value.setLineWidth(4); // 线条宽度signatureCtx.value.setStrokeStyle("#000000"); // 黑色线条signatureCtx.value.setLineCap("round"); // 圆形线帽signatureCtx.value.setLineJoin("round"); // 圆形连接if (signatureCtx.value.setGlobalCompositeOperation) {signatureCtx.value.setGlobalCompositeOperation("source-over");}// signatureCtx.value.setGlobalCompositeOperation('source-over'); // 正常绘制模式// 渲染初始背景signatureCtx.value.draw(true);}}).exec();};// 触摸开始const onTouchStart = (e) => {isDrawing.value = true;hasSignatureText.value = true;const touch = e.touches[0];lastPoint.value = {x: touch.x,y: touch.y};currentPath.value = [{x: touch.x,y: touch.y}];};// 触摸移动const onTouchMove = (e) => {if (!isDrawing.value) return;const touch = e.touches[0];const currentPoint = {x: touch.x,y: touch.y};// 每次绘制前重新设置线条样式,确保是实线signatureCtx.value.setLineWidth(4);signatureCtx.value.setStrokeStyle("#000000");signatureCtx.value.setLineCap("round");signatureCtx.value.setLineJoin("round");// 绘制从上一点到当前点的线段signatureCtx.value.beginPath();signatureCtx.value.moveTo(lastPoint.value.x, lastPoint.value.y);signatureCtx.value.lineTo(currentPoint.x, currentPoint.y);signatureCtx.value.stroke();signatureCtx.value.draw(true);// 添加这一段,判断用户是否实际移动了手指if (!hasSignature.value) {const distance = Math.sqrt(Math.pow(currentPoint.x - lastPoint.value.x, 2) +Math.pow(currentPoint.y - lastPoint.value.y, 2));// 如果移动距离大于阈值,认为用户确实在签名if (distance > 2) {hasSignature.value = true;}}currentPath.value.push(currentPoint);lastPoint.value = currentPoint;};// 触摸结束const onTouchEnd = () => {if (isDrawing.value) {signaturePaths.value.push([...currentPath.value]);currentPath.value = [];}isDrawing.value = false;};// 清除签名const clearSignature = () => {// 获取canvas实际尺寸进行清除const query = uni.createSelectorQuery().in(instance);query.select(".signature-canvas").boundingClientRect((data) => {if (data) {// 清除canvas内容signatureCtx.value.clearRect(0, 0, data.width, data.height);// 重新设置白色背景signatureCtx.value.setFillStyle("#ffffff");signatureCtx.value.fillRect(0, 0, data.width, data.height);// 重新设置线条样式signatureCtx.value.setLineWidth(4);signatureCtx.value.setStrokeStyle("#000000");signatureCtx.value.setLineCap("round");signatureCtx.value.setLineJoin("round");signatureCtx.value.draw(true);hasSignatureText.value = false}}).exec();hasSignature.value = false;signaturePaths.value = [];currentPath.value = [];};// 撤回上一笔const undoSignature = () => {if (signaturePaths.value.length > 0) {signaturePaths.value.pop();// 先检查是否还有签名路径if (signaturePaths.value.length === 0) {hasSignature.value = false;hasSignatureText.value = false;}redrawSignature();}};// 重绘签名const redrawSignature = () => {const query = uni.createSelectorQuery().in(instance);query.select(".signature-canvas").boundingClientRect((data) => {if (data) {// 清除canvassignatureCtx.value.clearRect(0, 0, data.width, data.height);// 设置白色背景signatureCtx.value.setFillStyle("#ffffff");signatureCtx.value.fillRect(0, 0, data.width, data.height);if (signaturePaths.value.length === 0) {hasSignature.value = false;hasSignatureText.value = false; // 没有签名时,隐藏签名文本signatureCtx.value.draw(true);return;}// 重新设置绘制样式,确保重绘时也是实线signatureCtx.value.setLineWidth(4);signatureCtx.value.setStrokeStyle("#000000");signatureCtx.value.setLineCap("round");signatureCtx.value.setLineJoin("round");signaturePaths.value.forEach((path) => {if (path.length > 0) {signatureCtx.value.beginPath();signatureCtx.value.moveTo(path[0].x, path[0].y);// 绘制连续的实线路径for (let i = 1; i < path.length; i++) {signatureCtx.value.lineTo(path[i].x, path[i].y);}signatureCtx.value.stroke();}});signatureCtx.value.draw(true);}}).exec();};// 完成签名并上传const completeSignature = async () => {if (!hasSignature.value) {uni.showToast({title: "请签字",icon: "none",});return;}try {// 将canvas转为临时文件const res = await new Promise((resolve, reject) => {uni.canvasToTempFilePath({canvasId: "signatureCanvas",success: resolve,fail: reject,});});console.log('签名文件===', res);// // 上传到OSS// const ossPath = "signature/examPromise/";// const uploadResult = await uploadToOss(ossPath, res.tempFilePath);// const signature_url = uploadResult.url.split(/\.com\//)[1];// evaluationApi// 	.signDataSubmit({// 		operation_examiner_id: examiner_id.value,// 		signature_url: signature_url,// 	})// 	.then((res) => {// 		if (res.code == 0) {// 			uni.hideLoading();// 			if (!examInfoData.value.exam_stu_pra_id && is_main.value == 1) { //没有考试信息 并且是主考评员// 				uni.redirectTo({ // 跳转学生选择页面// 					url: '/pages/evaluation/studentSelect/index'// 				})// 			} else {// 				const {// 					stu_pra_examiner_id,// 					exam_stu_pra_id,// 					is_ready,// 					is_direct// 				} = examInfoData.value// 				evaluationStore.setStu_pra_examiner_id(stu_pra_examiner_id) //有考场信息 保存// 				uni.redirectTo({ // 跳转赛前准备页面// 					url: '/pages/evaluation/readiness/index?pra_id=' + exam_stu_pra_id +// 						'&isReady=' + is_ready + '&isMain=' + is_direct// 				})// 			}// 		}// 	})// 	.catch((err) => {// 		uni.$star.showToast("签名保存失败,请重试!");// 		uni.hideLoading();// 	});} catch (error) {console.error("签名保存失败:", error);uni.hideLoading();uni.showToast({title: "签名保存失败",icon: "error",});}};
</script>

自定义样式:

<style lang="scss" scoped>
.grouped-list {
height: 100vh;
overflow: hidden;
padding-top: 200rpx;

        .sign-content {
width: 100%;
padding: 30rpx;
box-sizing: border-box;
}

        .signature-container {
background-color: #fff;
border-radius: 14rpx;
width: 100%;
}

        .canvas-wrapper {
position: relative;
width: 100%;
height: 400rpx;
border: 2rpx dashed #d3dcfd;
border-radius: 14rpx;
margin-bottom: 30rpx;
background-color: #fafafa;
overflow: hidden;
}

        .signature-canvas {
width: 100%;
height: 100%;
border-radius: 14rpx;
background-color: #f8f8f8;
}

        .btn-arr {
display: flex;
align-items: center;
justify-content: center;
}

        .common-btn {
width: 220rpx;
height: 60rpx;
line-height: 60rpx;
font-weight: normal;
}

        .placeholder-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #869de9;
font-size: 28rpx;
pointer-events: none;
}
}
</style>

http://www.dtcms.com/a/506800.html

相关文章:

  • 大语言模型如何精准调用函数—— Function Calling 系统笔记
  • 商业智能BI 浅谈数据孤岛和数据分析的发展
  • Chrome 浏览器扩展图片 提取大师
  • Uniapp微信小程序开发:修改了数据,返回父页面时,父页面数据重新加载
  • etcd-问题-调优-监控
  • 【国内电子数据取证厂商龙信科技】手机版Chrome调试方法
  • 做企业网站要怎么设计方案信产部网站备案
  • 成都爱站网seo站长查询工具上海跨境电商网站制作
  • Linux网络编程:Socket编程TCP
  • 库周报 | 25亿融资!天兵科技冲刺IPO;双十一3D打印机价格战打响;拓竹、爱乐酷等发新品
  • 替代传统电脑的共享云服务器如何实现1拖8SolidWorks设计办公
  • Python趣味算法:三色球问题:Python暴力枚举法的完美实践
  • Amazon Bedrock AgentCore Memory:亚马逊云科技的托管记忆解决方案
  • 赋能智慧城市:EasyGBS如何构筑智慧城市视觉中枢?
  • QT常用控件使用大全
  • 前端开发之ps基本使用
  • 建设局合同备案是哪个网站湛江哪家公司建网站最好
  • 揭开命令行的面纱:终端、CLI、Shell的终极辨析
  • 浏览器直接访问xxx.apk下载链接,无法直接下载apk
  • C# 基础——值类型与引用类型的本质区别
  • 19.8 基于Whisper+多模态的语音生成PPT实战:3秒出稿,92.4%准确率的深度整合方案
  • 2510d,d正式通过版本
  • Android Automotive相关术语
  • YOLOv2原理介绍
  • 长沙网站建立公司网络舆情研判分析报告
  • 网站建设创业计划书淘宝店铺推广
  • 华为鲲鹏 Aarch64 环境下多 Oracle 、mysql数据库汇聚到Cloudera CDP7.3操作指南
  • numpy中的meshgrid()的用法
  • 【C++高阶数据结构】红黑树
  • 最近我用springBoot开发了一个二手交易管理系统,分享一下实现方式~