资讯动态

微信小程序电子合同签署组件开发实战:从签名画布到后端对接

发布时间:2026/9/1 17:48:41 来源:尧图企业网站定制
简介面向微信小程序开发者这份代码压缩包提供电子合同签署组件的完整开发实战源码重点解决企业远程签约中的身份认证、合同展示、数字签名、存证服务和合同管理等核心问题。资源基于原生小程序框架实现使用小程序原生组件完成页面与交互可直接用于企业服务类小程序的功能集成或二次开发。压缩包共10个文件类型覆盖js逻辑脚本、wxml页面结构、wxss样式、json配置以及README说明文档包体仅12KB结构清晰轻量便于阅读和移植。其中js文件负责签署流程、合同数据与签名面板逻辑wxml与wxss实现签约页面展示json用于组件配置README则对运行方式和实现思路进行说明。目前已有78人学习使用。通过该资源读者可以了解签名页面中的签字板组件与合同数据工具模块的实现方式掌握调用微信实名认证、接入电子合同API及处理存证数据的思路还能参考项目目录划分快速定位核心代码结合自身业务场景完成签约功能落地节省从零开发的时间。 做微信小程序开发这些年被问得最多的业务需求里“电子合同签署”绝对排得上前三。尤其是疫情之后线下见面签约越来越不现实不管是租房合同、劳务协议还是采购单、报价确认函客户都希望在微信里把字签了。但小程序端的电子合同真不是简单放个PDF、让人点一下“同意”就完事涉及身份确认、手写签名采集、合同留痕、防篡改校验一堆事。这篇就把我做过的电子合同签署组件实战经验完整拆一遍从组件结构设计、签名画布实现到后端接口对接、常见坑位排查全部交代清楚适合正在做小程序签名功能、或者打算接电子签约却没头绪的开发者参考。1. 项目整体设计与方案选型1.1 电子签约在小程序端的核心场景先捋清楚业务场景。微信小程序里的电子合同签署常见的有两类一类是轻量级签名比如你签个服务确认单、门店会员协议、电子收据用户进入页面看到合同内容勾选同意在指定区域手写签名提交即可。另一类是强合规签署比如劳动用工合同、房屋租赁合同、金融借款协议这类必须走实名认证、短信验证码、人脸核身、时间戳、防篡改存证通常是接入e签宝、法大大这类第三方平台。我做的这个组件定位是服务于第一类轻量级场景同时预留对接第三方强合规平台的能力。核心需求拆出来有三条一是用户在页面内能完整阅读合同内容二是能采集用户手写签名并准确落到合同的约定位置三是签署结果要能追溯后端有完整的签署记录和签名底图留存。1.2 自研组件还是接第三方服务很多团队一上来就问要不要直接用第三方的签约SDK。我的建议是看签约频率和合规等级。如果是低频内部场景、合同本身不涉及法律强效力的自研完全够用而且灵活可控、不依赖外部服务、也没有按次计费的成本压力。如果是高频对外业务或者合同涉及法律仲裁等强效力场景那就别折腾自研了第三方的完整证据链、司法存证服务不是自己两三个月能补上的。我在这个项目里选了自研原因有三第一合同类型固定模板不多不需要复杂的模板引擎第二用户量不大签名数据我们自己存数据库就能管第三产品还在快速迭代接口要跟内部OA、CRM系统打通第三方平台反而碍手碍脚。1.3 前后端职责划分我的做法是前后端各管一半。前端小程序负责合同展示、签名采集、签名位置定位、提交确认后端服务负责合同数据下发、签名图片存储、签署记录登记、签后校验。前端组件不直接碰合同文件原始路径所有数据通过接口下发这样合同模板的调整、权限控制都在后端完成小程序端只做一个稳定的交互壳。2. 组件总体架构与核心数据结构2.1 组件功能模块拆分组件没有做成一个巨大的单文件而是拆成了三个模块合同展示模块、签名画布模块、签署确认模块。合同展示模块负责渲染合同内容。这个项目里合同是固定模板渲染成图片所以展示层就是一个普通的image组件加上前后翻页的交互。如果需要动态合同内容可以用微信小程序的rich-text组件或者嵌套web-view加载H5合同页但我建议尽量用图片最简单也最不容易出排版问题。签名画布模块是最核心的部分用canvas实现手写采集。这里面有几个关键点canvas的像素适配、触摸事件的坐标计算、笔迹的平滑处理、导出图片。后面单独开一节细讲。签署确认模块负责把最终的签署动作提交给后端。包括勾选“我已阅读并同意”、展示签署人身份信息、提交签名图片和位置坐标。2.2 签署流程的状态设计整个签署流程我定义了一个状态机组件内部流转清晰明了INIT初始状态等待合同数据加载READY合同加载完成用户可阅读此时“同意并签署”按钮可点击SIGNING签名画布弹出用户在画布上书写CONFIRMING签名完成正在提交后端SUCCESS签署成功展示结果FAILED签署失败可重试状态流转图我就不画了代码里就是几个事件触发加一个status字段控制。关键是防止用户在SIGNING、CONFIRMING状态下重复点击误触导致重复提交。组件内所有按钮的点击回调里都要先判断当前状态是否允许操作。2.3 关键数据结构设计组件对外暴露的参数用properties声明主要包括properties: { contractId: { type: String, value: }, signerName: { type: String, value: }, signerIdCard: { type: String, value: }, mode: { type: String, value: view // view: 仅查看sign: 查看并签署 } }内部data里存合同图片地址、当前页码、签名临时路径、签名坐标、是否勾选同意等。签署完成后上传给后端的数据结构我设计成{ contractId: HT20250513001, signer: { name: 张三, idCard: **************1234 }, signatureImage: base64或临时文件ID, signPosition: { page: 1, x: 128, y: 860 }, agreed: true }注意signatureImage这里不建议直接把base64丢过来小程序传base64体积太大也容易超时我用的是先调用wx.uploadFile把签名图片传到后端拿临时文件ID再提交签约数据。3. 签名画布与签署交互的核心实现3.1 高清签名画布组件的实现签名画布是整个组件里技术含量最高的一部分。最初版本就是拿canvas直接画结果真机一测签名模糊得没法看像用劣质圆珠笔写的。原因是canvas默认分辨率是逻辑像素在小程序里必须自己处理设备像素比。解决办法是拿到设备的dpr把canvas的实际宽高乘上dpr再用style固定缩放。具体代码是这样const query wx.createSelectorQuery().in(this); query.select(#signCanvas).fields({ node: true, size: true }).exec((res) { const canvas res[0].node; const ctx canvas.getContext(2d); const dpr wx.getSystemInfoSync().pixelRatio; canvas.width res[0].width * dpr; canvas.height res[0].height * dpr; ctx.scale(dpr, dpr); });这里有个细节canvas初始化一定要在页面onReady之后选择器才能正确获取节点不然拿到的是null整个画布直接白屏。笔迹绘制我用的是canvas触摸事件监听touchstart、touchmove、touchend。坐标需要注意e.touches[0].x和y是相对于canvas的坐标直接用就行不用再减偏移。代码如下onTouchStart(e) { if (this.data.status ! SIGNING) return; this._drawing true; const { x, y } e.touches[0]; this._lastX x; this._lastY y; }, onTouchMove(e) { if (!this._drawing) return; const { x, y } e.touches[0]; const ctx this._ctx; ctx.beginPath(); ctx.moveTo(this._lastX, this._lastY); ctx.lineTo(x, y); ctx.strokeStyle #333; ctx.lineWidth 2.5; ctx.lineCap round; ctx.lineJoin round; ctx.stroke(); this._lastX x; this._lastY y; }, onTouchEnd() { this._drawing false; }踩过的一个坑是touchstart和touchmove之间偶尔会断线尤其在Android机型上。原因是手指按下去到移动过快时touchmove的第一个事件丢掉了导致路径不连续。解决方法是touchstart里就画一个点或者用moveTo和lineTo相接保证连续。签名区域要不要清空重写肯定要用户写坏了不能不给机会。重写按钮的实现就是ctx.clearRect清掉整个画布。导出签名图片我用的是wx.canvasToTempFilePath({ canvas: this._canvas, success: (res) { this.setData({ signaturePath: res.tempFilePath }); } });canvasToTempFilePath这个接口版本兼容性有坑基础库2.9.0之后传入canvas实例老版本传canvasId。项目里我用的canvas 2d接口所以必须是传canvas对象这个要注意。3.2 合同展示与签名定位合同内容我用的是图片渲染方案一张长图或分页多张由后端生成下发。这样做的优点是前端不用考虑合同模板的细节后端换模板前端零改动而且图片天然无法被用户复制修改安全性和一致性都更好。签名位置这块有两种实现策略。第一种是固定位置比如合同底部指定区域就是签名区用户只需要画完签名坐标由前端写死。第二种是用户自由拖动签名位置把缩略签名图拖到合同上指定位置。我项目里用的是第一种固定位置最简单稳定但要注意不同合同版本可能签名位置不一样所以坐标不能硬编码在组件里要由后端随合同数据下发。数据结构里加一个signPosition字段包含pageX、pageY前端拿到后把签名区占位框渲染到对应位置用户看到虚线框后在里面签名。如果你确实要做自由拖动签名可以用movable-area和movable-view把签名缩略图包在movable-view里拖动结束后通过bindchange事件获取x、y坐标。不过实际体验下来自由拖动容易让用户误操作固定位置效果更可控除非产品强需求否则我建议用固定位置。3.3 签署提交与防重处理用户点“确认签署”前端做三件事一是校验签名是否为空没写就签了直接提示二是校验是否勾选了同意协议三是把签名图片上传到后端拿到图片ID后再提交签约数据。防重复提交这块我做了两层前端层在CONFIRMING状态下把提交按钮置灰禁用点击事件防止用户连续双击后端层签约接口要求携带一次性的signTokentoken在合同数据下发时生成签约成功后立刻失效二次使用直接拒绝。这个设计虽然简单但在实际业务里非常管用能挡住大部分重复签约、恶意重放的情况。另外有一个容易被忽略的点就是签名图片上传和签约数据提交的时序。如果先提交签约数据、后传图片万一图片上传失败后端记录了一条没有签名图的签约单查起来很麻烦。我这边是强制签名图片上传成功后才允许发起签约请求签约请求里带着图片ID。后端收到请求时先去查图片是否存在不存在直接返回错误保证数据一致性。4. 后端接口设计与安全合规要点4.1 签署流程的后端接口设计后端接口我设计成三个前端组件按顺序调用第一个接口获取合同详情与签署初始化数据。请求参数是合同ID和用户身份标识返回合同图片列表、签署人脱敏信息、signToken、签名位置坐标、合同有效期。这个接口在后端会校验用户是否有权访问该合同合同是否过期。第二个接口是签名图片上传。这个走的是wx.uploadFile把临时图片传到后端文件服务返回文件ID。上传时要注意设置header里的content-type为multipart/form-data文件名后缀尽量保留png方便后端存储和管理。第三个接口是确认签署。参数是contractId、signToken、signatureImageId、signPosition、agreed字段。后端收到请求后校验token有效性、校验签名图片是否存在、校验合同是否已签过都通过了才落库登记签署记录并更新合同状态为“已签署”。接口返回的数据结构我习惯统一封装成功、失败都有code、message、data三层前端组件里根据code判断后续流程方便后期扩展错误码排查问题。4.2 数据安全与签署留痕电子合同虽然不像纸质合同有物理原件但要有完整的电子痕迹。我这边后端至少存储了以下几类信息签署人姓名、身份证脱敏号、签名图片原图、签署时间取服务器时间、签署时使用的设备型号和微信OpenID、合同内容的哈希值。合同内容哈希值这个细节很重要。合同图片存储的时候后端同时生成一份文件hash签署记录里记录这个hash。将来如果双方对合同内容有争议可以重新计算当前合同文件的hash和当初签署时记录的hash做比对判断合同文件是否被篡改过。这算是最基础的一种防篡改手段真正的司法存证还要结合时间戳、CA证书这些但轻量场景下先做到hash留痕已经够用。安全性上还有几个注意点。签名图片接口要限制只能访问签署人本人上传的图片防止越权查看别人签名。签署接口建议加频率限制比如同一用户一分钟内最多签三次防止批量刷接口。合同有效期过了之后要返回特定错误码前端组件捕获后提示用户联系管理员处理。5. 实战中遇到的典型问题与解决记录5.1 真机与模拟器的画布兼容性问题小程序开发机模拟器上canvas跑得很流畅一上真机就出各种幺蛾子。最常见的是canvas内容不显示或者显示但无法书写。排查思路是先确认基础库版本canvas 2d接口要求基础库2.9.0以上低版本请用canvas-id写法或升级基础库。还有一个真实遇到的怪问题iOS真机上手写签名速度稍微快一点就断线像跳点一样。后来发现是canvas里没有设置canvas的style样式为touch-action: noneiOS上默认的触摸滚动行为会干扰canvas的触摸事件。加上这个样式属性后断线问题基本消失。5.2 签名位置偏移合同图上定位签名坐标出现偏移是很常见的事。根本原因是合同图片在组件里被缩放适配而后端下发坐标是按图片原始尺寸计算的。解决办法是前端计算实际显示尺寸和原始尺寸的比例把坐标做换算。比如后端返回的坐标x是600合同原图宽度是900而实际显示宽度是350那么实际签名框的x就是600 × (350/900)约等于233。这个换算逻辑我放在了组件的attached生命周期里拿到系统信息和图片尺寸后就算好后续直接渲染。5.3 高频问题速查表问题现象直接原因解决方案签名画布空白无内容canvas节点未创建完成就初始化在onReady后执行初始化或使用wx.nextTick真机签名模糊未适配设备像素比canvas宽高乘dprdraw时坐标不变iOS断线跳点触摸滚动干扰设置touch-action: none签名图片上传超时base64体积过大改用wx.uploadFile上传临时文件提交后提示token失效重复提交或过期确认流程状态token一次性使用签名位置对不上合同图片缩放比例未换算按实际渲染尺寸与原图尺寸比例换算坐标组件在分包页面无法使用组件路径引用错误检查json配置里的component路径是否正确5.4 分享一个印象深刻的排查案例有一次线上反馈部分用户签完合同后后台显示签名图片是空的。查了很久发现这部分用户都在弱网环境签名完成后还没等canvasToTempFilePath的success回调返回用户就直接点了确认签署前端拿到的signaturePath是空字符串上传了个空文件。这个问题典型的业务逻辑缺陷是用户交互时序没有控制好。后来我在签名完成和确认签署之间加了一个强制等待签名导出成功前“确认签署”按钮保持禁用导出成功后按钮才可点击并在导出过程中显示loading提示。加上这个限制之后线上就再没出现过空签名图的问题。最后说点实在的整个电子合同签署组件从开发到上线前后花了一个多月其中画布适配和坐标对齐就占了一半时间核心功能本身反而很快。现在回头看结构上最值得骄傲的是拿到了一个关键的时间点签名导出成功和用户点击确认之间必须强同步弱网和快手指下都有可能踩空。这个经验一开始没意识到等踩过坑才明白。如果你也在做类似的小程序签名功能建议一开始就把这块交互时序设计好能省去后面一大轮返工。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价