# 项目详情参数配置页面设计文档 **创建时间:** 2026-03-06 **作者:** Claude Code **状态:** 已批准 --- ## 1. 概述 ### 1.1 需求背景 纪检初核系统需要在项目详情页面中添加参数配置功能,允许用户为每个项目自定义模型参数配置。当前系统已有独立的模型参数配置页面(管理系统默认参数),需要将其功能复用到项目详情页面中。 ### 1.2 核心需求 1. **配置模式:** 自动切换模式(修改即切换为 custom) 2. **界面布局:** 完全复用独立页面的布局(模型下拉框 + 参数表格 + 保存按钮) 3. **重置功能:** 不提供切换回默认配置的功能 4. **初始化策略:** 查询时复制(按需创建自定义参数) ### 1.3 设计原则 1. **最小改动原则:** 前端组件直接复用代码,后端只修改必要的方法 2. **自动切换原则:** 用户保存参数时自动从 default 切换到 custom 3. **按需创建原则:** 只在首次保存时创建项目自定义参数,不预复制 4. **数据隔离原则:** 项目自定义参数与系统默认参数完全独立 --- ## 2. 架构设计 ### 2.1 整体架构 ``` ┌─────────────────────────────────────────────────────────┐ │ 项目详情页面 │ │ detail.vue │ │ ┌───────────────────────────────────────────────────┐ │ │ │ 菜单栏: 上传数据 | 参数配置 | 结果总览 | ... │ │ │ └───────────────────────────────────────────────────┘ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ ParamConfig 组件 │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ │ │ 模型选择下拉框 │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ │ │ 参数表格(可编辑) │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ │ │ 保存按钮 │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ │ │ API 调用 ▼ ┌─────────────────────────────────────────────────────────┐ │ 后端 CcdiModelParamController │ │ ┌───────────────────────────────────────────────────┐ │ │ │ GET /ccdi/modelParam/modelList?projectId={id} │ │ │ │ - 查询模型列表 │ │ │ └───────────────────────────────────────────────────┘ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ GET /ccdi/modelParam/list?projectId={id} │ │ │ │ - 查询模型参数列表 │ │ │ │ - 如果 configType=default,返回系统默认参数 │ │ │ │ - 如果 configType=custom,返回项目自定义参数 │ │ │ └───────────────────────────────────────────────────┘ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ POST /ccdi/modelParam/save │ │ │ │ - 保存参数 │ │ │ │ - 如果是首次保存,自动复制系统默认参数 │ │ │ │ - 更新 configType=custom │ │ │ └───────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ 数据库 ccdi_model_param │ │ - projectId=0:系统默认参数 │ │ - projectId>0:项目自定义参数 │ └─────────────────────────────────────────────────────────┘ ``` ### 2.2 数据模型 **表:ccdi_model_param** | 字段名 | 类型 | 说明 | |--------|------|------| | id | BIGINT | 主键ID | | project_id | BIGINT | 项目ID(0表示默认参数) | | model_code | VARCHAR(50) | 模型编码 | | model_name | VARCHAR(100) | 模型名称 | | param_code | VARCHAR(50) | 参数编码 | | param_name | VARCHAR(100) | 监测项名称 | | param_desc | VARCHAR(500) | 参数描述 | | param_value | VARCHAR(200) | 参数值 | | param_unit | VARCHAR(50) | 参数单位 | | sort_order | INT | 排序号 | | create_by | VARCHAR(64) | 创建者 | | create_time | DATETIME | 创建时间 | | update_by | VARCHAR(64) | 更新者 | | update_time | DATETIME | 更新时间 | | remark | VARCHAR(500) | 备注 | **表:ccdi_project(相关字段)** | 字段名 | 类型 | 说明 | |--------|------|------| | project_id | BIGINT | 项目ID | | config_type | VARCHAR(20) | 配置方式:default-全局默认,custom-自定义 | --- ## 3. 组件设计 ### 3.1 前端组件 **组件路径:** `ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue` **组件结构:** ```vue ``` ### 3.2 后端接口 **文件:** `CcdiModelParamServiceImpl.java` **修改的方法:** #### 3.2.1 selectParamList 方法 ```java @Override public List selectParamList(ModelParamQueryDTO queryDTO) { // 1. 查询项目信息 CcdiProject project = projectMapper.selectById(queryDTO.getProjectId()); if (project == null) { throw new ServiceException("项目不存在"); } // 2. 根据 configType 决定查询哪组参数 Long effectiveProjectId; if ("default".equals(project.getConfigType())) { // 使用系统默认参数 effectiveProjectId = 0L; } else { // 使用项目自定义参数 effectiveProjectId = queryDTO.getProjectId(); } // 3. 查询参数列表 return modelParamMapper.selectParamList(effectiveProjectId, queryDTO.getModelCode()); } ``` #### 3.2.2 saveParams 方法 ```java @Override @Transactional(rollbackFor = Exception.class) public void saveParams(ModelParamSaveDTO saveDTO) { try { // 1. 参数验证 if (saveDTO.getProjectId() == null) { throw new ServiceException("项目ID不能为空"); } if (StringUtils.isBlank(saveDTO.getModelCode())) { throw new ServiceException("模型编码不能为空"); } if (saveDTO.getParams() == null || saveDTO.getParams().isEmpty()) { throw new ServiceException("参数列表不能为空"); } // 2. 查询项目信息 CcdiProject project = projectMapper.selectById(saveDTO.getProjectId()); if (project == null) { throw new ServiceException("项目不存在"); } // 3. 如果是首次保存(configType=default),需要复制系统默认参数 if ("default".equals(project.getConfigType())) { int copiedCount = copyDefaultParamsToProject( saveDTO.getProjectId(), saveDTO.getModelCode() ); if (copiedCount == 0) { log.warn("系统默认参数为空,projectId={}, modelCode={}", saveDTO.getProjectId(), saveDTO.getModelCode()); } // 更新项目配置类型为 custom project.setConfigType("custom"); projectMapper.updateById(project); } // 4. 更新参数值 for (ModelParamSaveDTO.ParamValueItem item : saveDTO.getParams()) { int updated = modelParamMapper.updateParamValue( saveDTO.getProjectId(), saveDTO.getModelCode(), item.getParamCode(), item.getParamValue() ); if (updated == 0) { log.warn("参数不存在或未更新,paramCode={}", item.getParamCode()); } } } catch (ServiceException e) { // 业务异常,直接抛出 throw e; } catch (Exception e) { // 系统异常,记录日志并抛出 log.error("保存模型参数失败", e); throw new ServiceException("保存模型参数失败:" + e.getMessage()); } } /** * 复制系统默认参数到项目 * * @param projectId 项目ID * @param modelCode 模型编码 * @return 复制的参数数量 */ private int copyDefaultParamsToProject(Long projectId, String modelCode) { // 查询系统默认参数 List defaultParams = modelParamMapper.selectList( new LambdaQueryWrapper() .eq(CcdiModelParam::getProjectId, 0L) .eq(CcdiModelParam::getModelCode, modelCode) ); if (defaultParams.isEmpty()) { return 0; } // 复制到项目 List projectParams = defaultParams.stream() .map(param -> { CcdiModelParam newParam = new CcdiModelParam(); BeanUtils.copyProperties(param, newParam); newParam.setId(null); // 清空ID,让数据库自动生成 newParam.setProjectId(projectId); newParam.setCreateBy(null); // 清空审计字段,让 MyBatis Plus 自动填充 newParam.setCreateTime(null); newParam.setUpdateBy(null); newParam.setUpdateTime(null); return newParam; }) .collect(Collectors.toList()); // 批量插入 modelParamMapper.insertBatch(projectParams); return projectParams.size(); } ``` #### 3.2.3 Mapper 方法 **CcdiModelParamMapper.xml 新增:** ```xml UPDATE ccdi_model_param SET param_value = #{paramValue}, update_by = NULL, update_time = NOW() WHERE project_id = #{projectId} AND model_code = #{modelCode} AND param_code = #{paramCode} INSERT INTO ccdi_model_param ( project_id, model_code, model_name, param_code, param_name, param_desc, param_value, param_unit, sort_order, create_by, create_time, remark ) VALUES ( #{item.projectId}, #{item.modelCode}, #{item.modelName}, #{item.paramCode}, #{item.paramName}, #{item.paramDesc}, #{item.paramValue}, #{item.paramUnit}, #{item.sortOrder}, NULL, NOW(), #{item.remark} ) ``` --- ## 4. 数据流设计 ### 4.1 查看参数配置(configType=default) ``` 用户点击"参数配置"菜单 ↓ 前端调用 GET /ccdi/modelParam/modelList?projectId=123 ↓ 后端返回模型列表 ↓ 前端选择第一个模型,调用 GET /ccdi/modelParam/list?projectId=123&modelCode=MODEL_001 ↓ 后端查询项目,发现 configType=default ↓ 后端返回系统默认参数(projectId=0) ↓ 前端显示参数表格 ``` ### 4.2 查看参数配置(configType=custom) ``` 用户点击"参数配置"菜单 ↓ 前端调用 GET /ccdi/modelParam/modelList?projectId=123 ↓ 后端返回模型列表 ↓ 前端选择第一个模型,调用 GET /ccdi/modelParam/list?projectId=123&modelCode=MODEL_001 ↓ 后端查询项目,发现 configType=custom ↓ 后端返回项目自定义参数(projectId=123) ↓ 前端显示参数表格 ``` ### 4.3 首次保存参数(default → custom) ``` 用户修改参数值,点击"保存配置" ↓ 前端调用 POST /ccdi/modelParam/save { "projectId": 123, "modelCode": "MODEL_001", "params": [ {"paramCode": "THRESHOLD_1", "paramValue": "100"}, {"paramCode": "THRESHOLD_2", "paramValue": "50"} ] } ↓ 后端检查项目 configType=default ↓ 后端执行复制操作: 1. 查询系统默认参数(projectId=0, modelCode=MODEL_001) 2. 复制所有参数,设置 projectId=123 3. 批量插入到数据库 ↓ 后端更新项目的 configType=custom ↓ 后端更新参数值: UPDATE ccdi_model_param SET param_value='100' WHERE project_id=123 AND model_code='MODEL_001' AND param_code='THRESHOLD_1' ↓ 后端返回成功 ↓ 前端重新加载参数列表(此时查询的是项目自定义参数) ↓ 前端显示成功消息 ``` ### 4.4 再次保存参数(configType=custom) ``` 用户修改参数值,点击"保存配置" ↓ 前端调用 POST /ccdi/modelParam/save ↓ 后端检查项目 configType=custom ↓ 后端跳过复制步骤,直接更新参数值 ↓ 后端返回成功 ``` --- ## 5. 错误处理 ### 5.1 前端错误处理 **网络错误:** ```javascript async loadParamList() { try { const res = await listParams(this.queryParams) this.paramList = res.data } catch (error) { this.$message.error('加载参数列表失败:' + error.message) console.error('加载参数列表失败', error) } } ``` **保存验证:** ```javascript async handleSave() { // 验证是否有修改 const modifiedParams = this.paramList.filter(item => item.modified) if (modifiedParams.length === 0) { this.$message.info('没有需要保存的修改') return } // 验证参数值 const invalidParams = modifiedParams.filter( item => !item.paramValue || item.paramValue.trim() === '' ) if (invalidParams.length > 0) { this.$message.error('请填写所有参数值') return } // 保存 this.saving = true try { await saveParams(saveDTO) this.$message.success('保存成功') // 清除修改标记并重新加载 this.paramList.forEach(item => { item.modified = false }) await this.loadParamList() } catch (error) { if (error.response && error.response.data && error.response.data.msg) { this.$message.error('保存失败:' + error.response.data.msg) } else { this.$message.error('保存失败:' + error.message) } } finally { this.saving = false } } ``` ### 5.2 后端错误处理 **异常处理:** ```java @Override @Transactional(rollbackFor = Exception.class) public void saveParams(ModelParamSaveDTO saveDTO) { try { // 1. 参数验证 if (saveDTO.getProjectId() == null) { throw new ServiceException("项目ID不能为空"); } if (StringUtils.isBlank(saveDTO.getModelCode())) { throw new ServiceException("模型编码不能为空"); } if (saveDTO.getParams() == null || saveDTO.getParams().isEmpty()) { throw new ServiceException("参数列表不能为空"); } // 2. 查询项目信息 CcdiProject project = projectMapper.selectById(saveDTO.getProjectId()); if (project == null) { throw new ServiceException("项目不存在"); } // 3. 复制默认参数(如果需要) if ("default".equals(project.getConfigType())) { int copiedCount = copyDefaultParamsToProject( saveDTO.getProjectId(), saveDTO.getModelCode() ); if (copiedCount == 0) { log.warn("系统默认参数为空,projectId={}, modelCode={}", saveDTO.getProjectId(), saveDTO.getModelCode()); } // 更新项目配置类型 project.setConfigType("custom"); projectMapper.updateById(project); } // 4. 更新参数值 for (ModelParamSaveDTO.ParamValueItem item : saveDTO.getParams()) { int updated = modelParamMapper.updateParamValue( saveDTO.getProjectId(), saveDTO.getModelCode(), item.getParamCode(), item.getParamValue() ); if (updated == 0) { log.warn("参数不存在或未更新,paramCode={}", item.getParamCode()); } } } catch (ServiceException e) { // 业务异常,直接抛出 throw e; } catch (Exception e) { // 系统异常,记录日志并抛出 log.error("保存模型参数失败", e); throw new ServiceException("保存模型参数失败:" + e.getMessage()); } } ``` ### 5.3 错误场景处理表 | 错误场景 | 处理方式 | |---------|---------| | 项目不存在 | 返回 404 错误,提示"项目不存在" | | 系统默认参数为空 | 记录警告日志,继续执行(允许项目自定义参数) | | 参数值验证失败 | 前端拦截,不提交到后端 | | 数据库连接失败 | 返回 500 错误,提示"系统异常,请稍后重试" | | 事务回滚 | 自动回滚所有操作,保证数据一致性 | --- ## 6. 测试策略 ### 6.1 后端单元测试 **测试类:** `CcdiModelParamServiceImplTest.java` **测试用例:** 1. `testSelectParamList_DefaultConfig()` - 测试查询默认配置项目的参数列表 2. `testSelectParamList_CustomConfig()` - 测试查询自定义配置项目的参数列表 3. `testSaveParams_FirstTimeSave()` - 测试首次保存参数(触发 default → custom 切换) 4. `testSaveParams_SecondTimeSave()` - 测试再次保存参数(已为 custom 模式) ### 6.2 前端集成测试 **测试脚本:** `test-param-config.sh` **测试流程:** 1. 登录获取 Token 2. 创建测试项目 3. 查询模型列表 4. 查询参数列表(default 模式) 5. 首次保存参数(触发切换) 6. 查询参数列表(custom 模式) 7. 查询项目信息(验证 configType) 8. 清理测试数据 ### 6.3 手动测试清单 | 编号 | 测试场景 | 预期结果 | 通过标准 | |------|---------|---------|---------| | 1 | 新项目查看参数配置 | 显示系统默认参数 | 参数值与系统默认一致 | | 2 | 新项目修改并保存参数 | 自动切换为自定义配置 | configType 变为 custom | | 3 | 再次查看参数 | 显示项目自定义参数 | 参数值为修改后的值 | | 4 | 再次修改参数 | 直接更新参数值 | 参数值更新成功 | | 5 | 切换模型 | 正确加载不同模型的参数 | 参数列表正确切换 | | 6 | 不修改任何参数点击保存 | 提示"没有需要保存的修改" | 不发起保存请求 | | 7 | 清空参数值后保存 | 前端验证拦截 | 显示错误提示 | | 8 | 并发保存同一参数 | 后保存的值生效 | 数据一致性 | | 9 | 网络异常时保存 | 显示错误提示 | 不更新页面数据 | | 10 | 项目状态为"已归档"时保存 | 根据业务规则处理 | 符合业务逻辑 | ### 6.4 性能测试 | 测试项 | 测试方法 | 性能目标 | |--------|---------|---------| | 查询参数列表 | 模拟 100 个项目同时查询 | 响应时间 < 500ms | | 首次保存参数 | 模拟 50 个项目同时首次保存 | 响应时间 < 2s | | 数据库查询性能 | EXPLAIN 分析 SQL | 使用索引,无全表扫描 | | 并发保存 | 10 个并发请求保存同一项目 | 无死锁,数据一致 | --- ## 7. 实施计划 ### 7.1 实施步骤 1. **后端开发** - 修改 `CcdiModelParamServiceImpl.selectParamList()` 方法 - 修改 `CcdiModelParamServiceImpl.saveParams()` 方法 - 新增 `copyDefaultParamsToProject()` 私有方法 - 新增 Mapper XML 中的 `updateParamValue` 和 `insertBatch` 方法 2. **前端开发** - 实现 `ParamConfig.vue` 组件 - 复用 `ccdi/modelParam.js` API 接口 - 确保组件正确接收 `projectId` 和 `projectInfo` props 3. **测试** - 编写后端单元测试 - 编写集成测试脚本 - 执行手动测试清单 4. **文档** - 更新 API 文档 - 更新用户手册 ### 7.2 风险评估 | 风险项 | 影响 | 概率 | 应对措施 | |--------|------|------|---------| | 并发保存导致数据不一致 | 高 | 低 | 使用事务隔离,数据库行锁 | | 系统默认参数缺失 | 中 | 低 | 记录日志,允许项目自定义 | | 前端缓存导致参数不更新 | 低 | 中 | 保存后重新加载参数列表 | | 大批量参数复制性能问题 | 中 | 低 | 使用批量插入,控制事务大小 | --- ## 8. 附录 ### 8.1 相关文件清单 **前端文件:** - `ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue` - 参数配置组件 - `ruoyi-ui/src/api/ccdi/modelParam.js` - API 接口(已存在,无需修改) **后端文件:** - `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java` - Service 实现 - `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiModelParamMapper.java` - Mapper 接口 - `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml` - Mapper XML **测试文件:** - `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiModelParamServiceImplTest.java` - 单元测试 - `docs/test-scripts/test-param-config.sh` - 集成测试脚本 ### 8.2 参考文档 - 若依框架官方文档 - MyBatis Plus 官方文档 - Element UI 官方文档 - 项目 CLAUDE.md 开发规范 --- **设计完成时间:** 2026-03-06 **下一步:** 创建详细实施计划