# 前端 Bug 修复 + 代码质量提升设计文档

日期: 2026-05-12
状态: 待审核

## 背景

通过全面审计 Web 前端（Vue 3 + Element Plus），发现了 50+ 个问题。本文档聚焦于前端高优先级 Bug 修复和代码质量改进，分 3 个阶段执行。

## 阶段 1：关键 Bug 修复

### 1.1 修复 fetchUser 未调用

**问题**: `Layout.vue` 在 `onMounted` 时未调用 `authStore.fetchUser()`。页面刷新后 `authStore.username` 为空，头像显示 "U" 而非用户名首字母。

**修复**: 在 `Layout.vue` 的 `onMounted` 中添加 `await authStore.fetchUser()`。

**影响文件**: `web/frontend/src/views/Layout.vue`

### 1.2 修复静默吞错

**问题**: 以下文件的 catch 块完全为空，API 失败时用户无任何反馈：

| 文件 | 函数 | 行号 |
|------|------|------|
| `files/Images.vue` | `loadData()` | 165 |
| `files/Tables.vue` | `loadData()` | 161 |
| `files/Orders.vue` | `loadData()` | 168 |
| `Sync.vue` | `checkStatus()` | 127 |
| `Tasks.vue` | `loadStats()` | 207 |
| `Logs.vue` | `loadStats()` | 164 |
| `files/Images.vue` | `deleteFile()` | 272 |
| `files/Tables.vue` | `deleteFile()` | 250 |
| `files/Orders.vue` | `deleteFile()` | 242 |

**修复**: 所有 catch 块添加 `ElMessage.error()` 提示，使用中文错误消息。

**影响文件**: 上述 6 个 Vue 文件

### 1.3 修复 loading 状态管理

**问题**: `Images.vue`、`Tables.vue`、`Orders.vue` 的 `loadData()` 中 `loading.value = false` 不在 `finally` 块中。异常发生时 loading 转圈卡住。

**修复**: 统一改为 `try { ... } finally { loading.value = false }` 模式。

**影响文件**: `files/Images.vue`、`files/Tables.vue`、`files/Orders.vue`

### 1.4 修复内存统计回退逻辑

**问题**: `Memory.vue` 当 API 不返回 stats 时，从 `items.value`（当前页 50 条）计算置信度统计，显示为全局数据，严重误导用户。

**修复**: 移除误导性回退逻辑，当 stats 不可用时显示 "暂无统计数据" 占位。

**影响文件**: `web/frontend/src/views/Memory.vue`

### 1.5 添加全局错误处理

**问题**: `main.ts` 没有 `app.config.errorHandler`，未捕获的 Vue 错误只输出到 console.warn，用户无感知。

**修复**: 在 `main.ts` 注册全局错误处理器：

```typescript
app.config.errorHandler = (err, instance, info) => {
  console.error('Vue error:', err, info)
  ElMessage.error('操作失败，请稍后重试')
}
```

**影响文件**: `web/frontend/src/main.ts`

---

## 阶段 2：代码质量 — 提取共享逻辑

### 2.1 提取 useDebounce composable

**问题**: `useDebounce` 函数在 4 个文件中完全重复（Memory.vue:166-174, Barcodes.vue:190-198, Tasks.vue:144-152, Logs.vue:107-115）。

**修复**: 创建 `web/frontend/src/composables/useDebounce.ts`：

```typescript
import { ref } from 'vue'

export function useDebounce(fn: Function, delay = 300) {
  const timer = ref<ReturnType<typeof setTimeout> | null>(null)
  return (...args: any[]) => {
    if (timer.value) clearTimeout(timer.value)
    timer.value = setTimeout(() => fn(...args), delay)
  }
}
```

4 个文件改为 `import { useDebounce } from '@/composables/useDebounce'`。

**影响文件**:
- 新建: `web/frontend/src/composables/useDebounce.ts`
- 修改: `Memory.vue`、`Barcodes.vue`、`Tasks.vue`、`Logs.vue`

### 2.2 提取文件视图共享逻辑

**问题**: `Images.vue`、`Tables.vue`、`Orders.vue` 有大量重复代码：
- `statusType()`、`statusText()`、`fmtTime()` 函数完全相同
- 预览对话框模板 + 逻辑 + CSS 完全相同
- 详情对话框模板 + 逻辑完全相同
- 分页、多选、排序模式完全相同
- 批量删除模式完全相同

**修复**: 创建 3 个共享 composable：

#### `composables/useFileUtils.ts`
```typescript
export function statusType(status: string): string { ... }
export function statusText(status: string): string { ... }
export function fmtTime(t: string): string { ... }
```

#### `composables/useFilePreview.ts`
```typescript
export function useFilePreview() {
  // previewVisible, previewContent, previewTitle, previewLoading
  // openPreview(), closePreview()
  return { ... }
}
```

#### `composables/useFileSelection.ts`
```typescript
export function useFileSelection(fetchFn: Function) {
  // selectedFiles, currentPage, pageSize, total, sortProp, sortOrder
  // handleSelectionChange(), handlePageChange(), handleSortChange(), toggleSelectAll()
  return { ... }
}
```

3 个文件视图改为使用这些 composable，每个文件预计减少 100-150 行重复代码。

**影响文件**:
- 新建: `composables/useFileUtils.ts`、`composables/useFilePreview.ts`、`composables/useFileSelection.ts`
- 修改: `files/Images.vue`、`files/Tables.vue`、`files/Orders.vue`

### 2.3 清理死代码

| 文件 | 死代码 | 行号 |
|------|--------|------|
| `Layout.vue` | `navItems` 数组（未被引用） | 175-183 |
| `stores/processing.ts` | `pollTaskStatus` 函数（未被调用） | 124-129 |
| `router/index.ts` | `routeLoadingTimer` 逻辑（无消费者） | 87-94 |
| `Barcodes.vue` | `Plus` 图标导入（未使用） | 186 |

**影响文件**: 上述 4 个文件

---

## 阶段 3：小改进

### 3.1 修改密码表单验证

**问题**: `Layout.vue` 的修改密码对话框无任何验证：无最小长度要求、无确认密码字段。

**修复**:
- 添加 el-form 验证规则：密码最少 6 位
- 添加确认密码字段，验证两次输入一致
- 提交前调用 `formRef.validate()`

**影响文件**: `web/frontend/src/views/Layout.vue`

### 3.2 修复 Layout.vue 冗余代码

**问题**: `navigator.onLine !== false` 冗余（`navigator.onLine` 已经是 boolean）。

**修复**: 改为 `navigator.onLine`。

**影响文件**: `web/frontend/src/views/Layout.vue`

### 3.3 后端批量删除端点

**问题**: 前端批量删除是 N+1 API 调用（每个文件 2 次请求），50 个文件 = 100 次 HTTP 请求。

**修复**:
- 后端添加 `POST /api/files/batch-delete` 端点，接受 `files: [{directory, filename}]` 数组
- 前端批量删除改为单次 API 调用

**影响文件**:
- 新建/修改: `web/backend/routers/files.py`
- 修改: `files/Images.vue`、`files/Tables.vue`、`files/Orders.vue`

---

## 不在范围内

以下问题记录但不在本次修复范围内：
- 响应式布局（仅 Dashboard 有 @media 查询）
- 键盘快捷键
- 类型感知配置编辑器
- 后端安全问题（路径遍历、登录限流等）
- WebSocket 认证 token 刷新
- 产品记忆批量操作/导出
- 任务取消支持

## 验证标准

1. 页面刷新后头像正确显示用户名首字母
2. API 失败时用户看到错误提示 toast
3. loading 状态不会卡住
4. 统计数据准确或显示占位
5. 未捕获的 Vue 错误有用户提示
6. useDebounce 在 4 个文件中通过 import 使用，无重复定义
7. 3 个文件视图使用共享 composable，每个文件减少 100+ 行
8. 4 处死代码已清理
9. 修改密码表单有验证规则和确认字段
10. 批量删除为单次 API 调用
