feat:图表交互

This commit is contained in:
2026-10-15 03:38:20 -07:00
parent 565ecf4558
commit 9b64f9fc0b
43 changed files with 1537 additions and 727 deletions
+63 -1
View File
@@ -27,6 +27,18 @@ class MaterialTemplate(ExtensibleModel):
events: list[JsonObject] = Field(default_factory=list)
class EventOption(ExtensibleModel):
label: NonBlankString
value: NonBlankString = Field(description="事件类型,即节点 events[].type")
class EventGroup(ExtensibleModel):
"""多个物料共用的一组事件,物料按 key 引用;前端接口展开成 eventOptions 数组"""
key: NonBlankString
options: list[EventOption]
class ComponentDefinition(ExtensibleModel):
model_config = ConfigDict(serialize_by_alias=True)
key: NonBlankString = Field(description="目录中的唯一编号,不是 Screen 节点 ID")
@@ -49,7 +61,20 @@ class ComponentDefinition(ExtensibleModel):
description="字段分组:字符串引用目录的共享分组,对象为本物料独有的分组;"
"前端属性面板的 setters 由它派生,不单独保存",
)
eventOptions: list[JsonObject] = Field(default_factory=list)
event_groups: list[NonBlankString] = Field(
default_factory=list,
description="引用目录的共享事件组,按顺序展开成前端的 eventOptions",
)
rows_path: NonBlankString | None = Field(
default=None,
description="组件数据行(对象数组)的点路径,例如 props.option.dataset.source;"
"节点的 filters 作用在这里,后端检查按它取列;前端各物料自己知道数据行在哪",
)
data_source: bool = Field(
default=False,
description="组件可以绑定数据源(节点 dataId):绑定后运行时的数据行换成数据源的数据,不再读 rows_path;"
"前端按它决定是否显示数据源绑定。需要 rows_path",
)
usage_notes: list[NonBlankString] = Field(
default_factory=list, description="配置、数据绑定及使用限制说明"
)
@@ -67,8 +92,23 @@ class ComponentDefinition(ExtensibleModel):
raise ValueError("目录 type 必须与 schema.type 一致")
if "id" in self.schema_.model_extra:
raise ValueError("默认物料配置不能包含实例 ID")
if self.rows_path is not None:
try:
rows = lookup(self.template(), self.rows_segments)
except ValueError:
rows = MISSING
if not isinstance(rows, list):
raise ValueError(f"rows_path {self.rows_path} 在默认配置里不是数组")
if self.data_source and self.rows_path is None:
raise ValueError("data_source 的物料要声明 rows_path:绑定的数据源替换的是这里的数据行")
return self
@property
def rows_segments(self) -> list[str | int]:
return [
int(part) if part.isdigit() else part for part in self.rows_path.split(".")
]
def _default_problem(field, template: JsonObject) -> str | None:
"""默认值只有一个来源:默认配置有该路径时取它,没有时字段必须声明 default;
@@ -100,6 +140,18 @@ class ComponentCatalog(ExtensibleModel):
field_groups: list[FieldGroup] = Field(
default_factory=list, description="多个物料共用的字段分组,物料按 key 引用"
)
event_groups: list[EventGroup] = Field(
default_factory=list, description="多个物料共用的事件组,物料按 key 引用"
)
def event_options(self, component: ComponentDefinition) -> list[JsonObject]:
"""物料引用的事件组按顺序展开,即前端的 eventOptions"""
groups = {group.key: group for group in self.event_groups}
return [
option.model_dump(mode="json")
for key in component.event_groups
for option in groups[key].options
]
@model_validator(mode="after")
def unique_keys(self) -> Self:
@@ -109,6 +161,9 @@ class ComponentCatalog(ExtensibleModel):
shared = [group.key for group in self.field_groups]
if len(shared) != len(set(shared)):
raise ValueError("共享字段分组 key 不能重复")
event_groups = [group.key for group in self.event_groups]
if len(event_groups) != len(set(event_groups)):
raise ValueError("共享事件组 key 不能重复")
for component in self.components:
groups = [
group.key if isinstance(group, FieldGroup) else group
@@ -132,6 +187,13 @@ class ComponentCatalog(ExtensibleModel):
raise ValueError(
f"物料 {component.key} 的独有分组与共享分组重名: {shadowed}"
)
if missing := [
key for key in component.event_groups if key not in event_groups
]:
raise ValueError(f"物料 {component.key} 引用了不存在的事件组: {missing}")
events = [option["value"] for option in self.event_options(component)]
if len(events) != len(set(events)):
raise ValueError(f"物料 {component.key} 的事件类型重复")
by_key = {group.key: group for group in self.field_groups}
fields = [
field