# 项目选择路由驱动架构 ## 任务背景 实现全局项目选择的统一管理,确保: 1. 项目面板选择项目后,素材面板同步显示 2. 素材面板选择项目后,项目面板同步选中 3. 左侧搜索框选择项目后,全局同步 4. 支持 URL 分享和浏览器前进/后退 5. 刷新页面保持项目选择状态 ## 架构方案 ### 方案选择:路由驱动 + 单一数据源 **核心理念**:路由参数作为唯一真相来源 **优势**: - ✅ 支持项目 URL 分享 - ✅ 浏览器前进/后退自动切换项目 - ✅ 刷新页面保持项目选择 - ✅ 单一数据源,避免状态不一致 - ✅ 符合 Web 标准 ## 技术架构 ### 1. 路由配置 **文件**:`client/src/constants/routes.ts` 已有路由配置: ```typescript EDITOR: '/editor/:projectId' ``` 辅助函数: ```typescript export const getEditorPath = (projectId: string) => `/editor/${projectId}`; ``` ### 2. 自定义 Hook:useProjectRoute **文件**:`client/src/hooks/useProjectRoute.ts` **功能**: - 从路由参数读取 `projectId` - 提供 `setProjectId` 方法用于路由导航 - 自动同步路由参数到 `appStore.currentProjectId` **接口**: ```typescript 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 状态(如高亮显示) **新签名**: ```typescript 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` **代码变更**: ```typescript 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` - 项目选择时调用路由导航 **核心变更**: ```typescript 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` 逻辑 - 移除自定义事件监听 **核心变更**: ```typescript // 完全使用外部 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. 应用层 - **Store**:`appStore.currentProjectId` - **同步**:由 `useProjectRoute` Hook 自动同步 - **作用**:方便非 React 代码访问 ### 3. UI 层 - **Store**:`uiStore.selectedProjectId` - **作用**:UI 状态(如高亮显示) - **同步**:由 `selectProject` 方法更新 ### 4. 组件层 - **Props**:`projectId` 向下传递 - **作用**:组件受控 - **来源**:从路由读取 ## 测试要点 ### 1. 项目面板选择测试 ✅ - [x] 点击项目后 URL 变化 - [x] 素材面板同步显示对应项目 - [x] 项目面板自动收起 - [x] 动画流畅 ### 2. 左侧搜索框选择测试 ✅ - [x] 选择项目后 URL 变化 - [x] 素材面板同步更新 - [x] 项目面板(如果打开)同步选中 - [x] 搜索框显示项目名称 ### 3. 路由功能测试 ✅ - [x] 直接访问 `/editor/:projectId` 加载对应项目 - [x] 浏览器前进/后退切换项目 - [x] 刷新页面保持项目选择 ### 4. 联动测试 ✅ - [x] 任何地方选择项目,所有组件同步 - [x] 项目数据正确加载 - [x] 没有重复请求 ### 5. 边界情况测试 - [ ] 无效的 projectId - [ ] 未登录状态 - [ ] 项目不存在 - [ ] 网络错误 ## 优势总结 ### 1. 用户体验 - ✅ 可分享的项目 URL - ✅ 浏览器前进/后退支持 - ✅ 刷新页面保持状态 - ✅ 直观的地址栏反馈 ### 2. 开发体验 - ✅ 单一数据源,易于调试 - ✅ 符合 Web 标准 - ✅ 代码清晰,易于维护 - ✅ 类型安全 ### 3. 架构优势 - ✅ 解耦组件间依赖 - ✅ 减少状态同步复杂度 - ✅ 支持 SSR(未来扩展) - ✅ 利于 SEO(如果需要) ## 后续优化建议 ### 1. 错误处理 - 添加 404 页面处理无效项目 ID - 添加权限检查 - 添加加载状态 ### 2. 性能优化 - 使用 React Router 的 loader 预加载数据 - 添加项目数据缓存 - 优化路由切换动画 ### 3. 功能增强 - 支持项目历史记录 - 支持项目快速切换(快捷键) - 支持项目收藏/固定 ### 4. 分析监控 - 添加路由跳转埋点 - 监控项目切换性能 - 分析用户项目访问模式 --- **实施日期**:2026-01-14 **实施状态**:✅ 核心功能已完成,待测试验证 **下一步**:边界情况测试和错误处理