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

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 添加 storyboardIddisplayOrder 字段

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):

  • 添加 contentcontentType 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: "对白"

技术细节

元素块宽度计算

// 单个元素:占满分镜宽度
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 循环没有执行。

解决方案

// 如果 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