Skip to content

电子签名二维码实现笔记

功能概述

电子签名二维码功能主要实现PC端生成二维码,移动端扫码后进行签名,并将签名结果回传给PC端的过程。

技术要点

  • 二维码生成:使用QRCode.js库
  • 状态管理:Vue3 的响应式API
  • 环境适配:开发和生产环境的处理
  • URL参数处理:使用URLSearchParams API
  • 轮询机制:使用setInterval进行状态检查

核心实现

1. 二维码生成组件

vue
<template>
  <div class="signature-container">
    <a-button @click="handleSignatureClick">
      <template #icon><SignatureIcon /></template>
      电子签名
    </a-button>
    <a-modal v-model:open="showQRModal" title="手机扫码签名" :footer="null" @cancel="handleCancel">
      <div class="qr-container">
        <img v-if="qrCodeUrl" :src="qrCodeUrl" alt="签名二维码" />
        <p>请使用手机扫描二维码进行签名</p>
      </div>
    </a-modal>
  </div>
</template>

2. URL生成与处理

javascript
// 环境适配处理
const getLocalIP = () => {
  if (import.meta.env.DEV) {
    return '192.168.2.16'; // 开发环境IP
  }
  return window.location.hostname; // 生产环境域名
};

// 生成签名页面URL
const getSignaturePageUrl = () => {
  const currentUrl = window.location.href;
  const currentPort = window.location.port;
  const baseUrl = `http://${getLocalIP()}${currentPort ? ':' + currentPort : ''}`;
  return `${baseUrl}/m/sign?sid=${props.sid}&callback=${encodeURIComponent(currentUrl)}`;
};

sid:签名会话ID,确保签名的唯一性。进行组件调用时,需要传递sid参数。

javascript
const props = defineProps({
  sid: {
    type: String,
    required: true
  }
});
const emit = defineEmits(['signature-complete']);

3. 二维码生成与轮询检查

javascript
// 生成二维码
const handleSignatureClick = async () => {
  showQRModal.value = true;
  const signatureUrl = getSignaturePageUrl();
  try {
    // 生成二维码
    qrCodeUrl.value = await QRCode.toDataURL(signatureUrl, {
      errorCorrectionLevel: 'H',
      margin: 1,
      width: 200,
      type: 'image/png',
    });
    startPolling();
  } catch (err) {
    console.error('生成二维码失败:', err);
  }
};

// 检查 URL 参数
const checkSignatureUrl = () => {
  const urlParams = new URLSearchParams(window.location.search);
  const signatureUrl = urlParams.get('signatureUrl');

  if (signatureUrl) {
    // 发送签名URL给父组件
    emit('signature-complete', decodeURIComponent(signatureUrl));

    // 清除URL参数
    const newUrl = window.location.pathname + window.location.search.replace(/[?&]signatureUrl=[^&]*/, '');
    window.history.replaceState({}, '', newUrl);

    // 关闭二维码弹窗
    showQRModal.value = false;

    // 清除检查间隔
    if (checkInterval) {
      clearInterval(checkInterval);
      checkInterval = null;
    }
  }
};

技术细节解析

1. URL参数构建

  • sid: 签名会话ID,确保签名的唯一性
  • callback: 编码后的回调地址,用于签名完成后跳转
  • 环境判断: 通过import.meta.env.DEV区分开发和生产环境

2. 二维码配置

javascript
const qrCodeConfig = {
  errorCorrectionLevel: 'H', // 高容错率
  margin: 1,                 // 最小边距
  width: 200,               // 合适的尺寸
  type: 'image/png',        // 图片格式
};

3. 轮询机制

javascript
const startPolling = () => {
  if (!checkInterval) {
    checkInterval = setInterval(checkSignatureUrl, 1000);
  }
};

const stopPolling = () => {
  if (checkInterval) {
    clearInterval(checkInterval);
    checkInterval = null;
  }
};

Q & A

1. 为什么要区分开发和生产环境的IP处理?

javascript
const getLocalIP = () => {
  if (import.meta.env.DEV) {
    return '192.168.2.17'; // 开发环境需要固定IP便于移动端访问
  }
  return window.location.hostname; // 生产环境使用实际域名
};
  • 开发环境需要固定IP便于移动端访问
  • 生产环境使用实际域名确保可靠性
  • 便于开发调试和生产部署

2. 如何处理URL参数的安全性?

javascript
const getSignaturePageUrl = () => {
  const currentUrl = window.location.href;
  // 对回调URL进行编码
  return `${baseUrl}/m/sign?sid=${props.sid}&callback=${encodeURIComponent(currentUrl)}`;
};
  • 使用encodeURIComponent编码参数
  • 避免URL注入风险
  • 确保特殊字符正确传递

3. 轮询检查的优化方案?

javascript
// 优化的轮询检查
const checkSignatureUrl = () => {
  const urlParams = new URLSearchParams(window.location.search);
  const signatureUrl = urlParams.get('signatureUrl');

  if (signatureUrl) {
    emit('signature-complete', decodeURIComponent(signatureUrl));
    cleanupAfterSignature();
  }
};

const cleanupAfterSignature = () => {
  stopPolling();
  showQRModal.value = false;
  clearUrlParameters();
};
  • 合理的轮询间隔
  • 及时清理资源
  • 完善的清理机制

4. 如何确保二维码的可用性?

javascript
const generateQRCode = async (url) => {
  try {
    return await QRCode.toDataURL(url, {
      errorCorrectionLevel: 'H', // 高容错率
      margin: 1,
      width: 200,
      type: 'image/png',
    });
  } catch (err) {
    console.error('二维码生成失败:', err);
    throw new Error('二维码生成失败');
  }
};
  • 使用高容错级别
  • 合适的尺寸设置
  • 完善的错误处理

5. 组件生命周期管理的重要性?

javascript
onMounted(() => {
  checkSignatureUrl(); // 初始检查
});

onUnmounted(() => {
  stopPolling(); // 清理资源
});
  • 初始化检查
  • 资源清理
  • 防止内存泄漏