docs: 阶段3客户管理设计文档(个人客户+企业客户)
parent
9dbf05a9c8
commit
3a8ba073bf
|
|
@ -0,0 +1,171 @@
|
||||||
|
# 阶段3设计:客户管理(个人客户 + 企业客户)
|
||||||
|
|
||||||
|
- 状态: 用户已授权自主推进(批量决策已确认,不再逐项等待审批),自查通过后直接进入 writing-plans
|
||||||
|
- 上级文档: `2026-07-08-fryl-frontend-overall-design.md`(总体架构,阶段3=客户管理)
|
||||||
|
- 支撑材料: `2026-07-08-module-requirements-mapping.md` 第5节(2.5.1/2.5.2 字段与业务规则)、`凡荣e链前端业务需求说明书.md` 2.5.1/2.5.2 原文(约1094-1234行)、`swagger_project_scfs_2026-07-09_14-15-23.json`(`/api/customer-info/personal/*`、`/api/enterprise-customer/*`)
|
||||||
|
- 本文档产出物: ①个人客户管理页面(列表+新增/编辑+详情);②企业客户管理页面(列表+新增/编辑+详情,含股东高管子表单);③通用"文件上传+OCR"组件;④个人客户选择弹窗组件(供企业客户表单及后续阶段复用);⑤`渠道选择器`组件;⑥`validators.js` 补充证件号正则;⑦`缺失的后端接口.md` 新增"客户管理"一节
|
||||||
|
- 已确认的跨阶段批量决策(与本阶段相关部分):企业客户表单增加"法定代表人"选择字段;OCR/上传相关接口所需的"渠道编号(channelNo)"通过表单内"所属渠道"选择框获取(数据源为阶段2核心企业列表)
|
||||||
|
|
||||||
|
## 1. 范围
|
||||||
|
|
||||||
|
**包含**:
|
||||||
|
- 个人客户:查询列表、新增、编辑、查看详情、(复用已有批量删除模式的)删除
|
||||||
|
- 企业客户:查询列表、新增、编辑、查看详情、删除,含股东/高管/受益所有人子表单(选择已登记个人客户后字段快照回填)
|
||||||
|
- 身份证 OCR 识别(个人客户)、通用文件上传(个人客户身份证正反面、企业客户营业执照图片)
|
||||||
|
- 渠道选择器组件(复用阶段2核心企业数据源)
|
||||||
|
- 个人客户选择弹窗组件(企业客户"法定代表人"及"股东高管信息"选人使用)
|
||||||
|
|
||||||
|
**不包含(YAGNI/超出后端能力,已决策不问用户,列入下文"能力缺口"或直接排除)**:
|
||||||
|
- 原型页面按钮"关联用户"——customer_info 相关接口无对应能力,本阶段不做
|
||||||
|
- 原型页面按钮"钱包开立"/"贷款申请"——属于阶段4/5的入口,由阶段4/5各自的"选择客户"流程承接,客户列表本身不重复暴露跳转入口,避免在阶段4/5落地前出现指向空路由的按钮
|
||||||
|
- 查询条件"资料完整""客户状态"——个人客户 `FindIndividualCustomerListQto`、企业客户 `page` 请求体均无对应字段,客户实体本身也无"状态"属性,不做
|
||||||
|
- 列表列"性别""创建人""创建时间"(个人客户)——`IndividualCustomerListVo` 未返回这些字段,不展示(详情页仍展示"创建时间",因为 `IndividualCustomerDetailVo` 有该字段)
|
||||||
|
- 企业客户股东高管信息表单不展示"职务/持股比例/国籍/是否股东/联系电话/邮箱"——需求文档 2.5.2 的股东高管信息字段表只要求"证件类型/证件号码/证件生效日/证件到期日"4项(均为选中个人客户后只读回显),这些扩展字段属于阶段4企业开户"法人代表/实际控制人/受益人信息"的需求(2.6.x),两者共用同一后端 `EnterpriseRelatedPersonBto` 结构,但字段取用范围按各自需求文档取舍,不在客户管理阶段引入
|
||||||
|
|
||||||
|
## 2. 接口契约速查
|
||||||
|
|
||||||
|
### 2.1 个人客户(`/api/customer-info/personal/*`)
|
||||||
|
|
||||||
|
| 接口 | Body | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POST .../list` | `{organizationIdEq, customerCodeEq, customerNameLike, certificateNumberEq, page, pageSize}` | 响应 `{total,page,pageSize,records: IndividualCustomerListVo[]}`;`records` 字段:`id,organizationId,customerName,certificateType,certificateNumber,mobilePhone,ocrStatus,customerCode` |
|
||||||
|
| `POST .../detail` | `{id}` 或 `{customerCode}` | 响应 `IndividualCustomerDetailVo`(比列表多:`birthDate,gender,ethnicity,certificateEffectiveDate,certificateExpiryDate,issuingAuthority,certificateAddress,idCardFrontFileNo,idCardBackFileNo,occupation,creatorId,createTime,updateTime`) |
|
||||||
|
| `POST .../create` | `CreateIndividualCustomerBto`(见下) | 响应 `data` = 新建 id(int64) |
|
||||||
|
| `POST .../update` | `UpdateIndividualCustomerBto`(同 Create 字段 + `id`) | 响应 `data` = boolean |
|
||||||
|
| `POST .../ocr-idcard` | `{fileBase64, channelNo, fileType('01'人像面\|'02'国徽面)}`(均必填) | 响应 `OcrIdCardResultVo{name,sex,nation,birth,idcard,address,authority,validDate}`,用于新增页"上传后自动识别回显" |
|
||||||
|
| `POST .../upload-file` | `{fileBase64, channelNo}`(均必填) | 响应 `FileUploadResultVo{fileNo}`,通用上传,个人客户身份证正/反面、企业客户营业执照图片均调用此接口 |
|
||||||
|
|
||||||
|
`CreateIndividualCustomerBto` 字段:`organizationId,customerName,certificateType(固定ID_CARD),certificateNumber,birthDate,gender(MALE/FEMALE/UNKNOWN),ethnicity,certificateEffectiveDate,certificateExpiryDate,issuingAuthority,mobilePhone,certificateAddress,idCardFrontFileNo,idCardBackFileNo,occupation,ocrStatus(PENDING/PROCESSING/SUCCESS/FAILED),customerCode`。`customerCode`/`ocrStatus` 由前端在提交时按规则自动生成/设置(见 4.3),不作为用户可编辑输入项。
|
||||||
|
|
||||||
|
删除接口:未在 swagger 中检索到 `/api/customer-info/personal/delete`,**个人客户暂无删除接口**(见第7节能力缺口),本阶段列表页删除按钮先隐藏(不展示批量删除工具栏中的删除按钮),仅保留查询/新增/编辑/查看。
|
||||||
|
|
||||||
|
### 2.2 企业客户(`/api/enterprise-customer/*`)
|
||||||
|
|
||||||
|
| 接口 | Body | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POST .../page` | `{page,pageSize,enterpriseName,businessLicense,mobilePhone}` | 响应 `records: EnterpriseCustomerVo[]`:`id,organizationId,enterpriseName,businessLicense,certificateType,certificateNumber,certificateEffectiveDate,certificateExpiryDate,mobilePhone,certificateAddress,businessScope,enterpriseCode,ocrStatus,createTime` |
|
||||||
|
| `POST .../detail` | `{id}` | 响应 `EnterpriseCustomerDetailVo`(在 Vo 基础上另有 `legalRepresentativeId`,无 `businessLicenseFileNo`——文件编号只用于写入,查看详情不需要回显图片本身,只需状态) |
|
||||||
|
| `POST .../create` | `{createEnterpriseCustomerBto: CreateEnterpriseCustomerBto, enableOcr: boolean}` | 响应 `data` 无固定类型说明,前端按"提交成功即视为创建成功"处理,不依赖返回值内容 |
|
||||||
|
| `POST .../update` | `UpdateEnterpriseCustomerBto`(同 Create 字段 + `id`) | 响应 `data` 无固定类型说明,同上 |
|
||||||
|
|
||||||
|
`CreateEnterpriseCustomerBto` 字段:`organizationId,enterpriseName,businessLicense,certificateType(固定BUSINESS_LICENSE),certificateNumber,certificateEffectiveDate,certificateExpiryDate,mobilePhone,certificateAddress,businessScope,businessLicenseFileNo,enterpriseCode,legalRepresentativeId,ocrStatus,enterpriseRelatedPersonBtoList: EnterpriseRelatedPersonBto[]`。
|
||||||
|
|
||||||
|
`EnterpriseRelatedPersonBto`(本阶段仅使用其中 5 个字段,其余字段见第1节"不包含"说明):`enterpriseId,individualId,relatedType(SHAREHOLDER股东/EXECUTIVE高管/BENEFICIARY_OWNER受益所有人),certificateType,certificateNumber,certificateEffectiveDate,certificateExpiryDate`。
|
||||||
|
|
||||||
|
删除接口:同样未检索到 `/api/enterprise-customer/delete`,**企业客户暂无删除接口**,处理方式同 2.1。
|
||||||
|
|
||||||
|
### 2.3 枚举取值(均已从 swagger 摘取,非编造)
|
||||||
|
|
||||||
|
- `CertificateTypeEnum`: `ID_CARD`身份证 / `PASSPORT`护照 / `OTHER`其他 / `BUSINESS_LICENSE`营业执照
|
||||||
|
- `GenderEnum`: `MALE`男 / `FEMALE`女 / `UNKNOWN`未知
|
||||||
|
- `OcrStatusEnum`: `PENDING`待识别 / `PROCESSING`识别中 / `SUCCESS`识别成功 / `FAILED`识别失败
|
||||||
|
- `RelatedPersonTypeEnum`: `SHAREHOLDER`股东 / `EXECUTIVE`高管 / `BENEFICIARY_OWNER`受益所有人
|
||||||
|
|
||||||
|
## 3. 页面架构
|
||||||
|
|
||||||
|
沿用阶段2确立的目录风格,新增:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/views/customer/
|
||||||
|
personal/
|
||||||
|
PersonalList.vue # 列表页(ProTable模式)
|
||||||
|
PersonalForm.vue # 新增/编辑/查看(独立路由页,三态由query区分)
|
||||||
|
enterprise/
|
||||||
|
EnterpriseList.vue
|
||||||
|
EnterpriseForm.vue
|
||||||
|
src/components/
|
||||||
|
ChannelSelect.vue # 渠道(核心企业)下拉选择器,跨模块复用
|
||||||
|
PersonalCustomerPicker.vue # 个人客户选择弹窗,跨模块复用
|
||||||
|
IdCardUpload.vue # 身份证正/反面上传+OCR回显,个人客户专用
|
||||||
|
LicenseUpload.vue # 营业执照上传(无OCR同步接口,仅上传+预览),企业客户专用
|
||||||
|
src/api/customer.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**新增/编辑改为独立路由页而非 `<a-modal>`**(与阶段1/2的弹窗模式不同,是本阶段的必要调整):个人客户表单含身份证双面上传+OCR异步识别+9个必填字段;企业客户表单含营业执照上传+9个基础字段+法定代表人选择+股东高管子表单(可增删多行)。字段量与交互复杂度已超出弹窗合理承载范围,弹窗会导致内容被压缩、滚动区域嵌套滚动等体验问题。列表页仍是 `ProTable` 单文件模式并复用阶段2的批量删除交互组件(见第2节,本阶段实际不启用删除按钮,但保留 `ProTable` 默认能力不做特殊阉割,便于后端补齐删除接口后无需改动即可启用)。
|
||||||
|
|
||||||
|
路由:`componentRegistry.js` 新增映射:
|
||||||
|
```
|
||||||
|
'/customer/personal': 'customer/personal/PersonalList'
|
||||||
|
'/customer/personal/create': 'customer/personal/PersonalForm'
|
||||||
|
'/customer/personal/edit': 'customer/personal/PersonalForm'
|
||||||
|
'/customer/personal/detail': 'customer/personal/PersonalForm'
|
||||||
|
'/customer/enterprise': 'customer/enterprise/EnterpriseList'
|
||||||
|
'/customer/enterprise/create': 'customer/enterprise/EnterpriseForm'
|
||||||
|
'/customer/enterprise/edit': 'customer/enterprise/EnterpriseForm'
|
||||||
|
'/customer/enterprise/detail': 'customer/enterprise/EnterpriseForm'
|
||||||
|
```
|
||||||
|
`PersonalForm.vue`/`EnterpriseForm.vue` 通过路由 query `?mode=create|edit|detail&id=xxx` 区分三态(`detail` 模式下所有表单项 `disabled`,不展示上传/OCR操作按钮)。菜单节点(客户管理 > 个人客户/企业客户)需要在后端权限管理里配置,子路由(create/edit/detail)不建菜单节点,只在 `componentRegistry.js` 注册以支持动态路由 resolve,列表页内部用 `router.push` 跳转(此模式与阶段1/2"菜单项=路由=componentRegistry一一对应"略有不同,是本阶段为支撑独立表单页新增的必要例外,需要在 `dynamic.js` 确认:非菜单节点的路由是否也能被 `buildRouteRecords`/`resolveComponent` 处理——**技术方案**:创建/编辑/详情三个路由不通过后端菜单树生成,而是在 `router/index.js` 里作为 `MainLayout` 的静态 children 直接注册(懒加载 `resolveComponent` 复用同一套 `import.meta.glob`),不依赖 `componentRegistry.js` 的菜单映射表,避免破坏"菜单驱动路由"的既有约定;仅列表页两个路由继续走菜单驱动。
|
||||||
|
|
||||||
|
## 4. 关键交互设计
|
||||||
|
|
||||||
|
### 4.1 渠道选择器 `ChannelSelect.vue`
|
||||||
|
|
||||||
|
- Props: `v-model:value`,内部 `onMounted` 调 `fetchCoreEnterprisePageApi({page:1, pageSize:200})` 拉取全部核心企业,下拉选项 `label=enterpriseName, value=enterpriseCode`(渠道编号取核心企业的 `enterpriseCode`,与阶段2批量决策一致)
|
||||||
|
- 用于个人客户表单的"上传身份证/OCR识别"操作前置校验:未选择渠道时禁用上传按钮,并提示"请先选择所属渠道"
|
||||||
|
- 企业客户表单同理用于营业执照上传前置校验
|
||||||
|
- 该选择结果**不随表单提交持久化**到客户实体(customer-info/personal 与 enterprise-customer 的 Create/Update Bto 均无 channelNo 字段),仅作为调用 OCR/上传接口的临时参数,页面卸载/切换后不保留
|
||||||
|
|
||||||
|
### 4.2 `IdCardUpload.vue`(身份证上传+OCR)
|
||||||
|
|
||||||
|
- Props: `side`('front'|'back')、`channelNo`、`v-model:fileNo`、`v-model:ocrResult`(仅 side='front' 时输出,身份证反面'02国徽面'不含个人信息字段,只需上传不需识别結果回填,但仍调用同一 `ocr-idcard` 接口传 `fileType='02'` 以获得 `authority`/`validDate` 等国徽面才有的信息用于回显"签发机关"/"证件有效期")
|
||||||
|
- 内部用 `<a-upload :before-upload>` 拦截,`FileReader.readAsDataURL` 转 base64,依次调用 `ocrIdCardApi({fileBase64, channelNo, fileType})` 与 `uploadFileApi({fileBase64, channelNo})`(两次调用,分别拿到识别结果与文件编号;若只需其一亦可,但需求要求"文件必须持久化到凡荣e链"且"字段需OCR回显",两者都需要)
|
||||||
|
- 识别成功(`code===200` 且返回字段非空):`emit('update:ocrResult', data)`,父组件据此回填表单字段,并将本地 `ocrStatus` 置为 `SUCCESS`
|
||||||
|
- 识别失败(接口报错或返回字段为空):`message.warning('识别失败,请手动填写')`,`ocrStatus` 置为 `FAILED`,表单字段保持可编辑,不阻断继续手动填写和提交
|
||||||
|
- 上传中禁用重复点击(`uploading` 状态 + antd `loading` 效果),符合全局交互规则"异步操作展示 loading 防止重复点击"
|
||||||
|
|
||||||
|
### 4.3 个人客户 `customerCode`/`ocrStatus` 生成规则
|
||||||
|
|
||||||
|
- `customerCode`(客户编号):后端 schema 未标注自动生成机制,且创建响应只返回 `id` 不返回编号。为避免前端编造编号规则造成与后端实际生成逻辑冲突,**新增表单不提供"客户编号"输入框**,提交 `create` 时不传 `customerCode` 字段(交由后端生成,若后端要求必填但未生成,列入第7节能力缺口待核实,前端按"选填不传"实现,后端报错则在实施阶段据实际报错信息调整,不阻塞设计)
|
||||||
|
- `ocrStatus`:由 `IdCardUpload` 组件识别结果驱动(见4.2),不提供用户手动选择该字段的 UI
|
||||||
|
|
||||||
|
### 4.4 企业客户表单关键交互
|
||||||
|
|
||||||
|
- 基础信息区:企业名称/证件号(营业执照号)/证件生效日/证件到期日/手机号码/证件地址/经营范围均为可编辑输入框(**非只读**,因为无同步OCR接口,详见第7节能力缺口,采用"手动填写为主"方案);证件类型固定展示"营业执照"文本,不可编辑
|
||||||
|
- 营业执照上传:`LicenseUpload.vue` 仅调用 `uploadFileApi` 拿到 `businessLicenseFileNo`,不调用任何 OCR 接口(不存在);上传成功后仅做图片预览,不做字段回显
|
||||||
|
- 提交创建时固定传 `enableOcr: true`(告知后端可尝试异步识别,不影响前端已填字段,即便后端异步识别有结果也只影响该客户记录的 `ocrStatus` 字段,不会覆盖前端已提交的其它字段——这是基于当前 swagger 无法验证的合理假设,已登记入第7节,后续若后端行为不同以实测调整)
|
||||||
|
- "法定代表人"选择:点击"选择"按钮打开 `PersonalCustomerPicker`,选中后 `legalRepresentativeId` 赋值,同时展示所选人的 `customerName`(选择器组件内部把选中行的展示字段一并 emit 出来,便于表单展示,不需要额外调用 detail 接口)
|
||||||
|
- "股东高管信息"列表:表格形式,可"添加一行"→ 每行点击"选择个人客户"打开 `PersonalCustomerPicker` → 选中后该行 `individualId` 及只读展示的 `certificateType/certificateNumber/certificateEffectiveDate/certificateExpiryDate`(来自选中行的个人客户列表数据,若列表数据缺失证件生效日字段则追加一次 `detail` 查询补全,因为 `IndividualCustomerListVo` 不含 `certificateEffectiveDate`——需要在实施时确认并处理该字段缺口)→ 用户再选择该行"关联人类型"(股东/高管/受益所有人,下拉,必选)→ 可"删除该行";提交时逐行组装 `enterpriseRelatedPersonBtoList`,`enterpriseId` 在新增场景下留空(后端按当前企业客户上下文关联,若后端要求必填由后端报错驱动调整),编辑场景下回填当前企业客户 `id`
|
||||||
|
|
||||||
|
### 4.5 `PersonalCustomerPicker.vue`(个人客户选择弹窗)
|
||||||
|
|
||||||
|
- `<a-modal>` 内嵌一个只读用途的迷你 `ProTable`(复用现有组件,`rowSelection` 设为单选模式或直接用行点击选中,不需要批量删除工具栏,`#actions` slot 留空)
|
||||||
|
- 搜索条件:所属机构、客户编号、客户名称(与需求文档"索引要素:所属机构、客户编号、客户名称"一致)
|
||||||
|
- 列表来源:`fetchPersonalCustomerListApi`,选中一行后 `emit('select', record)` 并关闭弹窗
|
||||||
|
|
||||||
|
## 5. 表单校验补充(`src/utils/validators.js`)
|
||||||
|
|
||||||
|
新增:
|
||||||
|
- `ID_CARD_REGEX = /^\d{17}[\dXx]$/`(18位身份证号,末位允许X,不做校验码算术级校验,与手机号正则的"够用不过度设计"风格一致)+ `idCardValidatorRule()`
|
||||||
|
- `BUSINESS_LICENSE_REGEX = /^[0-9A-Z]{15,20}$/`(统一社会信用代码/营业执照号,15-20位数字大写字母组合,覆盖新旧两种编码位数)+ `businessLicenseValidatorRule()`
|
||||||
|
|
||||||
|
两者均遵循现有 `xxxValidatorRule()` 返回 `{validator: (_rule, value) => Promise}` 的写法。
|
||||||
|
|
||||||
|
## 6. 业务规则映射
|
||||||
|
|
||||||
|
| 需求规则 | 落地方式 |
|
||||||
|
| --- | --- |
|
||||||
|
| 同一证件号在同一机构下不可重复登记 | 前端不做预校验查重请求(无对应查重接口),提交后依赖后端 `create` 接口返回的 `code!==200` + `message`(如"该证件号已存在")直接展示,遵循全局约定"不做具体错误码分支,统一展示 message" |
|
||||||
|
| 手机号11位数字校验 | 复用现有 `phoneValidatorRule()` |
|
||||||
|
| OCR识别失败允许手动修正并提示 | 见4.2 `IdCardUpload` 失败分支 |
|
||||||
|
| 证件到期日早于当前日期提示"证件已过期" | 表单 `certificateExpiryDate` 字段值变化时前端本地比较 `dayjs(value).isBefore(dayjs())`,提示但不阻断提交(需求原文仅要求"提示",未要求强制阻断) |
|
||||||
|
| 仅能查看/操作所属机构及下级机构数据 | 依赖后端按当前用户 `organizationId` 做数据权限过滤(阶段1鉴权体系已处理,前端"所属机构"筛选框仍展示机构树供有权限跨机构查看的用户使用,不额外加前端侧过滤) |
|
||||||
|
| 股东/受益所有人须为已登记且证件有效的个人客户 | 前端在 `PersonalCustomerPicker` 列表查询时不做"证件有效"额外过滤(后端列表接口无此过滤参数),该规则视为后端 `create` 接口的服务端校验职责,前端仅展示报错 message |
|
||||||
|
|
||||||
|
## 7. 已识别的后端能力缺口(登记入 `缺失的后端接口.md`)
|
||||||
|
|
||||||
|
1. **个人客户、企业客户均无删除接口**(swagger 中未找到 `/api/customer-info/personal/delete` 或 `/api/enterprise-customer/delete`)——列表页暂不提供删除入口,待后端补充后再启用批量删除工具栏
|
||||||
|
2. **营业执照无同步OCR识别接口**——只有个人身份证 `ocr-idcard` 接口,企业客户创建时的 `enableOcr` 参数用途/时序(同步/异步、是否覆盖已填字段)缺乏文档说明,前端按"手动填写为主+提交时告知后端可异步识别"实现,行为以实测为准
|
||||||
|
3. **列表查询筛选能力收窄**——原型/需求文档描述的"资料完整""客户状态"查询项在真实接口中不存在(客户实体本身也无状态属<E68081>),不实现
|
||||||
|
4. **个人客户列表接口字段收窄**——`IndividualCustomerListVo` 不返回性别/创建人/创建时间,列表列相应精简
|
||||||
|
5. **`customerCode`(个人客户编号)生成机制不明确**——创建接口不返回编号,创建请求体是否需要前端传值也未标注必填,前端按"不传,交由后端生成"实现
|
||||||
|
6. **股东高管子表单证件生效日字段来源不确定**——`IndividualCustomerListVo` 缺少 `certificateEffectiveDate`,选择个人客户后可能需要额外一次 `detail` 查询才能补全该字段用于企业客户关联人快照,增加一次网络请求,建议后端后续在个人客户列表接口中补充该字段
|
||||||
|
7. **无"关联用户"相关能力**——原型按钮无对应后端接口,本阶段不实现
|
||||||
|
|
||||||
|
## 8. 自查(占位符/矛盾/歧义/范围)
|
||||||
|
|
||||||
|
- 已检查全文无 "TBD"/"待定" 类占位符;第4.4/7节中标注的"假设,以实测为准"属于基于现有 swagger 文档信息不足的合理工程假设,已明确标注,不是遗留占位符
|
||||||
|
- 范围与总体设计文档阶段3定义(客户管理=个人客户+企业客户)一致,未扩展至钱包/授信模块内容
|
||||||
|
- 第3节路由方案(非菜单驱动的表单子路由)与阶段1"菜单驱动路由"约定存在差异,已在文中说明差异原因与技术方案,不是矛盾遗漏
|
||||||
|
- 与批量决策结果核对:企业客户表单已包含"法定代表人"选择字段(4.4节);渠道编号来源已采用表单内"所属渠道"选择框(4.1节),与批量决策结论一致,无冲突
|
||||||
|
|
||||||
|
设计自查通过,直接进入 writing-plans 生成实施计划。
|
||||||
Loading…
Reference in New Issue