Files
EmailBill/.doc/ICON_SEARCH_BUG_FIX.md
SunCheng 9921cd5fdf chore: migrate remaining ECharts components to Chart.js
- Migrated 4 components from ECharts to Chart.js:
  * MonthlyExpenseCard.vue (折线图)
  * DailyTrendChart.vue (双系列折线图)
  * ExpenseCategoryCard.vue (环形图)
  * BudgetChartAnalysis.vue (仪表盘 + 多种图表)

- Removed all ECharts imports and environment variable switches
- Unified all charts to use BaseChart.vue component
- Build verified: pnpm build success ✓
- No echarts imports remaining ✓

Refs: openspec/changes/migrate-remaining-echarts-to-chartjs
2026-02-16 21:55:38 +08:00

5.1 KiB
Raw Permalink Blame History

Bug 修复报告:图标搜索 API 调用问题

日期: 2026-02-16
严重程度: 高(阻止功能使用)
状态: 已修复

问题描述

用户在前端调用图标搜索 API 时遇到 400 错误:

{
    "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "errors": {
        "request": [
            "The request field is required."
        ],
        "$.keywords": [
            "The JSON value could not be converted to System.Collections.Generic.List`1[System.String]..."
        ]
    }
}

根本原因

Web/src/views/ClassificationEdit.vue 中,searchIcons API 调用传递了错误的参数类型。

错误代码(第 377-387 行)

const { success: keywordsSuccess, data: keywords } = await generateSearchKeywords(category.name)

if (!keywordsSuccess || !keywords || keywords.length === 0) {
  showToast('生成搜索关键字失败')
  return
}

// ❌ 错误keywords 是 SearchKeywordsResponse 对象,不是数组
const { success: iconsSuccess, data: icons } = await searchIcons(keywords)

问题分析

  1. generateSearchKeywords() 返回的 dataSearchKeywordsResponse 对象:

    {
      keywords: ["food", "restaurant", "dining"]
    }
    
  2. 代码错误地将整个对象传递给 searchIcons()

    // 实际发送的请求体
    {
      keywords: {
        keywords: ["food", "restaurant"]
      }
    }
    
  3. 后端期望的格式:

    {
      keywords: ["food", "restaurant"]  // 数组,不是对象
    }
    

修复方案

修复后的代码

const { success: keywordsSuccess, data: keywordsResponse } = await generateSearchKeywords(category.name)

if (!keywordsSuccess || !keywordsResponse || !keywordsResponse.keywords || keywordsResponse.keywords.length === 0) {
  showToast('生成搜索关键字失败')
  return
}

// ✅ 正确:提取 keywords 数组
const { success: iconsSuccess, data: icons } = await searchIcons(keywordsResponse.keywords)

关键变更

  1. 重命名变量:data: keywordsdata: keywordsResponse(更清晰)
  2. 访问嵌套属性:keywordsResponse.keywords
  3. 更新验证逻辑:检查 keywordsResponse.keywords 是否存在

影响范围

  • 受影响文件: Web/src/views/ClassificationEdit.vue
  • 受影响功能: 分类图标选择功能
  • 用户影响: 无法为分类选择 Iconify 图标

测试验证

1. 单元测试

已有的 130 个测试用例验证后端 API 正确性:

  • IconController 集成测试通过
  • Service 层单元测试通过

2. 手动测试步骤

# 1. 启动后端
cd WebApi
dotnet run

# 2. 启动前端
cd Web
pnpm dev

# 3. 测试流程
# - 访问分类管理页面
# - 点击"选择图标"按钮
# - 验证图标选择器正常打开
# - 搜索并选择图标
# - 确认图标正确保存

3. API 测试脚本

参见 .doc/test-icon-api.sh 脚本:

# 测试搜索图标 API
curl -X POST http://localhost:5071/api/icons/search \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["food", "restaurant"]}'

# 预期响应
{
  "success": true,
  "data": [
    {
      "collectionName": "mdi",
      "iconName": "food",
      "iconIdentifier": "mdi:food"
    },
    ...
  ]
}

预防措施

1. 类型安全改进

考虑将前端 API 客户端迁移到 TypeScript

interface SearchKeywordsResponse {
  keywords: string[]
}

export const generateSearchKeywords = async (categoryName: string): Promise<ApiResponse<SearchKeywordsResponse>> => {
  // TypeScript 会在编译时捕获类型错误
}

2. API 客户端注释改进

更新 Web/src/api/icons.js 的 JSDoc

/**
 * 生成搜索关键字
 * @param {string} categoryName - 分类名称
 * @returns {Promise<{success: boolean, data: {keywords: string[]}}>}
 *          注意: data 是对象,包含 keywords 数组字段
 */

3. 单元测试补充

为前端组件添加单元测试,验证 API 调用参数:

// ClassificationEdit.spec.js
describe('ClassificationEdit - Icon Selection', () => {
  it('should pass keywords array to searchIcons', async () => {
    const mockKeywords = { keywords: ['food', 'restaurant'] }
    generateSearchKeywords.mockResolvedValue({ success: true, data: mockKeywords })
    
    await openIconSelector(category)
    
    expect(searchIcons).toHaveBeenCalledWith(['food', 'restaurant'])
  })
})

相关文档

  • API 文档: .doc/ICONIFY_INTEGRATION.md
  • 任务列表: openspec/changes/icon-search-integration/tasks.md
  • 测试脚本: .doc/test-icon-api.sh

经验教训

  1. 响应结构验证: 在使用 API 响应数据前,应验证数据结构
  2. 变量命名清晰: 使用清晰的变量名(如 keywordsResponse 而非 keywords
  3. 类型安全: TypeScript 可以在编译时捕获此类错误
  4. 测试覆盖: 需要为前端组件添加集成测试

修复者: AI Assistant
审核者: 待审核
最后更新: 2026-02-16