Vue3弹窗集成smooth-signature电子签名全流程实战
去年在开发一个医疗审批系统时,遇到一个棘手需求:医生需要在移动设备上完成处方电子签名。经过多次迭代,最终采用Vue3 + smooth-signature的方案完美解决了这个问题。本文将分享如何在Dialog弹窗中实现专业级电子签名功能,特别针对移动端横竖屏切换场景。
1. 环境准备与基础集成
首先创建一个全新的Vue3项目(如果已有项目可跳过此步)。推荐使用Vite作为构建工具,它能提供更快的开发体验:
npm create vite@latest vue3-signature-demo --template vue-ts cd vue3-signature-demo npm install smooth-signature对于UI库的选择,Element Plus和Vant都是不错的选择。这里以Element Plus为例:
npm install element-plus @element-plus/icons-vue在main.ts中完成基础配置:
import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app')2. 弹窗与签名组件封装
创建一个独立的ESign组件来处理签名逻辑。这个组件需要处理以下核心功能:
- 画布初始化
- 笔迹平滑处理
- 横竖屏切换
- 签名数据导出
<template> <div class="signature-container"> <el-dialog v-model="dialogVisible" title="电子签名" :fullscreen="isFullscreen" @closed="handleDialogClose" > <div class="canvas-wrapper"> <canvas ref="signatureCanvas"></canvas> </div> <div class="action-buttons"> <el-button @click="handleClear">清除</el-button> <el-button @click="handleUndo">撤销</el-button> <el-button @click="toggleOrientation"> {{ isFullscreen ? '竖屏' : '横屏' }} </el-button> <el-button type="primary" @click="handleSave">保存</el-button> </div> </el-dialog> </div> </template> <script setup lang="ts"> import { ref, onMounted, watch } from 'vue' import SmoothSignature from 'smooth-signature' const props = defineProps({ visible: Boolean }) const emit = defineEmits(['update:visible', 'save']) const dialogVisible = ref(false) const signatureCanvas = ref<HTMLCanvasElement | null>(null) const signatureInstance = ref<SmoothSignature | null>(null) const isFullscreen = ref(false) watch(() => props.visible, (val) => { dialogVisible.value = val }) const initSignature = () => { if (!signatureCanvas.value) return const options = { width: isFullscreen.value ? window.innerHeight - 120 : window.innerWidth - 60, height: isFullscreen.value ? window.innerWidth - 60 : 300, minWidth: 2, maxWidth: 6, openSmooth: true, bgColor: '#f8f8f8' } signatureInstance.value = new SmoothSignature(signatureCanvas.value, options) } const handleClear = () => { signatureInstance.value?.clear() } const handleUndo = () => { signatureInstance.value?.undo() } const toggleOrientation = () => { isFullscreen.value = !isFullscreen.value nextTick(() => { initSignature() }) } const handleSave = () => { if (signatureInstance.value?.isEmpty()) { ElMessage.warning('请先完成签名') return } const signatureData = signatureInstance.value.getPNG() emit('save', signatureData) dialogVisible.value = false } const handleDialogClose = () => { emit('update:visible', false) } onMounted(() => { initSignature() }) </script> <style scoped> .canvas-wrapper { margin: 20px 0; } canvas { width: 100%; border: 1px dashed #dcdfe6; border-radius: 4px; background-color: #f8f8f8; } .action-buttons { display: flex; justify-content: space-around; margin-top: 20px; } </style>3. 移动端适配与性能优化
移动端环境下需要特别注意以下几点:
触摸事件处理:
const handleTouchMove = (e: TouchEvent) => { e.preventDefault() } onMounted(() => { document.addEventListener('touchmove', handleTouchMove, { passive: false }) }) onUnmounted(() => { document.removeEventListener('touchmove', handleTouchMove) })横竖屏切换检测:
const checkOrientation = () => { isFullscreen.value = window.innerHeight > window.innerWidth } onMounted(() => { window.addEventListener('resize', checkOrientation) checkOrientation() })画布清晰度保障:
const setupCanvas = () => { const canvas = signatureCanvas.value if (!canvas) return const dpr = window.devicePixelRatio || 1 const rect = canvas.getBoundingClientRect() canvas.width = rect.width * dpr canvas.height = rect.height * dpr const ctx = canvas.getContext('2d') ctx?.scale(dpr, dpr) }内存管理优化表:
场景 问题 解决方案 频繁切换 内存泄漏 在组件卸载时销毁实例 大尺寸画布 性能下降 根据设备动态调整画布尺寸 长时间使用 卡顿 定期清理历史记录
4. 业务逻辑集成实战
在实际业务场景中,签名通常需要与具体业务数据关联。以下是几种常见集成模式:
合同签署场景:
const handleContractSign = async (contractId: string) => { const { data: contract } = await fetchContract(contractId) if (contract.signed) { ElMessage.warning('该合同已签署') return } signatureDialog.value = true }审批流程集成:
const submitApproval = () => { if (!signature.value) { ElMessage.warning('请先完成签名') return } const formData = new FormData() formData.append('signature', signature.value) formData.append('approvalData', JSON.stringify(approvalForm)) submitApproval(formData).then(() => { ElMessage.success('审批提交成功') }) }多页签名处理:
const multiPageSign = () => { const pages = [/* 页面数据 */] const signatures: Record<string, string> = {} pages.forEach((page, index) => { signatures[`page_${index}`] = getSignatureForPage(page) }) saveMultiSignatures(signatures) }
5. 高级功能扩展
对于需要更专业签名体验的场景,可以考虑以下增强功能:
笔锋效果增强:
const signature = new SmoothSignature(canvas, { // ...其他配置 minWidth: 1, maxWidth: 8, smoothing: 0.7, velocityFilterWeight: 0.5 })背景网格辅助线:
const drawGrid = (ctx: CanvasRenderingContext2D) => { const gridSize = 20 ctx.strokeStyle = '#e0e0e0' ctx.lineWidth = 0.5 for (let x = 0; x <= canvas.width; x += gridSize) { ctx.beginPath() ctx.moveTo(x, 0) ctx.lineTo(x, canvas.height) ctx.stroke() } for (let y = 0; y <= canvas.height; y += gridSize) { ctx.beginPath() ctx.moveTo(0, y) ctx.lineTo(canvas.width, y) ctx.stroke() } }签名验证功能:
const verifySignature = (signatureData: string) => { return new Promise((resolve) => { // 调用后端验证接口 api.verifySignature(signatureData).then(resolve) }) }压力感应支持���针对高端设备):
const handlePointerMove = (e: PointerEvent) => { if (e.pressure) { const pressure = e.pressure * 5 signatureInstance.value?.setLineWidth(pressure) } }
在实际项目中,我发现横屏模式下签名区域更大,用户体验更好,但需要处理好旋转后的坐标转换问题。另外,对于医疗等特殊行业,签名数据需要加密存储,可以考虑使用Web Crypto API进行前端加密。