SFT/docs/superpowers/specs/2026-07-20-personal-custome...

5.1 KiB

个人客户新增/编辑——身份证 OCR 识别等待态设计

背景

客户管理 -> 个人客户管理 -> 新增/编辑个人客户表单(src/views/customer/personal/PersonalForm.vue)中,上传身份证正面/反面图片后,IdCardUpload.vue 组件内部并发调用 ocrIdCardApi(身份证 OCR 识别)与 uploadCustomerFileApi(文件上传)。后端 OCR 接口响应较慢,识别期间目前只有上传框内一个局部菊花图标(LoadingOutlined),用户可以继续操作表单其他字段甚至重复提交,存在数据一致性风险,体验也不够明确。

需求:上传身份证正/反面图片后,等待识别结果期间,前端需要展示明显的等待态(菊花),且用户不能对页面进行操作。

范围确认(与用户确认过的细节)

  • 遮罩范围:仅覆盖新增/编辑个人客户表单卡片内容区(<a-form> + 提交/取消按钮),不包含卡片外的页头(<a-page-header>/返回按钮保持可操作)。
  • 并发场景:身份证正面、反面几乎同时上传时,只要任一张仍在识别中,遮罩就保持展示;两张都结束才解除。
  • 适用范围:仅个人客户表单(PersonalForm.vue),不改动企业客户表单等其他复用 IdCardUpload/文件上传组件的场景。
  • 提示内容:菊花 + 文字提示"身份证识别中,请稍候..."。
  • 生效模式:新增、编辑两种模式都生效(两者共用同一组件与同一段上传/OCR 代码路径,行为保持一致)。

方案

不新建全局 loading/遮罩组件(项目当前无此类基础设施,且需求范围只限于本表单),直接使用 Ant Design Vue 自带的 a-spin 嵌套遮罩("nested loading")能力::spinning 为真时,组件会在被包裹内容上叠加菊花 + 半透明遮罩,并对内容添加 pointer-events: none,天然满足"菊花 + 阻断操作"的要求,无需额外造轮子。

改动点 1:src/components/IdCardUpload.vue

  • 新增 emit('update:uploading', boolean),在组件内部已有的 uploading ref 变化时同步对外广播(即支持父组件用 v-model:uploading 接管此状态)。
  • 组件内部原有的局部菊花图标(LoadingOutlined)、a-upload:disabled 逻辑保持不变。

改动点 2:src/views/customer/personal/PersonalForm.vue

  • 新增两个 ref:idCardFrontUploadingidCardBackUploading,分别通过 v-model:uploading 绑定到正面/反面两个 IdCardUpload 实例。
  • 新增计算属性 isIdCardOcrProcessing = computed(() => idCardFrontUploading.value || idCardBackUploading.value)
  • <a-spin :spinning="isIdCardOcrProcessing" tip="身份证识别中,请稍候..."> 包裹 <a-card> 内的 <a-form>...</a-form> 与提交/取消 <a-space>,<a-page-header> 留在 a-spin 外部不受影响。

异常兜底

IdCardUpload.vuehandleBeforeUploadfinally 块已无条件将 uploading 置回 false(无论 OCR 成功、识别失败还是请求异常/超时),遮罩会随之自动消失,不需要额外的错误处理分支。

不做的事

  • 不新建可复用的全局 loading 组件(超出本次需求范围,且项目暂无其他场景需要)。
  • 不改动企业客户表单等其他复用同一上传组件的页面。
  • 不改变现有 OCR/上传接口调用方式(仍是 base64 并发调用 ocrIdCardApi + uploadCustomerFileApi)。

追加需求:身份证缩略图 hover 操作按钮

背景

身份证图片上传成功后,缩略图目前只是一张静态 <img>,没有任何交互提示。需要参照截图样式,鼠标 hover 缩略图时叠加半透明遮罩,展示"预览(眼睛)/ 重新上传(铅笔)/ 删除(垃圾桩)"三个操作图标。

范围确认(与用户确认过的细节)

  • 查看详情模式(isDetail)下缩略图只保留"预览"图标,隐藏"重新上传"/"删除"。新增、编辑模式三个图标都显示。
  • 点击删除图标直接清空图片和 fileNo,不弹二次确认;只影响当前这一侧(正面或反面),已回填的 OCR 表单字段不受影响。
  • 点击预览图标弹出一个简单 Modal 展示原图(不使用 antd Image 的缩放/旋转工具条)。
  • 点击重新上传(铅笔)图标直接复用现有上传入口重新选图并重新识别,覆盖旧缩略图和旧 fileNo,不做二次确认提示。

方案

  • IdCardUpload.vue:
    • 新增 readonly prop,由父组件传入 isDetail 控制。
    • 有预览图时,<img> 外包一层 .thumb-wrapper,内部叠加 .hover-mask(默认 opacity:0,:hoveropacity:1),放置 EyeOutlined/EditOutlined/DeleteOutlined 三个图标(readonly 时只渲染 EyeOutlined)。
    • 预览:点击眼睛图标(@click.stop 阻止冒泡触发文件选择)打开 <a-modal v-model:open="previewVisible"> 展示 previewUrl 原图。
    • 删除:点击垃圾桩图标(@click.stop)清空 previewUrlemit('update:fileNo', ''),不改动表单其他字段。
    • 重新上传:铅笔图标不调用 stopPropagation,复用 a-upload 自身"点击区域内任意位置即打开文件选择"的默认行为,点击后走既有的 handleBeforeUpload 全流程(OCR + 上传并发调用),自然覆盖旧数据。
  • PersonalForm.vue:两个 IdCardUpload 实例都新增 :readonly="isDetail"