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.
 

6.6 KiB

时间轴资源块点击自动切换资源库Tab功能

日期: 2026-01-28
类型: 功能增强
影响范围: 时间轴、资源库面板

概述

实现了时间轴资源块点击时,自动切换资源库面板到对应资源类型 tab 的功能。

需求

用户点击时间轴中的资源块时:

  • 点击角色资源 → 打开资源库 + 自动切换到角色 tab
  • 点击场景资源 → 打开资源库 + 自动切换到场景 tab
  • 点击道具资源 → 打开资源库 + 自动切换到道具 tab
  • 点击实拍资源 → 打开资源库 + 自动切换到实拍 tab

实现方案

采用方案 1:通过 UIStore 集中管理,符合 React 和 Zustand 最佳实践。

架构设计

TimelineItem (资源块点击)
    ↓
TimelineTrack.onResourceSelect
    ↓
useTimelineLogic.handleResourceSelect
    ↓
[查找资源对象,获取类型] → setResourcePanelActiveTab(type)
    ↓
UIStore.resourcePanelActiveTab (全局状态)
    ↓
LeftSidebar.activeCategory
    ↓
ProjectResourcePanel.filterType
    ↓
[自动切换到对应 Tab]

技术实现

1. UIStore 状态管理

文件: client/src/stores/uiStore.ts

新增状态和操作:

type ResourcePanelTab = 'character' | 'scene' | 'prop' | 'footage';

interface UIState {
  // ... 其他状态
  resourcePanelActiveTab: ResourcePanelTab;  // 新增:资源面板活动 tab
  
  // ... 其他操作
  setResourcePanelActiveTab: (tab: ResourcePanelTab) => void;  // 新增:设置活动 tab
}

状态持久化配置(已添加到 partialize):

  • 用户切换的 tab 会被保存到 localStorage
  • 下次打开时自动恢复上次选择的 tab

2. 时间轴资源选择逻辑

文件: client/src/hooks/useTimelineLogic.ts

增强 handleResourceSelect 函数:

const handleResourceSelect = useCallback(
  (_resourceId: string) => {
    const wasSelected = selectedResourceId === _resourceId;
    selectResource(_resourceId);
    
    // 查找资源对象以获取其类型
    const resource = resources?.find((r) => r.id === _resourceId);
    
    if (wasSelected) {
      toggleResourcesPanel();
    } else {
      setResourcesPanelOpen(true);
      
      // 根据资源类型切换到对应的 tab
      if (resource?.type) {
        const tabMap: Record<string, 'character' | 'scene' | 'prop' | 'footage'> = {
          character: 'character',
          scene: 'scene',
          prop: 'prop',
          footage: 'footage',
        };
        
        const targetTab = tabMap[resource.type];
        if (targetTab) {
          setResourcePanelActiveTab(targetTab);
        }
      }
    }
  },
  [selectResource, setResourcesPanelOpen, toggleResourcesPanel, selectedResourceId, resources, setResourcePanelActiveTab]
);

关键点

  • 通过 resources 数组查找当前资源的类型
  • 使用类型映射表确保类型安全
  • 只在打开面板时切换 tab(避免重复切换时的干扰)

3. 资源面板状态绑定

文件: client/src/components/features/project/ProjectResourcePanel.tsx

绑定全局状态:

const { resourcePanelActiveTab, setResourcePanelActiveTab } = useUIStore();

// 优先使用外部传入的 activeCategory,其次使用全局状态,最后使用内部状态
const filterType = activeCategory !== undefined ? activeCategory : resourcePanelActiveTab || internalFilterType;

const handleFilterTypeChange = (type: string) => {
  if (onCategoryChange) {
    onCategoryChange(type);
  } else {
    // 更新全局状态
    setResourcePanelActiveTab(type as 'character' | 'scene' | 'prop' | 'footage');
    setInternalFilterType(type);
  }
};

状态优先级

  1. 外部 props (activeCategory) - 最高优先级,支持完全受控
  2. 全局状态 (resourcePanelActiveTab) - 中等优先级,支持跨组件通信
  3. 本地状态 (internalFilterType) - 最低优先级,后备方案

4. 左侧边栏状态同步

文件: client/src/components/layout/LeftSidebar.tsx

移除本地状态,使用全局状态:

// 移除: const [activeCategory, setActiveCategory] = useState<string>(RESOURCE_CATEGORIES[0].key);
// 改为使用全局状态
const { resourcePanelActiveTab, setResourcePanelActiveTab } = useUIStore();

// 传递到子组件
<ProjectResourcePanel
  projectId={viewingProjectId}
  searchQuery={resourceSearchQuery}
  activeCategory={resourcePanelActiveTab}
  onCategoryChange={(category) => setResourcePanelActiveTab(category as 'character' | 'scene' | 'prop' | 'footage')}
/>

修改文件

  1. client/src/stores/uiStore.ts - 添加全局状态和操作
  2. client/src/hooks/useTimelineLogic.ts - 增强资源选择逻辑
  3. client/src/components/features/project/ProjectResourcePanel.tsx - 绑定全局状态
  4. client/src/components/layout/LeftSidebar.tsx - 移除本地状态,使用全局状态

测试要点

功能测试

  • 点击时间轴中的角色资源块,资源库应打开并显示角色 tab
  • 点击时间轴中的场景资源块,资源库应打开并显示场景 tab
  • 点击时间轴中的道具资源块,资源库应打开并显示道具 tab
  • 点击时间轴中的实拍资源块,资源库应打开并显示实拍 tab
  • 点击已选中的资源块,应切换资源库的打开/关闭状态
  • 手动切换 tab,状态应正确保存

状态持久化测试

  • 切换到某个 tab 后刷新页面,应恢复上次选择的 tab
  • 跨浏览器标签页,状态应保持一致

边界情况测试

  • 资源类型为空或未知时,不应崩溃
  • 资源库面板已打开时再次点击资源块,行为应符合预期

优点

  1. 集中状态管理:符合 Zustand 架构,易于维护和调试
  2. 状态持久化:用户体验更好,记住用户选择
  3. 类型安全:TypeScript 严格类型检查,减少运行时错误
  4. 易于扩展:未来可从其他地方控制资源面板 tab(如快捷键、搜索结果等)
  5. 向后兼容:保留了 props 传递方式,支持完全受控组件

性能考虑

  • 使用 useCallback 避免不必要的重新渲染
  • 状态更新只在必要时触发
  • 资源查找操作使用 Array.find(),时间复杂度 O(n),可接受

未来优化

  1. 如果资源列表非常大(>1000),考虑使用 Map 优化查找性能
  2. 可以添加资源块悬停预览功能
  3. 支持快捷键切换资源 tab

相关文档