Files
ccdi/docs/plans/2026-03-06-project-param-config-implementation.md

19 KiB
Raw Blame History

项目详情参数配置页面实施计划

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: 在项目详情页面实现参数配置功能,允许每个项目自定义模型参数,首次保存时自动从系统默认参数复制。

Architecture: 前端组件复用独立页面代码,后端修改 Service 根据 configType 返回对应参数,首次保存时自动复制默认参数并切换 configType。

Tech Stack: Spring Boot 3, MyBatis Plus, Vue.js 2, Element UI


Task 1: 修改后端 Mapper 接口

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiModelParamMapper.java

Step 1: 添加更新参数值方法

CcdiModelParamMapper.java 接口中添加方法:

/**
 * 更新参数值
 *
 * @param projectId 项目ID
 * @param modelCode 模型编码
 * @param paramCode 参数编码
 * @param paramValue 参数值
 * @return 影响行数
 */
int updateParamValue(@Param("projectId") Long projectId,
                     @Param("modelCode") String modelCode,
                     @Param("paramCode") String paramCode,
                     @Param("paramValue") String paramValue);

/**
 * 批量插入参数
 *
 * @param params 参数列表
 * @return 影响行数
 */
int insertBatch(@Param("list") List<CcdiModelParam> params);

Step 2: 提交

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiModelParamMapper.java
git commit -m "feat: 添加 Mapper 接口方法 updateParamValue 和 insertBatch"

Task 2: 修改后端 Mapper XML

Files:

  • Modify: ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml

Step 1: 添加 updateParamValue SQL

</mapper> 标签之前添加:

<!-- 更新参数值 -->
<update id="updateParamValue">
    UPDATE ccdi_model_param
    SET param_value = #{paramValue},
        update_time = NOW()
    WHERE project_id = #{projectId}
      AND model_code = #{modelCode}
      AND param_code = #{paramCode}
</update>

<!-- 批量插入参数 -->
<insert id="insertBatch" parameterType="java.util.List">
    INSERT INTO ccdi_model_param (
        project_id, model_code, model_name, param_code, param_name,
        param_desc, param_value, param_unit, sort_order, remark,
        create_time, update_time
    ) VALUES
    <foreach collection="list" item="item" separator=",">
        (
            #{item.projectId}, #{item.modelCode}, #{item.modelName},
            #{item.paramCode}, #{item.paramName}, #{item.paramDesc},
            #{item.paramValue}, #{item.paramUnit}, #{item.sortOrder},
            #{item.remark}, NOW(), NOW()
        )
    </foreach>
</insert>

Step 2: 提交

git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml
git commit -m "feat: 添加 Mapper XML SQL updateParamValue 和 insertBatch"

Task 3: 注入 ProjectMapper 依赖

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java

Step 1: 添加 import 语句

在文件顶部的 import 区域添加:

import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Step 2: 添加 Logger

在类开始处添加:

private static final Logger log = LoggerFactory.getLogger(CcdiModelParamServiceImpl.class);

Step 3: 注入 ProjectMapper

@Resource private CcdiModelParamMapper modelParamMapper; 之后添加:

@Resource
private CcdiProjectMapper projectMapper;

Step 4: 提交

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
git commit -m "feat: 注入 CcdiProjectMapper 依赖"

Task 4: 修改 selectParamList 方法

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java:52-71

Step 1: 替换 selectParamList 方法

完全替换 selectParamList 方法:

@Override
public List<ModelParamVO> selectParamList(ModelParamQueryDTO queryDTO) {
    // 1. 参数验证
    Long projectId = queryDTO.getProjectId();
    if (projectId == null) {
        projectId = 0L;
    }

    // 2. 如果是项目查询projectId > 0需要根据 configType 决定查询哪组参数
    Long effectiveProjectId = projectId;
    if (projectId > 0) {
        // 查询项目信息
        CcdiProject project = projectMapper.selectById(projectId);
        if (project == null) {
            throw new ServiceException("项目不存在");
        }

        // 根据 configType 决定查询哪组参数
        if ("default".equals(project.getConfigType())) {
            // 使用系统默认参数
            effectiveProjectId = 0L;
        } else {
            // 使用项目自定义参数
            effectiveProjectId = projectId;
        }
    }

    // 3. 查询参数列表
    List<CcdiModelParam> params = modelParamMapper.selectByProjectAndModel(
        effectiveProjectId,
        queryDTO.getModelCode()
    );

    // 4. 转换为 VO
    List<ModelParamVO> result = new ArrayList<>();
    params.forEach(param -> {
        ModelParamVO vo = new ModelParamVO();
        BeanUtils.copyProperties(param, vo);
        result.add(vo);
    });

    return result;
}

Step 2: 提交

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
git commit -m "feat: 修改 selectParamList 方法支持根据 configType 返回对应参数"

Task 5: 添加 copyDefaultParamsToProject 私有方法

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java

Step 1: 添加复制参数方法

saveParams 方法之后添加:

/**
 * 复制系统默认参数到项目
 *
 * @param projectId 项目ID
 * @param modelCode 模型编码
 * @return 复制的参数数量
 */
private int copyDefaultParamsToProject(Long projectId, String modelCode) {
    // 查询系统默认参数
    List<CcdiModelParam> defaultParams = modelParamMapper.selectByProjectAndModel(0L, modelCode);

    if (defaultParams.isEmpty()) {
        log.warn("系统默认参数为空modelCode={}", modelCode);
        return 0;
    }

    // 复制到项目
    List<CcdiModelParam> 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());

    // 批量插入
    int count = modelParamMapper.insertBatch(projectParams);

    log.info("复制系统默认参数到项目成功projectId={}, modelCode={}, count={}",
        projectId, modelCode, count);

    return count;
}

Step 2: 提交

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
git commit -m "feat: 添加 copyDefaultParamsToProject 私有方法"

Task 6: 修改 saveParams 方法

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java:74-122

Step 1: 替换 saveParams 方法

完全替换 saveParams 方法:

@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("参数列表不能为空");
        }

        Long projectId = saveDTO.getProjectId();

        // 2. 如果是项目保存projectId > 0需要检查是否首次保存
        if (projectId > 0) {
            // 查询项目信息
            CcdiProject project = projectMapper.selectById(projectId);
            if (project == null) {
                throw new ServiceException("项目不存在");
            }

            // 3. 如果是首次保存configType=default需要复制系统默认参数
            if ("default".equals(project.getConfigType())) {
                int copiedCount = copyDefaultParamsToProject(projectId, saveDTO.getModelCode());
                if (copiedCount == 0) {
                    log.warn("系统默认参数为空projectId={}, modelCode={}",
                        projectId, saveDTO.getModelCode());
                }

                // 更新项目配置类型为 custom
                project.setConfigType("custom");
                projectMapper.updateById(project);

                log.info("项目配置类型已更新为 customprojectId={}", projectId);
            }
        }

        // 4. 更新参数值
        String username = SecurityUtils.getUsername();
        for (ModelParamSaveDTO.ParamValueItem item : saveDTO.getParams()) {
            int updated = modelParamMapper.updateParamValue(
                projectId,
                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());
    }
}

Step 2: 提交

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
git commit -m "feat: 修改 saveParams 方法支持首次保存自动复制默认参数"

Task 7: 实现前端 ParamConfig 组件(模板部分)

Files:

  • Modify: ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue

Step 1: 替换模板部分

完全替换文件内容:

<template>
  <div class="param-config-container">
    <!-- 模型选择区域 -->
    <div class="filter-section">
      <el-form :inline="true" :model="queryParams">
        <el-form-item label="模型名称">
          <el-select
            v-model="queryParams.modelCode"
            placeholder="请选择模型"
            @change="handleModelChange"
          >
            <el-option
              v-for="model in modelList"
              :key="model.modelCode"
              :label="model.modelName"
              :value="model.modelCode"
            />
          </el-select>
        </el-form-item>
      </el-form>
    </div>

    <!-- 参数配置表格 -->
    <div class="table-section">
      <h3>阈值参数配置</h3>
      <el-table :data="paramList" border style="width: 100%">
        <el-table-column label="监测项" prop="paramName" width="200" />
        <el-table-column label="描述" prop="paramDesc" />
        <el-table-column label="阈值设置" width="200">
          <template #default="{ row }">
            <el-input
              v-model="row.paramValue"
              placeholder="请输入阈值"
              @input="markAsModified(row)"
            />
          </template>
        </el-table-column>
        <el-table-column label="单位" prop="paramUnit" width="120" />
      </el-table>
    </div>

    <!-- 操作按钮 -->
    <div class="button-section">
      <el-button
        type="primary"
        @click="handleSave"
        :loading="saving"
      >
        保存配置
      </el-button>
    </div>
  </div>
</template>

Step 2: 提交

git add ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue
git commit -m "feat: 实现 ParamConfig 组件模板部分"

Task 8: 实现前端 ParamConfig 组件(脚本部分)

Files:

  • Modify: ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue

Step 1: 添加脚本部分

</template> 之后添加:


<script>
import { listModels, listParams, saveParams } from "@/api/ccdi/modelParam";

export default {
  name: 'ParamConfig',
  props: {
    projectId: {
      type: [String, Number],
      required: true
    },
    projectInfo: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      modelList: [],
      queryParams: {
        modelCode: undefined,
        projectId: this.projectId
      },
      paramList: [],
      saving: false
    }
  },
  watch: {
    projectId(newVal) {
      this.queryParams.projectId = newVal
      this.loadModelList()
    }
  },
  created() {
    this.loadModelList()
  },
  methods: {
    /** 加载模型列表 */
    async loadModelList() {
      try {
        const res = await listModels({ projectId: this.projectId })
        this.modelList = res.data
        if (this.modelList.length > 0) {
          this.queryParams.modelCode = this.modelList[0].modelCode
          this.loadParamList()
        }
      } catch (error) {
        this.$message.error('加载模型列表失败:' + error.message)
        console.error('加载模型列表失败', error)
      }
    },

    /** 加载参数列表 */
    async loadParamList() {
      try {
        const res = await listParams(this.queryParams)
        this.paramList = res.data
      } catch (error) {
        this.$message.error('加载参数列表失败:' + error.message)
        console.error('加载参数列表失败', error)
      }
    },

    /** 模型切换 */
    handleModelChange() {
      this.loadParamList()
    },

    /** 标记为已修改 */
    markAsModified(row) {
      row.modified = true
    },

    /** 保存配置 */
    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
      }

      // 构造保存数据
      const saveDTO = {
        projectId: this.projectId,
        modelCode: this.queryParams.modelCode,
        params: modifiedParams.map(item => ({
          paramCode: item.paramCode,
          paramValue: item.paramValue
        }))
      }

      // 保存
      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
      }
    }
  }
}
</script>

Step 2: 提交

git add ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue
git commit -m "feat: 实现 ParamConfig 组件脚本部分"

Task 9: 实现前端 ParamConfig 组件(样式部分)

Files:

  • Modify: ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue

Step 1: 添加样式部分

</script> 之后添加:


<style scoped lang="scss">
.param-config-container {
  padding: 20px;
  background-color: #fff;
  min-height: 400px;
}

.filter-section {
  padding: 15px;
  background: #fff;
  border-radius: 4px;
  margin-bottom: 20px;
}

.table-section {
  padding: 20px;
  background: #fff;
  border-radius: 4px;
  margin-bottom: 20px;

  h3 {
    font-size: 16px;
    font-weight: bold;
    color: #333;
    margin: 0 0 15px 0;
  }
}

.button-section {
  padding: 15px;
  background: #fff;
  border-radius: 4px;
  text-align: left;
}
</style>

Step 2: 提交

git add ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue
git commit -m "feat: 实现 ParamConfig 组件样式部分"

Task 10: 手动测试功能

Step 1: 启动后端服务

提示用户手动启动后端服务(不要自动运行)。

Step 2: 启动前端服务

cd ruoyi-ui
npm run dev

Step 3: 访问测试页面

  1. 访问 http://localhost:80
  2. 登录系统admin/admin123
  3. 进入"项目管理"页面
  4. 点击任意项目的"详情"按钮
  5. 点击"参数配置"菜单

Step 4: 测试场景 1 - 查看默认配置

操作:

  • 新项目查看参数配置

预期结果:

  • 显示系统默认参数
  • 参数值与系统默认一致

Step 5: 测试场景 2 - 首次保存参数

操作:

  • 修改参数值
  • 点击"保存配置"

预期结果:

  • 显示"保存成功"提示
  • 项目的 configType 变为 custom
  • 再次查看参数显示修改后的值

Step 6: 测试场景 3 - 切换模型

操作:

  • 切换到另一个模型

预期结果:

  • 参数列表正确切换
  • 显示新模型的参数

Step 7: 测试场景 4 - 不修改参数点击保存

操作:

  • 不修改任何参数
  • 点击"保存配置"

预期结果:

  • 显示"没有需要保存的修改"提示

Step 8: 测试场景 5 - 清空参数值后保存

操作:

  • 清空某个参数值
  • 点击"保存配置"

预期结果:

  • 显示"请填写所有参数值"错误提示
  • 不发起保存请求

Step 9: 提交测试完成

git add .
git commit -m "test: 项目详情参数配置功能手动测试完成"

完成检查清单

  • 后端 Mapper 接口已修改
  • 后端 Mapper XML 已修改
  • 后端 Service 已修改
  • 前端 ParamConfig 组件已实现
  • 所有测试场景通过
  • 代码已提交到 git

注意事项

  1. 不要自动启动后端服务 - 提示用户手动启动
  2. 不需要后端单元测试 - 用户明确要求
  3. 首次保存会触发复制 - 确保系统默认参数存在
  4. 事务回滚 - 如果复制失败,事务会自动回滚
  5. 前端验证优先 - 参数值验证在前端完成