9.3 KiB
Changelog: 修复场景"景别"字段存储位置
日期: 2026-02-09
类型: Bug 修复
影响范围: 前端 - 场景信息编辑保存
问题描述
场景信息编辑时,"景别"字段(locationType)被错误地存储到数据库的 meta_data.location_type 中,而不是正确的 location 顶层字段。
根因分析
前端字段映射错误:
// ❌ 错误: usePreviewActions.ts - handleUpdateLocation
const metaData: Record<string, unknown> = {};
if (data.locationType !== undefined)
metaData.location_type = data.locationType; // 错误存入 meta_data
// 顶层字段
if (data.setting !== undefined)
payload.location = data.setting; // setting 字段已不再使用
数据库模型定义:
class ProjectLocation(SQLModel, table=True):
location_id: UUID
name: str
location: Optional[str] # ← 这是"景别"应该存储的字段
description: Optional[str]
meta_data: Dict[str, Any] # ← 不应该存储 location_type
解决方案
1. 前端保存逻辑修复
修改前端 handleUpdateLocation 函数,将 locationType 正确映射到数据库的 location 字段。
client/src/components/features/preview/hooks/usePreviewActions.ts
// ✅ 修复后
try {
// 构建 metadata:timeOfDay, weather, atmosphere, realWorldLocation
const metaData: Record<string, unknown> = {};
// locationType 不再存入 metaData
if (data.timeOfDay !== undefined) metaData.time_of_day = data.timeOfDay;
if (data.weather !== undefined) metaData.weather = data.weather;
if (data.atmosphere !== undefined) metaData.atmosphere = data.atmosphere;
if (data.realWorldLocation !== undefined)
metaData.real_world_location = data.realWorldLocation;
// 构建顶层字段
const payload: Record<string, unknown> = {};
if (data.name !== undefined) payload.name = data.name;
if (data.description !== undefined) payload.description = data.description;
// ✅ locationType(景别) 正确存入数据库的 location 字段
if (data.locationType !== undefined) payload.location = data.locationType;
// 合并 meta_data
if (Object.keys(metaData).length > 0) {
payload.meta_data = metaData;
}
// ...
}
2. 后端 meta_data 更新策略修复
修改后端更新逻辑,使用完全替换而非合并策略,避免保留旧字段。
server/app/services/project_element_service.py
# ❌ 修复前: 合并策略
current_meta = dict(location.meta_data or {}) # 保留旧数据
current_meta.update(meta_updates) # 合并新数据
# 结果: 旧的 location_type 被保留
# ✅ 修复后: 替换策略
location.meta_data = dict(meta_updates) # 完全替换
# 结果: 只保留前端发送的字段
3. 前端数据读取逻辑修复
修改 usePreviewData.ts 中的场景数据映射,正确从 location 字段读取景别值。
client/src/components/features/preview/hooks/usePreviewData.ts
// ❌ 修复前
return tags.map((tag, index) => ({
// ...
setting: locationDetail.location, // 错误映射
locationType: metadata.location_type as string | undefined, // 从不存在的 meta_data 读取
// ...
}));
// ✅ 修复后: 从顶层直接读取
return tags.map((tag, index) => ({
// ...
locationType: locationDetail.location,
timeOfDay: locationDetail.time_of_day, // 从顶层读取
weather: locationDetail.weather,
atmosphere: locationDetail.atmosphere,
realWorldLocation: locationDetail.real_world_location,
// ...
}));
4. 后端 Schema 字段提升(方案2实施)
为 ProjectLocationResponse 和 ProjectPropResponse 添加字段提升,与 ProjectCharacterResponse 保持一致。
server/app/schemas/project_element.py
class ProjectLocationResponse(BaseModel):
"""项目场景响应"""
# ... 现有字段 ...
# 场景详细字段(从 meta_data 提升到顶层)
time_of_day: Optional[str] = Field(None, description="时间")
weather: Optional[str] = Field(None, description="天气")
atmosphere: Optional[str] = Field(None, description="氛围")
real_world_location: Optional[str] = Field(None, description="现实取景地参考")
meta_data: Dict[str, Any] = Field(..., description="元数据")
def model_post_init(self, __context: Any) -> None:
"""从 meta_data 中提取字段到顶层"""
if self.meta_data:
if not self.time_of_day and 'time_of_day' in self.meta_data:
self.time_of_day = self.meta_data.get('time_of_day')
# ... 其他字段同理
class ProjectPropResponse(BaseModel):
"""项目道具响应"""
# ... 现有字段 ...
# 道具详细字段(从 meta_data 提升到顶层)
prop_type: Optional[str] = Field(None, description="道具类型")
usage: Optional[str] = Field(None, description="使用方法/关键功能")
meta_data: Dict[str, Any] = Field(..., description="元数据")
def model_post_init(self, __context: Any) -> None:
"""从 meta_data 中提取字段到顶层"""
if self.meta_data:
if not self.prop_type and 'prop_type' in self.meta_data:
self.prop_type = self.meta_data.get('prop_type')
# ...
字段映射对照表
场景(Location)字段映射
| 前端字段 | API 响应位置 | 数据库存储位置 | 说明 |
|---|---|---|---|
name |
顶层 | name (顶层) |
场景名称 |
locationType |
顶层 location |
location (顶层) |
景别(外景/内景) |
description |
顶层 | description (顶层) |
场景描述 |
timeOfDay |
顶层 time_of_day |
meta_data.time_of_day |
时间(白天/夜晚) |
weather |
顶层 weather |
meta_data.weather |
天气 |
atmosphere |
顶层 atmosphere |
meta_data.atmosphere |
氛围 |
realWorldLocation |
顶层 real_world_location |
meta_data.real_world_location |
现实取景地参考 |
道具(Prop)字段映射
| 前端字段 | API 响应位置 | 数据库存储位置 | 说明 |
|---|---|---|---|
name |
顶层 | name (顶层) |
道具名称 |
description |
顶层 | description (顶层) |
道具描述 |
propType |
顶层 prop_type |
meta_data.prop_type |
道具类型 |
usage |
顶层 usage |
meta_data.usage |
使用方法/关键功能 |
字段提升说明
方案: 采用与角色(ProjectCharacterResponse)一致的字段提升模式
API 响应示例:
{
"location_id": "xxx",
"name": "街道",
"location": "外景",
"time_of_day": "白天", // ✅ 提升到顶层
"weather": "晴天", // ✅ 提升到顶层
"atmosphere": "温馨", // ✅ 提升到顶层
"real_world_location": "横店", // ✅ 提升到顶层
"meta_data": {
"time_of_day": "白天", // 同时保留在 meta_data
"weather": "晴天"
}
}
废弃字段
→ 已移除,改用settinglocationType→location
数据流
前端展示
↑ locationType: locationDetail.location
usePreviewData.ts (读取)
↑
API: GET /projects/{id}/locations/{id}
↑
数据库: project_locations.location = '外景'
↓
API: PUT /projects/{id}/locations/{id}
↓ payload.location = data.locationType
handleUpdateLocation() (保存)
↓ locationType: '外景'
前端编辑
完整的数据循环
- 前端展示:
locationDetail.location→locationType→ 显示在"景别"字段 - 前端编辑: 用户修改"景别" →
data.locationType - 前端保存:
data.locationType→payload.location→ 发送到后端 - 后端保存:
payload.location→location.location→ 存入数据库 - 前端读取: 数据库
location→ API 响应locationDetail.location→ 映射到locationType
影响范围
- ✅ 修复: 场景"景别"字段正确存储到
location字段 - ✅ 无破坏性变更: 仅修改字段映射逻辑
- ⚠️ 历史数据: 如果之前有场景数据将 location_type 存入 meta_data,需要手动迁移
测试验证
手动测试步骤
- 打开场景详情面板
- 编辑"景别"字段(如:外景/内景)
- 点击"保存"
- 验证数据库:
SELECT location_id, name, location, meta_data
FROM project_locations
ORDER BY updated_at DESC
LIMIT 5;
预期结果
location字段包含"景别"值(如 "外景")meta_data不包含location_type键meta_data正确包含time_of_day、weather、atmosphere、real_world_location
相关文件
前端:
client/src/components/features/preview/hooks/usePreviewActions.ts- 前端保存逻辑client/src/components/features/preview/hooks/usePreviewData.ts- 前端数据读取与映射client/src/components/features/preview/types.ts- 类型定义client/src/components/features/preview/PreviewInfoPanel.tsx- 前端编辑表单
后端:
server/app/models/project_location.py- 数据库模型server/app/services/project_element_service.py- 后端服务层
额外修复
同时修复了场景和道具的 update_location 和 update_prop 方法,应用与 update_character 相同的 JSONB 变更检测修复,确保 meta_data 字段正确更新到数据库。