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

882 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 创建项目功能 - 前端实施计划
> **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
---
**前端实施计划完成!**