# 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. 比例计算规则 #### 类型 → 比例映射 ```typescript 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 ```typescript interface SingleViewPlayerProps { // ... 原有 props onAdd?: () => void; // 添加按钮点击回调 contentType?: ContentType; // 当前内容类型 footageAspectRatios?: Record; // 实拍素材比例映射 } ``` #### ThumbnailStrip ```typescript interface ThumbnailStripProps { items: StoryboardItem[]; selectedId: string | null; onSelect: (id: string) => void; onAdd?: () => void; // 可选,不提供则不显示添加按钮 thumbnailPosition: ThumbnailPosition; projectAspectRatio: number; // 项目比例 footageAspectRatios?: Record; // 实拍比例映射 contentType: ContentType; // 当前内容类型 } ``` #### ThumbnailItem ```typescript interface ThumbnailItemProps { item: StoryboardItem; isSelected: boolean; onClick: () => void; aspectRatio: number; // 动态计算的比例 isHorizontal: boolean; // 布局方向 } ``` ### 4. 工具函数 ```typescript // 获取内容类型的默认比例 function getDefaultAspectRatio( type: ContentType, projectAspectRatio: number ): number; // 获取单个项的比例 function getItemAspectRatio( item: StoryboardItem, projectAspectRatio: number, footageAspectRatios?: Record ): number; ``` ## 实施细节 ### 文件结构 ``` client/src/components/features/preview/ ├── SingleViewPlayer.tsx (重构) ├── ThumbnailStrip.tsx (新建) ├── ThumbnailItem.tsx (新建) └── PreviewInfoPanel.tsx (不变) ``` ### 性能优化 1. **ThumbnailItem 使用 React.memo** - 仅在 `item`、`isSelected`、`aspectRatio` 变化时重渲染 - 避免父组件更新导致所有缩略图重渲染 2. **useCallback 稳定回调** - `handleScroll`、`handleSelect` 使用 `useCallback` - 避免每次渲染创建新函数 3. **图片懒加载** - 缩略图添加 `loading="lazy"` 属性 - 减少初始加载时间 ### 添加按钮设计 - **位置**:固定在缩略图列表开头 - **样式**: - 虚线边框(`border-dashed`) - 居中显示 `+` 图标 - hover 时高亮 - **尺寸**:与当前内容类型的缩略图尺寸一致 - **行为**:点击触发 `onAdd` 回调(具体逻辑由父组件实现) ## 使用示例 ```tsx 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. 测试性能(大量缩略图场景) ## 相关文档 - [React Best Practices](https://react.dev/learn/you-might-not-need-an-effect) - [React.memo 优化指南](https://react.dev/reference/react/memo) - [useCallback 使用场景](https://react.dev/reference/react/useCallback)