记录-我的第一篇

168 阅读4分钟

基于 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

五、关键功能说明

  1. 签名体验优化

    • 支持鼠标 / 触摸双端操作
    • 线条平滑(通过 velocityFilterWeight 配置)
    • 笔触宽度动态变化(模拟真实笔锋)
  2. 合法性校验

    • 监听签名开始 / 结束事件,标记 hasSigned 状态
    • 提交前通过 isEmpty() 方法二次校验,避免空签名提交
  3. 数据处理

    • 生成 PNG/JPG 格式的 Base64 字符串(兼容大多数后端存储场景)
    • 文件名使用时间戳 / UUID 避免冲突
  4. 错误处理

    • 网络错误捕获与提示
    • 后端返回失败时的友好提示

六、使用注意事项

  1. 签名区域高度建议设置为 200px+,过低会影响签名体验
  2. 移动端使用时,需确保父容器没有设置 overflow: hidden 或 pointer-events: none(避免事件被拦截)
  3. 若需要更高清晰度,可在 signatureOptions 中设置 canvasWidth 和 canvasHeight(实际渲染尺寸)
  4. 后端存储时,Base64 字符串体积较大(约为图片文件的 1.3 倍),建议转存为图片文件后只保存文件路径

通过以上代码,可快速实现生产级别的签名功能,支持前后端完整链路。如果需要调整样式或功能,只需修改 signatureOptions 配置项或组件内的方法即可。