# 阶段1+阶段2 Mock 接口替换为真实后端接口 —— 设计文档 - 状态: 已与用户确认(设计阶段问答见本文档"5. 关键决策记录") - 上级文档: `2026-07-08-fryl-frontend-overall-design.md`(总体架构与阶段划分)、`2026-07-09-list-batch-delete-and-phase2-base-config-design.md`(批量删除交互、基础配置模块原设计) - 依据材料: `swagger_project_scfs_2026-07-09_14-15-23.json`(后端首版真实接口文档,项目根目录) - 本文档产出物: ①请求层与鉴权体系重写方案 ②系统管理(用户/角色/机构 + 新增权限管理)接口切换方案 ③基础配置(核心企业/客户经理)接口切换与字段重构方案 ④批量删除前端模拟方案 ⑤动态路由组件映射机制调整方案 ⑥`缺失的后端接口.md` 更新大纲 ## 0. 变更背景 阶段1(工程基础设施+登录页+系统管理)与阶段2(基础配置:核心企业/客户经理)开发时后端接口尚未提供,前端按自行设计的契约用 `mock-server/` 完整模拟。现后端提供了首版真实接口(见 swagger 文件),经比对发现**不是简单的路径替换**,而是存在以下结构性差异,需要系统性调整: 1. **接口风格**:真实后端几乎全部使用 `POST + Body`(极少数查询树/列表用 `GET + Query`),不使用 RESTful 路径参数(如 `/system/user/{id}` 不存在,改为 `POST /api/user/update` 且 `id` 放在 body 内)。 2. **响应包裹字段**:`message` 替代 `msg`;无任何具体业务错误码定义,只有 `{code, message, data}`,`code !== 200` 即失败。 3. **鉴权模型**:登录接口不再直接返回 `menuTree`/`buttonCodes`,需要登录后单独查询当前用户的扁平权限列表并在前端重建树。登录方式拆分为"账号密码登录"与"手机验证码登录"两个独立接口,不支持之前设计的"先校验账号密码→发验证码→账号密码+验证码一起提交"双重校验流程。 4. **角色权限模型**:新增/编辑角色只提交基础信息(名称、所属机构),权限绑定通过独立的 `role-permission/bind`、`unbind` 接口维护;角色模型中**没有"数据权限范围"(dataScope)字段**。 5. **批量操作**:用户/角色/机构/核心企业/客户经理的删除接口均为**单条删除**,无 batch 接口。 6. **基础配置字段**:核心企业、客户经理的字段与之前设计的字段**完全不同**(如渠道编号/应用编号/母户开户行 → 企业编号/营业执照号/联系人/联系电话/状态),客户经理"机构号/机构名称"从自由文本改为关联真实机构树。 7. **新发现能力**:后端提供了完整的权限字典管理接口(`/api/permission/*`),对应网站参考图中已存在但阶段1/2未纳入开发范围的"系统管理 > 权限管理"页面,本次决定一并开发。 ## 1. 范围 **包含**: - `src/api/request.js` 基础设施调整(baseURL 前缀、响应字段、通用错误处理) - `src/api/auth.js`、`src/api/system.js`、`src/api/baseConfig.js` 全部接口函数重写 - 登录页(`Login.vue`)登录方式重构、强制改密调整 - `src/stores/auth.js`、`src/router/index.js`、`src/router/dynamic.js` 鉴权状态与动态路由重建机制调整 - 新增 `src/router/componentRegistry.js`(routePath → 组件手工映射表) - 用户管理(`UserList.vue`)、角色管理(`RoleList.vue` + `PermissionTree.vue`)、机构管理(`OrgList.vue`)接口切换与交互调整 - **新增**:权限管理页面(`src/views/system/permission/PermissionList.vue`)及路由/菜单接入 - 核心企业管理(`ChannelList.vue`)、客户经理管理(`ManagerList.vue`)字段与接口全量重构 - 批量删除前端模拟工具(`src/utils/loopDelete.js`)及各列表页接入 - `缺失的后端接口.md` 全文重写 - `mock-server/` 保留但不再是本次改造对象(仍可作为无后端环境下的本地兜底,详见第7节) **不包含**:客户管理、钱包管理、授信管理、支付管理、订单管理、发票管理、分账等尚未开发的业务模块(swagger 中已有对应接口,但属于后续阶段范围,本次只在缺失文档中不做处理,留待对应阶段开发时再对齐)。 ## 2. 全局基础设施调整 ### 2.1 `src/api/request.js` - `getBaseUrl()` 返回值需与真实后端 `/api` 前缀拼接:约定 `VITE_API_BASE_URL` 仍配置为服务根地址(如 `http://localhost:8080`),所有业务接口路径统一带上 `/api` 前缀(在各 `api/*.js` 里书写完整路径 `/api/xxx`,而不是在 `request.js` 里拼接,避免影响 `/auth/refresh-token` 之外的裸 axios 调用遗漏前缀)。 - 响应拦截器 `const { code, msg }` 改为 `const { code, message: msgText }`(变量名避免与 `ant-design-vue` 的 `message` 冲突),失败提示文案来源改为 `msgText`。 - 移除任何对特定业务错误码(如原设计的 40001/40404 等)的分支判断——现有代码里业务层目前只在拿到 `res.data.code === 200` 时才处理成功分支,失败分支已经统一交给响应拦截器的全局提示,**不需要额外改动特定分支**(核实结论,写入验证清单)。 - `refreshAccessToken()` 内的裸 axios 调用地址从 `` `${getBaseUrl()}/auth/refresh` `` 改为 `` `${getBaseUrl()}/api/auth/refresh-token` ``,且该接口是 `POST`(无 body),响应 `data` 直接是 `LoginVo`(含新 `token` 字段,字段名从 `accessToken` 变为 `token`,需要在 store 里同步改名或做映射)。 ### 2.2 鉴权状态与登录流程(`src/stores/auth.js` / `Login.vue` / `router/`) - **登录方式**:登录页改为 `a-tabs` 两个选项: - Tab1「账号密码登录」→ `POST /api/auth/login-password`,body `{ username, password }` - Tab2「手机验证码登录」→ 输入手机号 + 发送验证码按钮(调用 `POST /api/auth/send-sms-code`,body `{ phone, businessType: 'LOGIN' }`,`businessType` 取值需与后端约定,暂定 `LOGIN`,登记到缺失文档待确认枚举值)+ `POST /api/auth/login-phone`,body `{ phone, smsCode }` - 去掉原先"输入账号密码后发验证码,再一起提交"的整合流程与 `SmsCodeInput` 在密码 Tab 内的复用(验证码输入组件改为只在手机登录 Tab 使用) - 两个登录接口响应均为 `LoginVo`:`{ token, username, realName, userId, phone, userType, organizationId, forceChangePwd }` - **登录成功后菜单/权限重建**(核心架构调整): 1. `authStore.login()` 拿到 `token` 后立即 `setAccessToken(token)`,并保存 `userInfo`(从 `LoginVo` 摘取 `userId/username/realName/phone/userType/organizationId`)、`mustChangePassword = forceChangePwd` 2. 调用 `GET /api/auth/user/permission-detail?userId={userId}`,得到 `UserPermissionDetailVo[]`(扁平列表,字段:`id/permissionCode/permissionName/permissionType/parentId/level/sortOrder/icon/routePath/pageResourcePathList/apiResourcePathList`) 3. 前端新增工具函数 `src/utils/permissionTreeBuilder.js`: - `buildTreeFromFlat(list)`:按 `parentId` 重建树(根节点 `parentId` 为空/0) - `extractButtonCodes(list)`:筛选 `permissionType === 'BUTTON'` 的 `permissionCode` 集合 - `extractMenuTree(list)`:只保留 `permissionType === 'PAGE'` 的节点重建的菜单树(供路由与侧边栏渲染,`BUTTON` 节点不出现在菜单里) 4. `authStore.menuTree` 存储 `extractMenuTree` 结果,`authStore.buttonCodes` 存储 `extractButtonCodes` 结果,供 `router/dynamic.js` 与 `v-permission` 指令使用 5. 路由守卫刷新场景(`router/index.js` 中 `!authStore.routesReady`)不再调用 `/auth/menu`,改为同样调用 `permission-detail` 接口重建(需要先从本地能拿到的 `userInfo.id` 里取 `userId`;若 `userInfo` 丢失,需要先 `POST /api/auth/user-info` 拿回当前用户信息) - **首次登录强制改密**:`change-password` 接口需要 `{ oldPassword, newPassword }`。前端需要在强制改密弹窗里增加"当前密码"输入框(原设计只有新密码+确认新密码两个字段,现在必须让用户输入登录时用的临时密码作为 `oldPassword`),调用 `POST /api/auth/change-password`。 - **管理员为用户重置密码**(`resetUserPasswordApi`):切到 `POST /api/user/reset-password`,body `{ userId }`,成功提示语调整为"密码已重置,新密码将通过短信通知用户"(具体重置逻辑后端未文档化,登记缺失文档)。 - **管理员直接指定新密码**("密码修改"按钮):无对应后端接口,按用户决策**保留入口但置灰禁用**,鼠标悬浮提示"该功能暂不支持,请使用「重置密码」"。 ### 2.3 动态路由与组件映射(`src/router/dynamic.js`) - 新增 `src/router/componentRegistry.js`,导出 `routePathComponentMap`,形如: ```js export const routePathComponentMap = { '/system/user': 'system/user/UserList', '/system/role': 'system/role/RoleList', '/system/org': 'system/org/OrgList', '/system/permission': 'system/permission/PermissionList', '/base-config/channel': 'base-config/channel/ChannelList', '/base-config/manager': 'base-config/manager/ManagerList' } ``` - `buildRouteRecords(menuTree)` 改为:对每个 `PAGE` 类型且非纯分组的节点,用 `node.routePath` 查 `routePathComponentMap` 得到组件相对路径再走现有 `resolveComponent`;查不到时 `console.error` 并跳过该节点(不阻断其余路由注册),避免后端权限字典里存在前端暂未实现的页面节点导致整体路由注册失败。 - `collectFirstPath` 逻辑不变,只是判断条件从 `node.component` 改为"能在 `routePathComponentMap` 命中"。 ## 3. 系统管理模块 ### 3.1 接口映射表 | 功能 | 旧 mock 契约 | 新真实契约 | 关键差异 | | --- | --- | --- | --- | | 用户列表 | `GET /system/user/list` | `POST /api/auth/user/page`,body `FindUserListQto{organizationId, usernameLike, phone, status(ACTIVE\|LOCKED), roleId, page, pageSize}` | 状态枚举从 `enabled/locked` 变为 `ACTIVE/LOCKED`;返回字段 `UserPageVo` 无 `roleIds`(只有 `roleNameList` 字符串数组),编辑时需要额外查询用户已绑定角色 | | 新增用户 | `POST /system/user` | `POST /api/auth/user/create`,body `CreateUserAndRoleBto{username, phone, realName, organizationId, status, email, userRoleBtoList:[{roleId}]}` | 需要传 `status`(默认 `ACTIVE`);响应仅 `{id, status}`,**无 initialPassword** | | 修改用户 | `PUT /system/user/{id}` | `POST /api/user/update`,body `UpdateUserBto{id, username, phone, realName, organizationId, email, userRoleBtoList:[{userId, roleId}]}` | 允许改 `username`(与原"用户名不可改"设计不同,表单保持禁用 username 编辑,只是接口层容忍传原值) | | 删除用户 | `DELETE /system/user/batch` | `POST /api/user/delete`,body `{ userId }`(单条) | 无 batch;前端循环调用(见第 6 节) | | 锁定/解锁 | `PUT /system/user/{id}/lock`/`unlock` | `POST /api/user/lock`/`POST /api/user/unlock`,body `{ userId }` | 方法从 PUT 变 POST | | 密码重置 | `PUT /system/user/{id}/password/reset` | `POST /api/user/reset-password`,body `{ userId }` | 具体重置值/短信通知逻辑未文档化 | | 管理员指定新密码 | `PUT /system/user/{id}/password` | **无对应接口** | 入口保留置灰,登记缺失文档 | | 角色列表 | `GET /system/role/list` | `POST /api/role/page`,body `{page, pageSize, roleName}` | 不支持按 `orgId`/`status` 过滤,搜索表单去掉"所属机构"筛选项(登记缺失文档) | | 角色详情 | `GET /system/role/{id}` | `POST /api/role/detail`,body `{ roleId }` → `RoleDetailVo{roleName, id, organizationId, isBuiltIn, permissionIdList}` | 无 `dataScope`;权限只有 `permissionIdList`(数字 ID 数组,页面+按钮混合) | | 新增角色 | `POST /system/role` | `POST /api/role/create`,body `CreateRoleBto{roleName, organizationId, isBuiltIn}` | 只传基础信息,**不传权限**;`isBuiltIn` 前端固定传 `false`(内置角色由后端/初始数据控制) | | 修改角色 | `PUT /system/role/{id}` | `POST /api/role/update`,body `UpdateRoleBto{id, roleName, organizationId}` | 同上,不含权限 | | 角色权限绑定 | (创建/修改时一起提交 `menuIds`+`buttonCodes`) | `GET /api/auth/role-permission/list-by-role?roleId=` 查旧集合 → diff → `POST /api/auth/role-permission/bind`/`unbind`,body `{ roleId, permissionIdList }` | 见 3.2 详细流程 | | 删除角色 | `DELETE /system/role/batch` | `POST /api/role/delete`,body `{ roleId }`(单条) | 无 batch | | 权限字典树 | `GET /system/permission/tree` | `GET /api/permission/tree` → `PermissionTreeVo[]{id, permissionName, permissionCode, permissionType(PAGE\|BUTTON), parentId, level, sortOrder, icon, routePath, children, pageResourceList, apiResourceList}` | 按钮不再内嵌在 `buttons` 数组里,而是 `type=BUTTON` 的子节点,`PermissionTree.vue` 需要重写分组逻辑 | | 机构树查询 | `GET /system/org/list` | `GET /api/organization/query-tree`,query `{organizationName?, organizationId?}` → `OrganizationTreeNodeVo[]{id, organizationId, organizationName, parentId, isBuiltIn, remark, children}` | 字段改名(`name→organizationName`);`id`(数字)与 `organizationId`(字符串业务码)并存,前端统一用数字 `id` 做选择值与关联 | | 机构下拉(轻量) | (复用机构树接口) | `GET /api/organization/list-all`,query `{organizationName?}` → 扁平 `OrganizationSimpleVo[]` | `OrgTreeSelect.vue` 内部改造为可选两种数据源;角色/用户表单用扁平列表即可,机构管理页仍用带 `children` 的树接口 | | 新增机构 | `POST /system/org` | `POST /api/organization/create`,body `{organizationName, parentId, remark}` | 一级机构 `parentId` 不传 | | 修改机构 | `PUT /system/org/{id}` | `POST /api/organization/update`,body `{organizationId, organizationName, parentId, remark}` — 注意此处 `organizationId` 是**用于定位记录的标识**,需要用 `record.id`(数字)还是 `record.organizationId`(字符串业务码)由后端约定但未在 swagger 说明清楚,登记缺失文档,临时按数字 `id` 转字符串传入并做真机联调验证 | 见风险清单 | | 删除机构 | `DELETE /system/org/batch` | `POST /api/organization/delete`,body `{ organizationId }`(单条,标识含义同上疑点) | 无 batch | | 一级机构判定 | 前端硬编码 `form.id === 'org001'` | 改为读取 `record.isBuiltIn` | 修复原有硬编码写法 | ### 3.2 角色权限绑定详细流程 新增角色:①`POST /api/role/create` 创建基础信息——**该接口响应 `data` 无类型定义(经与 swagger 原始 `responses` 节点核对确认为未定义结构,实测极可能不返回新建记录 `id`)**,前端创建成功后改为调用 `POST /api/role/page`,body `{ roleName: 表单角色名, pageSize: 100, page: 1 }` 查询,在返回的 `records` 中按 `organizationId === 表单所属机构` 精确匹配取得新建角色的 `id`(登记缺失文档:建议后端创建类接口直接返回新建记录 `id`) ②若表单勾选了权限,用上一步取得的 `id` 调用 `POST /api/auth/role-permission/bind`,body `{ roleId, permissionIdList: 勾选的全部ID }`。 修改角色:①`POST /api/role/update` 更新基础信息 ②`GET /api/auth/role-permission/list-by-role?roleId=` 取旧集合 `oldIds` ③计算 `toBind = 勾选集合 - oldIds`、`toUnbind = oldIds - 勾选集合` ④分别调用 `bind`(`toBind` 非空时)与 `unbind`(`toUnbind` 非空时)。此策略无论后端 `bind`/`unbind` 的语义是"全量替换"还是"增量追加"都能得到正确结果,语义本身登记到缺失文档请后端确认。 `PermissionTree.vue` 改造: - 去掉"数据权限范围"单选区块 - 树节点勾选:`PermissionTreeVo` 的 `children` 已经混合 PAGE/BUTTON 类型,`a-tree` 直接渲染全树(不再分"页面权限列"和"功能权限列"两栏,改为单棵可勾选树,`BUTTON` 节点渲染为叶子节点即可,标题加类型角标区分,简化交互同时贴合真实数据结构) - 勾选值统一是 `permissionIdList`(数字 ID 数组),不再区分 `menuIds`/`buttonCodes` 两个字段;`RoleList.vue` 提交前把 `permissionIdList` 按 `permissionType` 拆出 `isPage`/`isButton` 仅用于本地校验"至少勾选一个页面权限" ### 3.3 新增:权限管理页面 - 路径 `/system/permission`,组件 `src/views/system/permission/PermissionList.vue` - 数据来源:`GET /api/permission/tree`(树形展示,复用现有 `a-table` 树形模式 `default-expand-all-rows`,不用分页) - 列:序号、资源名称(`permissionName`)、父级资源(展示父节点 `permissionName`,根节点显示"无")、资源层级(`level`)、类型(`permissionType`,`PAGE`→"页面"/`BUTTON`→"按钮"标签)、页面资源路径(`pageResourceList` 拼接展示)、API资源路径(`apiResourceList` 拼接展示) - 操作: - 「一级新增」:新增根节点,弹窗字段 `permissionName/permissionCode/routePath/icon/sortOrder/pageResourcePaths/apiResourcePaths`,`permissionType` 固定 `PAGE`,`parentId` 不传,`level` 传 `1` - 「子级新增」(行内按钮):在选中节点下新增子节点,弹窗额外要求选择 `permissionType`(PAGE/BUTTON),`parentId` 传当前行 `id`,`level` 传当前行 `level+1` - 「修改」:`POST /api/permission/update` - 「删除」:`POST /api/permission/delete`,body `{ id }`(单条;有子节点时是否允许删除由后端校验,前端不做额外拦截,失败提示走通用错误处理) - 菜单接入:在 `componentRegistry.js` 注册 `'/system/permission': 'system/permission/PermissionList'`;该页面自身的权限字典节点(以及用户/角色/机构/核心企业/客户经理各页面节点)**需要部署后由管理员通过本页面手动创建**,并把对应 `id` 勾选进内置管理员角色的 `permissionIdList`——这是一次性数据初始化步骤,写入实施计划的部署清单,不属于代码开发任务。 ### 3.4 UserList.vue / RoleList.vue / OrgList.vue 交互调整要点 - `UserList.vue`:状态筛选下拉值改为 `ACTIVE`/`INACTIVE`→`LOCKED`(`StatusTag.vue` 映射表同步更新);角色下拉数据源 `fetchRoleListApi` 返回结构不变(仍是 `{list,total}`),但角色列表接口不再支持 `status` 过滤,前端改为客户端不筛选或去掉该查询参数(所有角色都可选,由后端保证只有启用角色可分配的规则若不存在则不做限制,登记缺失文档);编辑用户时角色多选框的已选值需要额外调用 `GET /api/auth/user-role/list-by-user?userId=` 获取当前角色 ID 列表(因为 `UserPageVo` 不返回 `roleIds`);新增/修改后不再展示 `initialPassword`。 - `RoleList.vue`:去掉搜索表单里的"所属机构"筛选(接口不支持);`builtin` 字段改名读取 `isBuiltIn`;批量删除按钮行为改为循环单删(见第 6 节)。 - `OrgList.vue`:搜索参数 `id` 改为 `organizationId`(注意与树节点显示用的数字 `id` 区分,搜索框仍允许用户输入机构业务编号做模糊/精确匹配,具体匹配规则由后端实现,前端不做校验);`isRootRecord` 判断改为 `record.isBuiltIn`。 ## 4. 基础配置模块 ### 4.1 核心企业管理(`ChannelList.vue`)—— 字段与接口全量重构 | 旧字段(渠道) | 新字段(核心企业) | 说明 | | --- | --- | --- | | `name`(渠道名称) | `enterpriseName`(企业名称) | | | `channelCode`(渠道编号) | `enterpriseCode`(企业编号) | | | `appCode`(应用编号) | — | 字段取消 | | `motherAccountBank`(母户开户行) | — | 字段取消 | | — | `businessLicense`(营业执照号) | 新增字段 | | — | `contactPerson`(联系人)、`contactPhone`(联系电话) | 新增字段 | | — | `status`(`ACTIVE`/`INACTIVE`,启用/停用) | 新增字段,表单增加状态选择,默认 `ACTIVE` | | — | `remark`(备注) | 新增字段 | | `orgId` | `organizationId` | 关联机构树,交互不变 | 接口:列表 `POST /api/core-enterprise/page`(body 仅支持 `{page, pageSize, enterpriseName}`,搜索表单去掉"渠道编号"精确筛选,只保留企业名称模糊 + 所属机构本地过滤或去掉机构筛选,登记缺失文档说明过滤能力收窄);新增 `POST /api/core-enterprise/create`;修改 `POST /api/core-enterprise/update`(body 含 `id`);删除 `POST /api/core-enterprise/delete`,body `{ id }`(单条,前端循环模拟批量)。 ### 4.2 客户经理管理(`ManagerList.vue`)—— 字段与接口全量重构 | 旧字段 | 新字段 | 说明 | | --- | --- | --- | | `orgCode`(机构号,自由文本) | — | 取消,改为关联真实机构 | | `orgName`(机构名称,自由文本) | `organizationId` | 改为 `OrgTreeSelect` 关联选择 | | `jobNumber`(工号) | `managerCode`(工号) | 改名 | | `name`(姓名) | `managerName`(姓名) | 改名 | | — | `mobilePhone`(手机号) | 新增必填字段 | | — | `department`(所属部门) | 新增字段 | | — | `status`(`ACTIVE`/`INACTIVE`,在职/离职) | 新增字段,默认 `ACTIVE` | | — | `remark`(备注) | 新增字段 | 接口:列表 `POST /api/customer-manager/page`(body 仅支持 `{page, pageSize, managerName}`,搜索表单去掉"机构号/机构名称/工号"筛选,只保留姓名模糊,登记缺失文档);新增 `POST /api/customer-manager/create`;修改 `POST /api/customer-manager/update`;删除 `POST /api/customer-manager/delete`,body `{ id }`(单条)。原"查询无结果提示客户经理不存在"的交互保留(前端本地判断 `total === 0` 后触发,与后端无关)。 ## 5. 关键决策记录(设计阶段问答结论) | 决策点 | 结论 | | --- | --- | | 登录方式 | 拆分为"账号密码/验证码"二选一 Tab,去掉双重校验流程 | | 角色数据权限范围(dataScope) | 移除该字段,登记缺失接口 | | 批量删除 | 前端循环调用单条删除接口模拟批量交互,不改变现有 UI | | 管理员直接改密 | 保留入口但禁用,提示"暂不支持,请使用重置密码" | | 新增用户初始密码提示 | 改为提示"已通过短信通知用户" | | 权限管理页面 | 本次一并开发 | | 业务错误码 | 统一按 `code !== 200` 判断失败,展示 `message` | ## 6. 批量删除前端模拟方案 新增 `src/utils/loopDelete.js`: ```js // deleteOne: (id) => Promise,统一约定其 response.data 结构为 {code, message, data} // 返回值兼容既有 showBatchDeleteResult({successIds, failed}) 的入参结构 export async function loopDelete(ids, deleteOne, getName) { const successIds = [] const failed = [] for (const id of ids) { try { const res = await deleteOne(id) if (res.data.code === 200) successIds.push(id) else failed.push({ id, name: getName(id), reason: res.data.message || '删除失败' }) } catch (e) { failed.push({ id, name: getName(id), reason: e.message || '请求异常' }) } } return { successIds, failed } } ``` 各列表页 `handleBatchDelete` 改为:`const result = await loopDelete(selectedRowKeys.value, (id) => deleteXxxApi(id), (id) => list.value.find(r => r.id === id)?.name)`,再调用现有 `showBatchDeleteResult(result)`,UI 与交互文案不变。 ## 7. mock-server 处理方式 `mock-server/` 不再是"开发期唯一后端",但保留作为**无真实后端环境时的本地兜底**(如联调环境不可用时临时本地演示)。本次不强制同步修改 mock-server 路由契约,后续若发现 mock 与真实契约差异导致本地演示误导,再单独调整;`README`/启动脚本里补充一句说明"默认对接真实后端,`npm run mock` 仅用于应急本地演示,契约可能与真实后端存在差异"。 ## 8. `缺失的后端接口.md` 更新大纲 重写全文档结构,分两大部分: **Part 1:已提供接口契约速查**(按 8.1 通用鉴权 / 8.2 基础通用能力 / 8.3 系统管理 / 8.4 基础配置 四节,逐条更新为真实 path、method、body/query、响应结构,标注与前端实际调用的对应关系)。8.2 节更新为:文件上传(`/api/customer-info/personal/upload-file`)、身份证 OCR(`/api/customer-info/personal/ocr-idcard`)、通用短信发送(`/api/auth/send-sms-code`)已提供(新契约,字段与原设计不同);**营业执照 OCR、通用短信校验(verify)仍缺失**。 **Part 2:待后端确认/建议补充事项**: 1. 批量删除接口(用户/角色/机构/核心企业/客户经理共 5 处),当前前端已临时用循环单删规避,建议后端评估补充原子性 batch 接口 2. 管理员直接为其他用户指定新密码的接口(无需旧密码),当前功能入口已禁用 3. 新增用户(`/api/auth/user/create`)与重置密码(`/api/user/reset-password`)后,初始/新密码是否已通过短信通知用户?具体重置后的密码规则(固定值/随机)?需要后端确认,前端目前假定"已短信通知"仅做提示文案,不做实际校验 4. 具体业务错误码字典缺失,前端已改为通用 `code !== 200` + 展示 `message` 处理,若后端后续需要前端做特定错误码的差异化处理(如账号锁定单独弹窗而非普通提示),需提供错误码枚举 5. 角色权限 `role-permission/bind`、`unbind` 的语义(全量替换 vs 增量追加)未在 swagger 说明,前端已按"先查旧集合再计算 diff 分别调用"的保守策略实现,不受该语义影响,但建议后端在文档中明确 6. 机构模块 `organizationId` 字段存在双重含义(`OrganizationDetailVo` 内既有数字 `id` 又有字符串业务码 `organizationId`;而 `update`/`delete` 接口的定位字段又叫 `organizationId`),与其他模块把 `organizationId` 当作数字外键的用法不一致,需要后端澄清该字段在"机构自身增删改"接口里到底应传数字 `id` 还是字符串业务码 7. 角色列表(`/api/role/page`)不支持按所属机构/状态过滤,核心企业列表(`/api/core-enterprise/page`)、客户经理列表(`/api/customer-manager/page`)只支持单一名称模糊搜索,过滤能力相比原设计收窄,建议后端评估是否补充多条件查询参数 8. 角色数据权限范围(本机构/本机构及下级/全部)字段在真实模型中不存在,若产品侧仍需要该能力需要后端评估补充 9. 用户列表/详情不返回 `roleIds`,编辑用户角色前需要额外调用 `user-role/list-by-user`,建议后端在 `UserPageVo`/`UserDetailVo` 中直接补充 `roleIdList` 以减少一次请求 10. `POST /api/role/create` 响应未定义返回新建记录 `id`,前端已改为"创建后按 roleName+organizationId 反查列表"规避,建议后端补充直接返回 `id`(核心企业、客户经理创建接口同样未返回 `id`,但当前无需立即二次操作,不阻塞使用,仍建议一并补充以保持接口一致性) ## 9. 风险与验证清单 - [ ] `/api` 前缀补充后,联调环境真实 baseURL 是否确实不含 `/api`(需要与后端确认真实部署域名规则) - [ ] `POST /api/organization/update`/`delete` 的 `organizationId` 参数到底传数字 `id` 或字符串业务码 —— 需真机联调验证,若报错则切换另一种取值 - [ ] `role-permission/bind`/`unbind` 实际语义验证(全量替换 or 增量追加均应正确工作,但仍需实测一次确认 diff 策略无副作用) - [ ] 权限字典种子数据(用户/角色/机构/权限/核心企业/客户经理各页面与按钮节点)部署后需要通过新的权限管理页面手动创建并授予内置管理员角色,否则登录后看不到任何菜单 - [ ] `send-sms-code` 的 `businessType` 枚举值需要后端确认合法取值(暂定 `LOGIN`) - [ ] 强制改密弹窗新增"当前密码"字段后需要用户确认交互是否符合预期(用户之前只看到"新密码+确认新密码"两个字段) - [ ] `POST /api/role/create` 创建后"按 roleName+organizationId 反查列表取新 id"的规避方案需真机联调验证(若同机构同名角色并发创建等极端场景可能反查到错误记录,概率极低,暂不做并发保护) - [ ] `POST /api/auth/refresh-token` 的鉴权机制未文档化(依赖即将过期的 `Authorization` 头,还是依赖 Cookie 中的独立 refresh token):前端实现为"仍带上当前(即将过期的)accessToken"作为尝试,若后端机制不同导致刷新必定失败,401 时的自动登出兜底逻辑仍会生效(不影响功能正确性,只是刷新静默续期可能不起作用),需后端确认后按需调整