diff --git a/docs/superpowers/specs/2026-07-23-personal-open-confirm-modal-design.md b/docs/superpowers/specs/2026-07-23-personal-open-confirm-modal-design.md new file mode 100644 index 0000000..21a3328 --- /dev/null +++ b/docs/superpowers/specs/2026-07-23-personal-open-confirm-modal-design.md @@ -0,0 +1,74 @@ +# 个人开户"确认"二次确认弹窗 + 邀请扫码办理 设计文档 + +## 背景 + +`src/views/wallet/personal-open/PersonalOpenForm.vue`(新增个人开户申请页)当前点击顶部"确认"按钮后,直接执行前置校验并调用 `create-draft` 接口提交,没有任何二次确认环节。产品要求新增二次确认弹窗,并在弹窗中提供"邀请扫码办理"选项,允许柜员生成二维码交由客户扫码办理(扫码后走向的移动端 H5 页面本身不在本项目前端实现范围内,仅需生成二维码内容并展示)。 + +本次改动只影响 `PersonalOpenForm.vue` 的**新增(create)模式**"确认"按钮流程;编辑(edit)模式的"保存"按钮不受影响。 + +## 交互流程 + +1. 用户在新增个人开户表单页填写完信息,点击顶部"确认"按钮。 +2. 前端执行现有的全部前置校验(原 `handleSubmit` 中 441-470 行逻辑): + - 是否已勾选协议 + - 是否已选择渠道归属 + - 是否已选择客户 + - 证件照片是否仍在上传中 + - 创建模式下是否已上传身份证正反面 + 正面免冠照 + - 身份证 OCR 信息是否与所选客户一致 + - 表单 `formRef.value.validate()` 是否通过 + - 任意一项不通过:按现有方式提示错误(`showErrorModal`),**不弹出二次确认弹窗**。 +3. 全部通过后,弹出"提示"二次确认弹窗(对应产品提供的截图),弹窗内容:"是否确认开户?",三个按钮:`保存`(主按钮)/ `取消` / `邀请扫码办理`。 +4. 弹窗内点击**保存**: + - 调用 `create-draft` 接口(逻辑与现有提交一致:构造 `payload` + `fileList`)。 + - 成功(`code === 200`):记录 `submittedApplicationNo`,`message.success('申请已提交')`,关闭弹窗。表单页因 `submittedApplicationNo` 有值而自动切换到现有的"开户申请已提交"结果面板(既有 template 条件不变)。 + - 失败:由全局响应拦截器统一弹出错误提示(`showErrorModal`,见 `src/api/request.js` 响应拦截器),弹窗保持打开,允许用户重试或取消。 +5. 弹窗内点击**取消**:仅关闭弹窗,不发任何请求,不做页面跳转,表单数据保留,用户可以继续修改后重新点击"确认"。 +6. 弹窗内点击**邀请扫码办理**: + - 调用与"保存"完全相同的提交逻辑(同一个 `create-draft` 接口),拿到 `applicationNo` 并同样记录到 `submittedApplicationNo`(因此背后的表单页状态与点"保存"效果一致,会进入"已提交"结果面板)。 + - 成功后**不关闭弹窗**,而是把弹窗内容切换为"二维码展示"态:显示二维码图片 + "申请编号:{applicationNo}" 文字 + "关闭"按钮;不额外弹出"申请已提交"的 message 提示(避免和二维码展示态叠加打扰)。 + - 二维码内容为一个完整 URL:`${VITE_H5_QRCODE_BASE_URL}${VITE_H5_QRCODE_PATH}?applicationNo=${applicationNo}`。域名/路径通过环境变量配置(见下),当前域名尚未确定,未配置时使用占位域名照常生成二维码(不阻断流程)。 + - 用户点击"关闭"或弹窗右上角 X:仅关闭弹窗,此时表单页已处于"已提交"结果面板状态(与点保存效果一致)。 + - 失败:同上由全局拦截器提示,弹窗保持在"确认"态,允许重试。 + +## UI 结构 + +复用 `ant-design-vue` 的 `a-modal`: +- `title="提示"` +- `:footer="null"`(按钮放在正文里自定义,与截图一致) +- `:mask-closable="false"`(避免误触遮罩关闭) +- 内部用一个 `confirmModalStep` ref(`'confirm' | 'qrcode'`)控制展示"确认文案+三按钮"还是"二维码+关闭按钮"两种视图。 + +二维码组件使用新增依赖 `qrcode.vue`(Vue3 兼容,`peerDependencies: vue ^3.0.0`),以 `` 方式渲染。 + +## 代码结构调整(`PersonalOpenForm.vue`) + +将原 `handleSubmit` 拆分为职责更清晰的几个函数,避免重复代码: + +- `validateBeforeSubmit()`:抽取原 441-470 行的全部前置校验(含 `formRef.value.validate()`),返回 `boolean`(或 reject)。供"确认"按钮预检和 `isEdit` 保存流程共用。 +- `buildSubmitPayload()`:抽取原 473-495 行 `fileList`/`payload` 构造逻辑,供 create 提交与 edit 保存共用。 +- `submitCreateDraft()`:仅负责调用 `createPersonalOpenDraftApi`,成功后设置 `submittedApplicationNo` 并返回 `applicationNo`;失败返回 `null`(错误提示交给全局拦截器)。供弹窗内"保存"与"邀请扫码办理"共用,各自维护自己的 loading ref(`modalSaving` / `modalInviting`),防止重复点击。 +- `handleConfirmButtonClick()`:新增个人开户"确认"按钮的点击处理——调用 `validateBeforeSubmit()`,通过后打开确认弹窗(`confirmModalVisible = true`,`confirmModalStep = 'confirm'`)。 +- `handleEditSubmit()`:原 `handleSubmit` 中 `isEdit` 分支逻辑原样保留(校验 + 调用 `updatePersonalOpenApplicationApi`),仅改名,行为不变,绑定到编辑模式"保存"按钮。 + +## 环境变量 + +新增于 `.env.development` 与 `.env.production`: + +``` +VITE_H5_QRCODE_BASE_URL= +VITE_H5_QRCODE_PATH=/mobile/personal-open +``` + +`VITE_H5_QRCODE_BASE_URL` 留空时使用代码内占位域名(如 `https://example.com`)兜底拼接,保证二维码始终可以生成;域名确定后运维/开发只需在 `.env.production` 中填入真实域名即可,无需改代码。 + +## 依赖变更 + +新增 `qrcode.vue`(dependencies)。 + +## 范围边界 + +- 不实现移动端 H5 扫码后的确认页面(`personal-opening/mobile/*`),本次改动只负责柜员端生成二维码内容并展示。 +- 不改动 `isEdit`(编辑)模式的提交流程与按钮。 +- 不改动 `isDetail`(查看)模式。 +- 不引入全局的 `busy`/`a-spin` 统一遮罩规范补齐(该页面现状缺口不在本次需求范围内,维持现状,仅用按钮级 loading 防重复点击)。