# 项目资源面板 - 添加角色/场景/道具编辑删除功能 **日期**: 2026-02-09 **类型**: Feature + Bugfix **影响范围**: 前端 - 项目资源面板,后端 - 项目元素删除逻辑 ## 概述 为项目资源面板中的角色(Character)、场景(Location)、道具(Prop)添加更多菜单,支持编辑和删除操作,与现有的实拍(Footage)功能保持一致。同时修复了后端删除接口的关键 Bug。 ## 🐛 Bug 修复(重要) ### 问题描述 后端删除接口(角色/场景/道具)存在严重 Bug: - `ProjectElementService` 的 `delete_*` 方法将整个对象传递给 Repository - 但 Repository 的 `delete` 方法期望接收 UUID - 导致 500 错误:`invalid input for query argument $1` ### 错误示例 ``` DELETE /api/v1/projects/{project_id}/characters/{character_id} 500 错误信息:invalid UUID "character_id=UUID('...') description='...' role_type=2...": length must be between 32..36 characters, got 499 ``` ### 根本原因 `BaseRepository.delete(id: UUID)` 期望接收 UUID,但被传递了完整的模型对象。 ### 修复方案 **后端修改**: 1. **Service 层** (`server/app/services/project_element_service.py`) - ✅ 修改 `delete_character`:传递 `character_id` 而非 `character` 对象 - ✅ 修改 `delete_location`:传递 `location_id` 而非 `location` 对象 - ✅ 修改 `delete_prop`:传递 `prop_id` 而非 `prop` 对象 2. **Repository 层**(新增 delete 方法覆盖) - ✅ `ProjectCharacterRepository.delete(character_id)` - 正确使用 `character_id` - ✅ `ProjectLocationRepository.delete(location_id)` - 正确使用 `location_id` - ✅ `ProjectPropRepository.delete(prop_id)` - 正确使用 `prop_id` **技术细节**: - 各 Repository 继承自 `BaseRepository`,但主键字段名称不同 - ProjectCharacter 使用 `character_id`(非标准的 `id`) - ProjectLocation 使用 `location_id` - ProjectProp 使用 `prop_id` - 必须覆盖 `delete` 方法以使用正确的主键字段 ## 变更内容 ### 1. 前端 API 层 (`client/src/services/api/project-elements.ts`) **新增方法**: - `deleteCharacter(projectId, characterId)` - 删除角色 - `deleteLocation(projectId, locationId)` - 删除场景 - `deleteProp(projectId, propId)` - 删除道具 - `updateCharacter(projectId, characterId, payload)` - 更新角色 - `updateLocation(projectId, locationId, payload)` - 更新场景 - `updateProp(projectId, propId, payload)` - 更新道具 **对接后端接口**: - `DELETE /api/v1/projects/{project_id}/characters/{character_id}` - `DELETE /api/v1/projects/{project_id}/locations/{location_id}` - `DELETE /api/v1/projects/{project_id}/props/{prop_id}` - `PUT /api/v1/projects/{project_id}/characters/{character_id}` - `PUT /api/v1/projects/{project_id}/locations/{location_id}` - `PUT /api/v1/projects/{project_id}/props/{prop_id}` ### 2. Hooks 层 (`client/src/hooks/api/useResourceLibrary.ts`) **新增 Hooks**: - `useDeleteProjectElement(projectId)` - 删除项目元素(角色/场景/道具) - `useUpdateProjectElement(projectId)` - 更新项目元素(角色/场景/道具) **特性**: - 自动根据元素类型调用对应的 API 方法 - 操作成功后自动刷新资源库列表缓存 - 支持 TanStack Query 的 mutation 特性(loading、error 状态) ### 3. UI 组件 (`client/src/components/features/project/ProjectResourcePanel.tsx`) **功能增强**: 1. **更多菜单**:所有资源类型(角色/场景/道具/实拍)的卡片右上角显示三点菜单 2. **编辑功能**: - 点击"编辑"打开弹窗 - 支持修改名称和描述 - 验证名称不能为空 - 显示保存中状态 3. **删除功能**: - 点击"删除"打开确认弹窗 - 显示资源名称提醒用户 - 根据资源类型调用对应的删除接口 **实现细节**: - 复用现有的 Dialog 组件(延迟加载) - 编辑弹窗根据资源类型动态显示标题 - 删除逻辑区分实拍和项目元素,调用不同的 API - 更新逻辑区分实拍和项目元素,调用不同的 API ## 用户体验 ### 操作流程 **编辑资源**: 1. 鼠标悬停资源卡片,右上角显示三点菜单 2. 点击三点菜单 → 选择"编辑" 3. 弹窗中修改名称和描述 4. 点击"保存"按钮 5. 保存成功后自动关闭弹窗并刷新列表 **删除资源**: 1. 鼠标悬停资源卡片,右上角显示三点菜单 2. 点击三点菜单 → 选择"删除" 3. 确认弹窗显示资源名称 4. 点击"确认删除" 5. 删除成功后自动刷新列表 ### UI 一致性 - 所有资源类型(角色/场景/道具/实拍)使用统一的菜单样式 - 编辑和删除图标与文案保持一致 - 删除选项使用红色(error)强调危险操作 ## 技术细节 ### API 调用逻辑 ```typescript // 删除逻辑 if (filterType === RESOURCE_TYPES.FOOTAGE) { // 实拍使用 project-resources API deleteResource.mutate({ id: resource.id }); } else { // 角色/场景/道具使用 project-elements API deleteElement.mutate({ type: filterType as 'character' | 'location' | 'prop', elementId: resource.id, }); } // 更新逻辑 if (filterType === RESOURCE_TYPES.FOOTAGE) { // 实拍使用 project-resources API updateResource.mutate({ id, name, description }); } else { // 角色/场景/道具使用 project-elements API updateElement.mutate({ type: filterType as 'character' | 'location' | 'prop', elementId, payload: { name, description }, }); } ``` ### 缓存失效策略 - 使用 TanStack Query 的 `invalidateQueries` - 操作成功后仅失效对应类型的资源列表: ```typescript queryClient.invalidateQueries({ queryKey: resourceLibraryKeys.list(projectId!, type) }); ``` ## 后续优化 1. **批量操作**:支持多选后批量删除 2. **拖拽排序**:支持通过拖拽调整资源顺序 3. **快捷键**:支持键盘快捷键(如 Delete 键删除选中资源) 4. **撤销操作**:删除后支持撤销恢复 ## 代码变更统计 ``` 前端: client/src/components/features/project/ProjectResourcePanel.tsx | +167 -62 client/src/hooks/api/useResourceLibrary.ts | +77 client/src/services/api/project-elements.ts | +38 后端(Bug 修复): server/app/services/project_element_service.py | +3 -3 server/app/repositories/project_character_repository.py | +10 server/app/repositories/project_location_repository.py | +10 server/app/repositories/project_prop_repository.py | +10 文档: docs/client/changelogs/2026-02-09-project-element-menu.md | +262 (新增) -------------------------------------------------------------------- 总计: 253 insertions(+), 65 deletions(-) ``` ## 测试建议 ### 功能测试 1. **编辑功能**: - ✅ 编辑角色名称和描述 - ✅ 编辑场景名称和描述 - ✅ 编辑道具名称和描述 - ✅ 编辑实拍名称和描述 - ✅ 名称为空时禁用保存按钮 - ✅ 保存中显示 loading 状态 2. **删除功能**: - ✅ 删除角色 - ✅ 删除场景 - ✅ 删除道具 - ✅ 删除实拍 - ✅ 删除后列表自动刷新 - ✅ 删除确认弹窗显示正确的资源名称 3. **边界情况**: - ✅ 网络错误时显示错误提示 - ✅ 重复点击菜单不会多次打开弹窗 - ✅ 弹窗打开时点击背景可关闭 ### UI 测试 1. **菜单显示**: - ✅ 鼠标悬停卡片时显示三点菜单 - ✅ 菜单图标位置正确(右上角) - ✅ 菜单背景半透明黑色 2. **弹窗样式**: - ✅ 编辑弹窗标题根据资源类型动态变化 - ✅ 删除按钮使用红色(destructive)样式 - ✅ 表单布局整齐,间距合理 ## 关联文档 - 后端 API 文档:`server/app/api/v1/project_elements.py` - 项目元素 Schema:`server/app/schemas/project_element.py` - ADR: 项目级资源所有权 - `docs/server/adrs/01-project-level-resource-ownership.md`