# 创建项目功能设计文档

**文档版本：** v1.0
**创建日期：** 2026-02-26
**设计人员：** Claude Code

---

## 1. 概述

### 1.1 功能描述

新增"创建项目"功能，允许用户在首页点击"新建项目"按钮后，通过弹窗表单创建新的纪检初核项目。

### 1.2 核心需求

- 弹窗包含3个字段：项目名称、项目描述、配置方式
- 配置方式为单选按钮：全局默认模型参数配置 / 自定义项目规则参数配置
- 项目列表展示项目名称和描述（上下排列）、状态、目标人数、预警人数、创建人、创建时间
- 预警人数为各级别风险人数之和，悬停显示详细分布

---

## 2. 数据库设计

### 2.1 表结构

**表名：** `ccdi_project`

**字段列表：**

| 字段名               | 类型       | 长度  | 必填 | 默认值               | 说明                           |
|-------------------|----------|-----|----|-------------------|------------------------------|
| project_id        | BIGINT   | -   | 是  | 自增                | 项目ID（主键）                     |
| project_name      | VARCHAR  | 100 | 是  | -                 | 项目名称                         |
| project_desc      | VARCHAR  | 500 | 否  | NULL              | 项目描述                         |
| config_type       | VARCHAR  | 20  | 是  | 'default'         | 配置方式：default-全局默认，custom-自定义 |
| project_status    | CHAR     | 1   | 是  | '0'               | 项目状态：0-进行中，1-已完成，2-已归档       |
| target_count      | INT      | -   | 是  | 0                 | 目标人数                         |
| high_risk_count   | INT      | -   | 是  | 0                 | 高风险人数                        |
| medium_risk_count | INT      | -   | 是  | 0                 | 中风险人数                        |
| low_risk_count    | INT      | -   | 是  | 0                 | 低风险人数                        |
| create_by         | VARCHAR  | 64  | 否  | ''                | 创建者                          |
| create_time       | DATETIME | -   | 否  | CURRENT_TIMESTAMP | 创建时间                         |
| update_by         | VARCHAR  | 64  | 否  | ''                | 更新者                          |
| update_time       | DATETIME | -   | 否  | CURRENT_TIMESTAMP | 更新时间                         |
| remark            | VARCHAR  | 500 | 否  | NULL              | 备注                           |

**索引设计：**

- 主键索引：`PRIMARY KEY (project_id)`
- 项目名称索引：`INDEX idx_project_name (project_name)`
- 项目状态索引：`INDEX idx_project_status (project_status)`
- 创建时间索引：`INDEX idx_create_time (create_time)`

### 2.2 SQL 脚本

```sql
-- 创建项目表
CREATE TABLE `ccdi_project` (
  `project_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '项目ID',
  `project_name` VARCHAR(100) NOT NULL COMMENT '项目名称',
  `project_desc` VARCHAR(500) DEFAULT NULL COMMENT '项目描述',
  `config_type` VARCHAR(20) NOT NULL DEFAULT 'default' COMMENT '配置方式：default-全局默认，custom-自定义',
  `project_status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '项目状态：0-进行中，1-已完成，2-已归档',
  `target_count` INT NOT NULL DEFAULT 0 COMMENT '目标人数',
  `high_risk_count` INT NOT NULL DEFAULT 0 COMMENT '高风险人数',
  `medium_risk_count` INT NOT NULL DEFAULT 0 COMMENT '中风险人数',
  `low_risk_count` INT NOT NULL DEFAULT 0 COMMENT '低风险人数',
  `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`project_id`),
  INDEX `idx_project_name` (`project_name`),
  INDEX `idx_project_status` (`project_status`),
  INDEX `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='纪检初核项目表';

-- 插入项目状态字典
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
VALUES ('项目状态', 'ccdi_project_status', '0', 'admin', NOW(), '纪检初核项目状态');

INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES
(1, '进行中', '0', 'ccdi_project_status', '', 'primary', 'Y', '0', 'admin', NOW()),
(2, '已完成', '1', 'ccdi_project_status', '', 'success', 'N', '0', 'admin', NOW()),
(3, '已归档', '2', 'ccdi_project_status', '', 'info', 'N', '0', 'admin', NOW());

-- 插入配置方式字典
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
VALUES ('配置方式', 'ccdi_config_type', '0', 'admin', NOW(), '项目配置方式');

INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES
(1, '全局默认模型参数配置', 'default', 'ccdi_config_type', '', 'primary', 'Y', '0', 'admin', NOW()),
(2, '自定义项目规则参数配置', 'custom', 'ccdi_config_type', '', 'warning', 'N', '0', 'admin', NOW());

-- 插入菜单权限
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES ('纪检初核管理', 0, 1, 'ccdi', NULL, 'M', '0', '0', '', 'monitor', 'admin', NOW());

SET @parent_id = LAST_INSERT_ID();

INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES ('项目管理', @parent_id, 1, 'project', 'ccdiProject/index', 'C', '0', '0', 'ccdi:project:list', 'project', 'admin', NOW());

SET @menu_id = LAST_INSERT_ID();

INSERT INTO sys_menu (menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
VALUES
('创建项目', @menu_id, 1, 'F', '0', '0', 'ccdi:project:add', 'admin', NOW()),
('编辑项目', @menu_id, 2, 'F', '0', '0', 'ccdi:project:edit', 'admin', NOW()),
('删除项目', @menu_id, 3, 'F', '0', '0', 'ccdi:project:remove', 'admin', NOW()),
('查询项目', @menu_id, 4, 'F', '0', '0', 'ccdi:project:query', 'admin', NOW());

-- 为管理员角色分配权限
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT 1, menu_id FROM sys_menu WHERE perms LIKE 'ccdi:project:%' OR perms = 'ccdi:project:list';
```

---

## 3. 后端架构设计

### 3.1 实体类

**类名：** `CcdiProject`
**位置：** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/`

```java
@Data
public class CcdiProject {
    /** 项目ID */
    private Long projectId;

    /** 项目名称 */
    private String projectName;

    /** 项目描述 */
    private String projectDesc;

    /** 配置方式：default-全局默认，custom-自定义 */
    private String configType;

    /** 项目状态：0-进行中，1-已完成，2-已归档 */
    private String projectStatus;

    /** 目标人数 */
    private Integer targetCount;

    /** 高风险人数 */
    private Integer highRiskCount;

    /** 中风险人数 */
    private Integer mediumRiskCount;

    /** 低风险人数 */
    private Integer lowRiskCount;

    /** 创建者 */
    private String createBy;

    /** 创建时间 */
    private Date createTime;

    /** 更新者 */
    private String updateBy;

    /** 更新时间 */
    private Date updateTime;

    /** 备注 */
    private String remark;
}
```

### 3.2 DTO 设计

**类名：** `CcdiProjectSaveDTO`
**位置：** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/`

```java
@Data
public class CcdiProjectSaveDTO {
    /** 项目名称（必填） */
    @NotBlank(message = "项目名称不能为空")
    @Length(max = 100, message = "项目名称长度不能超过100个字符")
    private String projectName;

    /** 项目描述（可选） */
    @Length(max = 500, message = "项目描述长度不能超过500个字符")
    private String projectDesc;

    /** 配置方式（必填）：default-全局默认，custom-自定义 */
    @NotBlank(message = "配置方式不能为空")
    private String configType;
}
```

### 3.3 VO 设计

**类名：** `CcdiProjectVO`
**位置：** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/`

```java
@Data
public class CcdiProjectVO {
    /** 项目ID */
    private Long projectId;

    /** 项目名称 */
    private String projectName;

    /** 项目描述 */
    private String projectDesc;

    /** 配置方式 */
    private String configType;

    /** 项目状态 */
    private String projectStatus;

    /** 目标人数 */
    private Integer targetCount;

    /** 高风险人数 */
    private Integer highRiskCount;

    /** 中风险人数 */
    private Integer mediumRiskCount;

    /** 低风险人数 */
    private Integer lowRiskCount;

    /** 创建时间 */
    private Date createTime;

    /** 创建者 */
    private String createBy;
}
```

### 3.4 Controller 接口

**类名：** `CcdiProjectController`
**位置：** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/controller/`

**接口列表：**

| 接口路径                        | 方法     | 说明         | 权限标识                  |
|-----------------------------|--------|------------|-----------------------|
| `/ccdi/project`             | POST   | 创建项目       | `ccdi:project:add`    |
| `/ccdi/project`             | PUT    | 更新项目       | `ccdi:project:edit`   |
| `/ccdi/project/{projectId}` | DELETE | 删除项目       | `ccdi:project:remove` |
| `/ccdi/project/{projectId}` | GET    | 查询项目详情     | `ccdi:project:query`  |
| `/ccdi/project/list`        | GET    | 查询项目列表（分页） | `ccdi:project:list`   |

**示例代码：**

```java
@RestController
@RequestMapping("/ccdi/project")
@Api(tags = "纪检初核项目管理")
public class CcdiProjectController extends BaseController {

    @Resource
    private ICcdiProjectService projectService;

    @PostMapping
    @ApiOperation("创建项目")
    @PreAuthorize("@ss.hasPermi('ccdi:project:add')")
    public AjaxResult createProject(@Validated @RequestBody CcdiProjectSaveDTO dto) {
        CcdiProjectVO vo = projectService.createProject(dto);
        return AjaxResult.success("项目创建成功", vo);
    }

    @GetMapping("/list")
    @ApiOperation("查询项目列表")
    @PreAuthorize("@ss.hasPermi('ccdi:project:list')")
    public TableDataInfo listProject(CcdiProjectQueryDTO queryDTO) {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Page<CcdiProjectVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
        Page<CcdiProjectVO> result = projectService.selectProjectPage(page, queryDTO);
        return getDataTable(result.getRecords(), result.getTotal());
    }
}
```

### 3.5 Service 层

**接口名：** `ICcdiProjectService`
**实现类名：** `CcdiProjectServiceImpl`
**位置：** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/`

```java
public interface ICcdiProjectService {
    /**
     * 创建项目
     * @param dto 项目保存DTO
     * @return 项目VO
     */
    CcdiProjectVO createProject(CcdiProjectSaveDTO dto);

    /**
     * 更新项目
     * @param dto 项目更新DTO
     * @return 项目VO
     */
    CcdiProjectVO updateProject(CcdiProjectSaveDTO dto);

    /**
     * 删除项目
     * @param projectId 项目ID
     * @return 是否成功
     */
    boolean deleteProject(Long projectId);

    /**
     * 查询项目详情
     * @param projectId 项目ID
     * @return 项目VO
     */
    CcdiProjectVO getProjectById(Long projectId);

    /**
     * 分页查询项目列表
     * @param page 分页对象
     * @param queryDTO 查询条件
     * @return 分页结果
     */
    Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO);
}
```

**实现类示例：**

```java
@Service
public class CcdiProjectServiceImpl implements ICcdiProjectService {

    @Resource
    private CcdiProjectMapper projectMapper;

    @Override
    public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) {
        CcdiProject project = new CcdiProject();
        BeanUtils.copyProperties(dto, project);

        // 设置默认值
        project.setProjectStatus("0"); // 进行中
        project.setTargetCount(0);
        project.setHighRiskCount(0);
        project.setMediumRiskCount(0);
        project.setLowRiskCount(0);

        projectMapper.insert(project);

        CcdiProjectVO vo = new CcdiProjectVO();
        BeanUtils.copyProperties(project, vo);
        return vo;
    }
}
```

### 3.6 Mapper 层

**接口名：** `CcdiProjectMapper`
**位置：** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/`

```java
public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
    /**
     * 分页查询项目列表
     * @param page 分页对象
     * @param queryDTO 查询条件
     * @return 分页结果
     */
    Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, @Param("queryDTO") CcdiProjectQueryDTO queryDTO);
}
```

**XML 文件：** `CcdiProjectMapper.xml`
**位置：** `ruoyi-info-collection/src/main/resources/mapper/info/collection/`

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiProjectMapper">

    <resultMap id="ProjectVOResultMap" type="com.ruoyi.info.collection.domain.vo.CcdiProjectVO">
        <id property="projectId" column="project_id"/>
        <result property="projectName" column="project_name"/>
        <result property="projectDesc" column="project_desc"/>
        <result property="configType" column="config_type"/>
        <result property="projectStatus" column="project_status"/>
        <result property="targetCount" column="target_count"/>
        <result property="highRiskCount" column="high_risk_count"/>
        <result property="mediumRiskCount" column="medium_risk_count"/>
        <result property="lowRiskCount" column="low_risk_count"/>
        <result property="createTime" column="create_time"/>
        <result property="createBy" column="create_by"/>
    </resultMap>

    <!-- 分页查询项目列表 -->
    <select id="selectProjectPage" resultMap="ProjectVOResultMap">
        SELECT
            project_id, project_name, project_desc, config_type,
            project_status, target_count, high_risk_count,
            medium_risk_count, low_risk_count, create_time, create_by
        FROM ccdi_project
        <where>
            <if test="queryDTO.projectName != null and queryDTO.projectName != ''">
                AND project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%')
            </if>
            <if test="queryDTO.projectStatus != null and queryDTO.projectStatus != ''">
                AND project_status = #{queryDTO.projectStatus}
            </if>
        </where>
        ORDER BY create_time DESC
    </select>
</mapper>
```

---

## 4. 前端架构设计

### 4.1 组件修改

**组件名称：** `AddProjectDialog.vue`
**位置：** `ruoyi-ui/src/views/ccdiProject/components/`

**修改内容：**

1. **简化表单字段**：只保留项目名称、项目描述、配置方式3个字段
2. **移除字段**：目标人员、开始日期、结束日期、目标人数、高级设置
3. **默认值**：配置方式默认为 `'default'`

**关键代码：**

```vue
<template>
  <el-dialog
    :visible.sync="dialogVisible"
    :title="title"
    width="600px"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
    @close="handleClose"
  >
    <el-form
      ref="projectForm"
      :model="formData"
      :rules="rules"
      label-width="100px"
      label-position="right"
    >
      <!-- 项目名称 -->
      <el-form-item label="项目名称" prop="projectName">
        <el-input
          v-model="formData.projectName"
          placeholder="请输入项目名称"
          maxlength="100"
          show-word-limit
        />
      </el-form-item>

      <!-- 项目描述 -->
      <el-form-item label="项目描述" prop="projectDesc">
        <el-input
          v-model="formData.projectDesc"
          type="textarea"
          :rows="4"
          placeholder="请输入项目描述"
          maxlength="500"
          show-word-limit
        />
      </el-form-item>

      <!-- 配置方式 -->
      <el-form-item label="配置方式" prop="configType">
        <el-radio-group v-model="formData.configType">
          <el-radio label="default">全局默认模型参数配置</el-radio>
          <el-radio label="custom">自定义项目规则参数配置</el-radio>
        </el-radio-group>
      </el-form-item>
    </el-form>

    <div slot="footer" class="dialog-footer">
      <el-button @click="handleClose">取 消</el-button>
      <el-button type="primary" :loading="submitting" @click="handleSubmit">
        创建项目
      </el-button>
    </div>
  </el-dialog>
</template>

<script>
import { createProject } from '@/api/ccdiProject'

export default {
  name: 'AddProjectDialog',
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    title: {
      type: String,
      default: '新建项目'
    }
  },
  data() {
    return {
      submitting: false,
      formData: {
        projectName: '',
        projectDesc: '',
        configType: 'default'
      },
      rules: {
        projectName: [
          { required: true, message: '请输入项目名称', trigger: 'blur' },
          { min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
        ],
        configType: [
          { required: true, message: '请选择配置方式', trigger: 'change' }
        ]
      }
    }
  },
  computed: {
    dialogVisible: {
      get() {
        return this.visible
      },
      set(val) {
        if (!val) {
          this.handleClose()
        }
      }
    }
  },
  methods: {
    handleSubmit() {
      this.$refs.projectForm.validate(valid => {
        if (valid) {
          this.submitting = true
          createProject(this.formData).then(response => {
            this.$message.success('项目创建成功')
            this.submitting = false
            this.$emit('submit', response.data)
            this.handleClose()
          }).catch(() => {
            this.submitting = false
          })
        }
      })
    },

    handleClose() {
      this.$emit('close')
      this.$refs.projectForm.resetFields()
      this.formData = {
        projectName: '',
        projectDesc: '',
        configType: 'default'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.dialog-footer {
  text-align: right;

  .el-button + .el-button {
    margin-left: 8px;
  }
}

:deep(.el-radio-group) {
  display: flex;
  flex-direction: column;
  gap: 12px;

  .el-radio {
    line-height: 32px;
  }
}
</style>
```

### 4.2 项目列表表格

**组件名称：** `ProjectTable.vue`
**位置：** `ruoyi-ui/src/views/ccdiProject/components/`

**关键特性：**

1. **项目名称和描述上下排列**：同一单元格内，项目名称加粗深色，项目描述常规浅色
2. **预警人数悬停提示**：显示高、中、低风险人数详细分布
3. **预警人数样式**：根据风险级别自动调整颜色

**表格列配置：**

| 列名   | 宽度      | 对齐方式 | 说明                    |
|------|---------|------|-----------------------|
| 项目名称 | 最小300px | 左对齐  | 包含项目名称（上）+项目描述（下），自适应 |
| 项目状态 | 100px   | 居中对齐 | 固定宽度                  |
| 目标人数 | 100px   | 居中对齐 | 固定宽度                  |
| 预警人数 | 120px   | 居中对齐 | 悬停显示详细风险分布            |
| 创建人  | 120px   | 居中对齐 | 固定宽度                  |
| 创建时间 | 160px   | 居中对齐 | 格式化显示                 |
| 操作   | 280px   | 居中对齐 | 固定在右侧                 |

**关键代码：**

```vue
<!-- 项目名称（含描述） -->
<el-table-column label="项目名称" min-width="300" align="left">
  <template slot-scope="scope">
    <div class="project-info-cell">
      <div class="project-name">{{ scope.row.projectName }}</div>
      <div class="project-desc">{{ scope.row.projectDesc || '暂无描述' }}</div>
    </div>
  </template>
</el-table-column>

<!-- 预警人数（带悬停详情） -->
<el-table-column label="预警人数" width="120" align="center">
  <template slot-scope="scope">
    <el-tooltip placement="top" effect="light">
      <div slot="content">
        <div style="padding: 8px;">
          <div style="margin-bottom: 8px; font-weight: bold; color: #303133;">
            风险人数统计
          </div>
          <div style="margin-bottom: 6px;">
            <span style="color: #f56c6c;">● 高风险：</span>
            <span style="font-weight: bold;">{{ scope.row.highRiskCount }} 人</span>
          </div>
          <div style="margin-bottom: 6px;">
            <span style="color: #e6a23c;">● 中风险：</span>
            <span style="font-weight: bold;">{{ scope.row.mediumRiskCount }} 人</span>
          </div>
          <div>
            <span style="color: #909399;">● 低风险：</span>
            <span style="font-weight: bold;">{{ scope.row.lowRiskCount }} 人</span>
          </div>
        </div>
      </div>
      <div class="warning-count-wrapper">
        <span :class="getWarningClass(scope.row)" style="cursor: pointer;">
          {{ scope.row.highRiskCount + scope.row.mediumRiskCount + scope.row.lowRiskCount }}
        </span>
      </div>
    </el-tooltip>
  </template>
</el-table-column>
```

**样式代码：**

```scss
.project-info-cell {
  padding: 8px 0;
  line-height: 1.5;

  .project-name {
    font-size: 14px;
    font-weight: 600;
    color: #303133;
    margin-bottom: 4px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .project-desc {
    font-size: 12px;
    color: #909399;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.text-danger {
  color: #f56c6c;
}

.text-warning {
  color: #e6a23c;
}

.text-info {
  color: #909399;
}

.text-bold {
  font-weight: bold;
}
```

**预警人数样式规则：**

- 高风险 > 0：红色加粗
- 中风险 > 0：橙色加粗
- 低风险 > 0：灰色
- 无预警：普通黑色

### 4.3 API 接口

**文件名：** `ccdiProject.js`
**位置：** `ruoyi-ui/src/api/`

```javascript
import request from '@/utils/request'

// 创建初核项目
export function createProject(data) {
  return request({
    url: '/ccdi/project',
    method: 'post',
    data: data
  })
}

// 查询初核项目列表（分页）
export function listProject(query) {
  return request({
    url: '/ccdi/project/list',
    method: 'get',
    params: query
  })
}

// 查询初核项目详细
export function getProject(projectId) {
  return request({
    url: '/ccdi/project/' + projectId,
    method: 'get'
  })
}

// 修改初核项目
export function updateProject(data) {
  return request({
    url: '/ccdi/project',
    method: 'put',
    data: data
  })
}

// 删除初核项目
export function delProject(projectId) {
  return request({
    url: '/ccdi/project/' + projectId,
    method: 'delete'
  })
}
```

---

## 5. 实施计划

### 5.1 实施步骤

#### 阶段一：数据库与后端开发（预计 2.5 小时）

1. **创建数据库表**（15 分钟）
    - 执行 `ccdi_project` 表创建脚本
    - 插入字典数据和菜单数据

2. **后端开发**（2 小时）
    - 创建实体类 `CcdiProject`
    - 创建 DTO `CcdiProjectSaveDTO`
    - 创建 VO `CcdiProjectVO`
    - 创建 Mapper 接口和 XML
    - 创建 Service 接口和实现类
    - 创建 Controller 接口
    - 添加 Swagger 注解

3. **后端测试**（30 分钟）
    - 使用 Swagger 测试创建项目接口
    - 使用 Swagger 测试查询项目列表接口
    - 验证数据字典显示

#### 阶段二：前端开发（预计 2.5 小时）

4. **前端组件开发**（1.5 小时）
    - 修改 `AddProjectDialog.vue` 组件
    - 修改 `ProjectTable.vue` 组件
    - 更新 API 接口文件 `ccdiProject.js`
    - 修改父组件调用逻辑

5. **前端联调**（1 小时）
    - 测试创建项目功能
    - 测试项目列表显示
    - 测试预警人数悬停提示
    - 测试字典数据展示

---

## 6. 注意事项

### 6.1 数据完整性

- 创建项目时，`project_status` 默认为 `'0'`（进行中）
- 创建项目时，风险计数字段默认为 `0`
- `config_type` 默认为 `'default'`
- 项目名称和描述不能为空

### 6.2 权限控制

- 创建项目需要 `ccdi:project:add` 权限
- 编辑项目需要 `ccdi:project:edit` 权限
- 删除项目需要 `ccdi:project:remove` 权限
- 查询项目需要 `ccdi:project:list` 权限

### 6.3 前端验证

- 项目名称：必填，2-100字符
- 项目描述：可选，最多500字符
- 配置方式：必填，只能选择 `default` 或 `custom`

### 6.4 后端验证

- 使用 `@Validated` 注解进行参数校验
- 项目名称长度校验
- 配置方式枚举值校验

### 6.5 性能优化

- 项目列表分页查询
- 项目名称和状态字段添加索引
- 字典数据使用缓存

### 6.6 用户体验

- 提交按钮显示 loading 状态
- 创建成功后自动刷新列表
- 预警人数悬停提示详细信息
- 项目名称和描述上下排列，层次分明

---

## 7. 测试清单

### 7.1 后端测试

- [ ] 创建项目成功（必填字段）
- [ ] 创建项目失败（缺少必填字段）
- [ ] 创建项目失败（字段长度超限）
- [ ] 查询项目列表（分页）
- [ ] 查询项目详情
- [ ] 更新项目
- [ ] 删除项目
- [ ] 字典数据正确返回
- [ ] 权限验证正确

### 7.2 前端测试

- [ ] 弹窗显示正确（3个字段）
- [ ] 表单验证正常（必填项）
- [ ] 表单验证正常（长度限制）
- [ ] 项目名称和描述上下排列
- [ ] 项目名称样式正确（加粗深色）
- [ ] 项目描述样式正确（常规浅色）
- [ ] 项目状态标签正确显示
- [ ] 预警人数计算正确（高+中+低）
- [ ] 预警人数悬停提示显示
- [ ] 预警人数颜色根据风险级别变化
- [ ] 创建人正确显示
- [ ] 创建时间格式化正确
- [ ] 操作按钮权限控制
- [ ] 提交按钮 loading 状态
- [ ] 创建成功后列表刷新

---

## 8. 附录

### 8.1 参考文档

- 若依框架官方文档
- Element UI 组件库文档
- MyBatis Plus 官方文档

### 8.2 相关文件

- 数据库脚本：`sql/ccdi_project.sql`
- 设计截图：`doc/创建项目功能/ScreenShot_2026-02-26_153149_900.png`
- 设计截图：`doc/创建项目功能/ScreenShot_2026-02-26_162233_965.png`

---

**文档结束**
