"""在大屏 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): if type(segment) is not int: raise ValueError(f"数组路径必须使用整数下标: {location}") if not 0 <= segment < len(current): raise ValueError(f"数组下标越界: {location}") else: 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)