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
12 KiB
RFC 131: 分镜资源 Mock 数据完善
状态: 已实施
创建日期: 2026-01-24
实施日期: 2026-01-24
概述
根据后端服务文档(project-resource-service.md, storyboard-resource-service.md, storyboard-service.md),完善前端 Mock 数据,确保数据结构与后端 API 规范完全一致,并补充完整的时间轴轨道数据。
背景
当前问题
-
分镜资源关联不完整
storyboards.ts中的resources字段使用简单 ID 字符串- 应该使用
{ resourceId, displayOrder }结构
-
Timeline 缺少完整轨道数据
- 只有分镜和资源轨道
- 缺少视频、音效、字幕、配音轨道
-
分镜缺少后端新增字段
- 缺少
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.storyboardId→storyboards.id - ✅
storyboard_videos.storyboardId→storyboards.id - ✅
storyboard_dialogues.storyboardId→storyboards.id - ✅
storyboard_voiceovers.storyboardId→storyboards.id - ✅
storyboard_voiceovers.dialogueId→storyboard_dialogues.dialogueId - ✅
sound_effects.storyboardId→storyboards.id - ✅
storyboards.resources.*.resourceId→project_resources.id
实施步骤
- ✅ 创建
storyboard-images.ts- 10 条图片数据 - ✅ 创建
storyboard-videos.ts- 10 条视频数据 - ✅ 创建
storyboard-dialogues.ts- 10 条对白数据 - ✅ 创建
storyboard-voiceovers.ts- 10 条配音数据 - ✅ 创建
sound-effects.ts- 21 条音效数据 - ✅ 重构
timeline.ts- 实现完整的 6 种轨道(包含音效轨道) - ⏳ 更新
storyboards.ts- 修正 resources 字段结构(待后续完成) - ✅ 更新
index.ts- 导出新文件 - ✅ 创建 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.tsclient/src/mocks/storyboard-videos.tsclient/src/mocks/storyboard-dialogues.tsclient/src/mocks/storyboard-voiceovers.tsclient/src/mocks/sound-effects.ts
修改文件
client/src/mocks/timeline.ts- 完全重写client/src/mocks/index.ts- 新增导出client/src/mocks/storyboards.ts- 待后续更新
向后兼容
- ✅ 保留
mockTimeline和mockTimelineTracks导出 - ✅ 现有代码无需修改
- ✅ 新代码可以使用
getTimelineByProjectId()实时计算
Timeline 数据统计
完成后的时间轴数据规模:
- 轨道数量: 6 种(分镜、资源、视频、音效、字幕、配音)
- 时间轴元素总数: 约 90+ 个
- 分镜轨道: 13 个元素
- 资源轨道: 约 40 个元素(角色、场景、道具、实拍)
- 视频轨道: 10 个元素
- 音效轨道: 21 个元素
- 字幕轨道: 10 个元素
- 配音轨道: 10 个元素
后续工作
-
更新 storyboards.ts
- 修正所有分镜的
resources字段结构 - 补充后端新增字段(shotNumber, shotSize, cameraMovement 等)
- 修正所有分镜的
-
类型定义完善
- 在
types/storyboard.ts中补充新类型 - 在
types/timeline.ts中补充新类型
- 在
-
API 集成
- 将 Mock 数据结构应用到真实 API 调用
- 确保前后端数据格式一致
参考文档
RFC 版本: v1.0
最后更新: 2026-01-24