2025-08-15 16:34:37 +08:00
|
|
|
|
from abc import ABC, abstractmethod
|
2025-08-28 09:20:15 +08:00
|
|
|
|
from collections.abc import Awaitable, Iterable
|
2025-08-18 23:08:22 +08:00
|
|
|
|
from typing import Any
|
2025-08-15 16:34:37 +08:00
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
|
2025-08-18 23:08:22 +08:00
|
|
|
|
from zhenxun.services.renderer.protocols import Renderable
|
2025-08-28 09:20:15 +08:00
|
|
|
|
from zhenxun.utils.pydantic_compat import compat_computed_field, model_dump
|
2025-08-18 23:08:22 +08:00
|
|
|
|
|
|
|
|
|
|
__all__ = ["ContainerComponent", "RenderableComponent"]
|
2025-08-15 16:34:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-08-18 23:08:22 +08:00
|
|
|
|
class RenderableComponent(BaseModel, Renderable):
|
2025-08-28 09:20:15 +08:00
|
|
|
|
"""
|
|
|
|
|
|
所有可渲染UI组件的数据模型基类。
|
|
|
|
|
|
|
|
|
|
|
|
它继承自 Pydantic 的 `BaseModel` 用于数据校验和结构化,同时实现了 `Renderable`
|
|
|
|
|
|
协议,确保其能够被 `RendererService` 正确处理。
|
|
|
|
|
|
它还提供了一些所有组件通用的样式属性,如 `inline_style`, `variant` 等。
|
|
|
|
|
|
"""
|
2025-08-15 16:34:37 +08:00
|
|
|
|
|
2025-08-18 23:08:22 +08:00
|
|
|
|
_is_standalone_template: bool = False
|
2025-08-28 09:20:15 +08:00
|
|
|
|
inline_style: dict[str, str] | None = None
|
|
|
|
|
|
component_css: str | None = None
|
|
|
|
|
|
extra_classes: list[str] | None = None
|
|
|
|
|
|
variant: str | None = None
|
2025-08-18 23:08:22 +08:00
|
|
|
|
|
2025-08-15 16:34:37 +08:00
|
|
|
|
@property
|
|
|
|
|
|
def template_name(self) -> str:
|
2025-08-18 23:08:22 +08:00
|
|
|
|
"""
|
|
|
|
|
|
返回用于渲染此组件的Jinja2模板的路径。
|
|
|
|
|
|
这是一个抽象属性,所有子类都必须覆盖它。
|
|
|
|
|
|
"""
|
|
|
|
|
|
raise NotImplementedError(
|
|
|
|
|
|
"Subclasses must implement the 'template_name' property."
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def prepare(self) -> None:
|
|
|
|
|
|
"""[可选] 生命周期钩子,默认无操作。"""
|
2025-08-15 16:34:37 +08:00
|
|
|
|
pass
|
2025-08-18 23:08:22 +08:00
|
|
|
|
|
2025-08-28 09:20:15 +08:00
|
|
|
|
def get_children(self) -> Iterable["RenderableComponent"]:
|
|
|
|
|
|
"""默认实现:非容器组件没有子组件。"""
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
2025-08-18 23:08:22 +08:00
|
|
|
|
def get_required_scripts(self) -> list[str]:
|
|
|
|
|
|
"""[可选] 返回此组件所需的JS脚本路径列表 (相对于assets目录)。"""
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
def get_required_styles(self) -> list[str]:
|
|
|
|
|
|
"""[可选] 返回此组件所需的CSS样式表路径列表 (相对于assets目录)。"""
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
def get_render_data(self) -> dict[str, Any | Awaitable[Any]]:
|
|
|
|
|
|
"""默认实现,返回模型自身的数据字典。"""
|
2025-08-28 09:20:15 +08:00
|
|
|
|
return model_dump(
|
|
|
|
|
|
self, exclude={"inline_style", "component_css", "inline_style_str"}
|
|
|
|
|
|
)
|
2025-08-18 23:08:22 +08:00
|
|
|
|
|
2025-08-28 09:20:15 +08:00
|
|
|
|
@compat_computed_field
|
|
|
|
|
|
def inline_style_str(self) -> str:
|
|
|
|
|
|
"""[新增] 一个辅助属性,将内联样式字典转换为CSS字符串"""
|
|
|
|
|
|
if not self.inline_style:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
return "; ".join(f"{k}: {v}" for k, v in self.inline_style.items())
|
|
|
|
|
|
|
|
|
|
|
|
def get_extra_css(self, context: Any) -> str | Awaitable[str]:
|
2025-08-18 23:08:22 +08:00
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContainerComponent(RenderableComponent, ABC):
|
|
|
|
|
|
"""
|
|
|
|
|
|
一个为容器类组件设计的抽象基类,封装了预渲染子组件的通用逻辑。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
2025-08-28 09:20:15 +08:00
|
|
|
|
def get_children(self) -> Iterable[RenderableComponent]:
|
2025-08-18 23:08:22 +08:00
|
|
|
|
"""
|
2025-08-28 09:20:15 +08:00
|
|
|
|
一个抽象方法,子类必须实现它来返回一个可迭代的子组件。
|
2025-08-18 23:08:22 +08:00
|
|
|
|
"""
|
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
2025-08-28 09:20:15 +08:00
|
|
|
|
def get_required_scripts(self) -> list[str]:
|
|
|
|
|
|
"""[新增] 聚合所有子组件的脚本依赖。"""
|
|
|
|
|
|
scripts = set(super().get_required_scripts())
|
|
|
|
|
|
for child in self.get_children():
|
|
|
|
|
|
if child:
|
|
|
|
|
|
scripts.update(child.get_required_scripts())
|
|
|
|
|
|
return list(scripts)
|
2025-08-18 23:08:22 +08:00
|
|
|
|
|
2025-08-28 09:20:15 +08:00
|
|
|
|
def get_required_styles(self) -> list[str]:
|
|
|
|
|
|
"""[新增] 聚合所有子组件的样式依赖。"""
|
|
|
|
|
|
styles = set(super().get_required_styles())
|
|
|
|
|
|
for child in self.get_children():
|
|
|
|
|
|
if child:
|
|
|
|
|
|
styles.update(child.get_required_styles())
|
|
|
|
|
|
return list(styles)
|