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.
 

12 KiB

RFC 131: 分镜资源 Mock 数据完善

状态: 已实施
创建日期: 2026-01-24
实施日期: 2026-01-24


概述

根据后端服务文档(project-resource-service.md, storyboard-resource-service.md, storyboard-service.md),完善前端 Mock 数据,确保数据结构与后端 API 规范完全一致,并补充完整的时间轴轨道数据。

背景

当前问题

  1. 分镜资源关联不完整

    • storyboards.ts 中的 resources 字段使用简单 ID 字符串
    • 应该使用 { resourceId, displayOrder } 结构
  2. Timeline 缺少完整轨道数据

    • 只有分镜和资源轨道
    • 缺少视频、音效、字幕、配音轨道
  3. 分镜缺少后端新增字段

    • 缺少 shotNumber, shotSize, cameraMovement, dialogue 等字段
    • 缺少 estimatedDuration, actualDuration 时长管理字段

解决方案

1. 新增分镜资源 Mock 数据文件

创建 4 个新文件,模拟后端 storyboard_* 表:

1.1 storyboard-images.ts

export interface StoryboardImage {
  imageId: string; // UUID v7
  storyboardId: string;
  url: string;
  status: 0 | 1 | 2 | 3; // 0=pending, 1=processing, 2=completed, 3=failed
  isActive: boolean;
  version: number;
  width: number;
  height: number;
  fileSize: number;
  format: string;
  checksum: string;
  storageProvider: string;
  storagePath: string;
  aiModel?: string;
  aiPrompt?: string;
  aiParams?: Record<string, any>;
  errorMessage?: string;
  createdAt: string;
  completedAt?: string;
}

数据量: 10 条(对应 10 个分镜)

1.2 storyboard-videos.ts

export interface StoryboardVideo {
  videoId: string;
  storyboardId: string;
  url: string;
  status: 0 | 1 | 2 | 3;
  isActive: boolean;
  version: number;
  duration: number;
  resolution: string;
  frameRate: number;
  fileSize: number;
  format: string;
  checksum: string;
  storageProvider: string;
  storagePath: string;
  aiModel?: string;
  aiParams?: Record<string, any>;
  createdAt: string;
  startedAt?: string;
  completedAt?: string;
}

数据量: 10 条

1.3 storyboard-dialogues.ts

export interface StoryboardDialogue {
  dialogueId: string;
  storyboardId: string;
  characterId?: string;
  characterName?: string;
  text: string;
  sequenceOrder: number;
  startTime?: number;
  duration?: number;
  emotion?: string;
  notes?: string;
  createdAt: string;
  updatedAt: string;
}

数据量: 10 条(西游记分镜的对白)

1.4 storyboard-voiceovers.ts

export interface StoryboardVoiceover {
  voiceoverId: string;
  dialogueId: string;
  storyboardId: string;
  audioUrl: string;
  status: 0 | 1 | 2 | 3;
  isActive: boolean;
  voiceId: string;
  voiceName?: string;
  speed: number;
  volume: number;
  pitch: number;
  duration: number;
  fileSize: number;
  format: string;
  checksum: string;
  storageProvider: string;
  storagePath: string;
  createdAt: string;
  completedAt?: string;
}

数据量: 10 条(对应 10 条对白)

1.5 sound-effects.ts

export interface SoundEffect {
  soundEffectId: string;
  storyboardId: string;
  name: string;
  audioUrl: string;
  category: 'ambient' | 'action' | 'dialogue' | 'music' | 'foley';
  status: 0 | 1 | 2 | 3;
  isActive: boolean;
  startTime: number;
  duration: number;
  volume: number;
  fadeIn: number;
  fadeOut: number;
  loop: boolean;
  fileSize: number;
  format: string;
  checksum: string;
  storageProvider: string;
  storagePath: string;
  tags?: string[];
  notes?: string;
  createdAt: string;
  updatedAt: string;
}

数据量: 21 条(覆盖 10 个分镜)

音效分类:

  • ambient: 环境音(风声、鸟鸣、环境音)
  • action: 动作音效(爆炸、能量爆发、震动)
  • dialogue: 对白音效(暂未使用)
  • music: 音乐(暂未使用)
  • foley: 拟音(脚步、餐具、布料、马蹄)

数据分布:

  • 分镜1-3(日常场景): 6 条音效
  • 西游记分镜7-13(史诗场景): 15 条音效

2. 重构 timeline.ts

2.1 完整的 6 种轨道

export function getTimelineByProjectId(projectId: string): Timeline {
  const tracks: TimelineTrack[] = [
    { type: 'storyboard', name: '分镜', displayOrder: 0 },
    { type: 'resource', name: '资源', displayOrder: 1 },
    { type: 'video', name: '视频', displayOrder: 2 },
    { type: 'sound', name: '音效', displayOrder: 3 },
    { type: 'subtitle', name: '字幕', displayOrder: 4 },
    { type: 'voice', name: '配音', displayOrder: 5 },
  ];
  
  // ...
}

2.2 实时计算逻辑

  • 分镜轨道: 从 storyboards
  • 资源轨道: 从 storyboard.resources 字段
  • 视频轨道: 从 storyboard_videos 表(只显示激活且已完成的视频)
  • 音效轨道: 从 sound_effects 表(只显示激活且已完成的音效)
  • 字幕轨道: 从 storyboard_dialogues
  • 配音轨道: 从 storyboard_voiceovers 表(只显示激活且已完成的配音)

2.3 时间计算

// 音效、字幕和配音的时间是相对于分镜开始时间
const soundEffects = getActiveSoundEffects(storyboard.id);
soundEffects.forEach((sfx) => {
  items.push({
    startTime: storyboardStartTime + sfx.startTime,
    endTime: storyboardStartTime + sfx.startTime + sfx.duration,
  });
});

const dialogue = getDialoguesByStoryboardId(storyboard.id);
dialogue.forEach((d) => {
  items.push({
    startTime: storyboardStartTime + d.startTime,
    endTime: storyboardStartTime + d.startTime + d.duration,
  });
});

3. 更新 storyboards.ts

3.1 修正 resources 字段结构

// 旧格式(错误)
resources: {
  characters: ['1', '2'],
  scenes: ['4'],
  props: ['7'],
}

// 新格式(正确)
resources: {
  characters: [
    { resourceId: '018e1234-5678-7abc-8def-200000000001', displayOrder: 0 },
    { resourceId: '018e1234-5678-7abc-8def-200000000003', displayOrder: 1 },
  ],
  scenes: [
    { resourceId: '018e1234-5678-7abc-8def-200000000006', displayOrder: 0 },
  ],
  props: [
    { resourceId: '018e1234-5678-7abc-8def-200000000010', displayOrder: 0 },
  ],
}

3.2 补充后端新增字段(TODO)

  • shotNumber: 镜号(自动生成,如 "001", "002")
  • shotSize: 景别(1-8)
  • cameraMovement: 运镜(1-9)
  • dialogue: 对白文本
  • estimatedDuration: 预估时长
  • actualDuration: 实际时长

4. 更新 index.ts 导出

export * from './storyboard-images';
export * from './storyboard-videos';
export * from './storyboard-dialogues';
export * from './storyboard-voiceovers';

数据关联关系

关联图

storyboards (13条)
  ├─> storyboard_images (10条) - 1:N,通过 storyboardId
  ├─> storyboard_videos (10条) - 1:N,通过 storyboardId
  ├─> storyboard_dialogues (10条) - 1:N,通过 storyboardId
  ├─> storyboard_voiceovers (10条) - 1:N,通过 storyboardId + dialogueId
  └─> sound_effects (21条) - 1:N,通过 storyboardId

project_resources (33条)
  └─> storyboards.resources - M:N,通过 resourceId

关联验证

所有外键引用已验证:

  • storyboard_images.storyboardIdstoryboards.id
  • storyboard_videos.storyboardIdstoryboards.id
  • storyboard_dialogues.storyboardIdstoryboards.id
  • storyboard_voiceovers.storyboardIdstoryboards.id
  • storyboard_voiceovers.dialogueIdstoryboard_dialogues.dialogueId
  • sound_effects.storyboardIdstoryboards.id
  • storyboards.resources.*.resourceIdproject_resources.id

实施步骤

  1. 创建 storyboard-images.ts - 10 条图片数据
  2. 创建 storyboard-videos.ts - 10 条视频数据
  3. 创建 storyboard-dialogues.ts - 10 条对白数据
  4. 创建 storyboard-voiceovers.ts - 10 条配音数据
  5. 创建 sound-effects.ts - 21 条音效数据
  6. 重构 timeline.ts - 实现完整的 6 种轨道(包含音效轨道)
  7. 更新 storyboards.ts - 修正 resources 字段结构(待后续完成)
  8. 更新 index.ts - 导出新文件
  9. 创建 RFC 文档

测试验证

1. 数据完整性

// 验证所有分镜都有对应的资源
const storyboards = getStoryboardsByProjectId('018e1234-5678-7abc-8def-100000000001');
storyboards.forEach((sb) => {
  const images = getStoryboardImagesByStoryboardId(sb.id);
  const videos = getStoryboardVideosByStoryboardId(sb.id);
  const dialogues = getDialoguesByStoryboardId(sb.id);
  const voiceovers = getVoiceoversByStoryboardId(sb.id);
  
  console.log(`分镜 ${sb.title}:`, {
    images: images.length,
    videos: videos.length,
    dialogues: dialogues.length,
    voiceovers: voiceovers.length,
  });
});

2. 时间轴计算

// 验证时间轴数据正确生成
const timeline = getTimelineByProjectId('018e1234-5678-7abc-8def-100000000001');
console.log('轨道数量:', timeline.tracks.length); // 应该是 6
console.log('元素数量:', timeline.items.length); // 应该 > 50

// 验证字幕和配音的时间计算
const subtitleItems = timeline.items.filter((item) => item.itemType === 'subtitle');
const voiceoverItems = timeline.items.filter((item) => item.itemType === 'voiceover');
console.log('字幕数量:', subtitleItems.length);
console.log('配音数量:', voiceoverItems.length);

3. 外键关联

// 验证所有资源 ID 都存在
storyboards.forEach((sb) => {
  sb.resources?.characters?.forEach((charRef) => {
    const resource = getResourceById(charRef.resourceId);
    if (!resource) {
      console.error(`资源不存在: ${charRef.resourceId}`);
    }
  });
});

影响范围

新增文件

  • client/src/mocks/storyboard-images.ts
  • client/src/mocks/storyboard-videos.ts
  • client/src/mocks/storyboard-dialogues.ts
  • client/src/mocks/storyboard-voiceovers.ts
  • client/src/mocks/sound-effects.ts

修改文件

  • client/src/mocks/timeline.ts - 完全重写
  • client/src/mocks/index.ts - 新增导出
  • client/src/mocks/storyboards.ts - 待后续更新

向后兼容

  • 保留 mockTimelinemockTimelineTracks 导出
  • 现有代码无需修改
  • 新代码可以使用 getTimelineByProjectId() 实时计算

Timeline 数据统计

完成后的时间轴数据规模:

  • 轨道数量: 6 种(分镜、资源、视频、音效、字幕、配音)
  • 时间轴元素总数: 约 90+ 个
    • 分镜轨道: 13 个元素
    • 资源轨道: 约 40 个元素(角色、场景、道具、实拍)
    • 视频轨道: 10 个元素
    • 音效轨道: 21 个元素
    • 字幕轨道: 10 个元素
    • 配音轨道: 10 个元素

后续工作

  1. 更新 storyboards.ts

    • 修正所有分镜的 resources 字段结构
    • 补充后端新增字段(shotNumber, shotSize, cameraMovement 等)
  2. 类型定义完善

    • types/storyboard.ts 中补充新类型
    • types/timeline.ts 中补充新类型
  3. API 集成

    • 将 Mock 数据结构应用到真实 API 调用
    • 确保前后端数据格式一致

参考文档


RFC 版本: v1.0
最后更新: 2026-01-24