- 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
5.1 KiB
5.1 KiB
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)
问题分析
-
generateSearchKeywords()返回的data是SearchKeywordsResponse对象:{ keywords: ["food", "restaurant", "dining"] } -
代码错误地将整个对象传递给
searchIcons():// 实际发送的请求体 { keywords: { keywords: ["food", "restaurant"] } } -
后端期望的格式:
{ 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)
关键变更
- 重命名变量:
data: keywords→data: keywordsResponse(更清晰) - 访问嵌套属性:
keywordsResponse.keywords - 更新验证逻辑:检查
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
经验教训
- 响应结构验证: 在使用 API 响应数据前,应验证数据结构
- 变量命名清晰: 使用清晰的变量名(如
keywordsResponse而非keywords) - 类型安全: TypeScript 可以在编译时捕获此类错误
- 测试覆盖: 需要为前端组件添加集成测试
修复者: AI Assistant
审核者: 待审核
最后更新: 2026-02-16