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.9 KiB

RFC 134: 缩略图组件重构与动态比例支持

状态: 已实施
创建日期: 2026-01-28
作者: AI Assistant
类型: 重构 + 功能增强

背景

SingleViewPlayer 组件承担了主预览区域和缩略图导航区域的双重职责,导致:

  1. 职责不清:组件代码超过 200 行,逻辑耦合严重
  2. 缺少动态比例:缩略图尺寸固定,未根据内容类型(分镜/角色/道具/实拍)调整
  3. 缺少添加功能:无法在缩略图区域添加新内容
  4. 性能问题:所有缩略图在父组件重渲染时都会重新渲染

目标

  1. 提取缩略图区域为独立组件,遵循单一职责原则
  2. 支持根据内容类型动态计算缩略图比例
  3. 添加"添加按钮",位置固定在缩略图列表开头
  4. 优化性能,减少不必要的重渲染

设计方案

1. 组件拆分

ThumbnailItem 组件

  • 职责:渲染单个缩略图
  • 优化:使用 React.memo 避免不必要的重渲染
  • 特性
    • 支持动态比例(通过 aspectRatio prop)
    • 支持选中状态高亮
    • 图片懒加载(loading="lazy"

ThumbnailStrip 组件

  • 职责:缩略图区域的布局、滚动、导航
  • 功能
    • 添加按钮(可选,通过 onAdd prop 控制)
    • 前后导航按钮(自动检测滚动状态)
    • 自动滚动到选中项
    • 支持四个方向布局(top/bottom/left/right)
  • 优化:使用 useCallback 稳定回调函数

2. 比例计算规则

类型 → 比例映射

character: 3/4 (0.75)      // 角色固定 3:4
prop: 1:1 (1)              // 道具固定 1:1
storyboard: 项目比例        // 分镜跟随项目
scene: 项目比例             // 场景跟随项目
footage: 实拍比例 || 项目比例 // 实拍优先使用实际比例,否则使用项目比例

尺寸计算

  • 水平布局(top/bottom):

    • 固定高度:56px
    • 宽度 = 高度 × 比例
    • 例如:16:9 → 56 × (16/9) ≈ 99.5px
  • 垂直布局(left/right):

    • 固定宽度:80px
    • 高度 = 宽度 ÷ 比例
    • 例如:3:4 → 80 ÷ (3/4) ≈ 106.7px

3. 新增 Props

SingleViewPlayer

interface SingleViewPlayerProps {
  // ... 原有 props
  onAdd?: () => void;                          // 添加按钮点击回调
  contentType?: ContentType;                   // 当前内容类型
  footageAspectRatios?: Record<string, number>; // 实拍素材比例映射
}

ThumbnailStrip

interface ThumbnailStripProps {
  items: StoryboardItem[];
  selectedId: string | null;
  onSelect: (id: string) => void;
  onAdd?: () => void;                          // 可选,不提供则不显示添加按钮
  thumbnailPosition: ThumbnailPosition;
  projectAspectRatio: number;                  // 项目比例
  footageAspectRatios?: Record<string, number>; // 实拍比例映射
  contentType: ContentType;                    // 当前内容类型
}

ThumbnailItem

interface ThumbnailItemProps {
  item: StoryboardItem;
  isSelected: boolean;
  onClick: () => void;
  aspectRatio: number;                         // 动态计算的比例
  isHorizontal: boolean;                       // 布局方向
}

4. 工具函数

// 获取内容类型的默认比例
function getDefaultAspectRatio(
  type: ContentType,
  projectAspectRatio: number
): number;

// 获取单个项的比例
function getItemAspectRatio(
  item: StoryboardItem,
  projectAspectRatio: number,
  footageAspectRatios?: Record<string, number>
): number;

实施细节

文件结构

client/src/components/features/preview/
├── SingleViewPlayer.tsx      (重构)
├── ThumbnailStrip.tsx        (新建)
├── ThumbnailItem.tsx         (新建)
└── PreviewInfoPanel.tsx      (不变)

性能优化

  1. ThumbnailItem 使用 React.memo

    • 仅在 itemisSelectedaspectRatio 变化时重渲染
    • 避免父组件更新导致所有缩略图重渲染
  2. useCallback 稳定回调

    • handleScrollhandleSelect 使用 useCallback
    • 避免每次渲染创建新函数
  3. 图片懒加载

    • 缩略图添加 loading="lazy" 属性
    • 减少初始加载时间

添加按钮设计

  • 位置:固定在缩略图列表开头
  • 样式
    • 虚线边框(border-dashed
    • 居中显示 + 图标
    • hover 时高亮
  • 尺寸:与当前内容类型的缩略图尺寸一致
  • 行为:点击触发 onAdd 回调(具体逻辑由父组件实现)

使用示例

<SingleViewPlayer
  items={storyboards}
  selectedId={selectedId}
  onSelect={setSelectedId}
  onAdd={() => console.log('添加新分镜')}
  aspectRatio={16 / 9}
  aspectRatioLabel="16:9"
  thumbnailPosition="bottom"
  contentType="storyboard"
  footageAspectRatios={{
    'footage-1': 9 / 16,  // 竖屏实拍
    'footage-2': 4 / 3,   // 4:3 实拍
  }}
/>

优势

单一职责:每个组件职责清晰,易于维护
性能优化:memo + useCallback 减少重渲染
类型安全:完整的 TypeScript 类型定义
灵活性:支持动态比例,适配不同内容类型
可扩展:添加按钮为未来功能预留接口

后续优化

  1. 虚拟滚动:如果缩略图数量超过 100,考虑使用 react-window
  2. Intersection Observer:进一步优化图片加载
  3. 键盘导航:支持方向键切换缩略图
  4. 拖拽排序:支持拖拽调整缩略图顺序

测试建议

  1. 测试不同内容类型的比例计算
  2. 测试四个方向的布局
  3. 测试滚动和自动居中
  4. 测试添加按钮的显示/隐藏
  5. 测试性能(大量缩略图场景)

相关文档