From 90c61054ba006fdc5234a324bba021b6dfbd91f2 Mon Sep 17 00:00:00 2001 From: halo Date: Mon, 10 Aug 2026 18:22:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BC=81=E4=B8=9A=E8=B4=B7?= =?UTF-8?q?=E6=AC=BE=E7=94=B3=E8=AF=B7=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...terprise-loan-application-form-redesign.md | 227 +++ src/constants/creditEnums.js | 27 + .../loan-application/EnterpriseLoanForm.vue | 1379 ++++++++++++----- 缺失的后端接口.md | 126 +- 4 files changed, 1362 insertions(+), 397 deletions(-) create mode 100644 docs/superpowers/specs/2026-08-10-enterprise-loan-application-form-redesign.md diff --git a/docs/superpowers/specs/2026-08-10-enterprise-loan-application-form-redesign.md b/docs/superpowers/specs/2026-08-10-enterprise-loan-application-form-redesign.md new file mode 100644 index 0000000..387f352 --- /dev/null +++ b/docs/superpowers/specs/2026-08-10-enterprise-loan-application-form-redesign.md @@ -0,0 +1,227 @@ +# 企业贷款申请表单按原型截图改造 设计文档 + +## 背景 + +产品提供了"授信管理 > 企业贷款申请"页面的新原型截图(页面标题"提款申请 > 授信申请单",来自旧版"电商服务工具平台",用户已确认这是本次要对齐的目标设计),与当前 `src/views/credit/loan-application/EnterpriseLoanForm.vue`(阶段设计,参照《缺失的后端接口.md》1.12节 `credit-apply/enterprise-loan-application` 契约实现)相比,卡片分组、字段数量与顺序均大幅不同: + +- 截图结构:授信基础信息 → 法定代表人信息(含"展开 更多信息")→ 企业信息(含"展开 更多信息")→ 影像信息(含"他行"征信报告、下载模板项)。 +- 现状结构:基础信息 → 融资条件 → 企业信息 → 特殊融资用途标识 → 法定代表人信息 → 法人配偶信息 → 紧急联系人信息 → 影像资料。 + +对照后端契约 `CreateEnterpriseLoanDraftBto`/`UpdateEnterpriseLoanApplicationBto`(64字段,均无 `required` 约束)核对,截图新增的"更多信息"区域约 60+ 个字段后端完全没有承接位置。经与产品确认,本次采用以下原则(与今日同批完成的 `PersonalLoanForm.vue` 改造采用的"无字段仅本地展示不提交"策略不同,此处产品明确要求严格对齐截图): + +1. **完全按截图结构重写**,现状"融资条件""特殊融资用途标识""法人配偶信息"三张卡片,以及"是否市场经营者""是否政府融资平台""贷款钱包账号"三个字段,截图均未出现,随本次改造**一并移除**(不在 UI 呈现,也不再提交,历史数据不受影响,仅本页面不可编辑)。 +2. **截图有但后端无对应字段**的约 60+ 项,前端仍新建 `form.xxx` 本地字段正常渲染输入控件,**随表单一起提交**(对象里直接带上,用推测的驼峰字段名),后端如做了未知属性容错则忽略,如严格校验则可能报错——此风险已与产品确认接受,逐项登记进《缺失的后端接口.md》Part 25,待后端评估是否正式建字段。 +3. 底部"手机号+获取验证码"验证交互:项目内没有任何"贷款申请场景发码"接口(唯一的 `send-sms-code` 是登录场景专用),本次**不实现**验证码发送/校验交互;"手机号"字段保留,挪到"授信基础信息"卡片内作为普通输入框,绑定已有的 `phoneNo` 字段(无验证逻辑)。 +4. "展开 更多信息"交互复用项目现状模式:一个"展开/收起"链接 + `v-if` 控制字段区域显隐(`PersonalLoanForm.vue`"经营实体信息"区已有同类"链接/开关 + v-if"先例,本次两处更多信息区各自维护一个 `ref`)。 + +## 一、整体卡片结构 + +`EnterpriseLoanForm.vue` 重写为以下卡片顺序: + +1. **授信基础信息** +2. **法定代表人信息**(含"展开 更多信息"次级区域) +3. **企业信息**(含"展开 更多信息"次级区域) +4. **影像信息** +5. 提交 / 取消按钮(沿用现状右上角 `a-page-header` `#extra` 布局,不引入个人贷款申请那套"二次确认弹窗+二维码"模式——截图本身没有该交互,维持企业贷款申请现状"提交后直接返回列表"的简单流程) + +**整体移除**(现状已实现但截图未出现,随本次改造删除 UI 与提交逻辑): + +| 移除内容 | 原字段 | +|---|---| +| "融资条件"卡片(8字段) | goodsCategory、paymentCycle、singleOrderLimit、financingRatio、estimatedReturnRate、loanTerm、orderFinancingTerm、loanPurpose(**注**:`loanPurpose` 字段被"授信品种"复用,见下,不是整个删除) | +| "特殊融资用途标识"卡片(5字段) | repayFundSource、acceptAgricultureType、industryAdjustType、industryUpgradeFlag、newIndustryType | +| "法人配偶信息"卡片(5字段) | spouseName、spousePhone、spouseGender、spouseCertType、spouseCertId | +| "紧急联系人信息"独立卡片 | 迁移进"法定代表人-更多信息"区"紧急联系人"一项,字段 `emergencyContactName`/`emergencyContactPhone` 不丢,仅位置变化 | +| 企业信息卡片内 2 个字段 | isMarketOperator(是否市场经营者)、isGovernmentPlatform(是否政府融资平台)——截图企业信息区无对应展示位,随卡片重写一并去掉 | +| 基础信息卡片内 1 个字段 | accountNo(贷款钱包账号选择下拉)——截图授信基础信息区无对应展示位,随卡片重写去掉;如后续发现贷款放款环节仍需指定钱包账号,需产品重新确认放置位置 | + +> 以上"是否市场经营者/是否政府融资平台/贷款钱包账号"三项的移除是本设计文档新增的判断(用户此前只明确确认了融资条件/特殊融资用途标识/法人配偶信息三项),**请在下方"spec 自查"通过后重点复核这一点**,如需保留请在 review 阶段告知。 + +## 二、字段清单与映射 + +标记:✅ 复用现有后端字段(沿用 `form.xxx`);🆕 前端新增本地字段,一起提交(用推测字段名);⚠️ 语义映射到现有字段但命名不完全对应,已确认按此处理。 + +### 2.1 授信基础信息 + +| 截图字段 | 处理 | 说明 | +|---|---|---| +| 授信申请人 | ✅ `customerId`/`customerName` | 沿用 `WalletCustomerPicker` | +| 机构 | ✅ `organizationId` | 沿用 `OrgTreeSelect` | +| 产品 | ✅ `loanProductCode` | 沿用现状文本输入框,不新增选择器 | +| 授信品种 | ⚠️ `loanPurpose` | 语义最接近的现有字段承接 | +| 手机号码 | ✅ `phoneNo` | 从底部验证区挪到此卡片,普通输入框 | +| 申请金额(万元) | ✅ `loanAmount` | 现状 label 是"申请金额(元)",本次按截图改为"(万元)",仅改文案,不做单位换算(后端字段本身是无单位约束的字符串),登记为缺口供后端确认单位口径 | +| 申请利率(%) | ✅ `applicationInterestRate` | 不变 | +| 授信种类 | 🆕 `creditCategory` | `a-select`,选项常量 `CREDIT_CATEGORY_OPTIONS`(新增于 `creditEnums.js`),目前只有截图可见的一项 `{value:'OTHER_COMPREHENSIVE_LIMIT', label:'其他综合额度'}`,默认选中;**完整选项列表待业务确认**,不臆造 | + +### 2.2 法定代表人信息 + +| 截图字段 | 处理 | +|---|---| +| 姓名 | ✅ `legalPersonName` | +| 身份证号码 | ✅ `legalPersonCertId` | +| 手机号码 | ✅ `legalPersonMobile` | +| 性别 | ✅ `legalPersonGender` | +| 出生日期 | ✅ `legalPersonBirthday` | +| 证件到期日期 + 长期 | ✅ `legalPersonCertExpiry`,新增 🆕 `legalPersonCertExpiryLongTerm`(勾选态,本地布尔,不提交,仅控制日期选择器禁用+回填哨兵日期) | +| 婚姻状况 | ✅ `legalPersonMarital` | +| 现居住地址 | ✅ `legalPersonResidence` | +| 通讯地址 | 🆕 `legalPersonMailAddress`(截图"更多信息"区重复出现同名字段,合并为同一个,不重复渲染两次) | +| 最高学历 | ✅ `legalPersonEducation` | +| 是否本地置业 | 🆕 `isLocalPropertyOwner`(Y/N,`a-radio-group`) | +| 子女是否本地就学 | 🆕 `isChildLocalSchool`(Y/N) | + +**更多信息**(链接 + `v-if`,本地 ref `legalPersonMoreVisible`): + +| 截图字段 | 处理 | +|---|---| +| 个人开户户名 | 🆕 `legalPersonAccountType`,枚举 `LOCAL_CUSTOMER`(本地客户)/`NON_LOCAL_CUSTOMER`(异地客户) | +| 是否本地户籍 | 🆕 `legalPersonLocalHousehold`(Y/N) | +| 个人是否有社保医保 | 🆕 `legalPersonHasSocialInsurance`(Y/N) | +| 民族 | ✅ `legalPersonNationality` | +| 经营公司名称 | 🆕 `legalPersonBusinessCompanyName` | +| 经营公司营业性质用途 | 🆕 `legalPersonBusinessNature` | +| 经营地址 | 🆕 `legalPersonBusinessAddress` | +| 经营邮编 | 🆕 `legalPersonBusinessZipCode` | +| 住处邮编 | 🆕 `legalPersonResidenceZipCode`(⚠️ 截图该行文字密集易读错,若实际是别的字段含义,review 时请指正) | +| 资信业务 | 🆕 `legalPersonCreditBusiness` | +| 电子邮箱 | ✅ `legalPersonEmail` | +| 通讯地址邮编 | 🆕 `legalPersonMailZipCode` | +| 紧急联系人 | ✅ `emergencyContactName` + `emergencyContactPhone`(从独立卡片迁移,两个输入框:姓名/电话) | +| 个人基本户开户 | 🆕 `legalPersonBasicAccountOpened`(Y/N) | +| 开户行别 | 🆕 `legalPersonBankType` | +| 个人基本户户名 | 🆕 `legalPersonBasicAccountName` | +| 个人基本户账号 | 🆕 `legalPersonBasicAccountNo`(不复用 `accountNo`,该字段随"贷款钱包账号"整体移除,语义也不同) | +| 股票代码 | 🆕 `legalPersonStockCode` | +| 备注 | 🆕 `legalPersonRemark`(`a-textarea`) | + +### 2.3 企业信息 + +| 截图字段 | 处理 | +|---|---| +| 营业执照编号 | 🆕 `businessLicenseNo` | +| 经济类型 | ⚠️ `enterpriseOrgType` | +| 隶属经济产权 | 🆕 `enterpriseEconomicOwnership` | +| 经营场所 | ⚠️ `businessScope`(沿用现状"经营范围"字段,必填) | +| 证件登记经营地址 | 🆕 `businessLicenseRegisteredAddress` | +| 证件失效日期 + 长期 | ✅ `businessLicenseExpiry`,新增 🆕 `businessLicenseExpiryLongTerm`(同上长期哨兵处理) | +| 企业经济区域 | 🆕 `enterpriseEconomicZone` | +| 通讯地址 | 🆕 `enterpriseMailAddress` | +| 上年末从业人数 | ✅ `annualEmployeeCount` | +| 上年营业收入 | ✅ `annualRevenue` | +| 上年末资产总额 | ✅ `annualTotalAssets` | +| 企业类型 | ✅ `enterpriseType` | +| 企业代码 | 🆕 `enterpriseCode` | +| 提款种类标识 | 🆕 `withdrawalTypeFlag` | +| 是否本地股票参与经营 | 🆕 `isLocalStockParticipated`(Y/N) | +| 注册资本 | ✅ `registeredCapital` + `registeredCapitalCurrency` | +| 实收资本 | 🆕 `paidInCapital` | +| 企业所在地地址 | 🆕 `enterpriseLocationAddress` | +| 企业所在地邮编 | 🆕 `enterpriseLocationZipCode` | + +**更多信息**(链接 + `v-if`,本地 ref `enterpriseMoreVisible`): + +| 截图字段 | 处理 | +|---|---| +| 企业基本户是否本行开立 | 🆕 `enterpriseBasicAccountLocalBank`(Y/N) | +| 企业基本户开户行 | 🆕 `enterpriseBasicAccountBank` | +| 企业基本户账号 | 🆕 `enterpriseBasicAccountNo` | +| 首要股东(占公司比例) | 🆕 `enterprisePrimaryShareholder` | +| 企业办公地址 | 🆕 `enterpriseOfficeAddress` | +| 办公地址邮编 | 🆕 `enterpriseOfficeZipCode` | +| 是否有商铺 | 🆕 `hasStorefront`(Y/N) | +| 业绩 | 🆕 `enterprisePerformance` | +| 是否上市公司 | ✅ `isListedCompany` | +| 是否私人控股 | ✅ `isPrivateControlled` | +| 上市代码 | 🆕 `listedStockCode` | +| 企业法定代表电话 / 企业法定代表人号码 | **不重复渲染**:与"法定代表人信息"卡片的手机号码(`legalPersonMobile`)/身份证号码(`legalPersonCertId`) 语义重复,截图这两行按重复字段处理,不新建控件 | +| 企业银行汇账户名 | 🆕 `enterpriseRemitAccountName` | +| 企业银行汇账账号 | 🆕 `enterpriseRemitAccountNo` | +| 企业银行汇账开户行网点 | 🆕 `enterpriseRemitBankBranch` | +| 企业货款账户名称 | 🆕 `enterpriseLoanPaymentAccountName` | +| 企业与他行建立信贷关系情况 | 🆕 `enterpriseOtherBankCreditRelation` | +| 财务报表编号 | 🆕 `financialStatementNo` | +| 企业营销所在区域 | 🆕 `enterpriseMarketingRegion` | +| 有无对外担保情况 | 🆕 `hasExternalGuarantee`(Y/N) | +| 是否属于中小企业 | 🆕 `isSmeEnterprise`(Y/N) | +| 是否属地生态化 | 🆕 `isLocalEcological`(Y/N) | +| 是否属于股权制 | 🆕 `isEquitySystem`(Y/N) | +| 高新技术产业标识 | 🆕 `highTechIndustryFlag`,枚举 `OBTAINED`(已获取)/`APPLYING`(正申请) | +| 公司代号及关系企业 | 🆕 `companyCodeAndAffiliates` | +| 公司e-Mail | 🆕 `enterpriseEmail` | +| 公司网址 | 🆕 `enterpriseWebsite` | +| 企业经营网址 | 🆕 `enterpriseBusinessWebsite` | +| 企业开户方式及运营开户 | 🆕 `enterpriseAccountOpeningMethod` | +| 企业主要产品或服务 | 🆕 `enterpriseMainProductsServices`(`a-textarea`) | +| 企业经营地址范围 | 🆕 `enterpriseBusinessAddressScope`(`a-textarea`;截图旁边有"+"按钮暗示多值,本次简化为单个多行文本框,不做数组结构,登记简化说明) | +| 企业股权其他情况 | 🆕 `enterpriseEquityOtherType`,枚举 `OWNED`(自有)/`LEASED`(租用)/`OTHER`(其他) | +| 企业上级公司名称 | 🆕 `parentCompanyName` | +| 企业上级公司所在地址 | 🆕 `parentCompanyAddress` | +| 企业上级公司持股比例 | 🆕 `parentCompanyShareRatio` | +| 备注 | 🆕 `enterpriseRemark`(`a-textarea`) | + +### 2.4 影像信息 + +| 截图字段 | 处理 | +|---|---| +| 营业主照 | ✅ 沿用 `LicenseUpload`(`businessLicenseFileNo`) | +| 法人代表身份证(人像面/国徽面) | ✅ 沿用 `IdCardUpload`(`legalPersonIdFrontFileNo`/`legalPersonIdBackFileNo`) | +| 他行 - 个人征信报告 | 🆕 复用 `LicenseUpload` 新实例(`upload-text="上传个人征信报告"`),本地 `otherBankPersonalCreditReportFileNo`,`fileType` 占位值 `'OTHER_BANK_PERSONAL_CREDIT_REPORT'` | +| 他行 - 企业征信报告 | 🆕 复用 `LicenseUpload` 新实例,本地 `otherBankEnterpriseCreditReportFileNo`,`fileType` 占位值 `'OTHER_BANK_ENTERPRISE_CREDIT_REPORT'` | +| 企业营业执照 - 下载模板 | 禁用占位按钮(`disabled title="模板文件待业务方提供"`,同 `PersonalLoanForm.vue` 现状写法) | +| 法定代表人 - 下载模板 | 同上,禁用占位按钮 | + +四个上传项统一走 `buildImageList()` 拼进 `loanApplicationImageBtoList`;两个新增的"他行"上传项因为没有真实 `fileType` 枚举,`fileType` 用上面列出的占位字符串,登记缺口。 + +## 三、长期哨兵日期处理 + +复用 `src/constants/customerEnums.js` 的 `CERTIFICATE_LONG_TERM_DATE`(`'9999-12-31 00:00:00'`),日期部分 `.slice(0, 10)` = `'9999-12-31'`。参照 `EnterpriseForm.vue` 的 `handleLongTermChange` 写法: + +```js +function handleLegalPersonCertExpiryLongTermChange(e) { + if (e.target.checked) { + legalPersonCertExpiryModel.value = undefined // 日期选择器禁用并清空展示 + form.legalPersonCertExpiry = CERTIFICATE_LONG_TERM_DATE_ONLY + } else { + form.legalPersonCertExpiry = '' + } +} +``` + +`businessLicenseExpiry` 同理新增一份。`a-date-picker :disabled="form.xxxLongTerm"` + `a-checkbox v-model:checked="form.xxxLongTerm" @change="handleXxxLongTermChange"`。 + +## 四、《缺失的后端接口.md》新增登记(Part 25) + +新增一节"Part 25:授信管理 —— 企业贷款申请表单按最新原型截图改造 —— 2026-08-10 补充",编号从 146 开始,内容涵盖: + +1. 本次移除的"融资条件/特殊融资用途标识/法人配偶信息"三张卡片 + "是否市场经营者/是否政府融资平台/贷款钱包账号"字段:现状 Bto 已支持但页面不再提供录入入口,历史数据不受影响。 +2. 完整列出上方 2.2/2.3 节全部 🆕 字段(约 60+ 项)及其推测提交字段名,说明 `CreateEnterpriseLoanDraftBto`/`UpdateEnterpriseLoanApplicationBto` 均无对应属性,前端仍随表单一起提交,若后端未做未知属性容错可能导致整单提交失败,需要后端评估。 +3. "授信种类"(`creditCategory`)完整枚举选项待业务确认,当前只有截图可见的"其他综合额度"一项。 +4. "证件到期日长期"哨兵日期方案(沿用已有 `CERTIFICATE_LONG_TERM_DATE` 惯例,非新缺口,仅说明本次两个新增位置的沿用情况)。 +5. 影像信息"他行个人/企业征信报告"两个新上传项的 `fileType` 占位值待后端确认;"下载模板"按钮维持禁用占位现状。 +6. "申请金额"单位展示由"(元)"改为"(万元)",字段本身无单位约束,是否需要前端做换算或后端补充单位字段,待确认。 +7. "企业经营地址范围"截图暗示可"+"多值录入,本次简化为单个多行文本框,非数组结构。 + +## 五、组件变更清单 + +| 文件 | 变更类型 | +|---|---| +| `src/views/credit/loan-application/EnterpriseLoanForm.vue` | 重写 | +| `src/constants/creditEnums.js` | 修改(新增 `CREDIT_CATEGORY_OPTIONS` 等本次新枚举常量) | +| `缺失的后端接口.md` | 修改(新增 Part 25) | + +复用不改动:`ChannelSelect.vue`、`OrgTreeSelect.vue`、`WalletCustomerPicker.vue`、`IdCardUpload.vue`、`LicenseUpload.vue`。 + +## 范围边界 + +- 不改动 `LoanApplicationList.vue`、路由映射、菜单层级。 +- 不改动 `PersonalLoanForm.vue`(今日已完成的独立改造,风格与本次不同,互不影响)。 +- 不实现底部短信验证码发送/校验交互(无接口支撑)。 +- 不引入"二次确认弹窗+二维码"提交流程(截图无此交互,维持企业贷款申请现状"提交后直接返回列表")。 +- 不做"企业经营地址范围"多值数组结构、不新建"授信品种/产品"选择弹窗(沿用文本输入)。 + +## 待 review 重点确认项(已在正文标注 ⚠️,此处汇总) + +1. 是否同意移除"是否市场经营者/是否政府融资平台/贷款钱包账号"三个截图未出现的现有字段(本文档新增判断,非用户此前明确确认项)。 +2. "住处邮编"(`legalPersonResidenceZipCode`)对应截图原文是否读取准确。 +3. "授信种类"完整枚举选项、"申请金额"单位换算规则,均需业务后续补充,当前先用最小可用值占位。 diff --git a/src/constants/creditEnums.js b/src/constants/creditEnums.js index 2eba857..dd6ffec 100644 --- a/src/constants/creditEnums.js +++ b/src/constants/creditEnums.js @@ -154,3 +154,30 @@ export const ENTERPRISE_RELATION_OPTIONS = [ { value: "ACTUAL_CONTROLLER", label: "实控人" } ] +// 企业贷款申请"授信种类"下拉选项:CreateEnterpriseLoanDraftBto/UpdateEnterpriseLoanApplicationBto +// 均无对应字段(已知缺口,登记于《缺失的后端接口.md》Part 25),当前只有原型截图上可见的一项, +// 完整选项列表待业务确认,不臆造更多选项。 +export const CREDIT_CATEGORY_OPTIONS = [{ value: "OTHER_COMPREHENSIVE_LIMIT", label: "其他综合额度" }] + +// 企业贷款申请法定代表人"个人开户户名"下拉选项:原型截图给出"本地客户/异地客户"两项, +// CreateEnterpriseLoanDraftBto 无对应字段(已知缺口,登记于《缺失的后端接口.md》Part 25)。 +export const LEGAL_PERSON_ACCOUNT_TYPE_OPTIONS = [ + { value: "LOCAL_CUSTOMER", label: "本地客户" }, + { value: "NON_LOCAL_CUSTOMER", label: "异地客户" } +] + +// 企业贷款申请企业信息"高新技术产业标识"下拉选项:原型截图给出"已获取/正申请"两项, +// CreateEnterpriseLoanDraftBto 无对应字段(已知缺口,登记于《缺失的后端接口.md》Part 25)。 +export const HIGH_TECH_INDUSTRY_FLAG_OPTIONS = [ + { value: "OBTAINED", label: "已获取" }, + { value: "APPLYING", label: "正申请" } +] + +// 企业贷款申请企业信息"企业股权其他情况"下拉选项:原型截图给出"自有/租用/其他"三项, +// CreateEnterpriseLoanDraftBto 无对应字段(已知缺口,登记于《缺失的后端接口.md》Part 25)。 +export const ENTERPRISE_EQUITY_OTHER_TYPE_OPTIONS = [ + { value: "OWNED", label: "自有" }, + { value: "LEASED", label: "租用" }, + { value: "OTHER", label: "其他" } +] + diff --git a/src/views/credit/loan-application/EnterpriseLoanForm.vue b/src/views/credit/loan-application/EnterpriseLoanForm.vue index b35d696..3069a81 100644 --- a/src/views/credit/loan-application/EnterpriseLoanForm.vue +++ b/src/views/credit/loan-application/EnterpriseLoanForm.vue @@ -1,14 +1,24 @@ + 2) legalPersonEducation/legalPersonMarital/legalPersonNationality/legalPersonGender 均为无枚举约束的 + 纯字符串字段,前端为提升可用性复用个人贷款相关枚举的取值域渲染下拉,但后端未对其做强校验; + 3) 本次按截图新增约60余个 CreateEnterpriseLoanDraftBto/UpdateEnterpriseLoanApplicationBto 完全没有 + 对应属性的字段(法定代表人/企业信息"更多信息"区),经产品确认仍随表单一起提交(用推测字段名), + 若后端未做未知属性容错可能导致整单提交失败,已逐项登记进 Part 25,待后端评估是否正式建字段; + 4) 现状"融资条件/特殊融资用途标识/法人配偶信息"三张卡片,以及"是否市场经营者/是否政府融资平台" + 字段,截图未出现,本次改造后页面不再提供录入入口(Bto 仍支持,只是没有 UI 承接); + 5) 底部"手机号+验证码"验证交互:无对应发码接口,本次不实现,"手机号"字段挪到"授信基础信息"卡片 + 作为普通输入框(phoneNo); + 6) 2026-08-10 第二轮补充:选择授信申请人后自动回填法定代表人/企业信息基础字段(需二次查询 + 企业客户详情+法定代表人个人详情,项目内无先例,新写);新增"钱包账号"必填选择(复用 + WalletAccountPicker);影像信息按更清晰的补充截图修正为"营业执照/法人代表身份证(含正面 + 免冠照人脸识别)/授权(个人征信授权+企业征信授权书)/企业章程/决议书"5行结构,详见 + 《缺失的后端接口.md》Part 25 第155-157条。 --> @@ -415,23 +731,31 @@ import ChannelSelect from '@/components/ChannelSelect.vue' import OrgTreeSelect from '@/components/OrgTreeSelect.vue' import IdCardUpload from '@/components/IdCardUpload.vue' import LicenseUpload from '@/components/LicenseUpload.vue' +import FacePhotoUpload from '@/components/FacePhotoUpload.vue' import WalletCustomerPicker from '@/components/WalletCustomerPicker.vue' -import { fetchWalletAccountPageApi } from '@/api/wallet' +import WalletAccountPicker from '@/components/WalletAccountPicker.vue' import { createEnterpriseLoanApplicationApi, updateEnterpriseLoanApplicationApi, fetchLoanApplicationDetailApi } from '@/api/credit' +import { fetchEnterpriseCustomerDetailApi, fetchPersonalCustomerDetailApi } from '@/api/customer' import { getCurrentOrganizationId } from '@/utils/orgScope' +import { CERTIFICATE_LONG_TERM_DATE } from '@/constants/customerEnums' import { - GOODS_CATEGORY_OPTIONS, - PAYMENT_CYCLE_OPTIONS, ETHNICITY_OPTIONS, EDUCATION_LEVEL_OPTIONS, MARITAL_STATUS_OPTIONS, - LOAN_APPLICATION_STATUS_OPTIONS + LOAN_APPLICATION_STATUS_OPTIONS, + LOAN_APPLICATION_IMAGE_FILE_TYPE, + CREDIT_CATEGORY_OPTIONS, + LEGAL_PERSON_ACCOUNT_TYPE_OPTIONS, + HIGH_TECH_INDUSTRY_FLAG_OPTIONS, + ENTERPRISE_EQUITY_OTHER_TYPE_OPTIONS } from '@/constants/creditEnums' +const CERTIFICATE_LONG_TERM_DATE_ONLY = CERTIFICATE_LONG_TERM_DATE.slice(0, 10) + const route = useRoute() const router = useRouter() @@ -445,12 +769,72 @@ const isDetail = computed(() => mode.value === 'detail') const pageTitle = computed(() => ({ create: '新增企业贷款申请', edit: '编辑企业贷款申请', detail: '查看企业贷款申请' }[mode.value])) const submitting = ref(false) -const loadingWallet = ref(false) -const walletOptions = ref([]) +const customerPickerRef = ref(null) +const walletPickerRef = ref(null) +// 选择授信申请人后自动回填法定代表人/企业信息基础字段(见 autoFillFromEnterpriseCustomer)期间的 +// 等待态,纳入下方 busy 统一遮罩。 +const autoFillLoading = ref(false) + +// "展开 更多信息"交互:链接 + v-if 控制显隐,复用 PersonalLoanForm.vue"经营实体信息"区域已有的 +// "开关/链接 + v-if"模式,不引入项目内没有先例的 a-collapse。 +const legalPersonMoreVisible = ref(false) +const enterpriseMoreVisible = ref(false) + +// "长期"哨兵日期:沿用 EnterpriseForm.vue/PersonalForm.vue 已有的 CERTIFICATE_LONG_TERM_DATE +// 权变方案,勾选后禁用日期选择器并把对应日期字段写为哨兵日期,不属于后端字段,仅前端本地态。 +const legalPersonCertExpiryLongTerm = ref(false) +const businessLicenseExpiryLongTerm = ref(false) + +// 影像资料各上传项的文件编号与上传中状态。"正面免冠照"复用 FacePhotoUpload(人脸识别), +// "个人征信授权/企业征信授权书/企业章程/决议书"复用 LicenseUpload,fileType 无对应枚举 +// (已知缺口,登记于《缺失的后端接口.md》Part 25),使用可读中文占位值。 const businessLicenseFileNo = ref('') const legalPersonIdFrontFileNo = ref('') const legalPersonIdBackFileNo = ref('') -const customerPickerRef = ref(null) +const legalPersonFacePhotoFileNo = ref('') +const personalCreditAuthFileNo = ref('') +const enterpriseCreditAuthFileNo = ref('') +const enterpriseArticlesFileNo = ref('') +const resolutionFileNo = ref('') +const businessLicenseUploading = ref(false) +const legalPersonIdFrontUploading = ref(false) +const legalPersonIdBackUploading = ref(false) +const legalPersonFacePhotoUploading = ref(false) +const personalCreditAuthUploading = ref(false) +const enterpriseCreditAuthUploading = ref(false) +const enterpriseArticlesUploading = ref(false) +const resolutionUploading = ref(false) + +const busy = computed( + () => + submitting.value || + autoFillLoading.value || + businessLicenseUploading.value || + legalPersonIdFrontUploading.value || + legalPersonIdBackUploading.value || + legalPersonFacePhotoUploading.value || + personalCreditAuthUploading.value || + enterpriseCreditAuthUploading.value || + enterpriseArticlesUploading.value || + resolutionUploading.value +) +const busyTip = computed(() => { + if (autoFillLoading.value) return '正在自动回填法定代表人与企业信息,请稍候...' + if (legalPersonFacePhotoUploading.value) return '人脸识别中,请稍候...' + if ( + businessLicenseUploading.value || + legalPersonIdFrontUploading.value || + legalPersonIdBackUploading.value || + personalCreditAuthUploading.value || + enterpriseCreditAuthUploading.value || + enterpriseArticlesUploading.value || + resolutionUploading.value + ) { + return '影像信息上传中,请稍候...' + } + if (submitting.value) return '提交中,请稍候...' + return '' +}) const detailInfo = reactive({ createdBy: '', applicationStatus: '' }) const statusText = computed( @@ -464,40 +848,16 @@ const form = reactive({ loanProductCode: '', customerType: 'ENTERPRISE', customerId: undefined, - customerName: '', - accountNo: undefined, - goodsCategory: undefined, - paymentCycle: undefined, - singleOrderLimit: '', - financingRatio: undefined, - estimatedReturnRate: undefined, - loanTerm: undefined, - orderFinancingTerm: undefined, - loanAmount: '', - emergencyContactPhone: '', - loanPurpose: '', enterpriseName: '', + accountNo: undefined, + phoneNo: '', + loanAmount: '', applicationInterestRate: '', - repayFundSource: '', - acceptAgricultureType: '', - industryAdjustType: '', - industryUpgradeFlag: false, - newIndustryType: '', - annualEmployeeCount: undefined, - annualRevenue: '', - annualTotalAssets: '', - registeredCapital: '', - registeredCapitalCurrency: '', - businessLicenseDate: '', - businessLicenseExpiry: '', - enterpriseType: '', - enterpriseOrgType: '', - enterpriseEconomyType: '', - industryCategory: '', - isMarketOperator: 'N', - isListedCompany: 'N', - isPrivateControlled: 'N', - isGovernmentPlatform: 'N', + loanPurpose: '', // 复用为"授信品种" + // 🆕 授信基础信息新增字段(Bto 无对应属性,已知缺口) + creditCategory: 'OTHER_COMPREHENSIVE_LIMIT', + + // 法定代表人信息(✅已支持) legalPersonCertType: '', legalPersonCertId: '', legalPersonName: '', @@ -511,24 +871,88 @@ const form = reactive({ legalPersonAnnualIncome: '', legalPersonMobile: '', legalPersonEmail: '', - spouseName: '', - spousePhone: '', - spouseCertType: '', - spouseGender: undefined, - spouseCertId: '', emergencyContactName: '', - businessScope: '', - phoneNo: '' + emergencyContactPhone: '', + // 🆕 法定代表人信息新增字段(Bto 无对应属性,已知缺口) + legalPersonMailAddress: '', + isLocalPropertyOwner: 'N', + isChildLocalSchool: 'N', + legalPersonAccountType: undefined, + legalPersonLocalHousehold: 'N', + legalPersonHasSocialInsurance: 'N', + legalPersonBusinessCompanyName: '', + legalPersonBusinessNature: '', + legalPersonBusinessAddress: '', + legalPersonBusinessZipCode: '', + legalPersonResidenceZipCode: '', + legalPersonCreditBusiness: '', + legalPersonMailZipCode: '', + legalPersonBasicAccountOpened: 'N', + legalPersonBankType: '', + legalPersonBasicAccountName: '', + legalPersonBasicAccountNo: '', + legalPersonStockCode: '', + legalPersonRemark: '', + + // 企业信息(✅已支持) + businessScope: '', // 复用为"经营场所" + enterpriseOrgType: '', // 复用为"经济类型" + businessLicenseExpiry: '', + annualEmployeeCount: undefined, + annualRevenue: '', + annualTotalAssets: '', + enterpriseType: '', + registeredCapital: '', + registeredCapitalCurrency: '', + isListedCompany: 'N', + isPrivateControlled: 'N', + // 🆕 企业信息新增字段(Bto 无对应属性,已知缺口) + businessLicenseNo: '', + enterpriseEconomicOwnership: '', + businessLicenseRegisteredAddress: '', + enterpriseEconomicZone: '', + enterpriseMailAddress: '', + enterpriseCode: '', + withdrawalTypeFlag: '', + isLocalStockParticipated: 'N', + paidInCapital: '', + enterpriseLocationAddress: '', + enterpriseLocationZipCode: '', + enterpriseBasicAccountLocalBank: 'N', + enterpriseBasicAccountBank: '', + enterpriseBasicAccountNo: '', + enterprisePrimaryShareholder: '', + enterpriseOfficeAddress: '', + enterpriseOfficeZipCode: '', + hasStorefront: 'N', + enterprisePerformance: '', + listedStockCode: '', + enterpriseRemitAccountName: '', + enterpriseRemitAccountNo: '', + enterpriseRemitBankBranch: '', + enterpriseLoanPaymentAccountName: '', + enterpriseOtherBankCreditRelation: '', + financialStatementNo: '', + enterpriseMarketingRegion: '', + hasExternalGuarantee: 'N', + isSmeEnterprise: 'N', + isLocalEcological: 'N', + isEquitySystem: 'N', + highTechIndustryFlag: undefined, + companyCodeAndAffiliates: '', + enterpriseEmail: '', + enterpriseWebsite: '', + enterpriseBusinessWebsite: '', + enterpriseAccountOpeningMethod: '', + enterpriseMainProductsServices: '', + enterpriseBusinessAddressScope: '', + enterpriseEquityOtherType: undefined, + parentCompanyName: '', + parentCompanyAddress: '', + parentCompanyShareRatio: '', + enterpriseRemark: '' }) -const businessLicenseDateModel = computed({ - get: () => (form.businessLicenseDate ? dayjs(form.businessLicenseDate) : undefined), - set: (val) => (form.businessLicenseDate = val ? val.format('YYYY-MM-DD') : '') -}) -const businessLicenseExpiryModel = computed({ - get: () => (form.businessLicenseExpiry ? dayjs(form.businessLicenseExpiry) : undefined), - set: (val) => (form.businessLicenseExpiry = val ? val.format('YYYY-MM-DD') : '') -}) const legalPersonBirthdayModel = computed({ get: () => (form.legalPersonBirthday ? dayjs(form.legalPersonBirthday) : undefined), set: (val) => (form.legalPersonBirthday = val ? val.format('YYYY-MM-DD') : '') @@ -537,31 +961,106 @@ const legalPersonCertExpiryModel = computed({ get: () => (form.legalPersonCertExpiry ? dayjs(form.legalPersonCertExpiry) : undefined), set: (val) => (form.legalPersonCertExpiry = val ? val.format('YYYY-MM-DD') : '') }) +const businessLicenseExpiryModel = computed({ + get: () => (form.businessLicenseExpiry ? dayjs(form.businessLicenseExpiry) : undefined), + set: (val) => (form.businessLicenseExpiry = val ? val.format('YYYY-MM-DD') : '') +}) + +function handleLegalPersonCertExpiryLongTermChange(e) { + if (e.target.checked) { + form.legalPersonCertExpiry = CERTIFICATE_LONG_TERM_DATE_ONLY + } else { + form.legalPersonCertExpiry = '' + } +} + +function handleBusinessLicenseExpiryLongTermChange(e) { + if (e.target.checked) { + form.businessLicenseExpiry = CERTIFICATE_LONG_TERM_DATE_ONLY + } else { + form.businessLicenseExpiry = '' + } +} function openCustomerPicker() { customerPickerRef.value.show() } -async function loadWalletOptions(enterpriseName) { - loadingWallet.value = true - walletOptions.value = [] - try { - const res = await fetchWalletAccountPageApi({ page: 1, pageSize: 50, accountName: enterpriseName }) - if (res.data.code === 200) { - walletOptions.value = (res.data.data.records || []).map((item) => ({ - label: item.accountNo, - value: item.accountNo - })) - } - } finally { - loadingWallet.value = false +function openWalletPicker() { + if (!form.enterpriseName) { + showErrorModal('请先选择授信申请人') + return } + walletPickerRef.value.show() } +function handleSelectWallet(record) { + form.accountNo = record.accountNo +} + +// 选择授信申请人(企业客户)后:①回填名称/customerId;②清空之前已选的钱包账号(避免与新客户 +// 不匹配);③自动回填法定代表人信息与企业信息基础字段,减少柜员重复录入。 async function handleSelectCustomer(record) { form.customerId = record.id form.enterpriseName = record.enterpriseName - await loadWalletOptions(record.enterpriseName) + form.accountNo = undefined + await autoFillFromEnterpriseCustomer(record.id) +} + +// EnterpriseCustomerDetailVo 本身没有法定代表人的姓名/证件等个人信息字段,只在 +// enterpriseRelatedPersonList(股东与高管信息列表)里通过 legalRepresentativeStatus 标记出 +// 哪一条股东/高管是法定代表人(仅含 individualId),需要再调用个人客户详情接口取姓名/证件号/ +// 手机号等。现有代码库(EnterpriseOpenForm.vue)是手动选法代,没有"自动从企业详情提取法代"的 +// 先例,此处新写。已知缺口(登记于《缺失的后端接口.md》Part 25): +// 1) EnterpriseCustomerDetailVo 没有"企业类型""是否私人控股"对应字段,回填时保持原值,需人工填写; +// 2) 若企业客户资料里没有任何一条被标记为法定代表人,不阻断选择流程,提示后由柜员手动填写; +// 3) 个人客户 educationLevel/maritalStatus/gender/ethnicity 是否与本表单复用的枚举取值域 +// 完全一致,以实测为准(与法人信息各字段原有的"无强校验"缺口一致,不重复登记)。 +async function autoFillFromEnterpriseCustomer(customerId) { + if (!customerId) return + autoFillLoading.value = true + try { + const res = await fetchEnterpriseCustomerDetailApi({ id: customerId }) + if (res.data.code !== 200) return + const detail = res.data.data || {} + form.businessLicenseNo = detail.businessLicense || form.businessLicenseNo + form.enterpriseCode = detail.enterpriseCode || form.enterpriseCode + form.businessScope = detail.businessScope || form.businessScope + form.businessLicenseRegisteredAddress = detail.certificateAddress || form.businessLicenseRegisteredAddress + form.businessLicenseExpiry = detail.certificateExpiryDate ? detail.certificateExpiryDate.slice(0, 10) : form.businessLicenseExpiry + businessLicenseExpiryLongTerm.value = form.businessLicenseExpiry === CERTIFICATE_LONG_TERM_DATE_ONLY + form.registeredCapital = detail.registeredCapital || form.registeredCapital + form.registeredCapitalCurrency = detail.registeredCapitalCurrency || form.registeredCapitalCurrency + form.annualEmployeeCount = detail.lastYearEmployeeCount ?? form.annualEmployeeCount + form.annualRevenue = detail.lastYearRevenue || form.annualRevenue + form.annualTotalAssets = detail.lastYearTotalAssets || form.annualTotalAssets + form.isListedCompany = detail.isListedCompany ? 'Y' : 'N' + + const legalRep = (detail.enterpriseRelatedPersonList || []).find((p) => p.legalRepresentativeStatus === 'LEGAL_REPRESENTATIVE') + if (!legalRep || !legalRep.individualId) { + message.info('未在企业客户资料中找到法定代表人信息,请手动填写法定代表人信息') + return + } + const personRes = await fetchPersonalCustomerDetailApi({ id: legalRep.individualId }) + if (personRes.data.code !== 200) return + const person = personRes.data.data || {} + form.legalPersonName = person.customerName || form.legalPersonName + form.legalPersonCertType = person.certificateType || form.legalPersonCertType + form.legalPersonCertId = person.certificateNumber || form.legalPersonCertId + form.legalPersonGender = person.gender || form.legalPersonGender + form.legalPersonBirthday = person.birthDate ? person.birthDate.slice(0, 10) : form.legalPersonBirthday + form.legalPersonCertExpiry = person.certificateExpiryDate ? person.certificateExpiryDate.slice(0, 10) : form.legalPersonCertExpiry + legalPersonCertExpiryLongTerm.value = form.legalPersonCertExpiry === CERTIFICATE_LONG_TERM_DATE_ONLY + form.legalPersonEducation = person.educationLevel || form.legalPersonEducation + form.legalPersonMarital = person.maritalStatus || form.legalPersonMarital + form.legalPersonNationality = person.ethnicity || form.legalPersonNationality + form.legalPersonResidence = person.certificateAddress || form.legalPersonResidence + form.legalPersonMobile = person.mobilePhone || form.legalPersonMobile + } catch { + // 自动回填失败不阻断选择流程,可人工手动补录法定代表人/企业信息 + } finally { + autoFillLoading.value = false + } } function toFullDate(dateStr) { @@ -573,16 +1072,14 @@ async function loadDetail() { if (res.data.code !== 200) return const data = res.data.data Object.assign(form, data, { - businessLicenseDate: data.businessLicenseDate ? data.businessLicenseDate.slice(0, 10) : '', - businessLicenseExpiry: data.businessLicenseExpiry ? data.businessLicenseExpiry.slice(0, 10) : '', legalPersonBirthday: data.legalPersonBirthday ? data.legalPersonBirthday.slice(0, 10) : '', - legalPersonCertExpiry: data.legalPersonCertExpiry ? data.legalPersonCertExpiry.slice(0, 10) : '' + legalPersonCertExpiry: data.legalPersonCertExpiry ? data.legalPersonCertExpiry.slice(0, 10) : '', + businessLicenseExpiry: data.businessLicenseExpiry ? data.businessLicenseExpiry.slice(0, 10) : '' }) + legalPersonCertExpiryLongTerm.value = form.legalPersonCertExpiry === CERTIFICATE_LONG_TERM_DATE_ONLY + businessLicenseExpiryLongTerm.value = form.businessLicenseExpiry === CERTIFICATE_LONG_TERM_DATE_ONLY detailInfo.createdBy = data.createdBy detailInfo.applicationStatus = data.applicationStatus - if (data.enterpriseName) { - await loadWalletOptions(data.enterpriseName) - } } onMounted(() => { @@ -596,6 +1093,11 @@ function buildImageList() { if (businessLicenseFileNo.value) list.push({ fileType: '营业执照', fileNo: businessLicenseFileNo.value }) if (legalPersonIdFrontFileNo.value) list.push({ fileType: '身份证正面', fileNo: legalPersonIdFrontFileNo.value }) if (legalPersonIdBackFileNo.value) list.push({ fileType: '身份证反面', fileNo: legalPersonIdBackFileNo.value }) + if (legalPersonFacePhotoFileNo.value) list.push({ fileType: LOAN_APPLICATION_IMAGE_FILE_TYPE.FACE_PHOTO, fileNo: legalPersonFacePhotoFileNo.value }) + if (personalCreditAuthFileNo.value) list.push({ fileType: '个人征信授权', fileNo: personalCreditAuthFileNo.value }) + if (enterpriseCreditAuthFileNo.value) list.push({ fileType: '企业征信授权书', fileNo: enterpriseCreditAuthFileNo.value }) + if (enterpriseArticlesFileNo.value) list.push({ fileType: '企业章程', fileNo: enterpriseArticlesFileNo.value }) + if (resolutionFileNo.value) list.push({ fileType: '决议书', fileNo: resolutionFileNo.value }) return list } @@ -605,7 +1107,28 @@ async function handleSubmit() { return } if (!form.enterpriseName) { - showErrorModal('请先选择企业客户') + showErrorModal('请先选择授信申请人(企业客户)') + return + } + if (!form.accountNo) { + showErrorModal('请先选择钱包账号') + return + } + if (!legalPersonIdFrontFileNo.value || !legalPersonIdBackFileNo.value) { + showErrorModal('请上传法人代表身份证人像面/国徽面') + return + } + if ( + businessLicenseUploading.value || + legalPersonIdFrontUploading.value || + legalPersonIdBackUploading.value || + legalPersonFacePhotoUploading.value || + personalCreditAuthUploading.value || + enterpriseCreditAuthUploading.value || + enterpriseArticlesUploading.value || + resolutionUploading.value + ) { + showErrorModal('影像信息仍在上传中,请稍候再提交') return } submitting.value = true @@ -617,39 +1140,14 @@ async function handleSubmit() { customerType: form.customerType, customerId: form.customerId, customerName: form.enterpriseName, - accountNo: form.accountNo, - goodsCategory: form.goodsCategory, - paymentCycle: form.paymentCycle, - singleOrderLimit: form.singleOrderLimit, - financingRatio: form.financingRatio, - estimatedReturnRate: form.estimatedReturnRate, - loanTerm: form.loanTerm, - orderFinancingTerm: form.orderFinancingTerm, - loanAmount: form.loanAmount, - emergencyContactPhone: form.emergencyContactPhone, - loanPurpose: form.loanPurpose, enterpriseName: form.enterpriseName, + accountNo: form.accountNo, + phoneNo: form.phoneNo, + loanAmount: form.loanAmount, applicationInterestRate: form.applicationInterestRate, - repayFundSource: form.repayFundSource, - acceptAgricultureType: form.acceptAgricultureType, - industryAdjustType: form.industryAdjustType, - industryUpgradeFlag: form.industryUpgradeFlag, - newIndustryType: form.newIndustryType, - annualEmployeeCount: form.annualEmployeeCount, - annualRevenue: form.annualRevenue, - annualTotalAssets: form.annualTotalAssets, - registeredCapital: form.registeredCapital, - registeredCapitalCurrency: form.registeredCapitalCurrency, - businessLicenseDate: toFullDate(form.businessLicenseDate), - businessLicenseExpiry: toFullDate(form.businessLicenseExpiry), - enterpriseType: form.enterpriseType, - enterpriseOrgType: form.enterpriseOrgType, - enterpriseEconomyType: form.enterpriseEconomyType, - industryCategory: form.industryCategory, - isMarketOperator: form.isMarketOperator, - isListedCompany: form.isListedCompany, - isPrivateControlled: form.isPrivateControlled, - isGovernmentPlatform: form.isGovernmentPlatform, + loanPurpose: form.loanPurpose, + creditCategory: form.creditCategory, + legalPersonCertType: form.legalPersonCertType, legalPersonCertId: form.legalPersonCertId, legalPersonName: form.legalPersonName, @@ -663,14 +1161,84 @@ async function handleSubmit() { legalPersonAnnualIncome: form.legalPersonAnnualIncome, legalPersonMobile: form.legalPersonMobile, legalPersonEmail: form.legalPersonEmail, - spouseName: form.spouseName, - spousePhone: form.spousePhone, - spouseCertType: form.spouseCertType, - spouseGender: form.spouseGender, - spouseCertId: form.spouseCertId, emergencyContactName: form.emergencyContactName, + emergencyContactPhone: form.emergencyContactPhone, + legalPersonMailAddress: form.legalPersonMailAddress, + isLocalPropertyOwner: form.isLocalPropertyOwner, + isChildLocalSchool: form.isChildLocalSchool, + legalPersonAccountType: form.legalPersonAccountType, + legalPersonLocalHousehold: form.legalPersonLocalHousehold, + legalPersonHasSocialInsurance: form.legalPersonHasSocialInsurance, + legalPersonBusinessCompanyName: form.legalPersonBusinessCompanyName, + legalPersonBusinessNature: form.legalPersonBusinessNature, + legalPersonBusinessAddress: form.legalPersonBusinessAddress, + legalPersonBusinessZipCode: form.legalPersonBusinessZipCode, + legalPersonResidenceZipCode: form.legalPersonResidenceZipCode, + legalPersonCreditBusiness: form.legalPersonCreditBusiness, + legalPersonMailZipCode: form.legalPersonMailZipCode, + legalPersonBasicAccountOpened: form.legalPersonBasicAccountOpened, + legalPersonBankType: form.legalPersonBankType, + legalPersonBasicAccountName: form.legalPersonBasicAccountName, + legalPersonBasicAccountNo: form.legalPersonBasicAccountNo, + legalPersonStockCode: form.legalPersonStockCode, + legalPersonRemark: form.legalPersonRemark, + businessScope: form.businessScope, - phoneNo: form.phoneNo, + enterpriseOrgType: form.enterpriseOrgType, + businessLicenseExpiry: toFullDate(form.businessLicenseExpiry), + annualEmployeeCount: form.annualEmployeeCount, + annualRevenue: form.annualRevenue, + annualTotalAssets: form.annualTotalAssets, + enterpriseType: form.enterpriseType, + registeredCapital: form.registeredCapital, + registeredCapitalCurrency: form.registeredCapitalCurrency, + isListedCompany: form.isListedCompany, + isPrivateControlled: form.isPrivateControlled, + businessLicenseNo: form.businessLicenseNo, + enterpriseEconomicOwnership: form.enterpriseEconomicOwnership, + businessLicenseRegisteredAddress: form.businessLicenseRegisteredAddress, + enterpriseEconomicZone: form.enterpriseEconomicZone, + enterpriseMailAddress: form.enterpriseMailAddress, + enterpriseCode: form.enterpriseCode, + withdrawalTypeFlag: form.withdrawalTypeFlag, + isLocalStockParticipated: form.isLocalStockParticipated, + paidInCapital: form.paidInCapital, + enterpriseLocationAddress: form.enterpriseLocationAddress, + enterpriseLocationZipCode: form.enterpriseLocationZipCode, + enterpriseBasicAccountLocalBank: form.enterpriseBasicAccountLocalBank, + enterpriseBasicAccountBank: form.enterpriseBasicAccountBank, + enterpriseBasicAccountNo: form.enterpriseBasicAccountNo, + enterprisePrimaryShareholder: form.enterprisePrimaryShareholder, + enterpriseOfficeAddress: form.enterpriseOfficeAddress, + enterpriseOfficeZipCode: form.enterpriseOfficeZipCode, + hasStorefront: form.hasStorefront, + enterprisePerformance: form.enterprisePerformance, + listedStockCode: form.listedStockCode, + enterpriseRemitAccountName: form.enterpriseRemitAccountName, + enterpriseRemitAccountNo: form.enterpriseRemitAccountNo, + enterpriseRemitBankBranch: form.enterpriseRemitBankBranch, + enterpriseLoanPaymentAccountName: form.enterpriseLoanPaymentAccountName, + enterpriseOtherBankCreditRelation: form.enterpriseOtherBankCreditRelation, + financialStatementNo: form.financialStatementNo, + enterpriseMarketingRegion: form.enterpriseMarketingRegion, + hasExternalGuarantee: form.hasExternalGuarantee, + isSmeEnterprise: form.isSmeEnterprise, + isLocalEcological: form.isLocalEcological, + isEquitySystem: form.isEquitySystem, + highTechIndustryFlag: form.highTechIndustryFlag, + companyCodeAndAffiliates: form.companyCodeAndAffiliates, + enterpriseEmail: form.enterpriseEmail, + enterpriseWebsite: form.enterpriseWebsite, + enterpriseBusinessWebsite: form.enterpriseBusinessWebsite, + enterpriseAccountOpeningMethod: form.enterpriseAccountOpeningMethod, + enterpriseMainProductsServices: form.enterpriseMainProductsServices, + enterpriseBusinessAddressScope: form.enterpriseBusinessAddressScope, + enterpriseEquityOtherType: form.enterpriseEquityOtherType, + parentCompanyName: form.parentCompanyName, + parentCompanyAddress: form.parentCompanyAddress, + parentCompanyShareRatio: form.parentCompanyShareRatio, + enterpriseRemark: form.enterpriseRemark, + loanApplicationImageBtoList: buildImageList() } let res @@ -694,9 +1262,32 @@ function goBack() { diff --git a/缺失的后端接口.md b/缺失的后端接口.md index f93a4e3..8ad5263 100644 --- a/缺失的后端接口.md +++ b/缺失的后端接口.md @@ -1886,9 +1886,129 @@ INSERT INTO "SFT"."AUTH__ROLE_PERMISSION" ("ID", "ROLE_ID", "PERMISSION_ID") VAL 补充 `VITE_H5_QRCODE_PATH_LOAN=/mobile/loan-application`,域名复用既有的 `VITE_H5_QRCODE_BASE_URL`(生产域名尚未确定时同样靠代码内 `|| 'https://example.com'` 兜底, 不阻断柜员操作)。**扫码后走向的移动端 H5 确认页(短信验证码+人脸识别,对应 - `POST /api/credit-apply/loan-application/confirm`)不在本项目前端实现范围**(与第19条一致), - 本次改动只负责柜员端生成并展示二维码内容,不做轮询/倒计时(与个人/企业开户的既定处理方式 - 一致,提交成功后也不提供"刷新申请状态"入口,如需查看最新状态需回到列表页重新查询)。 + `POST /api/credit-apply/loan-application/confirm`)不在本项目前端实现范围**(与第19条一致), + 本次改动只负责柜员端生成并展示二维码内容,不做轮询/倒计时(与个人/企业开户的既定处理方式 + 一致,提交成功后也不提供"刷新申请状态"入口,如需查看最新状态需回到列表页重新查询)。 + +## Part 25:授信管理 —— 企业贷款申请表单按最新原型截图改造 —— 2026-08-10 补充 + +产品提供了"授信管理 > 企业贷款申请"页面新原型截图(页面标题"提款申请 > 授信申请单",来自旧版 +"电商服务工具平台",产品已确认这是本次要对齐的目标设计,与第135-145条同批完成的 +`PersonalLoanForm.vue` 改造采用的"无对应字段仅本地展示不提交"策略不同,本次经产品明确要求严格 +按截图字段结构实现,`EnterpriseLoanForm.vue` 整体重写,详见 +`docs/superpowers/specs/2026-08-10-enterprise-loan-application-form-redesign.md`。 + +146. **现状三张卡片 + 三个字段随本次改造移除录入入口(Bto 仍支持,只是页面不再提供)**: + "融资条件"(`goodsCategory`/`paymentCycle`/`singleOrderLimit`/`financingRatio`/ + `estimatedReturnRate`/`loanTerm`/`orderFinancingTerm`)、"特殊融资用途标识" + (`repayFundSource`/`acceptAgricultureType`/`industryAdjustType`/`industryUpgradeFlag`/ + `newIndustryType`)、"法人配偶信息"(`spouseName`/`spousePhone`/`spouseCertType`/ + `spouseGender`/`spouseCertId`)三张卡片,以及"是否市场经营者"(`isMarketOperator`)、 + "是否政府融资平台"(`isGovernmentPlatform`)、"贷款钱包账号"(`accountNo`)三个字段,新原型 + 截图均未出现,本次改造后前端不再提供录入界面,历史数据不受影响,`CreateEnterpriseLoanDraftBto`/ + `UpdateEnterpriseLoanApplicationBto` 均未删除对应属性。若后续仍需采集,需产品重新确认承载位置。 +147. **底部"手机号+获取验证码"验证交互不实现**:项目内没有任何"企业贷款申请场景发码"接口(唯一的 + `POST /api/auth/send-sms-code` 是登录场景专用,`verifyCode` 只存在于 + `LoanApplicationDetailVo` 与移动端专属的 `/api/credit-apply/loan-application/confirm`, + 不在本项目前端实现范围),本次只保留"手机号"字段(挪到"授信基础信息"卡片,绑定既有 `phoneNo`), + 不做验证码发送/校验。 +148. **"授信基础信息"新增"授信种类"字段(`creditCategory`)无对应后端属性**:`a-select` 下拉, + 当前仅有截图可见的一项"其他综合额度"(`OTHER_COMPREHENSIVE_LIMIT`,见 + `src/constants/creditEnums.js` 的 `CREDIT_CATEGORY_OPTIONS`),完整选项列表待业务确认, + 前端不臆造更多选项;字段随表单一起提交,`CreateEnterpriseLoanDraftBto`/ + `UpdateEnterpriseLoanApplicationBto` 均无 `creditCategory` 属性,需后端评估是否正式建字段。 +149. **"授信品种"/"经营场所"/"经济类型"三个截图字段复用现有语义相近字段承接,非新建字段**: + "授信品种"复用 `loanPurpose`(原"贷款投向");"经营场所"复用 `businessScope`(原"经营范围", + 必填);"经济类型"复用 `enterpriseOrgType`(原"企业经济类型")。三者均为已有字段改变展示 + label,不是新增属性,如实际业务语义与原字段不完全一致,需后端/产品进一步确认。 +150. **"申请金额"展示单位由"(元)"改为"(万元)"**:`loanAmount` 字段本身是无单位约束的字符串, + 本次仅按截图改了输入框旁的单位文案,不做数值换算,是否需要前端换算或后端补充专门的单位字段, + 待业务确认。 +151. **法定代表人信息/企业信息"更多信息"展开区新增约55个字段,`CreateEnterpriseLoanDraftBto`/ + `UpdateEnterpriseLoanApplicationBto` 均无对应属性,经产品确认仍随表单一起提交(使用前端推测 + 的驼峰字段名)**:若后端序列化框架未配置"忽略未知属性"容错,可能导致整单创建/修改请求失败, + 需要后端评估。完整字段清单(提交时的 key 名,对应截图原文标签): + - 法定代表人信息主区:`legalPersonMailAddress`(通讯地址)、`isLocalPropertyOwner`(是否在本地 + 有置业,Y/N)、`isChildLocalSchool`(子女是否在本地就学,Y/N)。 + - 法定代表人信息"更多信息"区:`legalPersonAccountType`(个人开户户名,枚举见 + `LEGAL_PERSON_ACCOUNT_TYPE_OPTIONS`)、`legalPersonLocalHousehold`(是否本地户籍,Y/N)、 + `legalPersonHasSocialInsurance`(个人是否有社保医保,Y/N)、`legalPersonBusinessCompanyName` + (经营公司名称)、`legalPersonBusinessNature`(经营公司营业性质用途)、 + `legalPersonBusinessAddress`(经营地址)、`legalPersonBusinessZipCode`(经营邮编)、 + `legalPersonResidenceZipCode`(住处邮编,截图该行文字密集,读取准确性待复核)、 + `legalPersonCreditBusiness`(资信业务)、`legalPersonMailZipCode`(通讯地址邮编)、 + `legalPersonBasicAccountOpened`(个人基本户开户,Y/N)、`legalPersonBankType`(开户行别)、 + `legalPersonBasicAccountName`(个人基本户户名)、`legalPersonBasicAccountNo`(个人基本户 + 账号,不复用已移除的 `accountNo`)、`legalPersonStockCode`(股票代码)、`legalPersonRemark` + (备注)。截图"紧急联系人"一项复用已有的 `emergencyContactName`/`emergencyContactPhone` + (从独立卡片迁移至此区域,非新增字段)。 + - 企业信息主区:`businessLicenseNo`(营业执照编号)、`enterpriseEconomicOwnership`(隶属经济 + 产权)、`businessLicenseRegisteredAddress`(证件登记经营地址)、`enterpriseEconomicZone` + (企业经济区域)、`enterpriseMailAddress`(通讯地址)、`enterpriseCode`(企业代码)、 + `withdrawalTypeFlag`(提款种类标识)、`isLocalStockParticipated`(是否本地股票参与经营, + Y/N)、`paidInCapital`(实收资本)、`enterpriseLocationAddress`(企业所在地地址)、 + `enterpriseLocationZipCode`(企业所在地邮编)。 + - 企业信息"更多信息"区:`enterpriseBasicAccountLocalBank`(企业基本户是否本行开立,Y/N)、 + `enterpriseBasicAccountBank`(企业基本户开户行)、`enterpriseBasicAccountNo`(企业基本户 + 账号)、`enterprisePrimaryShareholder`(首要股东及占公司比例)、`enterpriseOfficeAddress` + (企业办公地址)、`enterpriseOfficeZipCode`(办公地址邮编)、`hasStorefront`(是否有商铺, + Y/N)、`enterprisePerformance`(业绩)、`listedStockCode`(上市代码)、 + `enterpriseRemitAccountName`(企业银行汇账户名)、`enterpriseRemitAccountNo`(企业银行汇账 + 账号)、`enterpriseRemitBankBranch`(企业银行汇账开户行网点)、 + `enterpriseLoanPaymentAccountName`(企业货款账户名称)、`enterpriseOtherBankCreditRelation` + (企业与他行建立信贷关系情况)、`financialStatementNo`(财务报表编号)、 + `enterpriseMarketingRegion`(企业营销所在区域)、`hasExternalGuarantee`(有无对外担保情况, + Y/N)、`isSmeEnterprise`(是否属于中小企业,Y/N)、`isLocalEcological`(是否属地生态化,Y/N)、 + `isEquitySystem`(是否属于股权制,Y/N)、`highTechIndustryFlag`(高新技术产业标识,枚举见 + `HIGH_TECH_INDUSTRY_FLAG_OPTIONS`)、`companyCodeAndAffiliates`(公司代号及关系企业)、 + `enterpriseEmail`(公司e-Mail)、`enterpriseWebsite`(公司网址)、`enterpriseBusinessWebsite` + (企业经营网址)、`enterpriseAccountOpeningMethod`(企业开户方式及运营开户)、 + `enterpriseMainProductsServices`(企业主要产品或服务)、`enterpriseBusinessAddressScope` + (企业经营地址范围,截图旁带"+"按钮暗示多值,本次简化为单个多行文本框,非数组结构)、 + `enterpriseEquityOtherType`(企业股权其他情况,枚举见 + `ENTERPRISE_EQUITY_OTHER_TYPE_OPTIONS`)、`parentCompanyName`(企业上级公司名称,选填)、 + `parentCompanyAddress`(企业上级公司所在地址,选填)、`parentCompanyShareRatio`(企业上级 + 公司持股比例,选填)、`enterpriseRemark`(备注)。 + - 截图"企业法定代表电话"/"企业法定代表人号码"两项因与"法定代表人信息"卡片的手机号码 + (`legalPersonMobile`)/身份证号码(`legalPersonCertId`)语义重复,未重复建控件。 +152. **法人证件到期日/营业执照证件失效日期新增"长期"哨兵日期勾选**:沿用项目已有的 + `src/constants/customerEnums.js` 的 `CERTIFICATE_LONG_TERM_DATE`(`'9999-12-31 00:00:00'`) + 权变方案(与 `EnterpriseForm.vue`/`PersonalForm.vue` 一致),非新缺口,仅记录本次两个新增 + 位置的沿用情况。 +153. **影像信息按更清晰的补充截图修正为5行,新增"正面免冠照/授权(个人征信授权+企业征信授权书)/ + 企业章程/决议书"共6个上传项,`fileType` 均无对应枚举**:初版按较模糊的截图实现的"他行个人/ + 企业征信报告"两项系误读,已改为按补充截图重做,最终结构为:①营业执照(不变);②法人代表身份证 + (人像面+国徽面不变,新增"正面免冠照"人脸识别项,复用 `FacePhotoUpload.vue`,`fileType` 沿用 + 项目已有的 `LOAN_APPLICATION_IMAGE_FILE_TYPE.FACE_PHOTO`(`"04"`,与 `PersonalLoanForm.vue` + "人脸识别信息"项同编码,非新缺口);③授权(新增"个人征信授权"/"企业征信授权书"两个上传项, + 复用 `LicenseUpload.vue`,`fileType` 分别用可读中文占位值 `"个人征信授权"`/`"企业征信授权书"`, + 各自搭配一个禁用占位的"下载模板"按钮);④企业章程(新增,`LicenseUpload.vue` + 禁用占位 + "下载模板"按钮,`fileType` 占位值 `"企业章程"`);⑤决议书(新增,同企业章程,`fileType` 占位值 + `"决议书"`)。以上占位 `fileType` 均需后端确认含义并评估是否需要文档化为正式枚举值。 +154. **"营业执照"影像项本身不带"下载模板"按钮**:与第153条修正一致,"营业执照"是单纯的证件上传, + "下载模板"按钮只出现在"授权/企业章程/决议书"三类需要柜员打印签署后再扫描回传的文件上,均 + 统一禁用占位(`title="模板文件待业务方提供"`),与 `PersonalLoanForm.vue` 现状处理方式一致, + 无对应后端接口。 +155. **选择授信申请人(企业客户)后自动回填法定代表人信息与企业信息基础字段**:`EnterpriseCustomerDetailVo` + 本身不含法定代表人姓名/证件号/手机号等个人信息,只能通过 `enterpriseRelatedPersonList`(股东与 + 高管信息列表)里 `legalRepresentativeStatus === 'LEGAL_REPRESENTATIVE'` 的条目取到 + `individualId`,再调用 `POST /api/customer-info/personal/detail` 取完整个人信息补全,这是 + 项目内首次实现"自动从企业详情提取法定代表人"(`EnterpriseOpenForm.vue` 现状是柜员手动选法代, + 无此先例)。已知缺口:①`EnterpriseCustomerDetailVo` 没有"企业类型"、"是否私人控股"对应字段, + 回填时保持原值,需柜员人工填写;②若企业客户资料的股东与高管列表里没有任何一条被标记为法定 + 代表人,不阻断选择流程,仅提示"未在企业客户资料中找到法定代表人信息,请手动填写";③个人客户 + `educationLevel`/`maritalStatus`/`gender`/`ethnicity` 取值是否与本表单复用的枚举取值域完全 + 一致,以实测为准,与法人信息各字段原有的"无强校验"缺口(见文件头部注释)同类,不重复登记。 +156. **新增"钱包账号"必填字段,选择授信申请人后需另外选择该企业客户已开立的钱包账户**:复用 + `WalletAccountPicker.vue`(`customer-type="ENTERPRISE"`),提交 `accountNo` + (`CreateEnterpriseLoanDraftBto` 已有字段,非新增)。重新选择授信申请人时会清空已选钱包账号, + 避免与新客户不匹配;提交前校验钱包账号必选。`WalletAccountPicker.vue` 查询企业钱包时未按 + 已选客户名称/ID 强制过滤(该组件本身支持按"客户名称"关键字搜索,但不会自动带入已选企业名称 + 做默认查询条件),柜员需要在弹窗内自行输入企业名称查询,属体验层面的已知限制,非接口缺口。 +157. **法人代表身份证(人像面/国徽面)提交前校验为必填**:对齐截图"*法定代表人身份证"的必填星号 + 标记,`handleSubmit` 增加前置校验,未上传时阻断提交并提示,`loanApplicationImageBtoList` + 本身对单个 `fileType` 是否必填无 swagger 声明,该校验规则纯前端本地实现。 +