mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-05 03:39:59 +08:00
✨ feat!(ui): 重构图表组件架构,实现数据与样式分离 (#2035)
* ✨ feat!(ui): 重构图表组件架构,实现数据与样式分离 🏗️ **架构重构** - 移除charts.py中所有硬编码样式参数(grid、tooltip、legend等) - 将样式配置迁移至主题层style.json文件 - 统一图表模板消费样式文件的能力 📊 **图表组件优化** - bar_chart: 移除grid和坐标轴show参数 - pie_chart: 移除tooltip、legend样式和series视觉参数 - line_chart: 移除tooltip、grid和坐标轴配置 - radar_chart: 移除tooltip硬编码 🎨 **主题系统增强** - 新增pie_chart、line_chart、radar_chart的style.json配置 - 更新bar_chart/style.json,添加grid、xAxis、yAxis样式 - 所有图表模板支持deepMerge样式合并逻辑 🔧 **Breaking Changes** - 图表工厂函数不再接受样式参数 - 主题开发者现可通过style.json完全定制图表外观 - 提升组件可维护性和主题灵活性 * 📦️ build(pyinstaller): 引入 resources.spec 并更新 .gitignore 规则 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
webjoin111
pre-commit-ci[bot]
parent
d9e65057cf
commit
7472cabd48
@@ -1,20 +1,29 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Iterator
|
||||
from collections.abc import Awaitable, Iterable
|
||||
from typing import Any
|
||||
|
||||
from nonebot.compat import model_dump
|
||||
from pydantic import BaseModel
|
||||
|
||||
from zhenxun.services.renderer.protocols import Renderable
|
||||
from zhenxun.utils.pydantic_compat import compat_computed_field, model_dump
|
||||
|
||||
__all__ = ["ContainerComponent", "RenderableComponent"]
|
||||
|
||||
|
||||
class RenderableComponent(BaseModel, Renderable):
|
||||
"""所有可渲染UI组件的抽象基类。"""
|
||||
"""
|
||||
所有可渲染UI组件的数据模型基类。
|
||||
|
||||
它继承自 Pydantic 的 `BaseModel` 用于数据校验和结构化,同时实现了 `Renderable`
|
||||
协议,确保其能够被 `RendererService` 正确处理。
|
||||
它还提供了一些所有组件通用的样式属性,如 `inline_style`, `variant` 等。
|
||||
"""
|
||||
|
||||
_is_standalone_template: bool = False
|
||||
inline_style: dict[str, str] | None = None
|
||||
component_css: str | None = None
|
||||
extra_classes: list[str] | None = None
|
||||
variant: str | None = None
|
||||
|
||||
@property
|
||||
def template_name(self) -> str:
|
||||
@@ -30,6 +39,10 @@ class RenderableComponent(BaseModel, Renderable):
|
||||
"""[可选] 生命周期钩子,默认无操作。"""
|
||||
pass
|
||||
|
||||
def get_children(self) -> Iterable["RenderableComponent"]:
|
||||
"""默认实现:非容器组件没有子组件。"""
|
||||
return []
|
||||
|
||||
def get_required_scripts(self) -> list[str]:
|
||||
"""[可选] 返回此组件所需的JS脚本路径列表 (相对于assets目录)。"""
|
||||
return []
|
||||
@@ -40,9 +53,18 @@ class RenderableComponent(BaseModel, Renderable):
|
||||
|
||||
def get_render_data(self) -> dict[str, Any | Awaitable[Any]]:
|
||||
"""默认实现,返回模型自身的数据字典。"""
|
||||
return model_dump(self)
|
||||
return model_dump(
|
||||
self, exclude={"inline_style", "component_css", "inline_style_str"}
|
||||
)
|
||||
|
||||
def get_extra_css(self, theme_manager: Any) -> str | Awaitable[str]:
|
||||
@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]:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -52,37 +74,24 @@ class ContainerComponent(RenderableComponent, ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def _get_renderable_child_items(self) -> Iterator[Any]:
|
||||
def get_children(self) -> Iterable[RenderableComponent]:
|
||||
"""
|
||||
一个抽象方法,子类必须实现它来返回一个可迭代的对象。
|
||||
迭代器中的每个项目都必须具有 'component' 和 'html_content' 属性。
|
||||
一个抽象方法,子类必须实现它来返回一个可迭代的子组件。
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def prepare(self) -> None:
|
||||
"""
|
||||
通用的 prepare 方法,负责预渲染所有子组件。
|
||||
"""
|
||||
from zhenxun.services import renderer_service
|
||||
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)
|
||||
|
||||
child_items = list(self._get_renderable_child_items())
|
||||
if not child_items:
|
||||
return
|
||||
|
||||
components_to_render = [
|
||||
item.component for item in child_items if item.component
|
||||
]
|
||||
|
||||
prepare_tasks = [
|
||||
comp.prepare() for comp in components_to_render if hasattr(comp, "prepare")
|
||||
]
|
||||
if prepare_tasks:
|
||||
await asyncio.gather(*prepare_tasks)
|
||||
|
||||
render_tasks = [
|
||||
renderer_service.render_to_html(comp) for comp in components_to_render
|
||||
]
|
||||
rendered_htmls = await asyncio.gather(*render_tasks)
|
||||
|
||||
for item, html in zip(child_items, rendered_htmls):
|
||||
item.html_content = html
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user