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.5 KiB
5.5 KiB
Timeline Multi-Element Blocks - 时间轴多元素块系统
日期: 2026-01-28
类型: Feature
影响范围: Timeline, Mock Data
概述
实现时间轴多元素块系统,支持一个分镜在不同轨道显示多个元素块(对白、视频、音效、配音),元素块在分镜时间范围内等分宽度显示。
变更内容
1. 类型扩展
文件: client/src/types/storyboard.ts, client/src/types/resource.ts
- ✅
Storyboard添加dialogues: StoryboardDialogue[]字段 - ✅
Video,SoundEffect,Voiceover添加storyboardId和displayOrder字段
2. Mock 数据
新增文件:
client/src/mocks/storyboard-voiceovers.ts- 配音数据client/src/mocks/storyboard-sound-effects.ts- 音效数据(包含多音效示例)client/src/mocks/storyboard-videos.ts- 视频数据(包含多视频片段示例)
更新文件:
client/src/mocks/storyboards.ts- 添加对白示例(分镜11、12、14)client/src/mocks/index.ts- 导出新数据
示例数据:
- 分镜1:单个视频 + 单个配音
- 分镜2:2个视频片段(镜头下移 + 悟空特写)
- 分镜11:1段对白
- 分镜12:1段对白
- 分镜14:2段对白(对话形式)
- 分镜19:2个音效(地震隆隆声 + 山石滚落)
- 分镜20:1个音效
3. 工具函数
新增文件: client/src/utils/timeline-elements.ts
// 获取轨道元素列表
getElementsForTrack(trackType, storyboard, videos, soundEffects, voiceovers)
// 计算元素位置(等分宽度 + 间隔)
calculateElementPositions(elements, startTime, endTime, totalDuration, gap)
功能:
- 根据轨道类型获取分镜的元素列表
- 计算多个元素块的位置和宽度(等分 + 2px 间隔)
- 按
displayOrder排序元素
4. 组件更新
TimelineItem (client/src/components/features/timeline/TimelineItem.tsx):
- ✅ 添加
content和contentTypeprops - ✅ 添加
isPlaceholderprop 支持占位块 - ✅ 实现占位块样式:虚线边框 + 半透明背景 + 居中文字
- ✅ 根据
content显示元素内容(对白文字、音效名称等) - ✅ 优先显示
content,回退到title - ✅ 占位块不显示调整手柄
TimelineTrack (client/src/components/features/timeline/TimelineTrack.tsx):
- ✅ 添加
videos,soundEffects,voiceoversprops - ✅ 对
subtitle,video,sound,voice轨道渲染多元素块 - ✅ 调用
getElementsForTrack获取元素列表 - ✅ 调用
calculateElementPositions计算位置 - ✅ 修复:当
track.items为空时,使用storyboards生成占位项 - ✅ 没有元素时渲染占位块(虚线边框 + 半透明背景)
- ✅ 元素块不支持单独移动/调整
useTimelineLogic (client/src/hooks/useTimelineLogic.ts):
- ✅ 导入 mock 数据
- ✅ 在 state 中添加
videos,soundEffects,voiceovers
TimelinePanel (client/src/components/features/timeline/TimelinePanel.tsx):
- ✅ 传递新的 props 给 TimelineTrack
5. 翻译更新
文件: client/public/locales/zh-CN/editor.json
- ✅ 将"台词"改为"对白"(
subtitle: "对白")
技术细节
元素块宽度计算
// 单个元素:占满分镜宽度
if (elements.length === 1) {
width = storyboardWidth;
}
// 多个元素:等分宽度
elementWidth = storyboardWidth / elements.length;
left = storyboardLeft + elementWidth * index;
轨道类型映射
| 轨道类型 | 元素来源 | 显示内容 |
|---|---|---|
subtitle |
storyboard.dialogues |
对白文字 |
video |
videos.filter(v => v.storyboardId) |
视频名称 |
sound |
soundEffects.filter(s => s.storyboardId) |
音效名称 |
voice |
voiceovers.filter(v => v.storyboardId) |
配音文字 |
元素状态颜色
元素块颜色基于 isFinalized 状态:
- 未定稿:灰色
bg-fill-default - 部分完成:黄色
#f59e0b - 已完成:绿色
#10b981
测试场景
- ✅ 单个元素块(分镜1视频、分镜20音效)
- ✅ 多个元素块等分宽度(分镜2视频、分镜14对白、分镜19音效)
- ✅ 元素间隔显示(2px gap)
- ✅ 没有元素时显示占位块(虚线边框 + 半透明背景)
- ✅ 占位块显示正确文字("添加视频"、"添加音效"等)
- ✅ 元素块显示正确内容(对白文字、音效名称)
- ✅ 元素块颜色基于定稿状态
关键修复
问题:占位块不显示
原因:视频、音效、对白、配音轨道的 track.items 为空,导致 map 循环没有执行。
解决方案:
// 如果 track.items 为空,使用 storyboards 生成占位项
const itemsToRender =
track.items.length === 0 &&
(track.type === 'video' || track.type === 'sound' ||
track.type === 'subtitle' || track.type === 'voice')
? storyboards.map((sb) => ({
id: sb.id,
storyboardId: sb.id,
startTime: sb.startTime,
endTime: sb.endTime,
// ...
}))
: track.items;
这样确保每个分镜在这些轨道上都有对应的项,可以正确渲染元素块或占位块。
后续工作
- 对接后端 API(替换 mock 数据)
- 实现元素块的编辑功能(点击打开编辑面板)
- 实现元素块的添加/删除功能
- 优化元素块的拖拽体验
- 添加元素块的右键菜单
相关文档
- RFC:
docs/client/rfcs/136-timeline-multi-element-blocks.md - 类型定义:
client/src/types/storyboard.ts,client/src/types/resource.ts - Mock 数据:
client/src/mocks/storyboard-*.ts