# RFC 130: Timeline Mock 数据重构 **状态**: 已实现 **创建日期**: 2026-01-24 **作者**: AI Assistant ## 背景 原有的 timeline mock 数据采用独立存储的方式,与后端 timeline-service.md 的设计理念不符。后端设计明确指出: > 时间轴管理服务是分镜数据的**可视化看板**,提供多轨道视图展示分镜及其关联的所有资源。所有数据实时从分镜及其关联表计算,无独立数据存储。 ## 问题 ### 1. 数据冗余 - timeline.ts 中维护了独立的 `mockTracks` 和 `mockItems` 数据 - 这些数据与 storyboards.ts 中的数据重复 - 需要手动维护数据同步 ### 2. 关联性错误 - 资源轨道的 resourceId 引用不正确 - 没有真正从 storyboard.resources 获取数据 - 时间计算逻辑与分镜数据脱节 ### 3. 扩展性差 - 添加新的分镜时需要同时更新 timeline 数据 - 容易出现数据不一致的问题 ## 解决方案 ### 核心设计 实现 `getTimelineByProjectId()` 函数,实时从 storyboards 计算时间轴数据: ```typescript export function getTimelineByProjectId(projectId: string): Timeline { // 1. 获取项目的所有分镜(已按 orderIndex 排序) const storyboards = getStoryboardsByProjectId(projectId); // 2. 初始化轨道 const tracks: TimelineTrack[] = [...]; const items: TimelineItem[] = []; // 3. 遍历分镜,累积计算时间位置 let currentTime = 0; for (const storyboard of storyboards) { const startTime = currentTime; const endTime = currentTime + storyboard.duration; // 3.1 添加分镜轨道项 items.push({ ... }); // 3.2 从 storyboard.resources 获取资源轨道项 if (storyboard.resources) { // 角色、场景、道具、实拍 } // 3.3 其他轨道(视频、音效、字幕、配音) // TODO: 待实现 currentTime = endTime; } return { tracks, items }; } ``` ### 数据流 ``` storyboards (分镜数据) └─ resources (关联的资源) ├─ characters ├─ scenes ├─ props └─ footages ↓ getTimelineByProjectId() 实时计算 ↓ 返回 Timeline { tracks, items } ``` ### 时间计算逻辑 ```typescript // 按 orderIndex 排序分镜 const storyboards = getStoryboardsByProjectId(projectId); // 累积计算每个分镜的时间位置 let currentTime = 0; for (const storyboard of storyboards) { const startTime = currentTime; const endTime = currentTime + storyboard.duration; // 添加轨道项... currentTime = endTime; // 累加时间 } ``` ## 实现细节 ### 1. 分镜轨道 直接从 storyboard 数据生成: ```typescript items.push({ id: storyboard.id, trackId: `track-storyboard-${projectId}`, itemType: 'storyboard', storyboardId: storyboard.id, startTime, endTime, displayOrder: storyboard.orderIndex, createdAt: storyboard.createdAt, updatedAt: storyboard.updatedAt, }); ``` ### 2. 资源轨道 从 `storyboard.resources` 字段获取: ```typescript if (storyboard.resources) { // 角色资源 storyboard.resources.characters?.forEach((charRef, index) => { const resource = getResourceById(charRef.resourceId); if (resource) { items.push({ id: `${storyboard.id}-character-${charRef.resourceId}`, trackId: `track-resource-${projectId}`, itemType: 'storyboard', storyboardId: storyboard.id, startTime, endTime, displayOrder: index, // ... }); } }); // 场景、道具、实拍同理 } ``` ### 3. 其他轨道 预留 TODO 注释,待后续实现: ```typescript // 3. 视频轨道项(如果分镜有生成的视频) // TODO: 当实现视频生成功能后,从 storyboard.generated_video 获取 // 4. 音效轨道项 // TODO: 当实现音效功能后,从 storyboard.sound_effects 获取 // 5. 字幕轨道项(对白) // TODO: 当实现对白功能后,从 storyboard.dialogues 获取 // 6. 配音轨道项 // TODO: 当实现配音功能后,从 storyboard.voiceovers 获取 ``` ## 向后兼容 为了不破坏现有代码,保留了以下导出: ```typescript // 向后兼容:使用默认项目 ID 生成 mockTimeline export const mockTimeline = getTimelineByProjectId('018e1234-5678-7abc-8def-100000000001'); // 向后兼容:保留 UI 展示用的简化数据 export const mockTimelineTracks: TimelineTrackUI[] = [...]; ``` ## 优势 ### 1. 无数据冗余 - 时间轴不存储独立数据 - 所有数据源自 storyboards - 避免数据同步问题 ### 2. 自动同步 - 分镜变化立即反映到时间轴 - 无需手动维护同步逻辑 ### 3. 关联正确 - 资源轨道真正从 storyboard.resources 获取 - 通过 getResourceById() 验证资源存在性 - 时间计算基于分镜的 duration ### 4. 易于扩展 - 添加新分镜时,时间轴自动更新 - 后续添加视频、音效等功能时,只需从 storyboard 对应字段获取 ### 5. 符合后端设计 - 完全遵循 timeline-service.md 的设计理念 - 实现了"实时计算"的核心原则 ## 测试建议 1. 验证时间轴数据正确生成 2. 验证资源轨道项与 storyboard.resources 一致 3. 验证时间位置累积计算正确 4. 验证向后兼容性(mockTimeline 导出) ## 后续工作 1. 实现视频轨道(从 storyboard.generated_video 获取) 2. 实现音效轨道(从 storyboard.sound_effects 获取) 3. 实现字幕轨道(从 storyboard.dialogues 获取) 4. 实现配音轨道(从 storyboard.voiceovers 获取) 5. 逐步迁移使用 getTimelineByProjectId() 替代 mockTimeline ## 参考文档 - [Timeline Service 文档](../../requirements/backend/04-services/project/timeline-service.md) - [Storyboard Service 文档](../../requirements/backend/04-services/project/storyboard-service.md) - [Mock 数据规范化 Changelog](../changelogs/2026-01-24-mock-data-normalization.md)