feat:优化执行步骤
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""布局意图:模型只说“怎么排”,坐标由后端算,展开成 move 和 resize 后才交给前端
|
||||
|
||||
这些动作只存在于模型提交的这一步,不会出现在返回给前端的动作里,前端协议不变
|
||||
"""
|
||||
|
||||
from typing import Annotated, Literal, Self
|
||||
|
||||
from pydantic import Field, model_validator
|
||||
|
||||
from app.schemas.screen import Number, PositiveNumber
|
||||
|
||||
from .base import ActionModel, BaseAction
|
||||
|
||||
NonNegativeNumber = Annotated[Number, Field(ge=0)]
|
||||
TargetIds = Annotated[
|
||||
list[str],
|
||||
Field(min_length=1, description="要排列的组件 ID,不能重复"),
|
||||
]
|
||||
|
||||
|
||||
class Area(ActionModel):
|
||||
"""画布上的一块矩形区域,坐标单位 px"""
|
||||
|
||||
x: Number = Field(description="左上角横坐标")
|
||||
y: Number = Field(description="左上角纵坐标")
|
||||
width: PositiveNumber
|
||||
height: PositiveNumber
|
||||
|
||||
|
||||
class _Group(ActionModel):
|
||||
target_ids: TargetIds
|
||||
|
||||
@model_validator(mode="after")
|
||||
def unique_ids(self) -> Self:
|
||||
if len(set(self.target_ids)) != len(self.target_ids):
|
||||
raise ValueError("target_ids 不能重复")
|
||||
return self
|
||||
|
||||
|
||||
class ArrangeAction(_Group):
|
||||
"""把一组组件平分一块区域:每个组件的位置和尺寸都由这里决定,彼此不重叠、不超出区域
|
||||
|
||||
用于“平分空间”“排成一行/一列”“两行两列铺满”等;按 target_ids 的顺序从左到右、从上到下放置
|
||||
"""
|
||||
|
||||
type: Literal["arrange"] = "arrange"
|
||||
mode: Literal["row", "column", "grid"] = Field(
|
||||
description="row:排成一行;column:排成一列;grid:按 columns 列排成网格"
|
||||
)
|
||||
columns: int | None = Field(
|
||||
default=None, ge=1, description="mode=grid 时每行几个,其他模式不填"
|
||||
)
|
||||
area: Area | None = Field(
|
||||
default=None, description="要铺满的区域,省略表示整个画布"
|
||||
)
|
||||
gap: NonNegativeNumber = Field(default=20, description="组件之间的间距,单位 px")
|
||||
padding: NonNegativeNumber = Field(
|
||||
default=0, description="区域四周留出的边距,单位 px"
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def columns_match_mode(self) -> Self:
|
||||
if (self.mode == "grid") != (self.columns is not None):
|
||||
raise ValueError("mode=grid 时必须填写 columns,其他模式不填")
|
||||
return self
|
||||
|
||||
|
||||
class AlignAction(_Group):
|
||||
"""按基准对齐,不改变尺寸;居中、靠左、靠右、顶部对齐等"""
|
||||
|
||||
type: Literal["align"] = "align"
|
||||
to: Literal["canvas", "selection"] = Field(
|
||||
default="canvas",
|
||||
description="canvas:对齐到画布;selection:对齐到这些组件合起来的外框",
|
||||
)
|
||||
horizontal: Literal["left", "center", "right"] | None = Field(
|
||||
default=None, description="水平方向的对齐方式,省略表示不调整水平位置"
|
||||
)
|
||||
vertical: Literal["top", "middle", "bottom"] | None = Field(
|
||||
default=None, description="垂直方向的对齐方式,省略表示不调整垂直位置"
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def require_direction(self) -> Self:
|
||||
if self.horizontal is None and self.vertical is None:
|
||||
raise ValueError("align 至少提供 horizontal 或 vertical")
|
||||
return self
|
||||
|
||||
|
||||
class DistributeAction(_Group):
|
||||
"""沿一个方向等间距排开,不改变尺寸;按当前位置的先后顺序排列"""
|
||||
|
||||
type: Literal["distribute"] = "distribute"
|
||||
axis: Literal["horizontal", "vertical"]
|
||||
gap: NonNegativeNumber | None = Field(
|
||||
default=None,
|
||||
description="固定间距,单位 px,从区域起点(没有 area 时是最前一个组件的位置)依次排开;"
|
||||
"省略表示把间距摊匀,首尾两个组件的外沿不动",
|
||||
)
|
||||
area: Area | None = Field(
|
||||
default=None, description="在这块区域内排开,省略表示用这些组件合起来的外框"
|
||||
)
|
||||
|
||||
|
||||
class SetLayoutAction(BaseAction):
|
||||
"""直接设置组件的绝对位置和尺寸,只写要改的项,其余保持不变;不需要自己换算位移"""
|
||||
|
||||
type: Literal["set_layout"] = "set_layout"
|
||||
x: Number | None = Field(default=None, description="目标左上角横坐标")
|
||||
y: Number | None = Field(default=None, description="目标左上角纵坐标")
|
||||
width: PositiveNumber | None = Field(default=None, description="目标宽度")
|
||||
height: PositiveNumber | None = Field(default=None, description="目标高度")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def require_field(self) -> Self:
|
||||
if all(v is None for v in (self.x, self.y, self.width, self.height)):
|
||||
raise ValueError("set_layout 至少提供 x、y、width、height 中的一项")
|
||||
return self
|
||||
Reference in New Issue
Block a user