# 创建项目功能 - 前端实施计划

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

**目标:** 实现创建项目功能的前端界面，包括弹窗表单、项目列表展示、API调用

**架构:** 基于 Vue 2.6.12 + Element UI 2.15.14，采用组件化开发

**技术栈:** Vue.js 2.6.12, Element UI 2.15.14, Axios 0.28.1

---

## 前置条件

- 后端接口已部署并测试通过
- 前端项目依赖已安装
- 已有测试账号（admin/admin123）
- 后端服务运行在 http://localhost:8080

---

## Task 1: 更新 API 接口文件

**文件:**

- Modify: `ruoyi-ui/src/api/ccdiProject.js`

**Step 1: 备份原文件**

```bash
cp ruoyi-ui/src/api/ccdiProject.js ruoyi-ui/src/api/ccdiProject.js.bak
```

**Step 2: 修改 API 文件**

将 `ruoyi-ui/src/api/ccdiProject.js` 修改为以下内容：

```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'
  })
}

// Mock数据：获取项目列表（保留用于测试）
export function getMockProjectList() {
  return Promise.resolve({
    code: 200,
    total: 3,
    rows: [
      {
        projectId: 1,
        projectName: '2024年Q1初核',
        projectDesc: '2024年第一季度纪检初核排查工作',
        createTime: '2024-01-01',
        projectStatus: '0',
        configType: 'default',
        targetCount: 500,
        highRiskCount: 5,
        mediumRiskCount: 10,
        lowRiskCount: 0
      },
      {
        projectId: 2,
        projectName: '2023年Q4初核',
        projectDesc: '2023年第四季度纪检初核排查工作',
        createTime: '2023-10-01',
        projectStatus: '1',
        configType: 'custom',
        targetCount: 480,
        highRiskCount: 8,
        mediumRiskCount: 15,
        lowRiskCount: 0
      },
      {
        projectId: 3,
        projectName: '2023年Q3初核',
        projectDesc: '2023年第三季度纪检初核排查工作',
        createTime: '2023-07-01',
        projectStatus: '2',
        configType: 'default',
        targetCount: 450,
        highRiskCount: 0,
        mediumRiskCount: 18,
        lowRiskCount: 5
      }
    ]
  })
}
```

**Step 3: 验证语法**

```bash
cd ruoyi-ui && npm run lint -- --fix src/api/ccdiProject.js
```

预期输出：无 ESLint 错误

**Step 4: 提交代码**

```bash
git add ruoyi-ui/src/api/ccdiProject.js
git commit -m "feat: 更新项目API接口，添加创建项目接口"
```

---

## Task 2: 修改 AddProjectDialog 组件

**文件:**

- Modify: `ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue`

**Step 1: 备份原文件**

```bash
cp ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue.bak
```

**Step 2: 重写组件**

将 `ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue` 重写为以下内容：

```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: '新建项目'
    },
    form: {
      type: Object,
      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()
        }
      }
    }
  },
  watch: {
    form: {
      handler(newVal) {
        if (newVal && Object.keys(newVal).length > 0) {
          this.formData = { ...this.formData, ...newVal }
        }
      },
      immediate: true,
      deep: true
    },
    visible(val) {
      if (val) {
        this.$nextTick(() => {
          if (this.$refs.projectForm) {
            this.$refs.projectForm.clearValidate()
          }
        })
      }
    }
  },
  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>
```

**Step 3: 验证语法**

```bash
cd ruoyi-ui && npm run lint -- --fix src/views/ccdiProject/components/AddProjectDialog.vue
```

预期输出：无 ESLint 错误

**Step 4: 提交代码**

```bash
git add ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue
git commit -m "feat: 简化项目创建弹窗，只保留3个核心字段"
```

---

## Task 3: 修改 ProjectTable 组件

**文件:**

- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`

**Step 1: 备份原文件**

```bash
cp ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue.bak
```

**Step 2: 重写组件**

将 `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue` 重写为以下内容：

```vue
<template>
  <div class="project-table-container">
    <el-table
      :data="dataList"
      :loading="loading"
      border
      style="width: 100%"
    >
      <!-- 项目名称（含描述） -->
      <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
        prop="projectStatus"
        label="项目状态"
        width="100"
        align="center"
      >
        <template slot-scope="scope">
          <el-tag :type="getStatusType(scope.row.projectStatus)">
            <dict-tag :options="dict.type.ccdi_project_status" :value="scope.row.projectStatus"/>
          </el-tag>
        </template>
      </el-table-column>

      <!-- 目标人数 -->
      <el-table-column
        prop="targetCount"
        label="目标人数"
        width="100"
        align="center"
      />

      <!-- 预警人数（带悬停详情） -->
      <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>

      <!-- 创建人 -->
      <el-table-column
        prop="createBy"
        label="创建人"
        width="120"
        align="center"
      />

      <!-- 创建时间 -->
      <el-table-column
        prop="createTime"
        label="创建时间"
        width="160"
        align="center"
      >
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.createTime) }}</span>
        </template>
      </el-table-column>

      <!-- 操作列 -->
      <el-table-column
        label="操作"
        width="200"
        align="center"
        fixed="right"
      >
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            @click="handleDetail(scope.row)"
          >详情</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleEdit(scope.row)"
          >编辑</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <el-pagination
      v-show="total > 0"
      :current-page="pageParams.pageNum"
      :page-size="pageParams.pageSize"
      :page-sizes="[10, 20, 30, 50]"
      :total="total"
      layout="total, sizes, prev, pager, next, jumper"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      style="margin-top: 16px; text-align: right;"
    />
  </div>
</template>

<script>
export default {
  name: 'ProjectTable',
  dicts: ['ccdi_project_status', 'ccdi_config_type'],
  props: {
    dataList: {
      type: Array,
      default: () => []
    },
    loading: {
      type: Boolean,
      default: false
    },
    total: {
      type: Number,
      default: 0
    },
    pageParams: {
      type: Object,
      default: () => ({
        pageNum: 1,
        pageSize: 10
      })
    }
  },
  methods: {
    getStatusType(status) {
      const statusMap = {
        '0': 'primary',  // 进行中
        '1': 'success',  // 已完成
        '2': 'info'      // 已归档
      }
      return statusMap[status] || 'info'
    },

    getWarningClass(row) {
      const total = row.highRiskCount + row.mediumRiskCount + row.lowRiskCount
      if (row.highRiskCount > 0) {
        return 'text-danger text-bold'
      } else if (row.mediumRiskCount > 0) {
        return 'text-warning text-bold'
      } else if (total > 0) {
        return 'text-info'
      }
      return ''
    },

    handleDetail(row) {
      this.$emit('detail', row)
    },

    handleEdit(row) {
      this.$emit('edit', row)
    },

    handleDelete(row) {
      this.$emit('delete', row)
    },

    handleSizeChange(val) {
      this.$emit('pagination', { pageNum: this.pageParams.pageNum, pageSize: val })
    },

    handleCurrentChange(val) {
      this.$emit('pagination', { pageNum: val, pageSize: this.pageParams.pageSize })
    }
  }
}
</script>

<style lang="scss" scoped>
.project-table-container {
  margin-top: 16px;
}

.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;
  }
}

.warning-count-wrapper {
  display: inline-block;
}

.text-danger {
  color: #f56c6c;
}

.text-warning {
  color: #e6a23c;
}

.text-info {
  color: #909399;
}

.text-bold {
  font-weight: bold;
}
</style>
```

**Step 3: 验证语法**

```bash
cd ruoyi-ui && npm run lint -- --fix src/views/ccdiProject/components/ProjectTable.vue
```

预期输出：无 ESLint 错误

**Step 4: 提交代码**

```bash
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
git commit -m "feat: 优化项目列表表格，添加预警人数悬停提示"
```

---

## Task 4: 修改父组件 index.vue

**文件:**

- Modify: `ruoyi-ui/src/views/ccdiProject/index.vue`

**Step 1: 备份原文件**

```bash
cp ruoyi-ui/src/views/ccdiProject/index.vue ruoyi-ui/src/views/ccdiProject/index.vue.bak
```

**Step 2: 修改父组件**

将 `ruoyi-ui/src/views/ccdiProject/index.vue` 的 `getList` 和 `handleSubmitProject` 方法修改为：

```javascript
/** 查询项目列表 */
getList() {
  this.loading = true
  // 使用真实API
  listProject(this.queryParams).then(response => {
    this.projectList = response.rows
    this.total = response.total
    this.loading = false
  }).catch(() => {
    this.loading = false
  })
},

/** 提交项目表单 */
handleSubmitProject(data) {
  // 不需要再次调用API，因为AddProjectDialog已经处理了
  this.addDialogVisible = false
  this.getList() // 刷新列表
}
```

**Step 3: 验证语法**

```bash
cd ruoyi-ui && npm run lint -- --fix src/views/ccdiProject/index.vue
```

预期输出：无 ESLint 错误

**Step 4: 提交代码**

```bash
git add ruoyi-ui/src/views/ccdiProject/index.vue
git commit -m "feat: 修改父组件，切换为真实API调用"
```

---

## Task 5: 启动前端并测试

**Step 1: 启动前端开发服务器**

```bash
cd ruoyi-ui && npm run dev
```

预期输出：前端服务启动成功，访问地址 http://localhost:80

**Step 2: 测试登录**

浏览器访问 http://localhost:80，使用测试账号登录：

- 用户名：admin
- 密码：admin123

预期结果：登录成功，进入首页

**Step 3: 测试项目列表**

导航到"纪检初核管理 > 项目管理"菜单：

预期结果：

- 项目列表正常显示
- 项目名称和描述上下排列
- 项目状态标签显示正确
- 预警人数悬停提示显示风险详情

**Step 4: 测试创建项目**

点击"新建项目"按钮：

预期结果：

- 弹窗正常打开
- 显示3个字段（项目名称、项目描述、配置方式）
- 配置方式默认选中"全局默认模型参数配置"

填写表单：

- 项目名称：测试项目001
- 项目描述：这是测试项目的描述
- 配置方式：选择"自定义项目规则参数配置"

点击"创建项目"按钮：

预期结果：

- 按钮显示 loading 状态
- 创建成功，提示"项目创建成功"
- 弹窗关闭
- 项目列表自动刷新，显示新创建的项目

**Step 5: 测试预警人数悬停**

在项目列表中，将鼠标悬停在预警人数上：

预期结果：

- 显示风险人数统计提示框
- 显示高风险、中风险、低风险人数
- 预警人数颜色根据风险级别变化

**Step 6: 测试表单验证**

不填写项目名称，直接点击"创建项目"：

预期结果：

- 提示"请输入项目名称"
- 表单不提交

**Step 7: 测试取消按钮**

点击"新建项目"，然后点击"取消"：

预期结果：

- 弹窗关闭
- 表单数据清空

---

## Task 6: 跨浏览器测试

**Step 1: Chrome 测试**

在 Chrome 浏览器中重复 Task 5 的所有测试：

预期结果：所有功能正常

**Step 2: Edge 测试**

在 Edge 浏览器中重复 Task 5 的所有测试：

预期结果：所有功能正常

**Step 3: Firefox 测试（可选）**

在 Firefox 浏览器中重复 Task 5 的所有测试：

预期结果：所有功能正常

---

## Task 7: 响应式测试

**Step 1: 测试不同分辨率**

调整浏览器窗口大小，测试以下分辨率：

- 1920x1080（桌面）
- 1366x768（笔记本）
- 768x1024（平板）

预期结果：

- 表格自适应宽度
- 弹窗居中显示
- 所有功能正常使用

**Step 2: 测试表格横向滚动**

缩小浏览器窗口，使表格宽度小于内容宽度：

预期结果：

- 表格出现横向滚动条
- 操作列固定在右侧
- 可以横向滚动查看所有列

---

## Task 8: 提交最终代码

**Step 1: 检查所有文件**

```bash
git status
```

预期输出：所有前端文件已提交

**Step 2: 推送到远程仓库**

```bash
git push origin dev
```

预期输出：推送成功

---

## 完成检查清单

- [ ] API 接口文件更新完成
- [ ] AddProjectDialog 组件简化完成（3个字段）
- [ ] ProjectTable 组件优化完成（上下排列、预警悬停）
- [ ] 父组件切换为真实API
- [ ] 前端服务启动成功
- [ ] 登录功能正常
- [ ] 项目列表显示正常
- [ ] 项目名称和描述上下排列正确
- [ ] 项目状态标签显示正确
- [ ] 预警人数悬停提示显示正常
- [ ] 预警人数颜色根据风险级别变化
- [ ] 创建项目弹窗打开正常
- [ ] 配置方式默认值正确
- [ ] 创建项目功能正常
- [ ] 创建成功后列表刷新
- [ ] 表单验证正常
- [ ] 取消按钮功能正常
- [ ] 跨浏览器测试通过
- [ ] 响应式测试通过
- [ ] 所有代码已提交到 git

---

**前端实施计划完成！**
