docs: 添加项目状态统计修复设计文档
This commit is contained in:
635
docs/plans/2026-02-27-project-status-counts-fix-design.md
Normal file
635
docs/plans/2026-02-27-project-status-counts-fix-design.md
Normal file
@@ -0,0 +1,635 @@
|
|||||||
|
# 项目管理标签页状态统计修复设计文档
|
||||||
|
|
||||||
|
## 文档信息
|
||||||
|
|
||||||
|
- **创建日期**: 2026-02-27
|
||||||
|
- **作者**: Claude Code
|
||||||
|
- **状态**: 待实施
|
||||||
|
|
||||||
|
## 问题背景
|
||||||
|
|
||||||
|
### 当前问题
|
||||||
|
|
||||||
|
项目管理页面的标签页筛选功能中,各状态的项目计数不正确。具体表现为:
|
||||||
|
|
||||||
|
- **现象**: 标签页显示的数量远小于实际总数
|
||||||
|
- **根本原因**: 前端只统计当前页的数据来计算各状态的数量
|
||||||
|
- **影响**: 用户无法了解各状态的完整项目分布情况
|
||||||
|
|
||||||
|
### 代码位置
|
||||||
|
|
||||||
|
**前端代码**:
|
||||||
|
- 页面组件: `ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||||
|
- API 定义: `ruoyi-ui/src/api/ccdiProject.js`
|
||||||
|
|
||||||
|
**后端代码**:
|
||||||
|
- Controller: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java`
|
||||||
|
- Service 接口: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java`
|
||||||
|
- Service 实现: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java`
|
||||||
|
|
||||||
|
### 当前实现分析
|
||||||
|
|
||||||
|
**问题代码** (index.vue:136-145):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
calculateTabCounts() {
|
||||||
|
// 注意:这里需要后端API返回所有状态的数量统计
|
||||||
|
// 目前暂时使用当前页的数据进行计算
|
||||||
|
this.tabCounts = {
|
||||||
|
all: this.total,
|
||||||
|
'0': this.projectList.filter(p => p.status === '0').length,
|
||||||
|
'1': this.projectList.filter(p => p.status === '1').length,
|
||||||
|
'2': this.projectList.filter(p => p.status === '2').length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题分析**:
|
||||||
|
- `projectList` 只包含当前页的数据(默认10条)
|
||||||
|
- 过滤计算只能得到当前页中各状态的数量
|
||||||
|
- 用户看到的是"当前页有5个进行中的项目",而非"总共有30个进行中的项目"
|
||||||
|
|
||||||
|
## 需求说明
|
||||||
|
|
||||||
|
### 功能需求
|
||||||
|
|
||||||
|
**预期行为**:
|
||||||
|
- 标签页应显示数据库中所有该状态的项目总数
|
||||||
|
- 状态统计不受搜索条件和分页影响
|
||||||
|
- 状态统计随列表一起刷新
|
||||||
|
|
||||||
|
**非功能性需求**:
|
||||||
|
- 性能: 统计查询应快速响应(< 100ms)
|
||||||
|
- 准确性: 统计数字必须与数据库一致
|
||||||
|
- 实时性: 每次刷新列表时同步更新统计
|
||||||
|
|
||||||
|
## 设计方案
|
||||||
|
|
||||||
|
### 整体架构
|
||||||
|
|
||||||
|
采用**独立统计接口**方案:
|
||||||
|
- 后端新增 `/ccdi/project/statusCounts` 接口
|
||||||
|
- 前端在加载列表时并行调用统计接口
|
||||||
|
- 两个请求独立,数据互不影响
|
||||||
|
|
||||||
|
### 后端设计
|
||||||
|
|
||||||
|
#### 1. 新增 VO 类
|
||||||
|
|
||||||
|
**文件**: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectStatusCountsVO.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目状态统计VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectStatusCountsVO {
|
||||||
|
/** 全部项目总数 */
|
||||||
|
private Long all;
|
||||||
|
/** 进行中项目数(状态0) */
|
||||||
|
private Long status0;
|
||||||
|
/** 已完成项目数(状态1) */
|
||||||
|
private Long status1;
|
||||||
|
/** 已归档项目数(状态2) */
|
||||||
|
private Long status2;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计说明**:
|
||||||
|
- 使用 `Long` 类型支持大数据量
|
||||||
|
- 字段命名清晰,便于前端使用
|
||||||
|
- 不包含搜索条件,始终统计全量数据
|
||||||
|
|
||||||
|
#### 2. Service 层
|
||||||
|
|
||||||
|
**接口定义** (`ICcdiProjectService.java`):
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 查询各状态的项目总数(不受搜索条件影响)
|
||||||
|
* @return 状态统计
|
||||||
|
*/
|
||||||
|
CcdiProjectStatusCountsVO getStatusCounts();
|
||||||
|
```
|
||||||
|
|
||||||
|
**实现类** (`CcdiProjectServiceImpl.java`):
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public CcdiProjectStatusCountsVO getStatusCounts() {
|
||||||
|
CcdiProjectStatusCountsVO vo = new CcdiProjectStatusCountsVO();
|
||||||
|
|
||||||
|
// 统计全部项目
|
||||||
|
vo.setAll(ccdiProjectMapper.selectCount(null));
|
||||||
|
|
||||||
|
// 统计各状态项目
|
||||||
|
vo.setStatus0(ccdiProjectMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<CcdiProject>()
|
||||||
|
.eq(CcdiProject::getStatus, "0")
|
||||||
|
));
|
||||||
|
vo.setStatus1(ccdiProjectMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<CcdiProject>()
|
||||||
|
.eq(CcdiProject::getStatus, "1")
|
||||||
|
));
|
||||||
|
vo.setStatus2(ccdiProjectMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<CcdiProject>()
|
||||||
|
.eq(CcdiProject::getStatus, "2")
|
||||||
|
));
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**实现要点**:
|
||||||
|
- 使用 MyBatis Plus 的 `selectCount` 方法
|
||||||
|
- 不添加任何过滤条件
|
||||||
|
- 可以优化为一次查询返回所有统计(使用 GROUP BY)
|
||||||
|
|
||||||
|
**优化方案**(可选):
|
||||||
|
|
||||||
|
如果性能要求高,可以改为单次查询:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public CcdiProjectStatusCountsVO getStatusCounts() {
|
||||||
|
// 查询各状态的统计
|
||||||
|
List<Map<String, Object>> counts = ccdiProjectMapper.countGroupByStatus();
|
||||||
|
|
||||||
|
CcdiProjectStatusCountsVO vo = new CcdiProjectStatusCountsVO();
|
||||||
|
vo.setAll(0L);
|
||||||
|
vo.setStatus0(0L);
|
||||||
|
vo.setStatus1(0L);
|
||||||
|
vo.setStatus2(0L);
|
||||||
|
|
||||||
|
for (Map<String, Object> item : counts) {
|
||||||
|
String status = (String) item.get("status");
|
||||||
|
Long count = (Long) item.get("count");
|
||||||
|
|
||||||
|
vo.setAll(vo.getAll() + count);
|
||||||
|
if ("0".equals(status)) {
|
||||||
|
vo.setStatus0(count);
|
||||||
|
} else if ("1".equals(status)) {
|
||||||
|
vo.setStatus1(count);
|
||||||
|
} else if ("2".equals(status)) {
|
||||||
|
vo.setStatus2(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Mapper 方法:
|
||||||
|
|
||||||
|
```java
|
||||||
|
List<Map<String, Object>> countGroupByStatus();
|
||||||
|
```
|
||||||
|
|
||||||
|
XML SQL:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<select id="countGroupByStatus" resultType="map">
|
||||||
|
SELECT
|
||||||
|
status,
|
||||||
|
COUNT(*) as count
|
||||||
|
FROM ccdi_project
|
||||||
|
GROUP BY status
|
||||||
|
</select>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Controller 层
|
||||||
|
|
||||||
|
**文件**: `CcdiProjectController.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 查询项目状态统计
|
||||||
|
*/
|
||||||
|
@GetMapping("/statusCounts")
|
||||||
|
@Operation(summary = "查询项目状态统计")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
|
||||||
|
public AjaxResult getStatusCounts() {
|
||||||
|
CcdiProjectStatusCountsVO counts = projectService.getStatusCounts();
|
||||||
|
return AjaxResult.success(counts);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计说明**:
|
||||||
|
- 不接收任何查询参数
|
||||||
|
- 使用与列表接口相同的权限注解
|
||||||
|
- 返回标准的 AjaxResult 格式
|
||||||
|
|
||||||
|
### 前端设计
|
||||||
|
|
||||||
|
#### 1. API 层
|
||||||
|
|
||||||
|
**文件**: `ruoyi-ui/src/api/ccdiProject.js`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 查询项目状态统计
|
||||||
|
export function getStatusCounts() {
|
||||||
|
return request({
|
||||||
|
url: '/ccdi/project/statusCounts',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 页面组件改造
|
||||||
|
|
||||||
|
**文件**: `ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||||
|
|
||||||
|
**修改点 1**: 导入新 API
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { listProject, getStatusCounts } from '@/api/ccdiProject'
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改点 2**: 重构 `getList()` 方法
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/** 查询项目列表 */
|
||||||
|
getList() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
// 并行请求列表数据和状态统计
|
||||||
|
Promise.all([
|
||||||
|
listProject(this.queryParams),
|
||||||
|
getStatusCounts()
|
||||||
|
]).then(([listResponse, countsResponse]) => {
|
||||||
|
// 处理列表数据
|
||||||
|
this.projectList = listResponse.rows
|
||||||
|
this.total = listResponse.total
|
||||||
|
|
||||||
|
// 处理状态统计
|
||||||
|
const counts = countsResponse.data
|
||||||
|
this.tabCounts = {
|
||||||
|
all: counts.all,
|
||||||
|
'0': counts.status0,
|
||||||
|
'1': counts.status1,
|
||||||
|
'2': counts.status2
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = false
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改点 3**: 删除旧方法
|
||||||
|
|
||||||
|
删除 `calculateTabCounts()` 方法及其调用(第 136-145 行)。
|
||||||
|
|
||||||
|
**设计说明**:
|
||||||
|
- 使用 `Promise.all` 并行请求,提高性能
|
||||||
|
- 两个请求独立失败互不影响(可以优化错误处理)
|
||||||
|
- 状态统计使用后端返回的准确数据
|
||||||
|
|
||||||
|
### 数据流设计
|
||||||
|
|
||||||
|
```
|
||||||
|
用户操作(打开页面/搜索/切换标签/翻页)
|
||||||
|
↓
|
||||||
|
前端调用 getList() 方法
|
||||||
|
↓
|
||||||
|
并行发送两个请求:
|
||||||
|
┌─────────────────────────┬────────────────────────┐
|
||||||
|
│ listProject(queryParams)│ getStatusCounts() │
|
||||||
|
│ - 带搜索条件 │ - 无参数 │
|
||||||
|
│ - 带分页参数 │ - 返回全量统计 │
|
||||||
|
│ - 返回当前页数据 │ │
|
||||||
|
└─────────────────────────┴────────────────────────┘
|
||||||
|
↓ ↓
|
||||||
|
后端处理 后端处理
|
||||||
|
- 根据条件过滤 - COUNT 查询全部
|
||||||
|
- 分页返回结果 - 按 status 分组统计
|
||||||
|
↓ ↓
|
||||||
|
前端接收响应 ←─────────────────┘
|
||||||
|
↓
|
||||||
|
更新状态:
|
||||||
|
- projectList: 列表数据(受搜索/分页影响)
|
||||||
|
- total: 当前筛选条件的总数
|
||||||
|
- tabCounts: 各状态的完整统计(不受搜索影响)
|
||||||
|
↓
|
||||||
|
页面渲染:
|
||||||
|
- 表格: 显示当前页项目
|
||||||
|
- 标签: 显示固定统计数字
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键特性**:
|
||||||
|
1. **并行请求**: 两个请求同时发出,不阻塞
|
||||||
|
2. **数据独立**: 列表数据和统计数据来源不同
|
||||||
|
3. **统计固定**: 标签页数字不随搜索/分页变化
|
||||||
|
4. **列表过滤**: 表格内容根据搜索条件正确过滤
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
### 后端测试
|
||||||
|
|
||||||
|
#### 1. 单元测试
|
||||||
|
|
||||||
|
**测试文件**: `CcdiProjectServiceTest.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void testGetStatusCounts() {
|
||||||
|
// 准备测试数据
|
||||||
|
// 创建 3 个进行中项目
|
||||||
|
// 创建 2 个已完成项目
|
||||||
|
// 创建 1 个已归档项目
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
CcdiProjectStatusCountsVO result = projectService.getStatusCounts();
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertEquals(6L, result.getAll());
|
||||||
|
assertEquals(3L, result.getStatus0());
|
||||||
|
assertEquals(2L, result.getStatus1());
|
||||||
|
assertEquals(1L, result.getStatus2());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 接口测试
|
||||||
|
|
||||||
|
**使用 Swagger UI 测试**:
|
||||||
|
|
||||||
|
1. 访问: `http://localhost:8080/swagger-ui/index.html`
|
||||||
|
2. 找到: 纪检初核项目管理 → GET /ccdi/project/statusCounts
|
||||||
|
3. 点击 "Try it out" → "Execute"
|
||||||
|
|
||||||
|
**预期响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"all": 100,
|
||||||
|
"status0": 30,
|
||||||
|
"status1": 50,
|
||||||
|
"status2": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**测试用例**:
|
||||||
|
|
||||||
|
| 用例编号 | 场景 | 预期结果 |
|
||||||
|
|---------|------|---------|
|
||||||
|
| TC01 | 数据库无项目 | all=0, status0=0, status1=0, status2=0 |
|
||||||
|
| TC02 | 只有进行中项目 | all=N, status0=N, status1=0, status2=0 |
|
||||||
|
| TC03 | 混合状态项目 | 各状态数字之和等于 all |
|
||||||
|
| TC04 | 大数据量(1000+) | 查询时间 < 100ms |
|
||||||
|
|
||||||
|
### 前端测试
|
||||||
|
|
||||||
|
#### 1. 页面加载测试
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 打开项目管理页面
|
||||||
|
2. 观察标签页数字
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 标签页显示正确的总数(例如:全部项目(100))
|
||||||
|
- 数字不随分页变化
|
||||||
|
|
||||||
|
#### 2. 搜索测试
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 在搜索框输入项目名称
|
||||||
|
2. 点击搜索
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 列表正确过滤
|
||||||
|
- 标签页数字保持不变(显示总数)
|
||||||
|
|
||||||
|
#### 3. 分页测试
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 切换到第 2 页
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 列表切换到第 2 页
|
||||||
|
- 标签页数字保持不变
|
||||||
|
|
||||||
|
#### 4. 状态切换测试
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 点击"进行中"标签
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 列表只显示进行中的项目
|
||||||
|
- 标签页数字保持不变(仍显示总数)
|
||||||
|
|
||||||
|
#### 5. 网络错误测试
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 模拟统计接口失败
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 列表正常显示
|
||||||
|
- 标签页显示 0 或保持上次数据
|
||||||
|
- 不阻塞用户操作
|
||||||
|
|
||||||
|
### 集成测试
|
||||||
|
|
||||||
|
**端到端测试流程**:
|
||||||
|
|
||||||
|
1. **准备数据**: 在数据库中插入测试项目
|
||||||
|
- 10 个进行中项目
|
||||||
|
- 15 个已完成项目
|
||||||
|
- 5 个已归档项目
|
||||||
|
|
||||||
|
2. **执行测试**:
|
||||||
|
```
|
||||||
|
a. 打开项目管理页面
|
||||||
|
b. 验证标签显示: 全部(30), 进行中(10), 已完成(15), 已归档(5)
|
||||||
|
c. 搜索项目名称(匹配 5 个)
|
||||||
|
d. 验证列表显示 5 个项目
|
||||||
|
e. 验证标签仍显示: 全部(30), 进行中(10), 已完成(15), 已归档(5)
|
||||||
|
f. 切换到第 2 页
|
||||||
|
g. 验证列表切换,标签数字不变
|
||||||
|
h. 点击"进行中"标签
|
||||||
|
i. 验证列表只显示进行中项目
|
||||||
|
j. 验证标签仍显示: 全部(30), 进行中(10), 已完成(15), 已归档(5)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **预期结果**: 所有验证点通过
|
||||||
|
|
||||||
|
## 性能考虑
|
||||||
|
|
||||||
|
### 后端性能
|
||||||
|
|
||||||
|
**统计查询优化**:
|
||||||
|
- 使用 `COUNT(*)` 查询,性能较好
|
||||||
|
- 考虑为 `status` 字段添加索引(如果项目数量 > 10000)
|
||||||
|
- 单次 GROUP BY 查询优于多次 COUNT 查询
|
||||||
|
|
||||||
|
**预估性能**:
|
||||||
|
- 项目数 < 1000: 响应时间 < 50ms
|
||||||
|
- 项目数 1000-10000: 响应时间 50-100ms
|
||||||
|
- 项目数 > 10000: 考虑添加缓存或索引
|
||||||
|
|
||||||
|
### 前端性能
|
||||||
|
|
||||||
|
**并行请求**:
|
||||||
|
- `Promise.all` 同时发起两个请求
|
||||||
|
- 总等待时间 = max(listTime, countsTime)
|
||||||
|
- 不影响用户体验
|
||||||
|
|
||||||
|
**请求频率**:
|
||||||
|
- 仅在页面加载、搜索、翻页时请求
|
||||||
|
- 不会产生过多网络流量
|
||||||
|
|
||||||
|
## 风险与应对
|
||||||
|
|
||||||
|
### 风险 1: 统计数据不一致
|
||||||
|
|
||||||
|
**场景**: 用户在查看页面时,后台数据被其他用户修改
|
||||||
|
|
||||||
|
**影响**: 标签页数字与实际不符
|
||||||
|
|
||||||
|
**应对方案**:
|
||||||
|
- 轻微问题,可接受
|
||||||
|
- 用户刷新页面后同步
|
||||||
|
- 不需要额外处理
|
||||||
|
|
||||||
|
### 风险 2: 统计接口失败
|
||||||
|
|
||||||
|
**场景**: 统计接口异常或超时
|
||||||
|
|
||||||
|
**影响**: 标签页显示 0 或空白
|
||||||
|
|
||||||
|
**应对方案**:
|
||||||
|
- 前端增加错误处理
|
||||||
|
- 列表接口不受影响
|
||||||
|
- 控制台记录错误日志
|
||||||
|
|
||||||
|
### 风险 3: 大数据量性能
|
||||||
|
|
||||||
|
**场景**: 项目数量超过 10000
|
||||||
|
|
||||||
|
**影响**: 统计查询变慢
|
||||||
|
|
||||||
|
**应对方案**:
|
||||||
|
- 为 status 字段添加索引
|
||||||
|
- 使用单次 GROUP BY 查询
|
||||||
|
- 考虑添加缓存(Redis)
|
||||||
|
|
||||||
|
## 实施计划
|
||||||
|
|
||||||
|
### 后端开发 (30 分钟)
|
||||||
|
|
||||||
|
1. 创建 VO 类 (5 分钟)
|
||||||
|
- 新建 `CcdiProjectStatusCountsVO.java`
|
||||||
|
- 添加字段和 Lombok 注解
|
||||||
|
|
||||||
|
2. Service 层开发 (15 分钟)
|
||||||
|
- 接口添加方法声明
|
||||||
|
- 实现类编写统计逻辑
|
||||||
|
- (可选) Mapper 添加 GROUP BY 查询
|
||||||
|
|
||||||
|
3. Controller 层开发 (5 分钟)
|
||||||
|
- 添加 `/statusCounts` 接口
|
||||||
|
- 添加 Swagger 注释
|
||||||
|
|
||||||
|
4. 本地测试 (5 分钟)
|
||||||
|
- 启动项目
|
||||||
|
- 使用 Swagger 测试接口
|
||||||
|
|
||||||
|
### 前端开发 (20 分钟)
|
||||||
|
|
||||||
|
1. API 层修改 (5 分钟)
|
||||||
|
- 添加 `getStatusCounts()` 函数
|
||||||
|
|
||||||
|
2. 页面组件修改 (10 分钟)
|
||||||
|
- 导入新 API
|
||||||
|
- 修改 `getList()` 方法
|
||||||
|
- 删除 `calculateTabCounts()` 方法
|
||||||
|
|
||||||
|
3. 本地测试 (5 分钟)
|
||||||
|
- 启动前端
|
||||||
|
- 验证功能
|
||||||
|
|
||||||
|
### 测试验证 (15 分钟)
|
||||||
|
|
||||||
|
1. 后端接口测试 (5 分钟)
|
||||||
|
- Swagger 测试各场景
|
||||||
|
|
||||||
|
2. 前端功能测试 (10 分钟)
|
||||||
|
- 执行测试方案中的所有用例
|
||||||
|
|
||||||
|
### 总计时间: 65 分钟
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
### 功能验收
|
||||||
|
|
||||||
|
- [ ] 后端 `/statusCounts` 接口返回正确的统计数字
|
||||||
|
- [ ] 前端标签页显示数据库中的完整统计
|
||||||
|
- [ ] 搜索不影响标签页统计数字
|
||||||
|
- [ ] 分页不影响标签页统计数字
|
||||||
|
- [ ] 状态过滤不影响标签页统计数字
|
||||||
|
|
||||||
|
### 性能验收
|
||||||
|
|
||||||
|
- [ ] 统计接口响应时间 < 100ms
|
||||||
|
- [ ] 页面加载时间无明显增加
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
|
||||||
|
- [ ] 后端代码符合项目规范
|
||||||
|
- [ ] 前端代码符合项目规范
|
||||||
|
- [ ] 添加必要的注释和文档
|
||||||
|
|
||||||
|
## 后续优化
|
||||||
|
|
||||||
|
### 短期优化 (可选)
|
||||||
|
|
||||||
|
1. **错误处理增强**
|
||||||
|
- 前端对统计接口失败进行友好提示
|
||||||
|
- 后端添加异常捕获和日志
|
||||||
|
|
||||||
|
2. **性能优化**
|
||||||
|
- 使用单次 GROUP BY 查询替代多次 COUNT
|
||||||
|
- 为 status 字段添加索引
|
||||||
|
|
||||||
|
### 长期优化 (可选)
|
||||||
|
|
||||||
|
1. **缓存机制**
|
||||||
|
- 使用 Redis 缓存统计结果
|
||||||
|
- 设置 5 分钟过期时间
|
||||||
|
- 项目变更时清除缓存
|
||||||
|
|
||||||
|
2. **实时更新**
|
||||||
|
- 使用 WebSocket 推送统计更新
|
||||||
|
- 减少轮询请求
|
||||||
|
|
||||||
|
## 附录
|
||||||
|
|
||||||
|
### 相关文件清单
|
||||||
|
|
||||||
|
**后端新增文件**:
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectStatusCountsVO.java`
|
||||||
|
|
||||||
|
**后端修改文件**:
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java`
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java`
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java`
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectMapper.java` (可选)
|
||||||
|
- `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectMapper.xml` (可选)
|
||||||
|
|
||||||
|
**前端修改文件**:
|
||||||
|
- `ruoyi-ui/src/api/ccdiProject.js`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||||
|
|
||||||
|
### 参考资料
|
||||||
|
|
||||||
|
- MyBatis Plus 官方文档: https://baomidou.com/
|
||||||
|
- Element UI 标签页组件: https://element.eleme.cn/#/zh-CN/component/tabs
|
||||||
|
- Promise.all 文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
|
||||||
Reference in New Issue
Block a user