Skip to content

电子签名实际运用笔记

功能场景

在招标项目审核页面中,电子签名功能主要用于法人签名确认环节,支持两种签名方式:

  1. 文件上传
  2. 扫码电子签名

组件集成

1. 组件引入和基础配置

vue
<template>
  <a-form-item label="法人签名" name="auditSignatureImage">
    <div class="signature-actions">
      
      <!-- 扫码签名组件 -->
      <electronic-signature 
        :sid="projectId" 
        @signature-complete="handleSignatureComplete" 
      />

      <!-- 下载按钮 -->
      <div class="upload-container">
        <a-button class="download-btn" @click="downloadSignature">
          <DownloadOutlined />
          下载签名文件
        </a-button>
      </div>
    </div>
  </a-form-item>
</template>

2. 签名结果处理

javascript
// 处理签名完成回调
const handleSignatureComplete = async (signatureUrl) => {
  if (signatureUrl) {
    formState.auditSignatureImage = signatureUrl;
    // 更新文件列表显示
    auditSignatureImageList.value = [{
      uid: '-1',
      name: 'signature.png',
      status: 'done',
      url: signatureUrl
    }];
  }
};

// 获取URL参数和签名
/**
 * 获取项目ID和签名URL
 * @returns {Object} 包含projectId和signatureUrl的对象
 * @description
 * 1. 从URL中解析projectId参数
 * 2. 从URL hash中解析签名URL
 * 3. 如果hash以#signature=开头,则解码并返回签名URL,否则返回空字符串
 */
const getCleanProjectIdAndSignature = () => {
  // 创建URLSearchParams对象解析URL参数
  const urlParams = new URLSearchParams(window.location.search);
  // 获取projectId参数值
  const projectId = urlParams.get('projectId');
  // 获取URL hash部分
  const hash = window.location.hash;
  // 解析签名URL - 如果hash以#signature=开头则解码,否则返回空字符串
  const signatureUrl = hash.startsWith('#signature=')
    ? decodeURIComponent(hash.replace('#signature=', ''))
    : '';
  return { projectId, signatureUrl };
};


// 处理签名完成
const handleSignatureComplete = async (signatureUrl) => {
  console.log('signatureUrl', signatureUrl);
  if (signatureUrl) {
    formState.auditSignatureImage = signatureUrl;
    // 更新文件列表显示
    auditSignatureImageList.value = [{
      uid: '-1',
      name: 'signature.png',
      status: 'done',
      url: signatureUrl
    }];
  }
};

3. 文件上传处理

javascript
// 文件上传前校验
const beforeUpload = (file) => {
  const isLt10M = file.size / 1024 / 1024 < 10;
  if (!isLt10M) {
    message.error('文件必须小于10MB!');
  }
  return isLt10M;
};

// 自定义上传实现
const customUploadName = async ({ file, onSuccess }) => {
  const formData = new FormData();
  formData.append('file', file);
  const res = await uploadFileAPI(formData);
  formState.auditSignatureImage = res.data.data;
  auditSignatureImage.value = [{
    uid: '-1',
    name: file.name,
    status: 'done',
    url: res.data
  }];
  message.success('文件上传成功');
  onSuccess();
};

业务流程处理

1. 初始化和数据加载

javascript
onMounted(() => {
  // 获取项目详情
  getProjectDetail(projectId);

  // 处理签名URL
  if (signatureUrl) {
    formState.auditSignatureImage = signatureUrl;
    auditSignatureImageList.value = [{
      uid: '-1',
      name: signatureUrl.split('/').pop(),
      status: 'done',
      url: signatureUrl
    }];
    // 清理URL参数
    window.history.replaceState({}, '', 
      `${window.location.pathname}?projectId=${projectId}`);
  }
});

2. 审核流程集成

javascript
// 同意审核
const handleAgree = async () => {
  const data = {
    id: formState.id,
    auditStatus: 2,
    auditType: 11,
    auditResult: formState.auditOpinion,
    projectRoundId: Number(projectId),
    auditSignatureImage: formState.auditSignatureImage
  }
  const res = await agreeOrRejectProjectAPI(data);
  router.replace('/ExamineProject');
};

// 拒绝审核
const handleRefuse = async () => {
  const params = {
    id: formState.id,
    auditStatus: 3,
    auditType: 11,
    auditResult: formState.auditOpinion,
    projectRoundId: Number(projectId),
    auditSignatureImage: formState.auditSignatureImage
  }
  const res = await agreeOrRejectProjectAPI(params);
  router.replace('/ExamineProject');
};

样式优化

css
.signature-actions {
  gap: 16px;
  align-items: center;
}

.signature-preview {
  margin-top: 16px;
  display: flex;
  align-items: center;
}

.upload-container {
  display: flex;
  gap: 16px;
  align-items: center;
}

.download-btn {
  margin-right: 8px;
}

注意事项

  1. 状态管理
javascript
// 按钮禁用状态控制
const isButtonDisabled = computed(() => {
  return formState.projectStatusId !== 3;
});
  1. 文件处理
javascript
// 文件删除处理
const handleRemove = async (file) => {
  const filePath = file.url || formState.auditSignatureImage;
  if (filePath) {
    await deleteFileAPI(filePath);
    formState.auditSignatureImage = '';
    message.success('文件删除成功');
  }
  return true;
};

// 文件下载处理
const downloadSignature = () => {
  handleFileDownload(formState.auditSignatureImage);
};
  1. URL参数处理
javascript
// 清理URL参数
if (signatureUrl) {
  window.history.replaceState({}, '', 
    `${window.location.pathname}?projectId=${projectId}`);
}

最佳实践

  1. 组件解耦
  • 将签名功能封装为独立组件
  • 通过事件通信处理签名结果
  1. 异常处理
  • 文件大小限制
  • 上传失败处理
  • 签名结果验证
  1. 用户体验
  • 提供多种签名方式
  • 实时预览
  • 清晰的操作反馈