# 项目管理页面改进设计文档

**日期：** 2026-02-27
**作者：** Claude Code
**状态：** 待实施

---

## 一、需求概述

项目管理页面存在以下三个问题需要改进：

1. **搜索框缺少搜索按钮** - 用户只能通过回车或清空触发搜索，缺少显式的搜索按钮
2. **标签页状态计数不准确** - 当前只统计当前页的数据，无法反映全局状态分布
3. **状态标签样式不够简约** - 当前使用带背景色的标签，视觉上较重

---

## 二、技术方案

### 2.1 方案选择

经过对比分析，选择 **方案1：独立统计接口 + 精准样式改造**

**理由：**
- 接口职责单一，易于维护
- 统计数据准确，不受分页影响
- 性能最优，统计数据量小（仅4个数字）
- 符合 RESTful 设计规范

---

## 三、后端设计

### 3.1 新增统计接口

**接口定义**

```
GET /ccdi/project/statusCounts
```

**权限要求**

```
ccdi:project:list
```

**Controller 层实现**

文件：`ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java`

```java
/**
 * 获取项目状态统计
 */
@GetMapping("/statusCounts")
@Operation(summary = "获取项目状态统计")
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public AjaxResult getStatusCounts() {
    Map<String, Long> counts = projectService.getStatusCounts();
    return AjaxResult.success(counts);
}
```

**Service 层接口**

文件：`ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java`

```java
/**
 * 获取各状态的项目数量
 * @return 返回格式：{"all": 总数, "0": 进行中数量, "1": 已完成数量, "2": 已归档数量}
 */
Map<String, Long> getStatusCounts();
```

**Service 层实现**

文件：`ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java`

```java
@Override
public Map<String, Long> getStatusCounts() {
    Map<String, Long> counts = new HashMap<>();

    // 使用 MyBatis Plus 分组查询
    QueryWrapper<CcdiProject> wrapper = new QueryWrapper<>();
    wrapper.select("status", "COUNT(*) as count")
           .groupBy("status");

    List<Map<String, Object>> results = baseMapper.selectMaps(wrapper);

    // 初始化各状态计数
    Long totalCount = 0L;
    Long inProgressCount = 0L;
    Long completedCount = 0L;
    Long archivedCount = 0L;

    // 遍历结果统计
    for (Map<String, Object> result : results) {
        String status = (String) result.get("status");
        Long count = (Long) result.get("count");

        totalCount += count;
        if ("0".equals(status)) {
            inProgressCount = count;
        } else if ("1".equals(status)) {
            completedCount = count;
        } else if ("2".equals(status)) {
            archivedCount = count;
        }
    }

    counts.put("all", totalCount);
    counts.put("0", inProgressCount);
    counts.put("1", completedCount);
    counts.put("2", archivedCount);

    return counts;
}
```

**响应示例**

```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "all": 15,
    "0": 8,
    "1": 5,
    "2": 2
  }
}
```

---

## 四、前端设计

### 4.1 API 接口定义

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

```javascript
// 获取项目状态统计
export function getProjectStatusCounts() {
  return request({
    url: '/ccdi/project/statusCounts',
    method: 'get'
  })
}
```

### 4.2 SearchBar 组件改造

**文件：** `ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue`

**改动说明：**
- 使用 Element UI 的 slot 功能在输入框右侧添加搜索按钮
- 保持回车和清空触发搜索的功能
- 调整输入框宽度以容纳按钮

**实现代码：**

```vue
<el-input
  v-model="searchKeyword"
  placeholder="请输入关键词搜索项目"
  clearable
  size="small"
  class="search-input"
  @keyup.enter.native="handleSearch"
  @clear="handleSearch"
>
  <el-button
    slot="append"
    icon="el-icon-search"
    @click="handleSearch"
  />
</el-input>
```

**样式调整：**

```scss
.search-input {
  width: 300px; // 从 240px 调整为 300px
  height: 40px;
}
```

### 4.3 ProjectTable 组件改造

**文件：** `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`

**改动说明：**
- 替换 `<el-tag>` + `<dict-tag>` 组合为简约的小圆点样式
- 使用不同颜色的小圆点标识不同状态
- 文字统一使用黑色，降低视觉干扰

**实现代码：**

```vue
<!-- 状态列 -->
<el-table-column
  prop="status"
  label="状态"
  width="120"
  align="center"
>
  <template slot-scope="scope">
    <span class="status-badge" :class="'status-' + scope.row.status">
      <span class="status-dot"></span>
      <span class="status-text">{{ getStatusText(scope.row.status) }}</span>
    </span>
  </template>
</el-table-column>
```

**新增方法：**

```javascript
getStatusText(status) {
  const statusMap = {
    '0': '进行中',
    '1': '已完成',
    '2': '已归档'
  }
  return statusMap[status] || '未知'
}
```

**移除方法：**

```javascript
// 删除不再使用的 getStatusType 方法
```

**样式设计：**

```scss
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;

  .status-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    display: inline-block;
  }

  .status-text {
    color: #333;
    font-size: 14px;
  }

  // 进行中 - 蓝色
  &.status-0 .status-dot {
    background-color: #1890ff;
  }

  // 已完成 - 绿色
  &.status-1 .status-dot {
    background-color: #52c41a;
  }

  // 已归档 - 灰色
  &.status-2 .status-dot {
    background-color: #8c8c8c;
  }
}
```

### 4.4 主页面集成

**文件：** `ruoyi-ui/src/views/ccdiProject/index.vue`

**改动说明：**
- 引入统计接口
- created 生命周期并发加载统计和列表数据
- 状态变更后同时刷新统计和列表
- 优雅的错误处理

**引入接口：**

```javascript
import { listProject, getProjectStatusCounts } from '@/api/ccdiProject'
```

**新增方法：**

```javascript
/** 获取状态统计 */
getStatusCounts() {
  return getProjectStatusCounts().then(response => {
    this.tabCounts = response.data
  }).catch(() => {
    // 统计接口失败时使用默认值，不影响列表显示
    this.tabCounts = {
      all: 0,
      '0': 0,
      '1': 0,
      '2': 0
    }
  })
}
```

**修改 created 生命周期：**

```javascript
created() {
  // 并发加载统计和列表
  Promise.all([
    this.getStatusCounts(),
    this.getList()
  ])
}
```

**优化 calculateTabCounts 方法：**

```javascript
/** 计算标签页数量 - 改为从统计接口获取 */
calculateTabCounts() {
  // 方法保留但不执行计算逻辑
  // 统计数据已从接口获取，直接使用 this.tabCounts
}
```

**修改状态变更处理：**

```javascript
/** 确认归档 */
handleConfirmArchive(data) {
  console.log('确认归档:', data)
  this.$modal.msgSuccess('项目已归档')
  this.archiveDialogVisible = false
  // 同时刷新统计和列表
  Promise.all([
    this.getStatusCounts(),
    this.getList()
  ])
}

/** 提交项目表单 */
handleSubmitProject(data) {
  this.addDialogVisible = false
  // 同时刷新统计和列表
  Promise.all([
    this.getStatusCounts(),
    this.getList()
  ])
}
```

**优化 getList 方法：**

```javascript
/** 查询项目列表 */
getList() {
  this.loading = true
  listProject(this.queryParams).then(response => {
    this.projectList = response.rows
    this.total = response.total
    this.loading = false
    // 不再需要调用 calculateTabCounts
  }).catch(() => {
    this.loading = false
    this.$modal.msgError('加载项目列表失败')
  })
}
```

---

## 五、数据流设计

### 5.1 数据加载时机

| 场景 | 刷新统计 | 刷新列表 | 说明 |
|------|---------|---------|------|
| 页面首次加载 | ✓ | ✓ | 并发请求 |
| 标签页切换 | - | ✓ | 仅列表变化 |
| 搜索触发 | - | ✓ | 仅列表变化 |
| 项目归档 | ✓ | ✓ | 状态变化 |
| 新建项目 | ✓ | ✓ | 总数增加 |
| 删除项目 | ✓ | ✓ | 总数减少 |
| 重新分析 | - | ✓ | 状态不变 |

### 5.2 并发加载优化

```javascript
// 使用 Promise.all 并发请求，提升加载速度
Promise.all([
  this.getStatusCounts(),  // 统计接口
  this.getList()           // 列表接口
])
```

### 5.3 错误处理策略

**统计接口失败：**
- 不阻塞页面加载
- 标签页显示 0，但列表正常显示
- 控制台记录错误日志

**列表接口失败：**
- 显示错误提示
- 保持现有数据不变
- Loading 状态正常关闭

---

## 六、性能考虑

### 6.1 接口性能

**统计接口：**
- 使用 COUNT + GROUP BY，无需查询详细字段
- 数据量极小（仅4个数字）
- 执行速度快，可在 100ms 内完成

**列表接口：**
- 保持现有分页逻辑
- 不受统计接口影响

### 6.2 前端性能

**并发请求：**
- 统计和列表同时发起，不串行等待
- 总耗时 = max(统计耗时, 列表耗时)

**缓存策略：**
- 统计数据在前端内存中保留
- 仅在状态变更时重新获取

---

## 七、测试要点

### 7.1 后端测试

**单元测试：**
- 测试统计接口返回数据格式
- 测试空数据情况（无项目时）
- 测试各状态的数据准确性

**集成测试：**
- 使用 Swagger 测试接口响应
- 验证权限控制

### 7.2 前端测试

**功能测试：**
- 搜索按钮点击触发搜索
- 回车键触发搜索
- 清空输入框触发搜索
- 标签页显示正确的统计数据
- 状态标签显示正确的颜色和文字

**UI 测试：**
- 搜索按钮样式与输入框融合
- 状态标签小圆点样式正确
- 不同状态的颜色区分明显

**错误场景测试：**
- 统计接口失败时页面正常显示
- 列表接口失败时错误提示正确
- 网络异常时的降级处理

---

## 八、改动文件清单

### 8.1 后端文件

1. `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java`
   - 新增统计接口方法

2. `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java`
   - 新增服务方法定义

3. `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java`
   - 实现统计逻辑

### 8.2 前端文件

1. `ruoyi-ui/src/api/ccdiProject.js`
   - 新增统计接口调用

2. `ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue`
   - 添加内嵌搜索按钮
   - 调整输入框宽度

3. `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
   - 改造状态标签样式
   - 新增 getStatusText 方法
   - 删除 getStatusType 方法

4. `ruoyi-ui/src/views/ccdiProject/index.vue`
   - 引入统计接口
   - 修改 created 生命周期
   - 新增 getStatusCounts 方法
   - 优化状态变更处理

---

## 九、实施计划

### 9.1 实施顺序

1. **后端开发**（优先）
   - 实现统计接口
   - Swagger 测试验证

2. **前端开发**
   - API 接口定义
   - SearchBar 组件改造
   - ProjectTable 组件改造
   - 主页面集成

3. **联调测试**
   - 前后端联调
   - 功能测试
   - UI 测试

### 9.2 预估工时

- 后端开发：1 小时
- 前端开发：2 小时
- 联调测试：1 小时
- **总计：4 小时**

---

## 十、风险评估

### 10.1 技术风险

**风险：** 统计接口性能问题
**影响：** 中
**缓解措施：**
- 使用 COUNT + GROUP BY 优化查询
- 添加数据库索引（status 字段）
- 监控接口响应时间

**风险：** 前端样式兼容性
**影响：** 低
**缓解措施：**
- 使用标准的 CSS 属性
- 测试主流浏览器

### 10.2 业务风险

**风险：** 统计数据与实际不符
**影响：** 高
**缓解措施：**
- 使用数据库事务保证一致性
- 状态变更时立即刷新统计
- 添加数据校验

---

## 十一、后续优化

### 11.1 短期优化

- 添加统计数据的本地缓存（5秒过期）
- 优化错误提示文案

### 11.2 长期优化

- 支持按时间范围统计
- 添加趋势图表
- 支持自定义状态

---

**设计完成日期：** 2026-02-27
**预计实施日期：** 待定
