# 共享资源选择器数据结构适配 > **日期**:2026-02-08 > **类型**:技术调整 > **影响范围**:前端 - ResourceSelectorPanel、API 类型定义 --- ## 变更概述 根据真实 API 数据结构调整共享资源选择器的数据模型,适配文件夹/项目树 API 响应格式。 ## 背景 初版使用的是简化的 mock 数据结构,与真实 API 返回的数据格式不匹配,需要调整以支持真实数据。 ## API 数据结构 ### 文件夹/项目树 API **接口**:`GET /api/v1/folders/tree?category=1` **响应结构**: ```json { "success": true, "code": 200, "message": "Success", "data": { "id": "root", "name": "根目录", "children": [ { "id": "019c2dcf-d972-7fe3-9078-99d90bf68253", "name": "文件夹1", "type": "folder", "description": "", "color": "#F59E0B", "level": 0, "folderCategory": 1, "projectCount": 4, "subfolderCount": 0, "children": [ { "id": "019c3167-493c-7ca2-9996-ad158b430ef7", "name": "西游记之大圣归来", "type": "project", "contentType": "series", "aspectRatio": "16:9", "status": 0, "children": [] } ] }, { "id": "019c2dd7-8bb7-79d2-badd-a5df4dbc6bd1", "name": "灭霸来了", "type": "project", "contentType": "ad", "aspectRatio": "16:9", "status": 0, "children": [] } ] } } ``` ### 层级关系 ``` 根目录 (虚拟) ├─ 我的项目 (虚拟节点,需要前端添加) │ ├─ 文件夹1 (level=0) │ │ ├─ 项目A │ │ └─ 项目B │ ├─ 文件夹2 (level=0) │ │ └─ 子文件夹2-1 (level=1) │ │ └─ 项目C │ └─ 项目D (根级项目,与文件夹同级) └─ 协作项目 (另一个虚拟节点,category=2) ``` **关键特点**: 1. API 返回的是扁平的 `root.children` 数组 2. 文件夹和项目可以在同一层级(根级项目) 3. 需要前端添加"我的项目"虚拟节点 4. `level` 字段表示文件夹嵌套层级(0 开始) ## 数据结构调整 ### 1. ResourceTreeNode 类型定义 **变更前**: ```typescript interface ResourceTreeNode { id: string; type: 'project' | 'folder' | 'character' | 'scene' | 'prop'; name: string; children?: ResourceTreeNode[]; characterCount?: number; sceneCount?: number; propCount?: number; } ``` **变更后**: ```typescript interface ResourceTreeNode { id: string; type: 'folder' | 'project' | 'character' | 'scene' | 'prop'; name: string; description?: string | null; children?: ResourceTreeNode[]; // 文件夹特有字段 color?: string; level?: number; folderCategory?: number; projectCount?: number; subfolderCount?: number; // 项目特有字段 contentType?: string | null; aspectRatio?: string | null; thumbnailUrl?: string | null; status?: number; isSubproject?: boolean; parentProjectId?: string | null; screenplayId?: string | null; // 资源统计(需要从其他接口获取) characterCount?: number; sceneCount?: number; propCount?: number; } ``` ### 2. Mock 数据结构更新 **变更前**: ```typescript const mockResourceTree = [ { id: 'folder-mine', name: '我的项目', children: [...] } ]; ``` **变更后**: ```typescript const mockResourceTree = [ { id: 'virtual-mine', type: 'folder', name: '我的项目', level: -1, // 虚拟根节点 children: [ { id: '019c2dcf-d972-7fe3-9078-99d90bf68253', name: '文件夹1', type: 'folder', color: '#F59E0B', level: 0, projectCount: 4, children: [...] }, { id: '019c2dd7-8bb7-79d2-badd-a5df4dbc6bd1', name: '灭霸来了', type: 'project', // 根级项目 contentType: 'ad', children: [] } ] } ]; ``` ### 3. 虚拟节点处理 前端需要将 API 返回的 `root.children` 包装到"我的项目"虚拟节点中: ```typescript // API 数据转换函数(待实现) function transformApiDataToTree(apiData: FolderTreeResponse): ResourceTreeNode[] { return [ { id: 'virtual-mine', type: 'folder', name: '我的项目', level: -1, children: apiData.data.children.map(transformNode), }, ]; } function transformNode(node: FolderTreeNode): ResourceTreeNode { return { ...node, children: node.children?.map(transformNode), }; } ``` ## 新增文件 ### 1. 类型定义文件 **文件**:`client/src/types/shared-resource.ts` **内容**: - `FolderTreeNode` - API 返回的文件夹/项目节点 - `FolderTreeResponse` - 文件夹树 API 响应 - `ProjectResourceNode` - 项目资源节点 - `ProjectResourcesResponse` - 项目资源列表响应 - `ResourceStats` - 资源统计 - `SharedResourceSubmit` - 共享资源提交数据 - `CreateProjectWithResourcesParams` - 创建项目参数 ### 2. API 服务文件 **文件**:`client/src/services/api/shared-resources.ts` **接口**: - `getShareableFoldersAndProjects()` - 获取文件夹/项目树 - `getProjectResources()` - 获取项目资源列表 - `getProjectResourceStats()` - 获取项目资源统计 - `getBatchProjectResourceStats()` - 批量获取资源统计 ## 代码调整 ### 1. ResourceSelectorPanel.tsx **变更点**: 1. 更新 `ResourceTreeNode` 类型定义(匹配 API 结构) 2. 更新 mock 数据(使用真实 API 结构) 3. 虚拟节点 ID 改为 `virtual-mine`(原 `folder-mine`) 4. 添加 API 集成准备(注释代码) **API 集成代码(待启用)**: ```typescript // 从 API 加载文件夹/项目树数据 const { data: folderTreeData, isLoading } = useQuery({ queryKey: ['shareable-folders-projects'], queryFn: () => getShareableFoldersAndProjects(1), // 1 = 我的项目 enabled: open, // 仅面板打开时加载 }); // 使用 API 数据(待实现数据转换) const treeData = folderTreeData ? transformApiDataToTree(folderTreeData) : mockResourceTree; ``` ### 2. 状态初始化 **变更前**: ```typescript const [expandedIds, setExpandedIds] = useState>( new Set(['folder-mine']) ); ``` **变更后**: ```typescript const [expandedIds, setExpandedIds] = useState>( new Set(['virtual-mine']) ); ``` ## 项目资源加载策略 ### 问题 项目节点需要显示资源统计(角色数、场景数、道具数),但: 1. 文件夹树 API 不返回资源统计 2. 需要额外调用 `/projects/{id}/resources/stats` 获取 3. 如果逐个项目请求会导致大量 API 调用 ### 解决方案 **方案 A:批量预加载(推荐)** ```typescript // 1. 加载文件夹树 const folderTree = await getShareableFoldersAndProjects(1); // 2. 提取所有项目 ID const projectIds = extractProjectIds(folderTree); // 3. 批量获取资源统计 const stats = await getBatchProjectResourceStats(projectIds); // 4. 合并数据 const treeWithStats = mergeStats(folderTree, stats); ``` **方案 B:懒加载** ```typescript // 展开项目时才加载其资源统计 const handleExpandProject = async (projectId: string) => { const stats = await getProjectResourceStats(projectId); updateProjectStats(projectId, stats); }; ``` **推荐使用方案 A**: - 优点:一次性加载,用户体验好,便于搜索和过滤 - 缺点:初始加载时间稍长 - 优化:后端实现批量统计接口,一次查询获取所有项目统计 ## 后续工作 ### Phase 1:API 集成(高优先级) - [ ] 实现 `transformApiDataToTree` 数据转换函数 - [ ] 实现批量资源统计接口(后端) - [ ] 集成 React Query 加载数据 - [ ] 添加加载状态 UI(骨架屏) - [ ] 添加错误处理 UI ### Phase 2:项目资源加载(中优先级) - [ ] 实现项目资源懒加载 - [ ] 展开项目时加载角色/场景/道具列表 - [ ] 资源节点可选择功能 - [ ] 资源节点搜索支持 ### Phase 3:性能优化(低优先级) - [ ] 实现虚拟滚动(项目 > 100 个) - [ ] 缓存已加载的资源数据 - [ ] 优化批量统计查询性能 ## 测试建议 ### 数据结构测试 - [ ] 文件夹嵌套层级正确显示(level 0, 1, 2...) - [ ] 根级项目与文件夹同级显示 - [ ] 虚拟节点"我的项目"正确展开 - [ ] 项目统计数据正确显示 ### API 集成测试 - [ ] API 加载失败时使用 mock 数据降级 - [ ] 加载状态正确显示 - [ ] 错误提示友好 - [ ] 数据刷新机制正常 ### 兼容性测试 - [ ] 空文件夹正确显示 - [ ] 无项目的文件夹正常渲染 - [ ] 深层嵌套(5+ 层)性能正常 ## 相关文档 - [共享资源选择器优化](./2026-02-08-resource-selector-optimization.md) - [ADR 02: 跨项目资源共享](../../server/adrs/02-cross-project-resource-sharing.md) ## 变更记录 - 2026-02-08:数据结构适配真实 API