26 KiB
个人开户移动端H5确认页 设计文档
背景
docs/superpowers/specs/2026-07-23-personal-open-confirm-modal-design.md 已实现管理后台侧"个人开户"新增表单的二次确认弹窗 + "邀请扫码办理",柜员点击后生成二维码,二维码内容为:
${VITE_H5_QRCODE_BASE_URL}${VITE_H5_QRCODE_PATH}?applicationNo=${applicationNo}
当前 VITE_H5_QRCODE_PATH=/mobile/personal-open。该设计文档明确将"扫码后跳转的移动端 H5 页面"排除在范围外。后端已提供对应的 personal-opening/mobile/draft-detail、personal-opening/mobile/submit 接口(见《缺失的后端接口.md》第19条),但前端从未实现这个 H5 页面。本次设计即补齐该页面。
范围
- 新建一个完全独立的子工程
./fryl_h5(独立package.json/构建产物/部署单元,不与管理后台共用依赖或构建配置)。 - 仅实现"个人开户"扫码核实场景一个页面流程,不涉及企业开户 confirm、贷款申请确认扫码等其他移动端确认场景。
- 不涉及管理后台侧代码改动(二维码生成逻辑已完成,本次不改)。
技术方案
- 技术栈:Vue3 + Vite + Vant(移动端 UI 组件库),纯 JavaScript(与主项目一致,不引入 TypeScript)。
- 工程定位:面向终端客户、无需登录鉴权的匿名 H5 页面(不同于管理后台的员工登录体系),因此不引入 Pinia/auth store/refresh-token 逻辑。
- 路由:
vue-router,单一业务路由/mobile/personal-open(与二维码VITE_H5_QRCODE_PATH保持一致),通过 query 参数applicationNo定位申请单。页面内部用组件本地状态(currentStepref,值为INFO/FACE/SMS)维护向导步骤,不为每个步骤单独设路由(避免用户直接分享/刷新中间步骤 URL 导致状态错乱)。 - BASE_URL:不提供页面内可配置入口(用户明确决定,区别于主项目登录页齿轮图标切换的默认惯例),仅通过
.env.development/.env.production的VITE_API_BASE_URL区分环境。
页面流程
2026-08-05 更新:原"步骤1 信息核对(含协议展示)"拆分为两个独立步骤,协议勾选不再耦合在 信息核对页面里,改为单独的"相关协议"步骤,且该步骤调用真实后端接口获取协议模板原文(不再是 占位文案)。整体变为 5 步向导:
进入页面 → 调用 draft-detail 加载草稿
├─ 失败/无效 applicationNo → 展示错误提示页(终态,无法继续)
└─ 成功 → 步骤1 信息核对 → 步骤2 相关协议 → 步骤3 人脸识别 → 步骤4 手机验证+提交 → 步骤5 结果页(终态)
2026-08-05 二次更新:按产品要求,客户在步骤2"相关协议"点击"下一步"后跳过步骤3人脸识别, 直接进入步骤4手机验证+提交:
进入页面 → 调用 draft-detail 加载草稿
├─ 失败/无效 applicationNo → 展示错误提示页(终态,无法继续)
└─ 成功 → 步骤1 信息核对 → 步骤2 相关协议 → 步骤4 手机验证+提交 → 步骤5 结果页(终态)
(步骤3 人脸识别:当前从向导流程跳过,代码未删除,见下方"步骤3"章节说明)
PersonalOpenMobile.vue 里 AgreementStep 的 @next 直接指向 currentStep = 'SMS',
SmsVerifyStep 的 @prev 直接指向 currentStep = 'AGREEMENT';FaceCompareStep.vue 组件、
faceRecognizeApi 接口封装、facePhoto/faceVerified 状态均未删除,只是不再被引用/声明,
如需恢复该环节,把上述两个事件改回指向 'FACE' 并重新引入组件即可(PersonalOpenMobile.vue
里保留了具体恢复步骤的代码注释)。
步骤1/2/4之间支持"上一步"返回(步骤3当前不在流程内,故不涉及);步骤5(结果页)为终态,
不支持返回上一步。页面整体视觉仍为
"每步骤单独全屏展示,点击下一步整页切换"(van-nav-bar + 组件内 currentStep 切换),不是
产品截图里"顶部业务确认banner + 左侧1-5编号竖向步骤条、未激活步骤仅显示标题"的单页累积式布局
——本次改动经与产品确认,只对齐内容/交互逻辑,不做整体视觉布局改版。
步骤0:加载态 / 错误态
- 页面挂载时读取 query 中的
applicationNo,为空则直接展示"链接无效"错误态(不发请求)。 - 调用
mobile/draft-detail接口:code === 200:进入步骤1。code !== 200(如申请单不存在/已过期/已提交过):展示错误提示页,用message原文展示,提供"关闭页面"提示文案(无可操作按钮,H5 场景下没有其它页面可跳转)。
步骤1:信息核对(2026-08-05 更新:不再包含协议展示/勾选,协议已独立为步骤2)
展示字段(与管理后台"个人开户申请详情"现有展示字段保持一致,全部只读):
| 字段 | 来源 |
|---|---|
| 客户姓名 | customerName |
| 证件类型 | certificateType |
| 证件号码 | certificateNumber |
| 证件有效期起 | certificateEffectiveDate |
| 证件失效日期 | certificateExpiryDate |
| 签发机关 | issuingAuthority |
| 证件地址 | certificateAddress |
| 性别 | gender |
| 民族 | ethnic |
| 职业 | occupation(+ occupationNote,当 occupation 为"其他"时展示备注) |
| 银行卡号 | bankCardNumber |
| 手机号 | mobilePhoneMask(2026-07-31 更新:后端直接提供掩码字段,前端不再自行计算,见接口契约章节) |
不展示证件照片本身(客户本人无需再核对自己的证件照片图像,且原始图片以文件形式存放不适合在此场景重复暴露)、不展示 applicationStatus/failReason/mainAccountNo/customerNo(这些是提交后的结果字段,核对阶段不应出现)。
点击"确认无误,下一步"进入步骤2(2026-08-05 更新:本步骤不再包含协议勾选/占位文案, 按钮不再有任何禁用条件,点击直接进入步骤2)。
步骤2:相关协议(2026-08-05 新增,独立步骤,接入真实协议模板接口)
- 展示2份协议的"我已阅读并同意"复选框:《浙江稠州商业银行用户支付服务协议》
(
PAY_SERVICE_AGREEMENT)、《浙江稠州商业银行用户支付服务隐私政策》(PAY_SERVICE_PRIVACY)。 取值来源于新增的BankAgreementFileTypeEnum(该枚举共4项,另外2项CREDIT_REPORT_AUTH个人信用信息报送查询使用授权书/THIRD_PARTY_DATA_AUTH第三方数据信息 查询和使用授权书不在本页面展示范围内——与产品确认,个人开户场景只需前2项)。 - 点击协议标题链接时调用
GET /api/customer-info/bank-agreement/base64?fileType=xxx拉取协议模板文件(base64 编码的 PDF),不能直接拼成data:application/pdf;base64,...赋给 iframe 的src——真实后端联调时实测 Safari/WebKit 会拦截 iframe 对data:URI 的 导航(表现为接口正常返回fileContent,但预览区域空白,Chrome/安卓不受影响),必须把 base64 转成Blob再用URL.createObjectURL()生成blob:URL 赋给 iframe(2026-08-05 二次更新,见AgreementStep.vue的base64ToBlobUrl函数),组件卸载时统一revokeObjectURL释放内存。用van-popup内嵌该 iframe 展示原文;同一份协议重复点击时 使用本地缓存(缓存的是 blob: URL),不重复请求/解码。 - 两份协议均勾选后"下一步"按钮才可点击,未勾选禁用;底部提供"上一步"文字链接返回步骤1 (协议勾选状态本次不做跨步骤保留,退回再进入需重新勾选,与产品截图行为一致——截图未展示 该步骤支持"上一步",此处出于与其它步骤一致的返回体验保留了该入口,纯前端交互决策, 不影响提交参数)。
- 点击"下一步"进入步骤3(人脸对比,2026-08-05 二次更新:当前从流程中跳过,实际直接进入 步骤4,见下方说明)。
- 2026-08-06 更新:两份协议都勾选后,自动在《用户支付服务协议》PDF末页填入客户姓名/证件
号码/当前日期并调用通用文件上传接口拿到
fileNo,"下一步"在文件生成并上传成功前保持禁用; 该fileNo会随最终提交一起传给后端(mobile/submit新增payAgreementFileNo字段)。 详细方案(PDF坐标测量方法、中文字体渲染的两个已知 bug 及规避方式、mock-server 配套改造)见 独立设计文档docs/superpowers/specs/2026-08-06-agreement-pdf-fill-and-upload-design.md, 接口契约缺口见《缺失的后端接口.md》Part 19。
步骤3:人脸对比(2026-08-05 二次更新:按产品要求,当前从向导流程中跳过,代码保留未删除)
AgreementStep(步骤2)点击"下一步"后直接进入步骤4(手机验证),不再经过本步骤;FaceCompareStep.vue、faceRecognizeApi、下方描述的识别逻辑均未删除,只是暂未被PersonalOpenMobile.vue引用/挂载。以下为该步骤原有设计描述,供后续恢复时参考:
- 提供"拍照"入口(调用 Vant 的图片选择/拍照能力,
accept="image/*" capture="camera"),拍照或选择后 立即调用后端真实提供的人脸识别接口POST /api/customer-info/personal/face-recognize(与主项目 管理后台src/components/FacePhotoUpload.vue调用同一接口),详见docs/superpowers/specs/2026-07-31-personal-open-mobile-h5-face-recognize-and-field-sync-design.md。 - 识别通过(响应
data.fileNo非空)才能点击"下一步";识别未通过则清空已选照片,强制重新拍摄。 - 识别中整页遮罩禁止操作,识别结果不会透传给
mobile/submit(该接口描述"影像资料由后端从本地 查询"),本步骤仅作为"识别通过才能继续"的前置校验关卡。 - 支持"上一步"返回步骤2(相关协议);从步骤4(手机验证)退回本步骤时,若照片与识别通过状态仍保留, 不重新触发识别。
步骤4:手机验证 + 提交
- 展示掩码手机号(直接使用
mobile/draft-detail返回的mobilePhoneMask,不再前端自行计算,见2026-07-31更新)。 - "获取验证码"按钮:点击调用通用发码接口
send-verify-code/general(见下),成功后按钮进入 60 秒倒计时禁用态,message.success('验证码已发送')。2026-08-06 更新:请求体channelNo取自draft-detail返回的MobileDraftDetailVo.channelNo(不再固定传空字符串)、cardName取自customerName(个人账户卡户名与客户姓名一致,MobileDraftDetailVo无独立的 "银行卡户名"字段)、tradeNo固定"221506"、tradeType固定"0"(此前两者取值写反且tradeType超长,导致真实后端报错短信类型字段长度超出,先临时改成"1",后端确认正确 取值应为"0",已二次修正)。真实后端联调还发现该短信类型要求cardNo/cardName/idNo三者必须同时非空(报错该短信类型银行卡号、户名、证件号码或者电子账户、金额必须填写), 故点击"获取验证码"前新增前置校验:mobile/channelNo/cardNo/cardName/idNo任一为空 都直接弹窗提示"请联系柜员核实"并不发起请求,避免带着必然失败的参数反复重试(见SmsVerifyStep.vue的handleSendCode函数)。 - 验证码输入框(6位数字)。
- "确认提交"按钮:仅当验证码已输入时可点击,点击后:
- 按钮
loading态防重复点击,期间整页van-overlay遮罩禁止操作(耗时操作统一等待态,与主项目busy/a-spin规范同一诉求,Vant 场景下用van-overlay+van-loading实现)。 - 调用
mobile/submit({applicationNo, verifyCode})。2026-08-06 更新:新增payAgreementFileNo字段,取自步骤2生成并上传的《用户支付服务协议》PDF文件编号(通过PersonalOpenMobile.vue提升到父组件状态后作为 prop 传入本组件),详见docs/superpowers/specs/2026-08-06-agreement-pdf-fill-and-upload-design.md。 code === 200:跳转步骤5,展示data.success对应的成功/失败内容。code !== 200(验证码错误等业务异常):停留在本页,用message原文以 VantshowFailToast/showNotify提示错误,允许用户重新输入验证码或重新获取验证码后重试;不跳转步骤5。
- 按钮
- 支持"上一步"返回步骤2(相关协议,2026-08-05 二次更新:步骤3人脸对比已跳过,"上一步" 不再经过它)。
步骤5:结果页(终态)
- 成功(
data.success === true):展示成功图标 + "开户成功" + 主账户号(mainAccountNo)+ 客户号(customerNo)。 - 失败(
data.success === false):展示失败图标 + "开户失败" + 失败原因(failReason)。 - 无任何可点击的跳转/重试按钮(H5 场景下没有其它页面可去,且提交已产生后端最终结果,不支持重复提交)。
接口契约
已有明确契约(来自 swagger,无需假设,2026-07-31 更新为完整真实字段)
POST /api/wallet-management/personal-opening/mobile/draft-detail
请求:{ applicationNo }
响应 data(MobileDraftDetailVo):customerName/channelNo(2026-08-06 新增字段,
send-verify-code/general 依赖它)/certificateType/certificateNumber/
certificateEffectiveDate/certificateExpiryDate/issuingAuthority/gender/certificateAddress/
occupation/occupationNote/mobilePhone(明文)/mobilePhoneMask(后端直接提供的掩码值)/
bankCardNumber/ethnic。
POST /api/wallet-management/personal-opening/mobile/send-verify-code/general
(2026-08-06 补全契约,SendVerifyCodeGeneralParamVo)
请求:{ channelNo, tradeNo(固定"221506"), tradeType(固定"0"), mobile, cardNo, cardName, idNo }
(cardNo/cardName/idNo 三者必须同时非空,否则后端报错该短信类型银行卡号、户名、证件 号码或者电子账户、金额必须填写)
响应:{ code, message, data: null }
POST /api/customer-info/personal/face-recognize(2026-07-31 新增,真实接口)
请求:{ fileData(人脸照片base64), idNo, name, channelNo }
响应 data(FaceRecognizeResultVo):fileNo/recode/recodeInfo/sysSerialNo/sysDate
POST /api/wallet-management/personal-opening/mobile/submit
请求:{ applicationNo, verifyCode }
响应 data(SubmitApplicationResultVo):success/mainAccountNo/customerNo/failReason
GET /api/customer-info/bank-agreement/base64(2026-08-05 新增,真实接口)
请求:query 参数 fileType(枚举 BankAgreementFileTypeEnum,本页面只用到
PAY_SERVICE_AGREEMENT/PAY_SERVICE_PRIVACY 两项,见步骤2说明)
响应 data(BankAgreementFileVo):fileType/fileName/fileContent(base64 编码的 PDF)
仍未解决的契约缺口(详见《缺失的后端接口.md》Part 12 第97条、Part 15 第97/99条)
——2026-08-06 更新:channelNo/bankCardName两个字段仍不存在MobileDraftDetailVo已补充channelNo字段,send-verify-code/general的channelNo已从 draft-detail 响应取值,不再是契约缺口;cardName(银行卡户名)仍无独立字段,用customerName代替(个人账户场景卡户名与客户姓名一致)。face-recognize的channelNo因该步骤当前已从流程跳过(见步骤3说明),暂不受影响,固定传空字符串的写法保留在faceRecognizeApi里未改动。- 匿名访问
face-recognize的可行性未经真实后端环境验证:该接口此前只被已登录的管理后台 调用,H5 侧本次调用不带Authorization,本地无法验证真实后端是否已将其纳入匿名白名单。
详见 docs/superpowers/specs/2026-07-31-personal-open-mobile-h5-face-recognize-and-field-sync-design.md
的"已知风险/遗留缺口"章节。
请求头规则
- 不携带
Authorization:这些接口面向未登录的终端客户,H5 页面无法获得管理后台的登录态 token,属于匿名接口(face-recognize是否真的允许匿名访问尚待后端确认,见上方契约缺口第2点)。 - 仍携带通用 trace header(
Appno/Serialno/Transdate/Transtradetime):沿用主项目src/utils/traceHeaders.js的实现逻辑(原样迁移一份到fryl_h5,无需改动)。 - 不携带
Channelno请求头:二维码 URL 中不包含渠道信息(?applicationNo=xxx),页面无渠道上下文,按现有规则("channelNo 如果页面没有渠道信息,则不需要传值")不传该 header。注意:这里指的是 trace header 里的Channelno,与请求体中的channelNo参数是两个独立概念,互不影响。 - 响应体统一
{code, message, data}判定成功用code === 200,与主项目一致。
工程结构(fryl_h5)
fryl_h5/
├── index.html
├── package.json # 独立依赖:vue、vue-router、vant、axios、dayjs、vite
├── vite.config.js # 参照主项目结构,@ 指向 ./src,server.port 另设(如 5174,避免与主项目 5173 冲突)
├── .env.development # VITE_API_BASE_URL=http://localhost:8888(与主项目 mock-server 共用端口)
├── .env.production # VITE_API_BASE_URL=(留空,构建时按实际环境注入或走同源部署)
└── src/
├── main.js
├── App.vue
├── router/index.js # 单路由 /mobile/personal-open
├── api/
│ ├── request.js # 精简版 axios 实例:baseURL + trace header注入 + 统一错误提示,无 auth/refresh 逻辑
│ └── personalOpen.js # fetchDraftDetailApi / sendVerifyCodeApi / faceRecognizeApi / submitApplicationApi / fetchBankAgreementApi(2026-08-05新增) / uploadFileApi(2026-08-06新增)
├── constants/
│ └── enums.js # 含 BANK_AGREEMENT_FILE_TYPE_LABELS(2026-08-05新增)
├── utils/
│ └── traceHeaders.js # 从主项目原样迁移
└── views/
└── PersonalOpenMobile.vue # 页面主组件,内部拆 5 个子组件(2026-08-05 由4个拆为5个):
├── steps/InfoConfirmStep.vue
├── steps/AgreementStep.vue # 2026-08-05 新增:相关协议,调用 fetchBankAgreementApi
├── steps/FaceCompareStep.vue
├── steps/SmsVerifyStep.vue
└── steps/ResultStep.vue
2026-08-06 更新:新增 src/utils/agreementPdfFill.js(用 pdf-lib 在《用户支付服务协议》
PDF末页填入客户姓名/证件号码/当前日期;姓名用 Canvas 渲染成小图片再嵌入,不嵌入自定义中文
字体——最初的字体嵌入方案已推翻,原因及推翻过程见
docs/superpowers/specs/2026-08-06-agreement-pdf-fill-and-upload-design.md),新增依赖
pdf-lib。
mock-server 配套改造(供本地联调,2026-07-31 更新:新增人脸识别路由 + 路由挂载顺序修复,详见
docs/superpowers/specs/2026-07-31-personal-open-mobile-h5-face-recognize-and-field-sync-design.md)
在 mock-server/routes/personalOpenMobile.js 实现三个路由,新增 mock-server/routes/faceRecognize.js
实现人脸识别路由(不带 requireAuth,管理后台与H5共用),mock-server/state.js 复用现有
genSmsCode/verifySmsCode(2026-07-28 更新:发码接口改为通用 /general 路径且不再携带
applicationNo,验证码 key 统一改用手机号):
POST /wallet-management/personal-opening/mobile/draft-detail:按applicationNo查找personalOpenApplications,不存在返回sendFail('申请单不存在', 404);存在则返回真实 swagger 字段,mobilePhone直接返回application.mobilePhone明文,mobilePhoneMask由 mock 自行按"前3后4"算法计算返回(后端真实接口已确认直接提供该字段,前端不再自行计算),occupationNote取application.jobNote(种子数据字段名未变,仅响应体字段名对齐真实swagger)。2026-08-06 更新:新增返回channelNo(取application.channelNo,种子数据已有此字段),真实 swagger 已补充该字段,不再是契约缺口;bankCardName仍无对应字段,前端改用customerName代替。POST /customer-info/personal/face-recognize:校验fileData/idNo/name非空,缺失返回sendFail;校验通过返回{fileNo, recode:'0000', recodeInfo:'识别通过', sysSerialNo, sysDate},不模拟"人脸不符"等业务失败场景(无可参照的真实失败样例,与主项目现状一致)。POST /wallet-management/personal-opening/mobile/send-verify-code/general:不再依赖applicationNo(新接口本身与申请单解耦),直接以请求体mobile校验非空后调用genSmsCode('WALLET_PERSONAL_OPEN_MOBILE', mobile)(以手机号作为 key),固定码123456同时生效(与现有短信验证码 mock 规则一致)。POST /wallet-management/personal-opening/mobile/submit:申请单不存在返回sendFail('申请单不存在', 404)(与其余 mock 路由一致,code!==200,前端停留本页提示,不进入结果页——现实中该场景仅发生于极端并发下的过期链接,重试大概率仍失败,但由前端统一按"停留本页"规则处理,不单独特殊化);校验verifySmsCode('WALLET_PERSONAL_OPEN_MOBILE', application.mobilePhone, verifyCode)失败返回sendFail('验证码错误或已过期', 400)(key 与发码环节的mobile保持一致);两者均为code!==200,触发前端"停留本页"分支。验证码校验通过后执行与现有query-progress相同的"生成主账户"逻辑(若尚未开立),返回code===200+{success: true, mainAccountNo, customerNo},前端据此跳转结果页展示成功。data.success === false的业务失败分支(如核心银行侧开户失败)mock 暂不模拟(无对应真实失败场景可参照),仅保留前端结果页对该字段的展示逻辑,供后端真实接口返回失败时兜底展示。GET /customer-info/bank-agreement/base64(2026-08-05 新增):按fileType查 mock 内置的 文件名->本地占位 PDF 映射表(mock-server/assets/bank-agreements/*.pdf,占位内容非正式条款 文本,仅用于验证前端 iframe 预览 PDF 的渲染效果),读取文件并转 base64 返回{fileType, fileName, fileContent};fileType不在枚举内返回sendFail('不支持的协议文件类型', 400)。 同样不带requireAuth,且路由定义在本文件内(personalOpenMobileRouter本身已在customerRouter等之前挂载,见下方"路由挂载顺序缺陷"说明),无需额外调整挂载顺序。
路由挂载顺序缺陷(2026-07-31 修复):mock-server/index.js 里 customerRouter/walletRouter/
userRouter/roleRouter/baseConfigRouter 等均以 app.use('/api', xxxRouter) 形式挂载且内部
用 router.use(requireAuth)(不带路径)拦截鉴权,会拦截"该 router 挂载前缀下的任意路径"而不仅是
自身定义的路由。此前 personalOpenMobileRouter 挂载顺序在它们之后,导致本地 mock 环境下 H5 匿名
接口全部被误拦截返回401(已实测复现)。已将 personalOpenMobileRouter/faceRecognizeRouter 调整
到这些 router 之前挂载,详见《缺失的后端接口.md》Part 15 第100条。
此改动仅为本地离线联调用,真实后端接口到位后无需删除(管理后台侧 mock 路由同样长期保留,供后续演示环境使用)。
环境变量
fryl_h5/.env.development:
VITE_API_BASE_URL=http://localhost:8888
fryl_h5/.env.production:
VITE_API_BASE_URL=
(留空表示构建产物部署时与后端网关同源,或由运维在部署时通过其它机制配置;不提供页面内配置入口,符合本次用户决策。)
2026-08-01 更新:本地联调真实后端遇 CORS 的临时规避方案——与根目录管理后台完全一致的做法:
在 fryl_h5/.env.development.local(已被 .gitignore 的 *.local 规则忽略,不会提交)中配置
DEV_API_PROXY_TARGET=真实后端地址,同时把该文件里的 VITE_API_BASE_URL 设为空字符串,
fryl_h5/vite.config.js 会把浏览器发往同源 /api 的请求在 Node 端转发给真实后端(changeOrigin
- 请求/响应体打印日志,与主项目
vite.config.js的代理配置逐条对应,仅端口号随本工程固定为 5174)。未配置DEV_API_PROXY_TARGET时行为不变(直接请求VITE_API_BASE_URL指向的地址, 默认走本地 mock server)。仅用于本地npm run dev调试,不影响生产构建逻辑。
依赖清单(新增)
vue、vue-router、vant、axios、dayjs(traceHeaders.js 依赖)、@vitejs/plugin-vue(devDependencies)、vite(devDependencies)。均为独立 fryl_h5/package.json,与根目录 package.json 完全隔离。
范围边界(本次不做)
不实现真实人脸活体检测/比对逻辑(后端无对应接口,仅做拍照UI占位)——2026-07-31 更新: 后端已提供真实人脸识别接口,已改为真实调用,见docs/superpowers/specs/2026-07-31-personal-open-mobile-h5-face-recognize-and-field-sync-design.md。不实现协议正式文本/链接(占位文案,后续产品补充后再替换,不涉及提交参数变更)。—— 2026-08-05 更新:后端已提供bank-agreement/base64真实接口,"相关协议"步骤已改为调用 真实接口展示协议原文(PDF),不再是占位文案。- 不修改管理后台侧任何代码(二维码生成逻辑已完成)。
- 不做 H5 页面内 BASE_URL 可配置入口。
- 不支持结果页之后的"重新提交"/跳转其它页面。
- 不引入 TypeScript、不引入状态管理库(Pinia 等,本页面状态足够简单,用组件内
ref管理即可)。