基于 vue-signature-pad 的完整集成方案,包含签名绘制、清空、合法性校验、Base64 转码及后端提交全流程。
一、安装依赖
bash
# 安装核心签名库
npm install vue-signature-pad --save
# (可选)若使用 Vant 组件库做提示,需安装
npm install vant --save
二、完整签名组件代码(SignatureComponent.vue)
vue
<template>
<div class="signature-container">
<!-- 签名区域 -->
<div class="signature-box">
<!-- 签名提示文字 -->
<div class="signature-tip">请在下方签名</div>
<!-- 签名组件核心 -->
<vue-signature-pad
ref="signaturePad"
:options="signatureOptions"
class="signature-canvas"
@begin="onSignBegin" <!-- 开始签名时触发 -->
@end="onSignEnd" <!-- 结束签名时触发 -->
/>
</div>
<!-- 操作按钮区 -->
<div class="signature-actions">
<van-button
type="default"
@click="handleClear"
class="action-btn"
>
清空签名
</van-button>
<van-button
type="primary"
@click="handleSubmit"
class="action-btn submit-btn"
:loading="isSubmitting"
>
提交签名
</van-button>
</div>
</div>
</template>
<script>
import VueSignaturePad from 'vue-signature-pad'
import { Button, Toast } from 'vant'
export default {
components: {
VueSignaturePad,
[Button.name]: Button
},
data() {
return {
// 签名配置项(详细参数见文档:https://github.com/szimek/signature_pad)
signatureOptions: {
penColor: '#000000', // 笔触颜色
backgroundColor: '#ffffff', // 背景色
minWidth: 2, // 最小笔触宽度
maxWidth: 4, // 最大笔触宽度(支持手写板压力感应)
dotSize: 2, // 点击时的点大小
throttle: 16, // 节流时间(优化性能)
velocityFilterWeight: 0.7 // 速度过滤权重(影响线条平滑度)
},
isSubmitting: false, // 提交状态
hasSigned: false // 是否已签名(用于校验)
}
},
methods: {
// 开始签名时标记状态
onSignBegin() {
this.hasSigned = true
},
// 结束签名时校验是否有效
onSignEnd() {
if (this.$refs.signaturePad.isEmpty()) {
this.hasSigned = false
}
},
// 清空签名
handleClear() {
if (this.$refs.signaturePad) {
this.$refs.signaturePad.clear()
this.hasSigned = false
Toast('已清空签名')
}
},
// 提交签名到后端
async handleSubmit() {
// 1. 校验签名是否为空
if (!this.hasSigned || this.$refs.signaturePad.isEmpty()) {
Toast('请先完成签名')
return
}
// 2. 生成签名数据(支持多种格式)
try {
this.isSubmitting = true
// 方式1:获取 PNG 格式 Base64(推荐,兼容性好)
const signData = this.$refs.signaturePad.toDataURL('image/png')
// 方式2:获取 JPG 格式(压缩率更高,可选)
// const signData = this.$refs.signaturePad.toDataURL('image/jpeg', 0.8)
// 方式3:获取原始 Canvas 数据(如需进一步处理)
// const canvas = this.$refs.signaturePad.getCanvas()
// 3. 调用后端接口提交(替换为你的实际接口)
const response = await this.$api.uploadSignature({
signature: signData, // 签名 Base64 数据
fileName: `sign_${Date.now()}.png`, // 文件名(可选)
userId: this.$store.state.user.id // 关联用户ID(可选)
})
// 4. 处理提交结果
if (response.success) {
Toast('签名提交成功')
this.handleClear() // 提交成功后清空
// (可选)跳转页面或关闭弹窗
// this.$emit('submitSuccess')
} else {
Toast(`提交失败:${response.message || '未知错误'}`)
}
} catch (error) {
console.error('签名提交失败:', error)
Toast('网络错误,请重试')
} finally {
this.isSubmitting = false
}
}
}
}
</script>
<style scoped>
.signature-container {
padding: 16px;
max-width: 600px;
margin: 0 auto;
}
.signature-box {
border: 1px solid #e5e5e5;
border-radius: 8px;
overflow: hidden;
}
.signature-tip {
padding: 12px;
font-size: 14px;
color: #666;
background-color: #f5f5f5;
text-align: center;
}
.signature-canvas {
width: 100%;
height: 200px; /* 固定签名区域高度 */
background-color: #fff;
}
.signature-actions {
display: flex;
gap: 12px;
margin-top: 16px;
}
.action-btn {
flex: 1; /* 按钮平分宽度 */
height: 44px;
font-size: 16px;
}
.submit-btn {
background-color: #55aac1; /* 主色调 */
border-color: #55aac1;
}
</style>
三、在页面中使用该组件
vue
<template>
<div class="page-container">
<!-- 其他页面内容 -->
<signature-component
@submitSuccess="onSignSuccess"
/>
</div>
</template>
<script>
import SignatureComponent from '@/components/SignatureComponent.vue'
export default {
components: { SignatureComponent },
methods: {
onSignSuccess() {
// 签名提交成功后的回调(如关闭弹窗、跳转页面)
console.log('签名已成功保存')
}
}
}
</script>
四、后端接口示例(Node.js/Express)
用于接收前端提交的 Base64 签名数据,转存为图片文件:
javascript
运行
const express = require('express')
const router = express.Router()
const fs = require('fs')
const path = require('path')
const { v4: uuidv4 } = require('uuid') // 用于生成唯一文件名
// 保存签名接口
router.post('/uploadSignature', async (req, res) => {
try {
const { signature, fileName, userId } = req.body
// 1. 校验 Base64 数据
if (!signature || !signature.startsWith('data:image/')) {
return res.json({ success: false, message: '无效的签名数据' })
}
// 2. 解析 Base64 数据(去掉前缀)
const base64Data = signature.replace(/^data:image/\w+;base64,/, '')
const buffer = Buffer.from(base64Data, 'base64')
// 3. 生成保存路径(确保目录存在)
const saveDir = path.join(__dirname, '../uploads/signatures')
if (!fs.existsSync(saveDir)) {
fs.mkdirSync(saveDir, { recursive: true })
}
// 4. 保存文件(使用唯一文件名避免冲突)
const fileExt = fileName?.split('.').pop() || 'png'
const uniqueName = `${uuidv4()}.${fileExt}`
const savePath = path.join(saveDir, uniqueName)
fs.writeFileSync(savePath, buffer)
// 5. (可选)保存文件路径到数据库(关联用户ID)
// await db.Signature.create({
// userId,
// filePath: `/uploads/signatures/${uniqueName}`,
// createTime: new Date()
// })
// 6. 返回成功结果
res.json({
success: true,
data: {
fileUrl: `/uploads/signatures/${uniqueName}` // 前端可用于预览
}
})
} catch (error) {
console.error('保存签名失败:', error)
res.json({ success: false, message: '服务器错误' })
}
})
module.exports = router
五、关键功能说明
-
签名体验优化:
- 支持鼠标 / 触摸双端操作
- 线条平滑(通过
velocityFilterWeight配置) - 笔触宽度动态变化(模拟真实笔锋)
-
合法性校验:
- 监听签名开始 / 结束事件,标记
hasSigned状态 - 提交前通过
isEmpty()方法二次校验,避免空签名提交
- 监听签名开始 / 结束事件,标记
-
数据处理:
- 生成 PNG/JPG 格式的 Base64 字符串(兼容大多数后端存储场景)
- 文件名使用时间戳 / UUID 避免冲突
-
错误处理:
- 网络错误捕获与提示
- 后端返回失败时的友好提示
六、使用注意事项
- 签名区域高度建议设置为
200px+,过低会影响签名体验 - 移动端使用时,需确保父容器没有设置
overflow: hidden或pointer-events: none(避免事件被拦截) - 若需要更高清晰度,可在
signatureOptions中设置canvasWidth和canvasHeight(实际渲染尺寸) - 后端存储时,Base64 字符串体积较大(约为图片文件的 1.3 倍),建议转存为图片文件后只保存文件路径
通过以上代码,可快速实现生产级别的签名功能,支持前后端完整链路。如果需要调整样式或功能,只需修改 signatureOptions 配置项或组件内的方法即可。