# ADR 003: 分镜看板基于 orderIndex 的性能优化 ## 状态 提议中 (Proposed) ## 背景 当前分镜看板基于时间轴(startTime/endTime)布局,存在以下问题: - 布局计算复杂(需要处理重叠、间隙) - 跳转定位困难(需要计算累积时间) - 不适合虚拟滚动(时间不连续) ## 决策 采用**基于 orderIndex 序号排序 + 虚拟滚动分页**的方案。 ### 核心设计理念 **抛开时间轴概念**: - 分镜按 orderIndex 顺序排列(紧密相连,无间隙) - 宽度由 duration 决定,位置由 orderIndex 决定 - 使用虚拟滚动只渲染可见分镜 ### 架构设计 #### 1. 数据分层 ``` Layer 1: 骨架数据(Skeleton) ├─ 用途:计算总宽度、跳转定位 ├─ 数据:id, orderIndex, duration └─ 大小:200 分镜 ≈ 5-10 KB Layer 2: 分页数据(Paginated) ├─ 用途:虚拟滚动渲染 ├─ 数据:按 orderIndex 范围加载(每页 50 个) └─ 大小:50 分镜 ≈ 25-30 KB ``` #### 2. API 设计 ##### 骨架数据接口(新增) ``` GET /api/v1/projects/{project_id}/storyboard-board/skeleton ``` **响应**: ```json { "projectId": "xxx", "storyboardCount": 200, "tracks": [ { "type": "storyboard", "items": [ { "id": "xxx", "orderIndex": 0, "duration": 3.0 }, { "id": "yyy", "orderIndex": 1, "duration": 5.0 } ] } ] } ``` **数据量**:200 分镜 ≈ 5-10 KB ##### 分页数据接口(新增) ``` GET /api/v1/projects/{project_id}/storyboard-board/paginated ?startIndex=0 &endIndex=49 &trackTypes=storyboard,resource ``` **响应**: ```json { "items": [ { "id": "xxx", "type": "storyboard", "orderIndex": 0, "duration": 3.0, "data": { "title": "分镜 1", "thumbnail_url": "...", "resources": {...} } } ] } ``` **数据量**:50 分镜 ≈ 25-30 KB #### 3. 前端架构 ##### 虚拟滚动实现 ```typescript import { VariableSizeList } from 'react-window'; // 1. 加载骨架数据 const { data: skeleton } = useStoryboardBoardSkeleton(projectId); // 2. 计算每个分镜的宽度 const getItemSize = (index: number) => { const item = skeleton.items[index]; return item.duration * pps; // pps = pixels per second }; // 3. 虚拟滚动渲染 {({ index, style }) => ( )} ``` ##### 分页加载策略 ```typescript // 计算可见范围的 orderIndex const calculateVisibleRange = (scrollLeft: number, containerWidth: number) => { let accumulatedWidth = 0; let startIndex = 0; let endIndex = 0; for (let i = 0; i < skeleton.items.length; i++) { const itemWidth = skeleton.items[i].duration * pps; if (accumulatedWidth <= scrollLeft && startIndex === 0) { startIndex = i; } if (accumulatedWidth <= scrollLeft + containerWidth) { endIndex = i; } else { break; } accumulatedWidth += itemWidth; } return { startIndex, endIndex }; }; // 加载可见范围的数据 const { data: visibleItems } = useStoryboardBoardPaginated( projectId, visibleRange.startIndex, visibleRange.endIndex ); ``` ##### 跳转定位实现 ```typescript // 点击分镜列表时跳转到对应分镜 const scrollToStoryboard = (storyboardId: string) => { // 1. 找到分镜的 orderIndex const targetItem = skeleton.items.find(item => item.id === storyboardId); if (!targetItem) return; // 2. 计算前面所有分镜的宽度总和 let scrollLeft = 0; for (let i = 0; i < targetItem.orderIndex; i++) { scrollLeft += skeleton.items[i].duration * pps; } // 3. 滚动到目标位置 listRef.current?.scrollTo(scrollLeft); }; ``` #### 4. 性能优化 ##### 预加载策略 ```typescript // 预加载相邻页(前后各 25 个分镜) const preloadRange = { startIndex: Math.max(0, visibleRange.startIndex - 25), endIndex: Math.min(skeleton.items.length - 1, visibleRange.endIndex + 25) }; useStoryboardBoardPaginated(projectId, preloadRange.startIndex, preloadRange.endIndex); ``` ##### 缓存策略 ```typescript // TanStack Query 缓存配置 const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 分钟 cacheTime: 10 * 60 * 1000, // 10 分钟 keepPreviousData: true, // 保留上一页数据,避免闪烁 }, }, }); ``` ## 实施计划 ### 阶段 1:后端 API(2-3 天) - [ ] 新增骨架数据接口 `/storyboard-board/skeleton` - [ ] 新增分页数据接口 `/storyboard-board/paginated` - [ ] 添加响应压缩(gzip) ### 阶段 2:前端基础架构(2-3 天) - [ ] 实现骨架数据加载 - [ ] 实现分页数据加载 - [ ] 实现数据缓存管理 ### 阶段 3:虚拟滚动(3-4 天) - [ ] 集成 react-window(水平虚拟滚动) - [ ] 实现可见范围计算 - [ ] 实现跳转定位功能 ### 阶段 4:优化和测试(2-3 天) - [ ] 性能测试(1000+ 分镜) - [ ] 跳转定位测试 - [ ] 用户体验优化 **总计**:9-13 天 ## 性能目标 | 指标 | 当前 | 目标 | |------|------|------| | 首次加载时间 | 2-5s | <300ms | | 骨架数据量 | 300-500 KB | <10 KB | | 分页数据量 | - | <30 KB | | 内存占用 | 随分镜数增长 | 恒定 ~50 MB | | 滚动 FPS | 30-40 | 60 | | 跳转定位时间 | - | <100ms | | 支持分镜数 | <200 | 1000+ | ## 优势 1. **架构简单**:无需复杂的时间轴计算 2. **性能卓越**:虚拟滚动 + 分页加载 3. **跳转快速**:基于 orderIndex 直接计算位置 4. **易于维护**:逻辑清晰,代码简洁 5. **可扩展性强**:支持大规模数据 ## 劣势 1. **需要重构**:现有时间轴逻辑需要调整 2. **开发周期**:预计 2 周 3. **测试成本**:需要大量测试 ## 与 ADR 002 的对比 | 维度 | ADR 002(时间轴) | ADR 003(orderIndex) | |------|------------------|---------------------| | 架构复杂度 | 高 | 低 | | 实施难度 | 高 | 中 | | 性能 | 优秀 | 卓越 | | 跳转定位 | 复杂 | 简单 | | 开发周期 | 2-3 周 | 1.5-2 周 | **推荐**:ADR 003(orderIndex 方案) ## 参考资料 - [React Window 文档](https://react-window.vercel.app/) - [TanStack Query 文档](https://tanstack.com/query/latest) - [虚拟滚动最佳实践](https://web.dev/virtualize-long-lists-react-window/)