# 执行计划 - 修复执行中心顶部状态卡在"运行中"

> **文档类型**: 执行计划文档
> **创建日期**: 2026-07-15
> **作者**: czj
> **关联文档**: `_问题处理_执行中心顶部状态卡在运行中.md`
> **状态**: 待执行

---

## 一、改动总览

| # | 改动项 | 文件 | 改动量 | 说明 |
|---|--------|------|--------|------|
| 1 | 新增轮询定时器变量 | `Execution.vue` | 2 行 | `activePollTimer` + 清除逻辑 |
| 2 | 新增 `pollActiveExecution` 方法 | `Execution.vue` | ~20 行 | 定时查询执行状态 |
| 3 | 修改 `connectWebSocket` | `Execution.vue` | 1 行 | 连接时同时启动轮询 |
| 4 | 修改 `disconnectWebSocket` | `Execution.vue` | 2 行 | 断开时清除轮询 |
| 5 | 修改 `onUnmounted` | `Execution.vue` | 1 行 | 确保清除轮询 |

**总计**：1 个文件，约 26 行改动。

---

## 二、详细执行步骤

### Step 1：新增轮询定时器变量

**文件**：`frontend/src/views/Execution.vue`

在 WebSocket 客户端变量后新增：

```typescript
// WebSocket 客户端
let wsClient: WebSocketClient | null = null
// 主动轮询定时器（双重保障：WebSocket 断连时仍能检测执行完成）
let activePollTimer: ReturnType<typeof setInterval> | null = null
```

### Step 2：新增 `pollActiveExecution` 方法

在 `disconnectWebSocket` 方法后新增：

```typescript
/**
 * 主动轮询当前执行状态
 * 
 * 当 WebSocket 断连或 execution_complete 事件丢失时，
 * 通过定时查询 API 确保执行状态最终能被正确更新。
 */
const startPolling = () => {
  stopPolling()
  activePollTimer = setInterval(async () => {
    if (!activeExecution.value || activeExecution.value.status !== 'running') {
      stopPolling()
      return
    }
    try {
      const latest = await executionApi.get(activeExecution.value.id)
      if (latest.status !== 'running') {
        // 状态已变化，更新 activeExecution
        activeExecution.value = latest
        if (latest.status === 'completed') {
          ElMessage.success(`执行完成: 通过率 ${latest.pass_rate}%`)
        }
        disconnectWebSocket()
        await loadExecutions()
      }
    } catch (error: any) {
      console.error('轮询执行状态失败:', error)
    }
  }, 5000) // 每 5 秒轮询一次
}

const stopPolling = () => {
  if (activePollTimer) {
    clearInterval(activePollTimer)
    activePollTimer = null
  }
}
```

### Step 3：修改 `connectWebSocket`

在 `connectWebSocket` 方法末尾（`wsClient.connect()` 后）添加启动轮询：

```typescript
const connectWebSocket = (executionId: string) => {
  // ... 已有代码 ...
  wsClient.connect()
  
  // 启动轮询双重保障
  startPolling()
}
```

### Step 4：修改 `disconnectWebSocket`

在 `disconnectWebSocket` 方法中添加清除轮询：

```typescript
const disconnectWebSocket = () => {
  stopPolling()  // ← 新增：清除轮询
  if (wsClient) {
    wsClient.disconnect()
    wsClient = null
  }
}
```

### Step 5：确认 `onUnmounted` 清除

当前 `onUnmounted` 已调用 `disconnectWebSocket()`，而 `disconnectWebSocket` 现在会调用 `stopPolling()`，所以无需额外改动。

---

## 三、验证步骤

### 3.1 构建验证

```bash
cd frontend && npm run build
```

### 3.2 功能验证

| 步骤 | 操作 | 预期结果 |
|------|------|----------|
| 1 | 执行用例，正常等待完成 | WebSocket 收到 `execution_complete`，顶部状态变为"已完成" |
| 2 | 执行用例，在执行中打开浏览器 DevTools 断开 WebSocket | 5 秒内轮询检测到完成，顶部状态变为"已完成" |
| 3 | 执行完成后刷新页面 | 列表中无 running 记录，顶部不显示运行中卡片 |
| 4 | 执行中离开页面（组件卸载） | 轮询定时器被清除，无内存泄漏 |

---

## 四、风险评估

| 风险 | 概率 | 影响 | 缓解 |
|------|------|------|------|
| 轮询增加 API 请求量 | 低 | 每执行任务 +1 次/5秒 | 仅在 running 时轮询，执行完成后立即停止 |
| 轮询与 WebSocket 同时更新 | 低 | 短暂重复 | `execution_complete` 事件会调用 `disconnectWebSocket` → `stopPolling`，轮询自动停止 |
| `executionApi.get` 请求失败 | 低 | 轮询中断 | catch 后不停止轮询，下次继续尝试 |

---

*本文档由 Claude Code 生成，遵循项目执行计划文档规范。*
