"""在大屏 JSON 副本上按路径更新组件属性,用于后端校验动作能否执行""" from copy import deepcopy from app.actions.base import BaseActionHandler from app.schemas.actions import UpdateAction from app.schemas.screen import Screen class UpdateHandler(BaseActionHandler[UpdateAction]): def apply(self, screen: Screen, action: UpdateAction) -> Screen: # 确认目标存在,后续路径访问和赋值均在大屏副本中进行 result, target = self.copy_target(screen, action.target_id) # 未设置的 style 视为空对象,只允许直接添加末级字段 if action.section == "style" and target.style is None: target.style = {} # 获取动作指定的属性区域,作为路径遍历的起点 current = getattr(target, action.section) # 逐段访问路径,只有到达末级时才写入新值 for index, segment in enumerate(action.path): # 标记当前是否为末级,用于区分继续访问和最终赋值 last = index == len(action.path) - 1 # 保存走到当前段的路径,异常时指出具体失败位置 location = f"{action.section} {action.path[: index + 1]!r}" # 如果当前容器是字典,路径段必须是字符串键 if isinstance(current, dict): if not isinstance(segment, str): raise ValueError(f"对象路径必须使用字符串键: {location}") # 中间层必须已存在,只允许在末级新增键,避免凭空构造嵌套结构 if not last and segment not in current: raise ValueError(f"中间路径不存在: {location}") elif isinstance(current, list): # 数组只接受整数下标,使用精确类型判断排除 bool if type(segment) is not int: raise ValueError(f"数组路径必须使用整数下标: {location}") # 只允许访问已有元素,不接受负下标,也不通过赋值扩展数组 if not 0 <= segment < len(current): raise ValueError(f"数组下标越界: {location}") else: # 标量或 None 不能继续按路径访问,停止执行并返回明确错误 raise ValueError(f"路径无法继续访问,当前值不是对象或数组: {location}") if last: # 新值也复制,避免后续修改结果时影响 Action current[segment] = deepcopy(action.value) else: # 尚未到达末级,进入已校验存在的下一层容器 current = current[segment] # 返回属性更新后的大屏副本,原始大屏和动作值均未被修改 return result if __name__ == "__main__": screen = Screen.model_validate( { "canvas": {"width": 1920, "height": 1080}, "nodes": [ { "id": "sales-chart", "type": "chart", "layout": {"x": 100, "y": 200, "width": 400, "height": 300}, "props": {"option": {"title": {"text": "销售额", "show": True}}}, } ], } ) action = UpdateAction( target_id="sales-chart", section="props", path=["option", "title", "text"], value="月度销售额", ) handler = UpdateHandler() final_screen = handler.apply(screen, action) print(final_screen.model_dump_json(indent=2)) print("原始标题:", screen.nodes[0].props["option"]["title"]["text"]) try: handler.apply( screen, UpdateAction( target_id="sales-chart", section="props", path=["missing", "text"], value="新标题", ), ) except ValueError as error: print("无效路径:", error)