You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

8.8 KiB

共享资源选择器数据结构适配

日期:2026-02-08 类型:技术调整 影响范围:前端 - ResourceSelectorPanel、API 类型定义


变更概述

根据真实 API 数据结构调整共享资源选择器的数据模型,适配文件夹/项目树 API 响应格式。

背景

初版使用的是简化的 mock 数据结构,与真实 API 返回的数据格式不匹配,需要调整以支持真实数据。

API 数据结构

文件夹/项目树 API

接口GET /api/v1/folders/tree?category=1

响应结构

{
  "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 类型定义

变更前

interface ResourceTreeNode {
  id: string;
  type: 'project' | 'folder' | 'character' | 'scene' | 'prop';
  name: string;
  children?: ResourceTreeNode[];
  characterCount?: number;
  sceneCount?: number;
  propCount?: number;
}

变更后

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 数据结构更新

变更前

const mockResourceTree = [
  {
    id: 'folder-mine',
    name: '我的项目',
    children: [...]
  }
];

变更后

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 包装到"我的项目"虚拟节点中:

// 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 集成代码(待启用)

// 从 API 加载文件夹/项目树数据
const { data: folderTreeData, isLoading } = useQuery({
  queryKey: ['shareable-folders-projects'],
  queryFn: () => getShareableFoldersAndProjects(1), // 1 = 我的项目
  enabled: open, // 仅面板打开时加载
});

// 使用 API 数据(待实现数据转换)
const treeData = folderTreeData 
  ? transformApiDataToTree(folderTreeData)
  : mockResourceTree;

2. 状态初始化

变更前

const [expandedIds, setExpandedIds] = useState<Set<string>>(
  new Set(['folder-mine'])
);

变更后

const [expandedIds, setExpandedIds] = useState<Set<string>>(
  new Set(['virtual-mine'])
);

项目资源加载策略

问题

项目节点需要显示资源统计(角色数、场景数、道具数),但:

  1. 文件夹树 API 不返回资源统计
  2. 需要额外调用 /projects/{id}/resources/stats 获取
  3. 如果逐个项目请求会导致大量 API 调用

解决方案

方案 A:批量预加载(推荐)

// 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:懒加载

// 展开项目时才加载其资源统计
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:数据结构适配真实 API