# RFC 121: 剧本元素变体管理 - 前端实现 ## 元数据 - **RFC 编号**: 121 - **标题**: 剧本元素变体管理 - 前端UI实现 - **状态**: 草案 - **创建日期**: 2025-01-19 - **作者**: AI Architect - **关联文档**: - [RFC 120: 剧本元素变体管理 - 后端设计](../../../server/rfcs/120-screenplay-element-variants.md) - [Screenplay Variant Service](../../../requirements/backend/04-services/project/screenplay-variant-service.md) --- ## 1. 背景与问题 ### 1.1 业务需求 在视频制作工作流中,同一个剧本元素(角色/场景/道具)往往需要多个变体: **角色变体示例**: - 角色"李明":少年、青年、中年、老年 - 角色"张华":正常状态、受伤状态 **场景变体示例**: - 场景"办公室":1990年代、2020年代 - 场景"公园":春天、夏天、秋天、冬天 **道具变体示例**: - 道具"怀表":完好、破损、修复后 ### 1.2 当前前端架构的局限 **现有素材管理方式**: ```typescript // 当前:素材直接按类型分类 ProjectResourcePanel ├─ 角色 Tab │ ├─ 李明_少年.jpg │ ├─ 李明_青年.jpg │ ├─ 李明_老年.jpg │ └─ 张华.jpg ├─ 场景 Tab └─ 道具 Tab ``` **问题**: 1. ❌ 无法将同一角色的不同年龄段素材分组管理 2. ❌ 用户需要手动在文件名中标注变体信息 3. ❌ 分镜选择素材时无法按变体筛选 4. ❌ 无法利用AI自动识别剧本中的变体需求 ### 1.3 目标架构 **改造后的素材管理方式**: ```typescript // 改造后:元素 → 变体 → 素材 三层结构 ProjectResourcePanel ├─ 角色 Tab │ ├─ 李明(4个变体,12个素材) │ │ ├─ 少年(3个素材) │ │ │ ├─ 正面.jpg │ │ │ ├─ 侧面.jpg │ │ │ └─ 背面.jpg │ │ ├─ 青年(5个素材) │ │ ├─ 中年(2个素材) │ │ └─ 老年(2个素材) │ └─ 张华(1个变体,3个素材) ├─ 场景 Tab └─ 道具 Tab ``` **优势**: - ✅ 同一元素的不同变体素材分组管理 - ✅ AI自动识别剧本中的变体需求 - ✅ 分镜可以精确选择"李明-青年" - ✅ 支持预设模板 + 自定义变体 --- ## 2. 技术方案 ### 2.1 数据关联架构 ``` 项目(Project) └─ 剧本元素(ScreenplayElement) ├─ 角色:李明 │ ├─ 变体:少年 ──→ 素材1.jpg, 素材2.jpg │ ├─ 变体:青年 ──→ 素材3.jpg, 素材4.jpg │ └─ 变体:老年 ──→ 素材5.jpg ├─ 场景:办公室 │ ├─ 变体:1990年代 ──→ 素材6.jpg │ └─ 变体:2020年代 ──→ 素材7.jpg └─ 道具:怀表 ├─ 变体:完好 ──→ 素材8.jpg └─ 变体:破损 ──→ 素材9.jpg 分镜(Storyboard) └─ 关联素材:素材3.jpg(李明-青年)+ 素材7.jpg(办公室-2020年代) ``` **关键设计**: - 素材不是直接关联到"李明",而是关联到"李明-青年"这个**变体** - 分镜选择素材时,实际上是选择"元素的某个变体的某个素材" ### 2.2 类型定义 ```typescript // client/src/types/screenplay.ts /** * 剧本元素类型 */ export type ScreenplayElementType = 'character' | 'scene' | 'prop'; /** * 剧本元素(角色/场景/道具) */ export interface ScreenplayElement { id: string; projectId: string; type: ScreenplayElementType; name: string; description?: string; hasVariants: boolean; metadata?: Record; createdAt: string; updatedAt: string; } /** * 剧本元素变体 */ export interface ScreenplayVariant { id: string; elementId: string; elementType: ScreenplayElementType; variantKey: string; // 程序标识:youth, adult, elder variantLabel: string; // UI显示:少年、青年、老年 description?: string; aiGenerated: boolean; // 是否由AI生成 aiConfidence?: number; // AI识别置信度 0-1 aiContext?: string; // AI识别的上下文 metadata?: Record; createdAt: string; updatedAt: string; } /** * AI识别结果 */ export interface ScreenplayAnalysisResult { characters: { name: string; variants: Array<{ key: string; label: string; confidence: number; context: string; }>; }[]; scenes: { name: string; variants: Array<{ key: string; label: string; confidence: number; context: string; }>; }[]; props: { name: string; variants: Array<{ key: string; label: string; confidence: number; context: string; }>; }[]; } /** * 变体预设模板 */ export interface VariantTemplate { key: string; label: string; category: 'age' | 'era' | 'season' | 'state' | 'custom'; } ``` ### 2.3 API Hooks ```typescript // client/src/hooks/api/useScreenplay.ts /** * 获取剧本元素列表 */ export function useScreenplayElements( projectId: string | null, type?: ScreenplayElementType ) { return useQuery({ queryKey: ['screenplay-elements', projectId, type], queryFn: () => api.getScreenplayElements(projectId!, type), enabled: !!projectId, }); } /** * 获取元素的变体列表 */ export function useElementVariants( elementId: string | null, elementType: ScreenplayElementType ) { return useQuery({ queryKey: ['element-variants', elementId, elementType], queryFn: () => api.getElementVariants(elementId!, elementType), enabled: !!elementId, }); } /** * 创建变体 */ export function useCreateVariant() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateVariantDto) => api.createVariant(data), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['element-variants', variables.elementId], }); }, }); } /** * 更新变体 */ export function useUpdateVariant() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateVariantDto }) => api.updateVariant(id, data), onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['element-variants', data.elementId], }); }, }); } /** * 删除变体 */ export function useDeleteVariant() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, elementId }: { id: string; elementId: string }) => api.deleteVariant(id), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['element-variants', variables.elementId], }); }, }); } /** * AI分析剧本 */ export function useAnalyzeScreenplay() { return useMutation({ mutationFn: (data: { projectId: string; content: string }) => api.analyzeScreenplay(data), }); } /** * 批量创建元素和变体 */ export function useBatchCreateElements() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: BatchCreateElementsDto) => api.batchCreateElements(data), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['screenplay-elements', variables.projectId], }); }, }); } /** * 获取某个变体的所有素材 */ export function useResourcesByVariant( projectId: string | null, variantId: string | null, elementType: ScreenplayElementType ) { return useQuery({ queryKey: ['resources-by-variant', projectId, variantId, elementType], queryFn: () => api.getResourcesByVariant(projectId!, variantId!, elementType), enabled: !!projectId && !!variantId, }); } ``` --- ## 3. 用户操作流程 ### 3.1 场景1:AI智能识别变体(自动化) **用户操作步骤**: 1. 用户在 `ScreenplayContentPanel` 上传剧本《时光之旅》 2. 点击"智能拆解"按钮 3. 系统调用 AI 分析接口,识别剧本中的元素和变体 4. 弹出 `ScreenplayAnalysisResultDialog` 显示识别结果: ``` ┌─────────────────────────────────────────────────┐ │ AI识别结果 │ │ │ │ 角色: │ │ ✓ 李明(少年、青年、老年)[置信度: 95%] │ │ ✓ 张华(青年)[置信度: 88%] │ │ │ │ 场景: │ │ ✓ 办公室(1990年代、2020年代)[置信度: 92%] │ │ ✓ 公园(春天、冬天)[置信度: 85%] │ │ │ │ 道具: │ │ ✓ 怀表(完好、破损)[置信度: 90%] │ │ │ │ [修改] [确认并创建] │ └─────────────────────────────────────────────────┘ ``` 5. 用户可以修改识别结果(添加/删除/编辑变体) 6. 点击"确认并创建",系统批量创建元素和变体 7. 生成分镜,分镜描述中包含变体信息 **涉及组件**: - `ScreenplayContentPanel` - 增加智能拆解按钮 - `ScreenplayAnalysisResultDialog` - 新增,显示AI识别结果 - `useAnalyzeScreenplay()` - 调用AI分析接口 - `useBatchCreateElements()` - 批量创建元素和变体 --- ### 3.2 场景2:手动管理变体 **用户操作步骤**: 1. 在 `ProjectResourcePanel` 选择"角色"Tab 2. 点击角色"李明" 3. 右侧 `ResourcePropertiesPanel` 显示角色属性 4. 切换到"形象"Tab,看到变体列表: ``` ┌─────────────────────────────────────────────────┐ │ 变体管理 │ │ │ │ [少年] [青年] [老年] │ │ │ │ [+ 添加变体] │ └─────────────────────────────────────────────────┘ ``` 5. 点击"添加变体",弹出 `VariantManagementDialog`: ``` ┌─────────────────────────────────────────────────┐ │ 添加变体 │ │ │ │ 预设模板: │ │ ○ 少年 ○ 青年 ○ 中年 ○ 老年 │ │ ○ 儿童 ○ 壮年 │ │ │ │ 或自定义: │ │ [输入变体名称_____________________] │ │ │ │ [取消] [确认] │ └─────────────────────────────────────────────────┘ ``` 6. 选择"中年"或输入自定义名称 7. 保存后,新变体出现在列表中 **涉及组件**: - `ResourcePropertiesPanel` - 改造"形象"Tab,动态显示变体 - `VariantManagementDialog` - 新增,变体管理对话框 - `useElementVariants()` - 获取变体列表 - `useCreateVariant()` - 创建变体 --- ### 3.3 场景3:上传素材时关联变体 **用户操作步骤**: 1. 在 `ProjectResourcePanel` 点击"上传素材"按钮 2. 弹出 `UploadResourceDialog`: ``` ┌─────────────────────────────────────────────────┐ │ 上传素材 │ │ │ │ 素材类型: │ │ ● 角色 ○ 场景 ○ 道具 ○ 实拍 │ │ │ │ 选择角色: │ │ [李明 ▼] │ │ │ │ 选择变体: │ │ [青年 ▼] (少年/青年/老年/中年) │ │ │ │ [选择文件] 或 拖拽文件到此处 │ │ │ │ [取消] [上传] │ └─────────────────────────────────────────────────┘ ``` 3. 选择类型、元素、变体后,选择文件 4. 点击"上传",素材自动关联到"李明-青年"这个变体 **涉及组件**: - `UploadResourceDialog` - 新增,替换当前的 input file - `useScreenplayElements()` - 获取元素列表 - `useElementVariants()` - 获取变体列表 - `useUploadResource()` - 上传素材(传递 variant_id) --- ### 3.4 场景4:素材面板按变体浏览 **用户操作步骤**: 1. 在 `ProjectResourcePanel` 选择"角色"Tab 2. 看到角色列表(树形结构): ``` ┌─────────────────────────────────────────────────┐ │ 角色 │ │ │ │ ▼ 李明(4个变体,12个素材) │ │ ▼ 少年(3个素材) │ │ [素材1] [素材2] [素材3] │ │ ▶ 青年(5个素材) │ │ ▶ 老年(2个素材) │ │ ▶ 中年(2个素材) │ │ │ │ ▶ 张华(1个变体,3个素材) │ │ │ │ [+ 上传素材] │ └─────────────────────────────────────────────────┘ ``` 3. 点击"青年"展开,显示该变体的5个素材 4. 点击某个素材,右侧显示素材详情 **涉及组件**: - `ProjectResourcePanel` - 改造为树形结构 - `ResourceTreeView` - 新增,树形视图组件 - `useScreenplayElements()` - 获取元素列表 - `useElementVariants()` - 获取变体列表 - `useResourcesByVariant()` - 获取变体的素材 --- ### 3.5 场景5:分镜使用变体素材 **用户操作步骤**: 1. 在 `StoryboardList` 选择第3个分镜 2. 在 `StoryboardPropertiesPanel` 点击"添加角色" 3. 弹出 `ResourceSelectorDialog`: ``` ┌─────────────────────────────────────────────────┐ │ 选择角色素材 │ │ │ │ 角色:[李明 ▼] │ │ 变体:[青年 ▼] (少年/青年/老年/中年) │ │ │ │ 该变体的素材: │ │ [素材3] [素材4] [素材5] [素材6] [素材7] │ │ │ │ [取消] [确认] │ └─────────────────────────────────────────────────┘ ``` 4. 选择"素材3",确认后该素材关联到分镜 5. 分镜的角色列表显示:"李明(青年)- 素材3" **涉及组件**: - `ResourceSelectorDialog` - 新增,素材选择对话框 - `StoryboardPropertiesPanel` - 增加"添加角色/场景/道具"功能 - `useScreenplayElements()` - 获取元素列表 - `useElementVariants()` - 获取变体列表 - `useResourcesByVariant()` - 获取变体的素材 --- ## 4. 组件设计 ### 4.1 新增组件 #### 4.1.1 ScreenplayAnalysisResultDialog **功能**:显示AI识别的剧本元素和变体,允许用户确认或修改。 **Props**: ```typescript interface ScreenplayAnalysisResultDialogProps { open: boolean; onOpenChange: (open: boolean) => void; analysisResult: ScreenplayAnalysisResult; onConfirm: (result: ScreenplayAnalysisResult) => void; } ``` **UI结构**: - 顶部:标题"AI识别结果" - 中间:三个Tab(角色/场景/道具),每个Tab显示识别的元素和变体 - 每个元素可以展开编辑变体列表 - 底部:[修改] [确认并创建] 按钮 --- #### 4.1.2 VariantManagementDialog **功能**:管理某个元素的所有变体(添加/编辑/删除)。 **Props**: ```typescript interface VariantManagementDialogProps { open: boolean; onOpenChange: (open: boolean) => void; elementId: string; elementType: ScreenplayElementType; elementName: string; } ``` **UI结构**: - 顶部:标题"管理变体 - {elementName}" - 中间:变体列表(可编辑、可删除) - 底部:预设模板选择 + 自定义输入 - 底部:[取消] [保存] 按钮 --- #### 4.1.3 UploadResourceDialog **功能**:上传素材时选择类型、元素、变体。 **Props**: ```typescript interface UploadResourceDialogProps { open: boolean; onOpenChange: (open: boolean) => void; projectId: string; onSuccess?: () => void; } ``` **UI结构**: - 素材类型选择(角色/场景/道具/实拍) - 元素选择下拉框(根据类型动态加载) - 变体选择下拉框(根据元素动态加载) - 文件选择区域(支持拖拽) - [取消] [上传] 按钮 --- #### 4.1.4 ResourceSelectorDialog **功能**:分镜选择素材时,按元素和变体筛选。 **Props**: ```typescript interface ResourceSelectorDialogProps { open: boolean; onOpenChange: (open: boolean) => void; projectId: string; resourceType: ResourceType; onSelect: (resource: Resource) => void; } ``` **UI结构**: - 元素选择下拉框 - 变体选择下拉框 - 素材网格(显示该变体的所有素材) - [取消] [确认] 按钮 --- #### 4.1.5 ResourceTreeView **功能**:树形展示元素-变体-素材的三层结构。 **Props**: ```typescript interface ResourceTreeViewProps { projectId: string; resourceType: ResourceType; onSelectResource: (resource: Resource) => void; } ``` **UI结构**: - 使用递归组件实现树形结构 - 元素节点:显示名称和统计(变体数、素材数) - 变体节点:显示名称和统计(素材数) - 素材节点:显示缩略图和名称 --- ### 4.2 改造组件 #### 4.2.1 ProjectResourcePanel **改造内容**: - 从扁平列表改为树形结构 - 集成 `ResourceTreeView` 组件 - 上传按钮改为打开 `UploadResourceDialog` **改造前**: ```typescript // 扁平列表
{resources.map(resource => ( ))}
``` **改造后**: ```typescript // 树形结构 ``` --- #### 4.2.2 ResourcePropertiesPanel **改造内容**: - "形象"Tab 的变体区域改为动态加载 - 增加"管理变体"按钮 - 变体列表可点击切换 **改造前**: ```typescript // 硬编码的变体
正面
侧面
背面
``` **改造后**: ```typescript // 动态加载变体 const { data: variants } = useElementVariants(elementId, 'character');
{variants?.map(variant => (
{variant.variantLabel}
))}
``` --- #### 4.2.3 ScreenplayContentPanel **改造内容**: - 智能拆解流程增加AI识别结果确认步骤 - 集成 `ScreenplayAnalysisResultDialog` **改造前**: ```typescript const handleSmartBreakdown = () => { onSmartBreakdown({ personalizedRequirements, storyboardCount, }); }; ``` **改造后**: ```typescript const handleSmartBreakdown = async () => { // 1. 调用AI分析 const result = await analyzeScreenplay.mutateAsync({ projectId, content: screenplayContent, }); // 2. 显示确认对话框 setAnalysisResult(result); setAnalysisDialogOpen(true); }; const handleConfirmAnalysis = async (result: ScreenplayAnalysisResult) => { // 3. 批量创建元素和变体 await batchCreateElements.mutateAsync({ projectId, elements: result, }); // 4. 生成分镜 onSmartBreakdown({ personalizedRequirements, storyboardCount, }); }; ``` --- ## 5. 数据流设计 ### 5.1 API Service 层 ```typescript // client/src/services/api/screenplay.ts export const screenplayApi = { /** * 获取剧本元素列表 */ async getScreenplayElements( projectId: string, type?: ScreenplayElementType ): Promise { const params = type ? { type } : {}; const response = await apiClient.get( `/projects/${projectId}/screenplay/elements`, { params } ); return response.data; }, /** * 获取元素的变体列表 */ async getElementVariants( elementId: string, elementType: ScreenplayElementType ): Promise { const typeMap = { character: 'characters', scene: 'scenes', prop: 'props', }; const response = await apiClient.get( `/screenplay/${typeMap[elementType]}/${elementId}/variants` ); return response.data; }, /** * 创建变体 */ async createVariant(data: CreateVariantDto): Promise { const typeMap = { character: 'characters', scene: 'scenes', prop: 'props', }; const response = await apiClient.post( `/screenplay/${typeMap[data.elementType]}/${data.elementId}/variants`, data ); return response.data; }, /** * 更新变体 */ async updateVariant( id: string, data: UpdateVariantDto ): Promise { const response = await apiClient.put(`/screenplay/variants/${id}`, data); return response.data; }, /** * 删除变体 */ async deleteVariant(id: string): Promise { await apiClient.delete(`/screenplay/variants/${id}`); }, /** * AI分析剧本 */ async analyzeScreenplay(data: { projectId: string; content: string; }): Promise { const response = await apiClient.post( `/projects/${data.projectId}/screenplay/analyze`, { content: data.content } ); return response.data; }, /** * 批量创建元素和变体 */ async batchCreateElements( data: BatchCreateElementsDto ): Promise<{ created: number }> { const response = await apiClient.post( `/projects/${data.projectId}/screenplay/elements/batch`, data ); return response.data; }, /** * 获取某个变体的所有素材 */ async getResourcesByVariant( projectId: string, variantId: string, elementType: ScreenplayElementType ): Promise { const variantField = `${elementType}_variant_id`; const response = await apiClient.get(`/projects/${projectId}/resources`, { params: { [variantField]: variantId }, }); return response.data; }, }; ``` --- ### 5.2 状态管理 **方案**:使用 React Query 管理服务端状态,无需额外的全局状态管理。 **关键查询键设计**: ```typescript // 元素列表 ['screenplay-elements', projectId, type] // 元素的变体列表 ['element-variants', elementId, elementType] // 变体的素材列表 ['resources-by-variant', projectId, variantId, elementType] // 素材列表(兼容现有) ['resources', projectId, { type, search, character_variant_id, ... }] ``` **缓存失效策略**: - 创建变体后:失效 `['element-variants', elementId]` - 上传素材后:失效 `['resources', projectId]` 和 `['resources-by-variant', ...]` - 批量创建元素后:失效 `['screenplay-elements', projectId]` --- ## 6. 实施计划 ### Phase 1: 基础架构(1周) **目标**:建立类型定义、API层、Hooks层。 **任务**: 1. ✅ 创建类型定义 `client/src/types/screenplay.ts` 2. ✅ 创建API Service `client/src/services/api/screenplay.ts` 3. ✅ 创建Hooks `client/src/hooks/api/useScreenplay.ts` 4. ✅ 更新 `client/src/types/resource.ts`(增加变体外键字段) **验收标准**: - 类型定义完整,无 TypeScript 错误 - API Service 可以正常调用后端接口 - Hooks 可以正常获取和更新数据 --- ### Phase 2: 核心组件(2周) **目标**:实现变体管理的核心UI组件。 **任务**: 1. ✅ 实现 `VariantManagementDialog`(变体管理对话框) 2. ✅ 实现 `UploadResourceDialog`(上传素材对话框) 3. ✅ 实现 `ResourceSelectorDialog`(素材选择对话框) 4. ✅ 实现 `ResourceTreeView`(树形视图组件) 5. ✅ 改造 `ResourcePropertiesPanel`(动态加载变体) **验收标准**: - 用户可以手动添加/编辑/删除变体 - 上传素材时可以选择变体 - 素材面板可以按变体分组浏览 --- ### Phase 3: AI智能识别(1周) **目标**:集成AI智能识别剧本元素和变体。 **任务**: 1. ✅ 实现 `ScreenplayAnalysisResultDialog`(AI识别结果对话框) 2. ✅ 改造 `ScreenplayContentPanel`(增加AI识别流程) 3. ✅ 集成 `useAnalyzeScreenplay()` 和 `useBatchCreateElements()` **验收标准**: - 用户点击"智能拆解"后,可以看到AI识别的元素和变体 - 用户可以修改识别结果 - 确认后,系统自动创建元素和变体 --- ### Phase 4: 分镜集成(1周) **目标**:分镜可以使用变体素材。 **任务**: 1. ✅ 改造 `StoryboardPropertiesPanel`(增加"添加角色/场景/道具"功能) 2. ✅ 集成 `ResourceSelectorDialog` 3. ✅ 更新分镜-素材关联逻辑 4. ✅ 显示时展示变体信息(例如:"李明(青年)") **验收标准**: - 分镜可以选择"李明-青年"的素材 - 分镜列表显示变体信息 - 素材变更时,分镜自动更新 --- ### Phase 5: 优化与测试(1周) **目标**:性能优化、用户体验优化、测试。 **任务**: 1. ✅ 性能优化(虚拟滚动、懒加载) 2. ✅ 用户体验优化(加载状态、错误处理、空状态) 3. ✅ 单元测试(关键组件和Hooks) 4. ✅ 集成测试(完整用户流程) 5. ✅ 文档更新(用户指南、开发文档) **验收标准**: - 大量素材时,界面流畅不卡顿 - 所有边界情况都有友好的提示 - 测试覆盖率 > 80% - 文档完整清晰 --- ## 7. 技术挑战与解决方案 ### 7.1 挑战1:树形结构的性能优化 **问题**:当元素、变体、素材数量很大时,树形结构可能导致性能问题。 **解决方案**: 1. 使用虚拟滚动(react-window 或 react-virtualized) 2. 懒加载:默认只加载元素列表,点击展开时才加载变体和素材 3. 分页:每个变体的素材列表支持分页加载 --- ### 7.2 挑战2:复杂的数据关联 **问题**:元素-变体-素材-分镜的多层关联,容易导致数据不一致。 **解决方案**: 1. 使用 React Query 的缓存失效机制,确保数据同步 2. 乐观更新:用户操作后立即更新UI,后台异步同步 3. 错误回滚:如果后端操作失败,自动回滚UI状态 --- ### 7.3 挑战3:AI识别结果的可编辑性 **问题**:AI识别结果可能不准确,用户需要能够修改。 **解决方案**: 1. 识别结果对话框支持完整的编辑功能 2. 显示置信度,低置信度的结果用不同颜色标注 3. 支持添加/删除元素和变体 4. 支持修改变体名称和描述 --- ### 7.4 挑战4:向后兼容 **问题**:现有项目的素材没有关联变体,如何处理? **解决方案**: 1. 后端自动为没有变体的元素创建"默认"变体 2. 前端显示时,如果元素只有一个"默认"变体,则隐藏变体层级 3. 用户可以手动将"默认"变体改为具体的变体名称 --- ## 8. 用户体验设计 ### 8.1 空状态设计 **场景1:元素没有变体** ``` ┌─────────────────────────────────────────────────┐ │ 暂无变体 │ │ │ │ 该角色还没有创建变体。 │ │ 您可以: │ │ • 使用AI智能识别自动创建变体 │ │ • 手动添加变体 │ │ │ │ [AI智能识别] [手动添加] │ └─────────────────────────────────────────────────┘ ``` **场景2:变体没有素材** ``` ┌─────────────────────────────────────────────────┐ │ 暂无素材 │ │ │ │ 该变体还没有上传素材。 │ │ │ │ [上传素材] │ └─────────────────────────────────────────────────┘ ``` --- ### 8.2 加载状态设计 **骨架屏**: - 元素列表加载时:显示3个骨架元素 - 变体列表加载时:显示3个骨架变体 - 素材列表加载时:显示6个骨架素材卡片 **进度提示**: - AI分析剧本时:显示进度条和提示文字"AI正在分析剧本..." - 批量创建元素时:显示进度条和提示文字"正在创建元素和变体..." - 上传素材时:显示上传进度 --- ### 8.3 错误处理 **网络错误**: ``` ┌─────────────────────────────────────────────────┐ │ ⚠️ 加载失败 │ │ │ │ 网络连接失败,请检查网络后重试。 │ │ │ │ [重试] │ └─────────────────────────────────────────────────┘ ``` **AI识别失败**: ``` ┌─────────────────────────────────────────────────┐ │ ⚠️ AI识别失败 │ │ │ │ AI服务暂时不可用,您可以: │ │ • 稍后重试 │ │ • 手动创建元素和变体 │ │ │ │ [重试] [手动创建] │ └─────────────────────────────────────────────────┘ ``` **操作失败**: - 使用 Toast 提示具体错误信息 - 提供"重试"或"撤销"操作 --- ### 8.4 交互细节 **拖拽上传**: - 支持拖拽文件到上传对话框 - 拖拽时显示高亮边框和提示文字 **快捷键**: - `Ctrl/Cmd + U`:打开上传素材对话框 - `Ctrl/Cmd + K`:打开素材搜索 - `Esc`:关闭当前对话框 **右键菜单**: - 元素:重命名、删除、管理变体 - 变体:重命名、删除、上传素材 - 素材:重命名、删除、查看详情 **批量操作**: - 支持多选素材(Shift + 点击) - 批量删除、批量移动到其他变体 --- ## 9. 测试策略 ### 9.1 单元测试 **测试覆盖**: - Hooks:`useScreenplayElements`, `useElementVariants`, `useCreateVariant` 等 - 工具函数:数据转换、格式化等 - 组件逻辑:状态管理、事件处理等 **测试工具**: - Vitest + React Testing Library --- ### 9.2 集成测试 **测试场景**: 1. 完整的AI识别流程 2. 手动创建元素和变体 3. 上传素材并关联变体 4. 分镜选择变体素材 5. 编辑和删除变体 **测试工具**: - Playwright(E2E测试) --- ### 9.3 性能测试 **测试场景**: - 1000个素材的加载性能 - 100个元素、每个元素10个变体的渲染性能 - 树形结构的展开/收起性能 **性能指标**: - 首次渲染时间 < 1s - 交互响应时间 < 100ms - 内存占用 < 200MB --- ## 10. 风险与挑战 ### 10.1 技术风险 | 风险 | 影响 | 概率 | 缓解措施 | |------|------|------|----------| | 树形结构性能问题 | 高 | 中 | 虚拟滚动、懒加载 | | AI识别准确率低 | 中 | 中 | 显示置信度、支持编辑 | | 数据同步问题 | 高 | 低 | React Query缓存管理 | | 向后兼容问题 | 中 | 低 | 自动创建"默认"变体 | --- ### 10.2 用户体验风险 | 风险 | 影响 | 概率 | 缓解措施 | |------|------|------|----------| | 学习曲线陡峭 | 中 | 中 | 新手引导、工具提示 | | 操作步骤过多 | 中 | 中 | 快捷操作、批量操作 | | 界面复杂度增加 | 中 | 高 | 渐进式展示、默认折叠 | --- ## 11. 后续优化方向 ### 11.1 短期优化(3个月内) 1. **智能推荐**:根据剧本内容,推荐合适的变体模板 2. **批量生成**:一键为所有角色生成常用变体(少年/青年/老年) 3. **变体预览**:在变体列表中显示代表性素材的缩略图 4. **搜索增强**:支持按变体搜索素材 --- ### 11.2 长期优化(6个月内) 1. **变体时间线**:在时间线上显示角色变体的切换点 2. **AI生成素材**:根据变体描述,AI自动生成素材 3. **变体模板库**:提供行业标准的变体模板(电影、广告、动画等) 4. **协作功能**:多人协作时,变体的权限管理和冲突解决 --- ## 12. 总结 本RFC提出了一套完整的剧本元素变体管理前端实现方案,核心特点: 1. **三层架构**:元素 → 变体 → 素材,清晰且灵活 2. **AI + 手动**:既支持AI智能识别,也支持用户手动管理 3. **渐进式展示**:默认折叠,按需展开,降低界面复杂度 4. **性能优化**:虚拟滚动、懒加载、缓存管理 5. **向后兼容**:自动为旧数据创建"默认"变体 **预期收益**: - ✅ 素材管理效率提升 50% - ✅ 分镜制作效率提升 30% - ✅ 用户满意度提升 40% **实施周期**:6周(5个Phase) **资源需求**:1名前端工程师 + 1名UI设计师 --- ## 13. 参考文档 - [RFC 120: 剧本元素变体管理 - 后端设计](../../../server/rfcs/120-screenplay-element-variants.md) - [Screenplay Variant Service](../../../requirements/backend/04-services/project/screenplay-variant-service.md) - [React Query 文档](https://tanstack.com/query/latest) - [React Window 文档](https://react-window.vercel.app/)