电子签名二维码实现笔记 
功能概述 
电子签名二维码功能主要实现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(); // 清理资源
});- 初始化检查
 - 资源清理
 - 防止内存泄漏