"""设置组件的交互:显示条件、数据筛选、事件;变量本身在画布上,用 update_canvas 写 variables""" from typing import Literal, Self from pydantic import Field, model_serializer, model_validator from app.schemas.base import ExtensibleModel, NonBlankString from app.schemas.screen import EventAction, RowFilter, VisibleWhen from .base import BaseAction # 这三个键只处理写出的:没写保持原样,写 null 删除节点上的该字段 INTERACTION_KEYS = ("visibleWhen", "filters", "events") class NodeEvent(ExtensibleModel): """组件事件;写变量的事件只写 type 和 action,保留已有的代码事件时把它原样带上""" type: NonBlankString = Field( description="事件类型,取物料 eventOptions 里的值:click 点击组件;itemClick 点击图表的某个数据项(只有图表有)" ) action: EventAction | None = Field( default=None, description="写变量;有 action 时不执行事件代码" ) @model_serializer(mode="wrap") def without_empty_action(self, handler): # 代码事件没有 action,不补出 null data = handler(self) if self.action is None: data.pop("action", None) return data class SetInteractionAction(BaseAction): """设置组件的显示条件、数据筛选和事件。只改写出的键,整体替换该键;写 null 删除该键;至少写一个键 变量要先在画布 variables 里声明(update_canvas),同一批里先后顺序不限,整批执行完再检查引用 """ type: Literal["set_interaction"] = "set_interaction" visibleWhen: VisibleWhen | None = Field( default=None, description="变量等于 equals 时才显示该组件,用于整页切换;同一变量取不同值的组件不会同时出现,可以放在同一位置", ) filters: list[RowFilter] | None = Field( default=None, description="按变量筛选组件的数据行,多条为且,变量为 null 时不过滤;只用于有数据行的组件(图表的 dataset.source、指标卡的 rows)," "field 必须是数据行里的列。被筛选的图表的数据要带上这一列,查询时不要对它加 WHERE", ) events: list[NodeEvent] | None = Field( default=None, description="组件的全部事件,整体替换:组件上已有的代码事件要保留时原样带上。" '例如 [{"type": "click", "action": {"setVar": "page", "value": "stock"}}]、' '[{"type": "itemClick", "action": {"setVar": "model", "fromField": "model"}}]', ) @model_validator(mode="after") def some_key(self) -> Self: if not self.model_fields_set & set(INTERACTION_KEYS): raise ValueError(f"至少写出 {'、'.join(INTERACTION_KEYS)} 中的一个") return self @model_serializer(mode="wrap") def written_keys_only(self, handler): # 交给前端时 null 表示删除,没写的键不能序列化成 null;检查点按序列化结果重建,也靠它保留"写了哪些键" data = handler(self) for key in INTERACTION_KEYS: if key not in self.model_fields_set: data.pop(key, None) return data