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.9 KiB

项目资源面板 - 添加角色/场景/道具编辑删除功能

日期: 2026-02-09
类型: Feature + Bugfix
影响范围: 前端 - 项目资源面板,后端 - 项目元素删除逻辑

概述

为项目资源面板中的角色(Character)、场景(Location)、道具(Prop)添加更多菜单,支持编辑和删除操作,与现有的实拍(Footage)功能保持一致。同时修复了后端删除接口的关键 Bug。

🐛 Bug 修复(重要)

问题描述

后端删除接口(角色/场景/道具)存在严重 Bug:

  • ProjectElementServicedelete_* 方法将整个对象传递给 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 调用逻辑

// 删除逻辑
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
  • 操作成功后仅失效对应类型的资源列表:
    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