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.
 

7.7 KiB

项目选择路由驱动架构

任务背景

实现全局项目选择的统一管理,确保:

  1. 项目面板选择项目后,素材面板同步显示
  2. 素材面板选择项目后,项目面板同步选中
  3. 左侧搜索框选择项目后,全局同步
  4. 支持 URL 分享和浏览器前进/后退
  5. 刷新页面保持项目选择状态

架构方案

方案选择:路由驱动 + 单一数据源

核心理念:路由参数作为唯一真相来源

优势

  • 支持项目 URL 分享
  • 浏览器前进/后退自动切换项目
  • 刷新页面保持项目选择
  • 单一数据源,避免状态不一致
  • 符合 Web 标准

技术架构

1. 路由配置

文件client/src/constants/routes.ts

已有路由配置:

EDITOR: '/editor/:projectId'

辅助函数:

export const getEditorPath = (projectId: string) => `/editor/${projectId}`;

2. 自定义 Hook:useProjectRoute

文件client/src/hooks/useProjectRoute.ts

功能

  • 从路由参数读取 projectId
  • 提供 setProjectId 方法用于路由导航
  • 自动同步路由参数到 appStore.currentProjectId

接口

interface UseProjectRouteReturn {
  projectId: string | null;
  setProjectId: (projectId: string) => void;
}

实现要点

  • 使用 useParams 读取路由参数
  • 使用 useNavigate 进行路由导航
  • 使用 useEffect 同步到全局状态

3. UI Store 集成

文件client/src/stores/uiStore.ts

修改内容

  • selectProject 方法增加 options 参数
  • 支持传入 navigateToProject 回调函数
  • 保留 selectedProjectId 用于 UI 状态(如高亮显示)

新签名

selectProject: (
  projectId: string | null, 
  projectName?: string, 
  options?: {
    autoCloseProjectPanel?: boolean;
    navigateToProject?: (projectId: string) => void;
  }
) => void;

实现步骤

步骤1:创建 useProjectRoute Hook

文件client/src/hooks/useProjectRoute.ts

实现内容

  • 封装路由参数读取逻辑
  • 封装项目切换的路由导航逻辑
  • 同步路由参数到 appStore

步骤2:修改 uiStore.ts

文件client/src/stores/uiStore.ts

修改内容

  • 修改 selectProject 方法签名,添加 options 参数
  • 支持传入 navigateToProject 回调
  • 调用回调函数进行路由导航

步骤3:修改 ProjectTreeNode.tsx

文件client/src/components/features/project/ProjectTreeNode.tsx

修改内容

  • 导入 useProjectRoute Hook
  • 使用 setProjectId 方法
  • 点击项目时传递导航函数给 selectProject

代码变更

const { setProjectId } = useProjectRoute();

const handleClick = () => {
  if (isProject && node.projectId) {
    selectProject(node.projectId.toString(), node.name, {
      autoCloseProjectPanel: true,
      navigateToProject: setProjectId,
    });
  }
};

步骤4:修改 LeftSidebar.tsx

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

修改内容

  • 导入 useProjectRoute Hook
  • 使用 projectId 替代 selectedProjectId
  • 项目选择时调用路由导航

核心变更

const { projectId, setProjectId } = useProjectRoute();
const { data: currentProject } = useProject(projectId || null);

const handleProjectSelect = (selectedProjectId: string, projectName: string) => {
  selectProject(selectedProjectId, projectName, {
    autoCloseProjectPanel: false,
    navigateToProject: setProjectId,
  });
  setInputValue(projectName);
};

步骤5:修改 ProjectResourcePanel.tsx

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

修改内容

  • 移除内部项目状态管理(internalSelectedProjectId
  • 完全使用外部 projectId prop
  • 简化 handleProjectSelect 逻辑
  • 移除自定义事件监听

核心变更

// 完全使用外部 projectId
const selectedProjectId = externalProjectId;

// 简化项目选择处理
const handleProjectSelect = useCallback((_projectId: string, _projectName: string) => {
  setSelectedResources(new Set());
  setSearchQuery('');
}, [setSearchQuery, setSelectedResources]);

数据流

用户操作流程

1. 从项目面板选择项目

用户点击项目
  ↓
ProjectTreeNode.handleClick()
  ↓
selectProject(projectId, projectName, { navigateToProject: setProjectId })
  ↓
useProjectRoute.setProjectId(projectId)
  ↓
navigate('/editor/:projectId')
  ↓
路由变化 → useParams 返回新 projectId
  ↓
LeftSidebar, ProjectResourcePanel 接收到新 projectId
  ↓
UI 自动同步更新

2. 从左侧搜索框选择项目

用户选择项目
  ↓
LeftSidebar.handleProjectSelect()
  ↓
selectProject(projectId, projectName, { navigateToProject: setProjectId })
  ↓
路由导航
  ↓
全局同步

3. 浏览器前进/后退

用户点击浏览器前进/后退
  ↓
路由自动变化
  ↓
useParams 返回新 projectId
  ↓
所有组件自动同步

4. 直接访问项目 URL

用户访问 /editor/123
  ↓
路由匹配
  ↓
useParams 返回 projectId: "123"
  ↓
组件加载对应项目数据

状态管理层次

1. 路由层(最高优先级)

  • 数据源:URL 参数 /editor/:projectId
  • 读取useParams<{ projectId: string }>()
  • 写入navigate(getEditorPath(projectId))
  • 作用:唯一真相来源

2. 应用层

  • StoreappStore.currentProjectId
  • 同步:由 useProjectRoute Hook 自动同步
  • 作用:方便非 React 代码访问

3. UI 层

  • StoreuiStore.selectedProjectId
  • 作用:UI 状态(如高亮显示)
  • 同步:由 selectProject 方法更新

4. 组件层

  • PropsprojectId 向下传递
  • 作用:组件受控
  • 来源:从路由读取

测试要点

1. 项目面板选择测试

  • 点击项目后 URL 变化
  • 素材面板同步显示对应项目
  • 项目面板自动收起
  • 动画流畅

2. 左侧搜索框选择测试

  • 选择项目后 URL 变化
  • 素材面板同步更新
  • 项目面板(如果打开)同步选中
  • 搜索框显示项目名称

3. 路由功能测试

  • 直接访问 /editor/:projectId 加载对应项目
  • 浏览器前进/后退切换项目
  • 刷新页面保持项目选择

4. 联动测试

  • 任何地方选择项目,所有组件同步
  • 项目数据正确加载
  • 没有重复请求

5. 边界情况测试

  • 无效的 projectId
  • 未登录状态
  • 项目不存在
  • 网络错误

优势总结

1. 用户体验

  • 可分享的项目 URL
  • 浏览器前进/后退支持
  • 刷新页面保持状态
  • 直观的地址栏反馈

2. 开发体验

  • 单一数据源,易于调试
  • 符合 Web 标准
  • 代码清晰,易于维护
  • 类型安全

3. 架构优势

  • 解耦组件间依赖
  • 减少状态同步复杂度
  • 支持 SSR(未来扩展)
  • 利于 SEO(如果需要)

后续优化建议

1. 错误处理

  • 添加 404 页面处理无效项目 ID
  • 添加权限检查
  • 添加加载状态

2. 性能优化

  • 使用 React Router 的 loader 预加载数据
  • 添加项目数据缓存
  • 优化路由切换动画

3. 功能增强

  • 支持项目历史记录
  • 支持项目快速切换(快捷键)
  • 支持项目收藏/固定

4. 分析监控

  • 添加路由跳转埋点
  • 监控项目切换性能
  • 分析用户项目访问模式

实施日期:2026-01-14
实施状态 核心功能已完成,待测试验证 下一步:边界情况测试和错误处理