Files
ccdi/doc/plans/2026-02-26-create-project-frontend-implementation.md

20 KiB
Raw Permalink Blame History

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

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: 备份原文件

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

Step 2: 修改 API 文件

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

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: 验证语法

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

预期输出:无 ESLint 错误

Step 4: 提交代码

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: 备份原文件

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 重写为以下内容:

<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: 验证语法

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

预期输出:无 ESLint 错误

Step 4: 提交代码

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: 备份原文件

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 重写为以下内容:

<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: 验证语法

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

预期输出:无 ESLint 错误

Step 4: 提交代码

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: 备份原文件

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

Step 2: 修改父组件

ruoyi-ui/src/views/ccdiProject/index.vuegetListhandleSubmitProject 方法修改为:

/** 查询项目列表 */
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: 验证语法

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

预期输出:无 ESLint 错误

Step 4: 提交代码

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

Task 5: 启动前端并测试

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

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: 检查所有文件

git status

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

Step 2: 推送到远程仓库

git push origin dev

预期输出:推送成功


完成检查清单

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

前端实施计划完成!