fix
This commit is contained in:
parent
313eaf822d
commit
ee1efe5f29
|
|
@ -0,0 +1,187 @@
|
|||
# ProjectMaterialsCenter.tsx 错误修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
`ProjectMaterialsCenter.tsx` 组件在使用新的 `ResourceCategoryServiceV2` 时出现了类型错误,主要问题是:
|
||||
|
||||
1. 新的 API 直接返回 `ResourceCategoryV2[]` 数组,而不是包装在响应对象中
|
||||
2. 代码仍然使用旧的响应格式 `response.status` 和 `response.data`
|
||||
3. TypeScript 类型检查失败
|
||||
|
||||
## 错误详情
|
||||
|
||||
### 原始错误代码
|
||||
|
||||
```typescript
|
||||
const loadCategories = async () => {
|
||||
setLoadingCategories(true)
|
||||
try {
|
||||
const response = await ResourceCategoryService.getAllCategories()
|
||||
if (response.status && response.data) { // ❌ 错误:response 是数组,没有 status 和 data 属性
|
||||
setCategories(response.data.filter(cat => cat.is_active)) // ❌ 错误:response.data 不存在
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load categories:', error)
|
||||
} finally {
|
||||
setLoadingCategories(false)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript 错误信息
|
||||
|
||||
```
|
||||
L49-49: Property 'status' does not exist on type 'ResourceCategoryV2[]'.
|
||||
L49-49: Property 'data' does not exist on type 'ResourceCategoryV2[]'.
|
||||
L50-50: Property 'data' does not exist on type 'ResourceCategoryV2[]'.
|
||||
L50-50: Parameter 'cat' implicitly has an 'any' type.
|
||||
```
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. 更新导入语句
|
||||
|
||||
```typescript
|
||||
// 修复前
|
||||
import { ResourceCategory, ResourceCategoryService } from '../services/resourceCategoryService'
|
||||
|
||||
// 修复后
|
||||
import { ResourceCategoryV2 as ResourceCategory, ResourceCategoryServiceV2 as ResourceCategoryService } from '../services/resourceCategoryServiceV2'
|
||||
```
|
||||
|
||||
### 2. 修复 API 调用
|
||||
|
||||
```typescript
|
||||
// 修复后的代码
|
||||
const loadCategories = async () => {
|
||||
setLoadingCategories(true)
|
||||
try {
|
||||
const categories = await ResourceCategoryService.getAllCategories()
|
||||
// 过滤出活跃的分类
|
||||
setCategories(categories.filter((cat: ResourceCategory) => cat.is_active))
|
||||
} catch (error) {
|
||||
console.error('Failed to load categories:', error)
|
||||
// 如果加载失败,设置为空数组
|
||||
setCategories([])
|
||||
} finally {
|
||||
setLoadingCategories(false)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 主要变化
|
||||
|
||||
### 1. API 响应格式变化
|
||||
|
||||
| 旧版本 | 新版本 |
|
||||
|--------|--------|
|
||||
| `{ status: boolean, data: ResourceCategory[] }` | `ResourceCategoryV2[]` |
|
||||
| 需要检查 `response.status` | 直接使用返回的数组 |
|
||||
| 使用 `response.data` | 直接使用返回值 |
|
||||
|
||||
### 2. 错误处理改进
|
||||
|
||||
```typescript
|
||||
// 新增:加载失败时设置空数组
|
||||
catch (error) {
|
||||
console.error('Failed to load categories:', error)
|
||||
setCategories([]) // 确保组件不会因为数据为 undefined 而崩溃
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 类型安全
|
||||
|
||||
```typescript
|
||||
// 明确指定类型,避免隐式 any
|
||||
categories.filter((cat: ResourceCategory) => cat.is_active)
|
||||
```
|
||||
|
||||
## 兼容性说明
|
||||
|
||||
### 向后兼容
|
||||
|
||||
通过使用别名导入,保持了组件内部代码的一致性:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
ResourceCategoryV2 as ResourceCategory,
|
||||
ResourceCategoryServiceV2 as ResourceCategoryService
|
||||
} from '../services/resourceCategoryServiceV2'
|
||||
```
|
||||
|
||||
这样做的好处:
|
||||
- 组件内部代码无需大量修改
|
||||
- 类型名称保持一致
|
||||
- 服务调用方式保持一致
|
||||
|
||||
### 功能增强
|
||||
|
||||
新版本 API 提供了更多功能:
|
||||
- 用户权限管理
|
||||
- 云端分类支持
|
||||
- 更好的错误处理
|
||||
- 统一的响应格式
|
||||
|
||||
## 测试验证
|
||||
|
||||
创建了 `ProjectMaterialsCenter.test.tsx` 来验证修复:
|
||||
|
||||
1. **分类加载测试**:验证正确加载和过滤活跃分类
|
||||
2. **错误处理测试**:验证加载失败时的错误处理
|
||||
3. **数量计算测试**:验证分类素材数量计算正确
|
||||
4. **加载状态测试**:验证加载状态显示
|
||||
5. **API 集成测试**:验证使用正确的新 API
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
# 运行单个组件测试
|
||||
npm test ProjectMaterialsCenter.test.tsx
|
||||
|
||||
# 运行所有测试
|
||||
npm test
|
||||
```
|
||||
|
||||
## 修复验证
|
||||
|
||||
### 1. TypeScript 编译
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
应该没有类型错误。
|
||||
|
||||
### 2. ESLint 检查
|
||||
|
||||
```bash
|
||||
npx eslint src/components/ProjectMaterialsCenter.tsx
|
||||
```
|
||||
|
||||
应该没有 linting 错误。
|
||||
|
||||
### 3. 功能测试
|
||||
|
||||
在浏览器中测试:
|
||||
1. 分类正确加载
|
||||
2. 分类筛选功能正常
|
||||
3. 错误情况下不会崩溃
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `src/components/ProjectMaterialsCenter.tsx` - 主要修复文件
|
||||
- `src/services/resourceCategoryServiceV2.ts` - 新的服务层
|
||||
- `src/components/ProjectMaterialsCenter.test.tsx` - 测试文件
|
||||
- `docs/ProjectMaterialsCenter_Fix.md` - 本文档
|
||||
|
||||
## 总结
|
||||
|
||||
通过这次修复:
|
||||
|
||||
✅ **解决了 TypeScript 类型错误**
|
||||
✅ **更新到最新的 API**
|
||||
✅ **改进了错误处理**
|
||||
✅ **保持了向后兼容性**
|
||||
✅ **添加了完整的测试覆盖**
|
||||
|
||||
组件现在可以正确使用新的 `ResourceCategoryServiceV2` API,同时保持了原有的功能和用户体验。
|
||||
|
|
@ -684,6 +684,7 @@ PostgreSQL 驱动 psycopg2 未安装。请安装:
|
|||
duration=row['duration'],
|
||||
material_count=row['material_count'],
|
||||
track_count=row['track_count'],
|
||||
draft_content=row["draft_content"],
|
||||
tags=row['tags'] if isinstance(row['tags'], list) else [],
|
||||
is_cloud=row['is_cloud'],
|
||||
user_id=row['user_id'],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,224 @@
|
|||
/**
|
||||
* ProjectMaterialsCenter 组件测试
|
||||
* 验证修复后的分类加载功能
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import '@testing-library/jest-dom'
|
||||
import ProjectMaterialsCenter from './ProjectMaterialsCenter'
|
||||
import { ResourceCategoryServiceV2 } from '../services/resourceCategoryServiceV2'
|
||||
|
||||
// Mock 服务
|
||||
jest.mock('../services/resourceCategoryServiceV2')
|
||||
jest.mock('../services/mediaService')
|
||||
jest.mock('./VideoPlayer', () => ({ isOpen, onClose }: any) =>
|
||||
isOpen ? <div data-testid="video-player">Video Player</div> : null
|
||||
)
|
||||
jest.mock('./ImportMaterialModal', () => ({ onCancel }: any) =>
|
||||
<div data-testid="import-modal">Import Modal</div>
|
||||
)
|
||||
jest.mock('./VideoPlayer2', () => ({ src }: any) =>
|
||||
<div data-testid="video-player2">Video Player 2: {src}</div>
|
||||
)
|
||||
|
||||
const mockResourceCategoryService = ResourceCategoryServiceV2 as jest.Mocked<typeof ResourceCategoryServiceV2>
|
||||
|
||||
describe('ProjectMaterialsCenter', () => {
|
||||
const mockProject = {
|
||||
id: 'test-project',
|
||||
product_name: 'Test Product',
|
||||
description: 'Test Description',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
}
|
||||
|
||||
const mockMaterials = [
|
||||
{
|
||||
id: 'material-1',
|
||||
filename: 'test-video.mp4',
|
||||
file_path: '/path/to/video.mp4',
|
||||
thumbnail_path: '/path/to/thumb.jpg',
|
||||
duration: 120,
|
||||
file_size: 1024000,
|
||||
tags: ['Test Product', '原始'],
|
||||
use_count: 0,
|
||||
segment_index: 0,
|
||||
project_id: 'test-project',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
}
|
||||
]
|
||||
|
||||
const mockModels = [
|
||||
{
|
||||
id: 'model-1',
|
||||
model_number: 'M001',
|
||||
name: 'Test Model',
|
||||
description: 'Test Model Description',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
}
|
||||
]
|
||||
|
||||
const mockCategories = [
|
||||
{
|
||||
id: 'cat-1',
|
||||
title: '视频素材',
|
||||
ai_prompt: '用于识别视频文件',
|
||||
color: '#FF6B6B',
|
||||
is_active: true,
|
||||
is_cloud: false,
|
||||
user_id: 'test-user',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
},
|
||||
{
|
||||
id: 'cat-2',
|
||||
title: '音频素材',
|
||||
ai_prompt: '用于识别音频文件',
|
||||
color: '#4ECDC4',
|
||||
is_active: false, // 这个分类应该被过滤掉
|
||||
is_cloud: false,
|
||||
user_id: 'test-user',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
}
|
||||
]
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('应该正确加载和过滤活跃的分类', async () => {
|
||||
// Mock 服务返回分类数据
|
||||
mockResourceCategoryService.getAllCategories.mockResolvedValue(mockCategories)
|
||||
|
||||
render(
|
||||
<ProjectMaterialsCenter
|
||||
project={mockProject}
|
||||
materials={mockMaterials}
|
||||
models={mockModels}
|
||||
onMaterialsChange={jest.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
// 等待分类加载完成
|
||||
await waitFor(() => {
|
||||
expect(mockResourceCategoryService.getAllCategories).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// 验证只显示活跃的分类
|
||||
expect(screen.getByText('视频素材 (1)')).toBeInTheDocument()
|
||||
expect(screen.queryByText('音频素材')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('应该正确处理分类加载失败的情况', async () => {
|
||||
// Mock 服务抛出错误
|
||||
mockResourceCategoryService.getAllCategories.mockRejectedValue(new Error('Network error'))
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
|
||||
|
||||
render(
|
||||
<ProjectMaterialsCenter
|
||||
project={mockProject}
|
||||
materials={mockMaterials}
|
||||
models={mockModels}
|
||||
onMaterialsChange={jest.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
// 等待错误处理完成
|
||||
await waitFor(() => {
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Failed to load categories:', expect.any(Error))
|
||||
})
|
||||
|
||||
// 验证显示全部分类选项(即使加载失败)
|
||||
expect(screen.getByText('全部 (1)')).toBeInTheDocument()
|
||||
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('应该正确计算分类的素材数量', async () => {
|
||||
mockResourceCategoryService.getAllCategories.mockResolvedValue(mockCategories)
|
||||
|
||||
render(
|
||||
<ProjectMaterialsCenter
|
||||
project={mockProject}
|
||||
materials={mockMaterials}
|
||||
models={mockModels}
|
||||
onMaterialsChange={jest.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockResourceCategoryService.getAllCategories).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// 验证分类数量计算正确
|
||||
expect(screen.getByText('全部 (1)')).toBeInTheDocument()
|
||||
expect(screen.getByText('视频素材 (1)')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('应该显示加载状态', () => {
|
||||
// Mock 一个永远不会 resolve 的 Promise 来模拟加载状态
|
||||
mockResourceCategoryService.getAllCategories.mockImplementation(
|
||||
() => new Promise(() => {}) // 永远不会 resolve
|
||||
)
|
||||
|
||||
render(
|
||||
<ProjectMaterialsCenter
|
||||
project={mockProject}
|
||||
materials={mockMaterials}
|
||||
models={mockModels}
|
||||
onMaterialsChange={jest.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
// 验证显示加载状态
|
||||
expect(screen.getByText('加载中...')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// 集成测试:验证新的 API 调用方式
|
||||
describe('ProjectMaterialsCenter API Integration', () => {
|
||||
it('应该使用新的 ResourceCategoryServiceV2 API', async () => {
|
||||
const mockCategories = [
|
||||
{
|
||||
id: 'cat-1',
|
||||
title: '测试分类',
|
||||
ai_prompt: '测试提示',
|
||||
color: '#FF0000',
|
||||
is_active: true,
|
||||
is_cloud: false,
|
||||
user_id: 'test-user',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
}
|
||||
]
|
||||
|
||||
mockResourceCategoryService.getAllCategories.mockResolvedValue(mockCategories)
|
||||
|
||||
const { rerender } = render(
|
||||
<ProjectMaterialsCenter
|
||||
project={{
|
||||
id: 'test',
|
||||
product_name: 'Test',
|
||||
description: 'Test',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
}}
|
||||
materials={[]}
|
||||
models={[]}
|
||||
onMaterialsChange={jest.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockResourceCategoryService.getAllCategories).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
// 验证调用了正确的方法
|
||||
expect(mockResourceCategoryService.getAllCategories).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
|
@ -45,12 +45,13 @@ const ProjectMaterialsCenter: React.FC<ProjectMaterialsCenterProps> = ({
|
|||
const loadCategories = async () => {
|
||||
setLoadingCategories(true)
|
||||
try {
|
||||
const response = await ResourceCategoryService.getAllCategories()
|
||||
if (response.status && response.data) {
|
||||
setCategories(response.data.filter(cat => cat.is_active))
|
||||
}
|
||||
const categories = await ResourceCategoryService.getAllCategories()
|
||||
// 过滤出活跃的分类
|
||||
setCategories(categories.filter((cat: ResourceCategory) => cat.is_active))
|
||||
} catch (error) {
|
||||
console.error('Failed to load categories:', error)
|
||||
// 如果加载失败,设置为空数组
|
||||
setCategories([])
|
||||
} finally {
|
||||
setLoadingCategories(false)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue