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
5.9 KiB
RFC 134: 缩略图组件重构与动态比例支持
状态: 已实施
创建日期: 2026-01-28
作者: AI Assistant
类型: 重构 + 功能增强
背景
SingleViewPlayer 组件承担了主预览区域和缩略图导航区域的双重职责,导致:
- 职责不清:组件代码超过 200 行,逻辑耦合严重
- 缺少动态比例:缩略图尺寸固定,未根据内容类型(分镜/角色/道具/实拍)调整
- 缺少添加功能:无法在缩略图区域添加新内容
- 性能问题:所有缩略图在父组件重渲染时都会重新渲染
目标
- 提取缩略图区域为独立组件,遵循单一职责原则
- 支持根据内容类型动态计算缩略图比例
- 添加"添加按钮",位置固定在缩略图列表开头
- 优化性能,减少不必要的重渲染
设计方案
1. 组件拆分
ThumbnailItem 组件
- 职责:渲染单个缩略图
- 优化:使用
React.memo避免不必要的重渲染 - 特性:
- 支持动态比例(通过
aspectRatioprop) - 支持选中状态高亮
- 图片懒加载(
loading="lazy")
- 支持动态比例(通过
ThumbnailStrip 组件
- 职责:缩略图区域的布局、滚动、导航
- 功能:
- 添加按钮(可选,通过
onAddprop 控制) - 前后导航按钮(自动检测滚动状态)
- 自动滚动到选中项
- 支持四个方向布局(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 (不变)
性能优化
-
ThumbnailItem 使用 React.memo
- 仅在
item、isSelected、aspectRatio变化时重渲染 - 避免父组件更新导致所有缩略图重渲染
- 仅在
-
useCallback 稳定回调
handleScroll、handleSelect使用useCallback- 避免每次渲染创建新函数
-
图片懒加载
- 缩略图添加
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 类型定义
✅ 灵活性:支持动态比例,适配不同内容类型
✅ 可扩展:添加按钮为未来功能预留接口
后续优化
- 虚拟滚动:如果缩略图数量超过 100,考虑使用
react-window - Intersection Observer:进一步优化图片加载
- 键盘导航:支持方向键切换缩略图
- 拖拽排序:支持拖拽调整缩略图顺序
测试建议
- 测试不同内容类型的比例计算
- 测试四个方向的布局
- 测试滚动和自动居中
- 测试添加按钮的显示/隐藏
- 测试性能(大量缩略图场景)