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.
 

5.8 KiB

RFC 130: Timeline Mock 数据重构

状态: 已实现
创建日期: 2026-01-24
作者: AI Assistant

背景

原有的 timeline mock 数据采用独立存储的方式,与后端 timeline-service.md 的设计理念不符。后端设计明确指出:

时间轴管理服务是分镜数据的可视化看板,提供多轨道视图展示分镜及其关联的所有资源。所有数据实时从分镜及其关联表计算,无独立数据存储。

问题

1. 数据冗余

  • timeline.ts 中维护了独立的 mockTracksmockItems 数据
  • 这些数据与 storyboards.ts 中的数据重复
  • 需要手动维护数据同步

2. 关联性错误

  • 资源轨道的 resourceId 引用不正确
  • 没有真正从 storyboard.resources 获取数据
  • 时间计算逻辑与分镜数据脱节

3. 扩展性差

  • 添加新的分镜时需要同时更新 timeline 数据
  • 容易出现数据不一致的问题

解决方案

核心设计

实现 getTimelineByProjectId() 函数,实时从 storyboards 计算时间轴数据:

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 }

时间计算逻辑

// 按 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 数据生成:

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 字段获取:

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 注释,待后续实现:

// 3. 视频轨道项(如果分镜有生成的视频)
// TODO: 当实现视频生成功能后,从 storyboard.generated_video 获取

// 4. 音效轨道项
// TODO: 当实现音效功能后,从 storyboard.sound_effects 获取

// 5. 字幕轨道项(对白)
// TODO: 当实现对白功能后,从 storyboard.dialogues 获取

// 6. 配音轨道项
// TODO: 当实现配音功能后,从 storyboard.voiceovers 获取

向后兼容

为了不破坏现有代码,保留了以下导出:

// 向后兼容:使用默认项目 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

参考文档