# 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` ```typescript // 获取轨道元素列表 getElementsForTrack(trackType, storyboard, videos, soundEffects, voiceovers) // 计算元素位置(等分宽度 + 间隔) calculateElementPositions(elements, startTime, endTime, totalDuration, gap) ``` **功能**: - 根据轨道类型获取分镜的元素列表 - 计算多个元素块的位置和宽度(等分 + 2px 间隔) - 按 `displayOrder` 排序元素 ### 4. 组件更新 **TimelineItem** (`client/src/components/features/timeline/TimelineItem.tsx`): - ✅ 添加 `content` 和 `contentType` props - ✅ 添加 `isPlaceholder` prop 支持占位块 - ✅ 实现占位块样式:虚线边框 + 半透明背景 + 居中文字 - ✅ 根据 `content` 显示元素内容(对白文字、音效名称等) - ✅ 优先显示 `content`,回退到 `title` - ✅ 占位块不显示调整手柄 **TimelineTrack** (`client/src/components/features/timeline/TimelineTrack.tsx`): - ✅ 添加 `videos`, `soundEffects`, `voiceovers` props - ✅ 对 `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: "对白"`) ## 技术细节 ### 元素块宽度计算 ```typescript // 单个元素:占满分镜宽度 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. ✅ 单个元素块(分镜1视频、分镜20音效) 2. ✅ 多个元素块等分宽度(分镜2视频、分镜14对白、分镜19音效) 3. ✅ 元素间隔显示(2px gap) 4. ✅ 没有元素时显示占位块(虚线边框 + 半透明背景) 5. ✅ 占位块显示正确文字("添加视频"、"添加音效"等) 6. ✅ 元素块显示正确内容(对白文字、音效名称) 7. ✅ 元素块颜色基于定稿状态 ## 关键修复 ### 问题:占位块不显示 **原因**:视频、音效、对白、配音轨道的 `track.items` 为空,导致 `map` 循环没有执行。 **解决方案**: ```typescript // 如果 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; ``` 这样确保每个分镜在这些轨道上都有对应的项,可以正确渲染元素块或占位块。 ## 后续工作 1. 对接后端 API(替换 mock 数据) 2. 实现元素块的编辑功能(点击打开编辑面板) 3. 实现元素块的添加/删除功能 4. 优化元素块的拖拽体验 5. 添加元素块的右键菜单 ## 相关文档 - 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`