diff --git a/docs/ProjectMaterialsCenter_Fix.md b/docs/ProjectMaterialsCenter_Fix.md new file mode 100644 index 0000000..989a77d --- /dev/null +++ b/docs/ProjectMaterialsCenter_Fix.md @@ -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,同时保持了原有的功能和用户体验。 diff --git a/python_core/database/template_postgres.py b/python_core/database/template_postgres.py index 9b48aa0..64cbc1d 100644 --- a/python_core/database/template_postgres.py +++ b/python_core/database/template_postgres.py @@ -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'], diff --git a/src/components/ProjectMaterialsCenter.test.tsx b/src/components/ProjectMaterialsCenter.test.tsx new file mode 100644 index 0000000..1862346 --- /dev/null +++ b/src/components/ProjectMaterialsCenter.test.tsx @@ -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 ?
Video Player
: null +) +jest.mock('./ImportMaterialModal', () => ({ onCancel }: any) => +
Import Modal
+) +jest.mock('./VideoPlayer2', () => ({ src }: any) => +
Video Player 2: {src}
+) + +const mockResourceCategoryService = ResourceCategoryServiceV2 as jest.Mocked + +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( + + ) + + // 等待分类加载完成 + 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( + + ) + + // 等待错误处理完成 + 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( + + ) + + 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( + + ) + + // 验证显示加载状态 + 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( + + ) + + await waitFor(() => { + expect(mockResourceCategoryService.getAllCategories).toHaveBeenCalledWith() + }) + + // 验证调用了正确的方法 + expect(mockResourceCategoryService.getAllCategories).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/components/ProjectMaterialsCenter.tsx b/src/components/ProjectMaterialsCenter.tsx index bf1c091..79a42b0 100644 --- a/src/components/ProjectMaterialsCenter.tsx +++ b/src/components/ProjectMaterialsCenter.tsx @@ -45,12 +45,13 @@ const ProjectMaterialsCenter: React.FC = ({ 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) }