# 技术设计 ## 需求追溯 | 需求 | AC | 设计方案 | |------|-----|----------| | FR-001 无权限引导页 | AC1.1 | 新建 `NoPermission.vue` 页面,路由守卫中检测权限接口返回无权限时跳转 | | | AC1.2 | 页面渲染「开通权限指引手册」按钮,链接从系统配置接口获取 | | | AC1.3 | 页面内启动 30s 定时轮询权限状态接口,权限变更后自动 `router.replace` 进入首页 | | FR-002 系统配置-权限指引 | AC2.1 | 系统配置页新增「权限指引手册」表单区块 | | | AC2.2/AC2.3 | 输入框 blur 时正则校验 URL 格式,不合法显示红色提示 | | FR-003 账号管理多角色 | AC3.1 | `AccountModal.vue` 中 `roleId` 改为 `roleIds: number[]`,NSelect 开启 `multiple` | | | AC3.2 | 后端处理权限并集,前端无需额外逻辑 | | | AC3.3 | 账号列表表头「登录账号」→「登录邮箱」,国际化 key 更新 | | | AC3.4 | 新增表单移除「编号」字段,标签改为「登录邮箱」,校验复用现有 `validateUserAccount` | | FR-004 保单类型设置 | AC4.1~AC4.8 | 系统配置页新增「保单类型设置」区块 + `InsuranceTypeModal.vue` 弹窗组件 | | FR-005 新增车辆步骤条 | AC5.1 | 新建 `AddVehicleStepper.vue`,n-steps 三步:车辆信息→保险信息→信息确认 | | | AC5.2 | 步骤1合并原车辆信息+卫星定位+其他信息,GPS 字段加「卫星定位装置」前缀 | | | AC5.3 | 每步「下一步」先前端 validate,通过后调后端校验接口 | | | AC5.4 | 「上一步」切换 step,数据保留在 reactive 对象中不清除 | | | AC5.5 | 「取消」弹二次确认,确认则 `router.back()` | | | AC5.6 | 最终步「确认并保存」调提交接口 | | FR-006 多保单录入 | AC6.1 | 保险步骤使用 `insuranceList: InsuranceItem[]` 数组,点击「添加」push 新项 | | | AC6.2 | 保险类型下拉选项从系统配置接口获取,已选类型通过 computed 过滤 | | | AC6.3 | 保险名称必填,1-50字符校验 | | | AC6.4 | PDF 上传,≤5MB,最多1个,复用现有 `beforePdfUpload` | | | AC6.5 | 第一个模块不显示删除按钮,其余显示 | | | AC6.6 | 保险类型文本 CSS `text-overflow: ellipsis` | | FR-007 信息确认与历史数据 | AC7.1 | 步骤3只读回显所有字段,图片/文件可点击预览 | | | AC7.2 | 编辑模式加载历史数据时,保险名称置空,保险资料仅保留第一份 | | | AC7.3 | 确认保存前校验保险名称必填 | | | AC7.4 | 运维 APP 数据展示为后端处理,前端无需改动 | | FR-008 测试计划复制 | AC8.1 | 列表操作栏增加「复制」按钮,跳转 `testPlanAdd` 并携带 `mode=copy&id=x` | | | AC8.2 | 复制模式下自动填充 `原名称_副本` | | | AC8.3 | 复制模式下回显原计划点位 | | | AC8.4 | 复制模式下清空车辆数据 | | | AC8.5 | 保存时调复制接口,toast 显示生成结果 | ## 现有实现分析 | 功能 | 文件 | 状态 | 说明 | |------|------|------|------| | 路由守卫 | `src/router/guard.ts` | 需改造 | 无权限检测逻辑需新增 | | 系统配置 | `src/views/system/config/index.vue` | 需扩展 | 新增权限指引+保单类型模块 | | 账号弹窗 | `src/views/system/accountPermission/accountManagement/components/AccountModal.vue` | 需改造 | roleId→roleIds 多选,移除编号字段 | | 账号列表 | `src/views/system/accountPermission/accountManagement/index.vue` | 需改造 | 列名「登录账号」→「登录邮箱」 | | 新增车辆 | `src/views/assetManagement/vehicleManagement/inventoryVehicle/add.vue` | 需重构 | 拆分为步骤条结构 | | 测试计划列表 | `src/views/testDemo/testPlanList/index.vue` | 需扩展 | 增加「复制」操作 | | 测试计划新增 | `src/views/testDemo/testPlanList/add.vue` | 需扩展 | 支持 copy 模式 | | API-测试计划 | `src/service/api/copyText.ts` | 无需改动 | 复制使用现有详情+新增接口 | ## 架构设计 ```mermaid graph TD subgraph 路由层 Guard[router/guard.ts] -->|无权限| NP[NoPermission.vue] Guard -->|有权限| App[正常页面] end subgraph 系统配置模块 Config[system/config/index.vue] Config --> PermGuide[权限指引手册区块] Config --> InsType[保单类型设置区块] InsType --> InsTypeModal[InsuranceTypeModal.vue] end subgraph 账号管理模块 AccList[accountManagement/index.vue] AccList --> AccModal[AccountModal.vue] end subgraph 车辆管理模块 AddVehicle[AddVehicleStepper.vue] AddVehicle --> Step1[步骤1: 车辆信息] AddVehicle --> Step2[步骤2: 保险信息] AddVehicle --> Step3[步骤3: 信息确认] Step2 --> InsItem[InsuranceFormItem.vue] end subgraph 测试计划模块 TPList[testPlanList/index.vue] TPList -->|复制| TPAdd[testPlanList/add.vue] end subgraph API层 API_Auth[权限校验/轮询] API_Config[系统配置CRUD] API_Account[账号管理] API_Vehicle[车辆+多保单] API_InsType[保险类型配置] API_TestPlan[测试计划复制] end ``` ## 文件结构 ``` src/ ├── views/ │ ├── no-permission/ │ │ └── index.vue # 无权限引导页(新建) │ ├── system/ │ │ ├── config/ │ │ │ ├── index.vue # 系统配置(扩展) │ │ │ └── components/ │ │ │ └── InsuranceTypeModal.vue # 保单类型弹窗(新建) │ │ └── accountPermission/ │ │ └── accountManagement/ │ │ ├── index.vue # 账号列表(改造) │ │ └── components/ │ │ └── AccountModal.vue # 账号弹窗(改造) │ ├── assetManagement/ │ │ └── vehicleManagement/ │ │ └── inventoryVehicle/ │ │ ├── add.vue # 重构为步骤条容器 │ │ └── components/ │ │ ├── VehicleInfoStep.vue # 步骤1-车辆信息(新建) │ │ ├── InsuranceStep.vue # 步骤2-保险信息(新建) │ │ ├── InsuranceFormItem.vue # 单个保单模块(新建) │ │ └── ConfirmStep.vue # 步骤3-信息确认(新建) │ └── testDemo/ │ └── testPlanList/ │ ├── index.vue # 测试计划列表(扩展) │ └── add.vue # 测试计划新增(扩展 copy 模式) ├── service/ │ └── api/ │ ├── system.ts # 扩展:权限指引链接、保险类型 CRUD │ └── vehicle.ts # 扩展:多保单提交结构 └── router/ ├── guard.ts # 改造:无权限跳转逻辑 └── routes.static.ts # 新增 noPermission 路由 ``` ## API 策略 本项目为现有项目,所有接口均对接真实后端 API。新增接口如下: | 端点 | 方法 | 描述 | 模块 | |------|------|------|------| | `/operation/api/v1/auth/checkPermission` | GET | 校验当前用户是否有后台权限 | FR-001 | | `/operation/api/v1/auth/permissionStatus` | GET | 轮询权限状态(30s间隔) | FR-001 | | `/operation/api/v1/system-config/permissionGuide` | GET/PUT | 获取/更新权限指引链接 | FR-002 | | `/operation/api/v1/insurance-type/list` | GET | 获取保险类型列表 | FR-004 | | `/operation/api/v1/insurance-type/add` | POST | 新增保险类型 | FR-004 | | `/operation/api/v1/insurance-type/sort` | POST | 保险类型排序 | FR-004 | | `/operation/api/v1/insurance-type/delete/{id}` | DELETE | 删除保险类型 | FR-004 | | `/operation/api/v1/vehicle/add` | POST | 新增车辆(多保单结构) | FR-005/006 | | `/operation/api/v1/vehicle/validate-step` | POST | 步骤间后端校验 | FR-005 | | `/operation/api/v1/xp-test-plan/detail/{id}` | GET | 获取测试计划详情(复制时回显) | FR-008 | ## 组件设计 | 组件 | 职责 | 文件路径 | |------|------|----------| | NoPermission | 无权限引导页,展示提示文案+指引按钮+轮询 | `src/views/no-permission/index.vue` | | InsuranceTypeModal | 保险类型配置弹窗,支持搜索/新增/拖拽排序/删除 | `src/views/system/config/components/InsuranceTypeModal.vue` | | VehicleInfoStep | 步骤1表单,合并车辆+GPS+其他信息 | `src/views/assetManagement/vehicleManagement/inventoryVehicle/components/VehicleInfoStep.vue` | | InsuranceStep | 步骤2容器,管理多保单模块列表 | `src/views/assetManagement/vehicleManagement/inventoryVehicle/components/InsuranceStep.vue` | | InsuranceFormItem | 单个保单填写模块(类型+名称+资料) | `src/views/assetManagement/vehicleManagement/inventoryVehicle/components/InsuranceFormItem.vue` | | ConfirmStep | 步骤3只读回显所有信息 | `src/views/assetManagement/vehicleManagement/inventoryVehicle/components/ConfirmStep.vue` | ## 关键设计决策 ### 1. 无权限检测时机 在 `router/guard.ts` 的 `initAuthRoute` 流程中,后端返回路由列表为空时判定为无权限,跳转 `noPermission` 路由。页面内 `setInterval(30000)` 轮询,检测到权限变更后 `clearInterval` 并跳转首页。 ### 2. 多保单数据结构 ```typescript interface InsuranceItem { insuranceTypeId: number | null // 保险类型ID insuranceName: string // 保险名称(必填 1-50字符) insuranceFile: UploadFileInfo[] // PDF附件(非必填,最多1个,≤5MB) } ``` 提交时转为数组结构随车辆数据一起发送。最大保单数 = 系统配置中的保险类型数量。 ### 3. 步骤条数据暂存 使用一个顶层 `reactive` 对象持有三步的所有数据(`vehicleInfo` + `insuranceList` + 确认步骤无新数据),切换步骤不清除,仅在「取消」时弹窗确认后丢弃。 ### 4. 账号多角色改造 - `formModel.roleId: string | null` → `formModel.roleIds: string[]` - `addAccount`/`editAccount` 接口参数 `roleId` → `roleIds: string[]` - 列表接口返回的 `roleId` 字段可能变为 `roleIds` 数组,需兼容处理 ### 5. 测试计划复制 复用 `add.vue` 页面,通过 `route.query.mode === 'copy'` 判断进入复制模式: - 调详情接口 `fetchTestPlanDetail(id)` 获取原计划数据 - 计划名称 = `${原名称}_副本` - 点位数据回显 - 车辆数据清空 - 去除测试计划 ID(视为全新计划) - 保存时调用现有的新增测试计划接口(非独立复制接口) ### 6. 保险类型拖拽排序 `InsuranceTypeModal` 使用 HTML5 drag/drop 或 naive-ui 的行拖拽功能实现排序,拖拽结束后调排序接口更新顺序。 ### 7. 历史数据兼容 编辑车辆加载历史数据时: - 原有字段(公司名称、保险号、保险类型、金额、生效/到期日期)保持不变 - 保险名称字段默认空白(用户需重新填写) - 保险资料仅保留第一份 PDF - 后端同步处理历史数据迁移逻辑