✨ 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完全定制图表外观
- 提升组件可维护性和主题灵活性
This commit is contained in:
webjoin111
2025-08-27 10:55:22 +08:00
parent d9e65057cf
commit 0558c7c0a7
59 changed files with 2268 additions and 648 deletions
+40 -10
View File
@@ -1,19 +1,49 @@
from . import widgets
from .core.layout import LayoutBuilder
from .core.markdown import MarkdownBuilder
from .core.notebook import NotebookBuilder
from .core.table import TableBuilder
from .presets.help_page import PluginHelpPageBuilder
from .presets.info_card import InfoCardBuilder
from .presets.plugin_menu import PluginMenuBuilder
from .charts import EChartsBuilder
from .components import (
AlertBuilder,
AvatarBuilder,
AvatarGroupBuilder,
BadgeBuilder,
DividerBuilder,
KpiCardBuilder,
ProgressBarBuilder,
TimelineBuilder,
UserInfoBlockBuilder,
)
from .core import (
CardBuilder,
DetailsBuilder,
LayoutBuilder,
ListBuilder,
MarkdownBuilder,
NotebookBuilder,
TableBuilder,
TextBuilder,
)
from .presets import (
PluginHelpPageBuilder,
PluginMenuBuilder,
)
__all__ = [
"InfoCardBuilder",
"AlertBuilder",
"AvatarBuilder",
"AvatarGroupBuilder",
"BadgeBuilder",
"CardBuilder",
"DetailsBuilder",
"DividerBuilder",
"EChartsBuilder",
"KpiCardBuilder",
"LayoutBuilder",
"ListBuilder",
"MarkdownBuilder",
"NotebookBuilder",
"PluginHelpPageBuilder",
"PluginMenuBuilder",
"ProgressBarBuilder",
"TableBuilder",
"widgets",
"TextBuilder",
"TimelineBuilder",
"UserInfoBlockBuilder",
]
+65 -5
View File
@@ -7,14 +7,24 @@ T_DataModel = TypeVar("T_DataModel", bound=BaseModel)
class BaseBuilder(Generic[T_DataModel]):
"""所有UI构建器的基类,提供通用的样式化和构建逻辑。"""
"""
所有UI构建器的通用基类。
它实现了Builder设计模式,提供了一个流畅的、链式调用的API来创建和配置UI组件的数据模型。
同时,它也提供了通用的样式化方法,如 `with_style`, `with_inline_style` 等。
参数:
T_DataModel: 与此构建器关联的 Pydantic 数据模型类型。
"""
def __init__(self, data_model: T_DataModel, template_name: str):
self._data: T_DataModel = data_model
self._style_name: str | None = None
self._template_name = template_name
self._inline_style: dict | None = None
self._extra_css: str | None = None
self._component_css: str | None = None
self._variant: str | None = None
self._extra_classes: list[str] = []
@property
def data(self) -> T_DataModel:
@@ -23,6 +33,12 @@ class BaseBuilder(Generic[T_DataModel]):
def with_style(self, style_name: str) -> Self:
"""
为组件应用一个特定的样式。
参数:
style_name: 在主题的CSS中定义的样式类名。
返回:
Self: 当前构建器实例,以支持链式调用。
"""
self._style_name = style_name
return self
@@ -32,26 +48,70 @@ class BaseBuilder(Generic[T_DataModel]):
为组件的根元素应用动态的内联样式。
参数:
style: 一个CSS样式字典,例如 {"background-color":"#fff","font-size":"16px"}
style: 一个CSS样式字典,例如
`{"background-color":"#fff","font-size":"16px"}`。
返回:
Self: 当前构建器实例,以支持链式调用。
"""
self._inline_style = style
return self
def with_extra_css(self, css: str) -> Self:
def with_variant(self, variant_name: str) -> Self:
"""
为组件应用一个特定的变体/皮肤。
参数:
variant_name: 在组件的 `skins/` 目录下定义的变体名称。
返回:
Self: 当前构建器实例,以支持链式调用。
"""
self._variant = variant_name
return self
def with_component_css(self, css: str) -> Self:
"""
向页面注入一段自定义的CSS样式字符串。
参数:
css: 包含CSS规则的字符串。
返回:
Self: 当前构建器实例,以支持链式调用。
"""
self._extra_css = css
self._component_css = css
return self
def with_classes(self, *class_names: str) -> Self:
"""
为组件的根元素添加一个或多个CSS工具类。
这些类来自主题预定义的工具集。
示例: .with_classes("p-4", "text-center", "font-bold")
"""
self._extra_classes.extend(class_names)
return self
def build(self) -> T_DataModel:
"""
构建并返回配置好的数据模型。
这是构建过程的最后一步,它会将所有配置应用到数据模型上。
返回:
T_DataModel: 最终配置好的、可被渲染服务使用的数据模型实例。
"""
if self._style_name and hasattr(self._data, "style_name"):
setattr(self._data, "style_name", self._style_name)
if self._inline_style and hasattr(self._data, "inline_style"):
setattr(self._data, "inline_style", self._inline_style)
if self._component_css and hasattr(self._data, "component_css"):
setattr(self._data, "component_css", self._component_css)
if self._variant and hasattr(self._data, "variant"):
setattr(self._data, "variant", self._variant)
if self._extra_classes and hasattr(self._data, "extra_classes"):
setattr(self._data, "extra_classes", self._extra_classes)
return self._data
+150 -61
View File
@@ -2,87 +2,176 @@ from typing import Any, Generic, Literal, TypeVar
from typing_extensions import Self
from ..models.charts import (
BarChartData,
BaseChartData,
LineChartData,
LineChartSeries,
PieChartData,
PieChartDataItem,
EChartsAxis,
EChartsData,
EChartsGrid,
EChartsSeries,
EChartsTitle,
EChartsTooltip,
)
from .base import BaseBuilder
T_ChartData = TypeVar("T_ChartData", bound=BaseChartData)
class BaseChartBuilder(BaseBuilder[T_ChartData], Generic[T_ChartData]):
"""所有图表构建器的基类"""
class EChartsBuilder(BaseBuilder[EChartsData], Generic[T_ChartData]):
"""
一个统一的、泛型的 ECharts 图表构建器。
提供了设置 ECharts `option` 的核心方法,以及一些常用图表的便利方法。
"""
def set_title(self, title: str) -> Self:
self._data.title = title
return self
class BarChartBuilder(BaseChartBuilder[BarChartData]):
"""链式构建柱状图的辅助类 (支持横向和竖向)"""
def __init__(
self, title: str, direction: Literal["horizontal", "vertical"] = "horizontal"
):
data_model = BarChartData(
title=title, direction=direction, category_data=[], data=[]
def __init__(self, template_name: str, title: str):
model = EChartsData(
template_path=template_name,
title=EChartsTitle(text=title),
grid=None,
tooltip=None,
xAxis=None,
yAxis=None,
legend=None,
background_image=None,
)
super().__init__(data_model, template_name="components/charts/bar_chart")
super().__init__(model, template_name=template_name)
def add_data(self, category: str, value: float) -> Self:
"""添加一个数据点"""
self._data.category_data.append(category)
self._data.data.append(value)
return self
def add_data_items(
self, items: list[tuple[str, int | float]] | list[dict[str, Any]]
def set_title(
self, text: str, left: Literal["left", "center", "right"] = "center"
) -> Self:
for item in items:
if isinstance(item, tuple):
self.add_data(item[0], item[1])
elif isinstance(item, dict):
self.add_data(item.get("category", ""), item.get("value", 0))
self._data.title_model = EChartsTitle(text=text, left=left)
return self
def set_background_image(self, background_image: str) -> Self:
"""设置背景图片 (仅横向柱状图模板支持)"""
self._data.background_image = background_image
def set_grid(
self,
left: str | None = None,
right: str | None = None,
top: str | None = None,
bottom: str | None = None,
containLabel: bool = True,
) -> Self:
self._data.grid_model = EChartsGrid(
left=left, right=right, top=top, bottom=bottom, containLabel=containLabel
)
return self
class PieChartBuilder(BaseChartBuilder[PieChartData]):
"""链式构建饼图的辅助类"""
def __init__(self, title: str):
data_model = PieChartData(title=title, data=[])
super().__init__(data_model, template_name="components/charts/pie_chart")
def add_slice(self, name: str, value: float) -> Self:
"""添加一个饼图扇区"""
self._data.data.append(PieChartDataItem(name=name, value=value))
def set_tooltip(self, trigger: Literal["item", "axis", "none"]) -> Self:
self._data.tooltip_model = EChartsTooltip(trigger=trigger)
return self
def set_x_axis(
self,
type: Literal["category", "value", "time", "log"],
data: list[Any] | None = None,
show: bool = True,
) -> Self:
self._data.x_axis_model = EChartsAxis(type=type, data=data, show=show)
return self
class LineChartBuilder(BaseChartBuilder[LineChartData]):
"""链式构建折线图的辅助类"""
def __init__(self, title: str):
data_model = LineChartData(title=title, category_data=[], series=[])
super().__init__(data_model, template_name="components/charts/line_chart")
def set_categories(self, categories: list[str]) -> Self:
"""设置X轴的分类标签"""
self._data.category_data = categories
def set_y_axis(
self,
type: Literal["category", "value", "time", "log"],
data: list[Any] | None = None,
show: bool = True,
) -> Self:
self._data.y_axis_model = EChartsAxis(type=type, data=data, show=show)
return self
def add_series(
self, name: str, data: list[int | float], smooth: bool = False
self, type: str, data: list[Any], name: str | None = None, **kwargs: Any
) -> Self:
"""添加一条折线"""
self._data.series.append(LineChartSeries(name=name, data=data, smooth=smooth))
series = EChartsSeries(type=type, data=data, name=name, **kwargs)
self._data.series_models.append(series)
return self
def set_legend(
self,
data: list[str],
orient: Literal["horizontal", "vertical"] = "horizontal",
left: str = "auto",
) -> Self:
self._data.legend_model = {"data": data, "orient": orient, "left": left}
return self
def set_option(self, key: str, value: Any) -> Self:
"""
[高级] 设置 ECharts `option` 中的一个原始键值对。
这会覆盖由其他流畅API方法设置的同名配置。
"""
self._data.raw_options[key] = value
return self
def set_background_image(self, image_name: str) -> Self:
"""【兼容】为横向柱状图设置背景图片。"""
self._data.background_image = image_name
return self
def bar_chart(
title: str,
items: list[tuple[str, int | float]],
direction: Literal["horizontal", "vertical"] = "horizontal",
) -> EChartsBuilder:
"""便捷工厂函数:创建一个柱状图构建器。"""
builder = EChartsBuilder("components/charts/bar_chart", title)
categories = [item[0] for item in items]
values = [item[1] for item in items]
if direction == "horizontal":
builder.set_x_axis(type="value")
builder.set_y_axis(type="category", data=categories)
builder.add_series(
type="bar",
data=values,
)
else:
builder.set_x_axis(type="category", data=categories)
builder.set_y_axis(type="value")
builder.add_series(type="bar", data=values)
return builder
def pie_chart(title: str, items: list[tuple[str, int | float]]) -> EChartsBuilder:
"""便捷工厂函数:创建一个饼图构建器。"""
builder = EChartsBuilder("components/charts/pie_chart", title)
data = [{"name": name, "value": value} for name, value in items]
legend_data = [item[0] for item in items]
builder.set_legend(data=legend_data)
builder.add_series(
name=title,
type="pie",
data=data,
)
return builder
def line_chart(
title: str, categories: list[str], series: list[dict[str, Any]]
) -> EChartsBuilder:
"""便捷工厂函数:创建一个折线图构建器。"""
builder = EChartsBuilder("components/charts/line_chart", title)
builder.set_x_axis(type="category", data=categories)
builder.set_y_axis(type="value")
for s in series:
builder.add_series(
type="line",
name=s.get("name", ""),
data=s.get("data", []),
smooth=s.get("smooth", False),
)
return builder
def radar_chart(
title: str, indicators: list[tuple[str, int | float]], series: list[dict[str, Any]]
) -> EChartsBuilder:
"""便捷工厂函数:创建一个雷达图构建器。"""
builder = EChartsBuilder("components/charts/radar_chart", title)
legend_data = [s.get("name", "") for s in series]
radar_indicators = [{"name": name, "max": max_val} for name, max_val in indicators]
builder.set_legend(data=legend_data)
builder.set_option("radar", {"indicator": radar_indicators})
builder.add_series(type="radar", data=series)
return builder
@@ -0,0 +1,25 @@
"""
小组件构建器模块
包含各种UI小组件的构建器
"""
from .alert import AlertBuilder
from .avatar import AvatarBuilder, AvatarGroupBuilder
from .badge import BadgeBuilder
from .divider import DividerBuilder
from .kpi_card import KpiCardBuilder
from .progress_bar import ProgressBarBuilder
from .timeline import TimelineBuilder
from .user_info_block import UserInfoBlockBuilder
__all__ = [
"AlertBuilder",
"AvatarBuilder",
"AvatarGroupBuilder",
"BadgeBuilder",
"DividerBuilder",
"KpiCardBuilder",
"ProgressBarBuilder",
"TimelineBuilder",
"UserInfoBlockBuilder",
]
+23
View File
@@ -0,0 +1,23 @@
from typing import Literal
from typing_extensions import Self
from ...models.components.alert import Alert
from ..base import BaseBuilder
class AlertBuilder(BaseBuilder[Alert]):
"""链式构建提示/标注框组件的辅助类"""
def __init__(
self,
title: str,
content: str,
type: Literal["info", "success", "warning", "error"] = "info",
):
data_model = Alert(title=title, content=content, type=type)
super().__init__(data_model, template_name="components/widgets/alert")
def hide_icon(self) -> Self:
"""隐藏提示框的默认图标"""
self._data.show_icon = False
return self
+38
View File
@@ -0,0 +1,38 @@
from typing import Literal
from typing_extensions import Self
from ...models.components.avatar import Avatar, AvatarGroup
from ..base import BaseBuilder
class AvatarBuilder(BaseBuilder[Avatar]):
"""链式构建单个头像的辅助类"""
def __init__(self, src: str):
data_model = Avatar(src=src, shape="circle", size=50)
super().__init__(data_model, template_name="components/widgets/avatar")
def set_shape(self, shape: Literal["circle", "square"]) -> Self:
self._data.shape = shape
return self
def set_size(self, size: int) -> Self:
self._data.size = size
return self
class AvatarGroupBuilder(BaseBuilder[AvatarGroup]):
"""链式构建头像组的辅助类"""
def __init__(self):
data_model = AvatarGroup(avatars=[], spacing=-15, max_count=None)
super().__init__(data_model, template_name="components/widgets/avatar_group")
def add_avatar(self, avatar: Avatar | AvatarBuilder | str) -> Self:
if isinstance(avatar, str):
self._data.avatars.append(Avatar(src=avatar, shape="circle", size=50))
elif isinstance(avatar, AvatarBuilder):
self._data.avatars.append(avatar.build())
else:
self._data.avatars.append(avatar)
return self
+20
View File
@@ -0,0 +1,20 @@
from typing import Literal
from ...models.components.divider import Divider
from ..base import BaseBuilder
class DividerBuilder(BaseBuilder[Divider]):
"""链式构建分割线组件的辅助类"""
def __init__(
self,
margin: str = "2em 0",
color: str = "#f7889c",
style: Literal["solid", "dashed", "dotted"] = "solid",
thickness: str = "1px",
):
data_model = Divider(
margin=margin, color=color, style=style, thickness=thickness
)
super().__init__(data_model, template_name="components/widgets/divider")
@@ -0,0 +1,31 @@
from typing import Any, Literal
from typing_extensions import Self
from ...models.components.kpi_card import KpiCard
from ..base import BaseBuilder
class KpiCardBuilder(BaseBuilder[KpiCard]):
"""链式构建统计卡片(KPI Card)的辅助类"""
def __init__(self, label: str, value: Any):
data_model = KpiCard(label=label, value=value)
super().__init__(data_model, template_name="components/widgets/kpi_card")
def with_unit(self, unit: str) -> Self:
"""设置数值的单位"""
self._data.unit = unit
return self
def with_change(
self, change: str, type: Literal["positive", "negative", "neutral"] = "neutral"
) -> Self:
"""设置与上一周期的变化率"""
self._data.change = change
self._data.change_type = type
return self
def with_icon(self, svg_path: str) -> Self:
"""设置卡片图标 (提供SVG path data)"""
self._data.icon_svg = svg_path
return self
@@ -0,0 +1,28 @@
from typing_extensions import Self
from ...models.components.timeline import Timeline, TimelineItem
from ..base import BaseBuilder
class TimelineBuilder(BaseBuilder[Timeline]):
"""链式构建时间轴组件的辅助类"""
def __init__(self):
data_model = Timeline(items=[])
super().__init__(data_model, template_name="components/widgets/timeline")
def add_item(
self,
timestamp: str,
title: str,
content: str,
*,
icon: str | None = None,
color: str | None = None,
) -> Self:
"""向时间轴中添加一个事件点"""
item = TimelineItem(
timestamp=timestamp, title=title, content=content, icon=icon, color=color
)
self._data.items.append(item)
return self
+8
View File
@@ -3,14 +3,22 @@
包含基础的UI构建器类
"""
from .card import CardBuilder
from .details import DetailsBuilder
from .layout import LayoutBuilder
from .list import ListBuilder
from .markdown import MarkdownBuilder
from .notebook import NotebookBuilder
from .table import TableBuilder
from .text import TextBuilder
__all__ = [
"CardBuilder",
"DetailsBuilder",
"LayoutBuilder",
"ListBuilder",
"MarkdownBuilder",
"NotebookBuilder",
"TableBuilder",
"TextBuilder",
]
+26
View File
@@ -0,0 +1,26 @@
from typing_extensions import Self
from ...models.core.base import RenderableComponent
from ...models.core.card import CardData
from ..base import BaseBuilder
class CardBuilder(BaseBuilder[CardData]):
"""链式构建通用卡片容器的辅助类"""
def __init__(self, content: "RenderableComponent | BaseBuilder"):
content_model = content.build() if isinstance(content, BaseBuilder) else content
data_model = CardData(content=content_model)
super().__init__(data_model, template_name="components/core/card")
def set_header(self, header: "RenderableComponent | BaseBuilder") -> Self:
"""设置卡片的头部组件"""
header_model = header.build() if isinstance(header, BaseBuilder) else header
self._data.header = header_model
return self
def set_footer(self, footer: "RenderableComponent | BaseBuilder") -> Self:
"""设置卡片的尾部组件"""
footer_model = footer.build() if isinstance(footer, BaseBuilder) else footer
self._data.footer = footer_model
return self
+19
View File
@@ -0,0 +1,19 @@
from typing import Any
from typing_extensions import Self
from ...models.core.details import DetailsData, DetailsItem
from ..base import BaseBuilder
class DetailsBuilder(BaseBuilder[DetailsData]):
"""链式构建描述列表(键值对)的辅助类"""
def __init__(self, title: str | None = None):
data_model = DetailsData(title=title, items=[])
super().__init__(data_model, template_name="components/core/details")
def add_item(self, label: str, value: Any) -> Self:
"""向列表中添加一个键值对项目"""
value_str = str(value)
self._data.items.append(DetailsItem(label=label, value=value_str))
return self
+34 -22
View File
@@ -19,16 +19,32 @@ class LayoutBuilder(BaseBuilder[LayoutData]):
self._options: dict[str, Any] = {}
@classmethod
def column(cls, **options: Any) -> Self:
def column(
cls, *, gap: str = "20px", align_items: str = "stretch", **options: Any
) -> Self:
builder = cls()
builder._template_name = "layouts/column"
builder._template_name = "components/core/layouts/column"
builder._options["gap"] = gap
builder._options["align_items"] = align_items
builder._options.update(options)
return builder
@classmethod
def row(cls, **options: Any) -> Self:
def row(
cls, *, gap: str = "10px", align_items: str = "center", **options: Any
) -> Self:
builder = cls()
builder._template_name = "layouts/row"
builder._template_name = "components/core/layouts/row"
builder._options["gap"] = gap
builder._options["align_items"] = align_items
builder._options.update(options)
return builder
@classmethod
def grid(cls, columns: int = 2, **options: Any) -> Self:
builder = cls()
builder._template_name = "components/core/layouts/grid"
builder._options["columns"] = columns
builder._options.update(options)
return builder
@@ -56,15 +72,15 @@ class LayoutBuilder(BaseBuilder[LayoutData]):
metadata: dict[str, Any] | None = None,
) -> Self:
"""
向布局中添加一个组件,支持多种组件类型的添加。
向布局中添加一个组件项。
参数:
component: 一个 Builder 实例 (如 TableBuilder) 或一个 RenderableComponent
数据模型。
metadata: (可选) 与此项目关联的元数据,可用于模板。
component: 一个 `BaseBuilder` 实例 (如 `TableBuilder()`) 或一个已构建的
`RenderableComponent` 数据模型。
metadata: (可选) 与此项目关联的元数据,可在布局模板中访问。
返回:
Self: 返回当前布局构建器实例,支持链式调用。
Self: 当前构建器实例,以支持链式调用。
"""
component_data = (
component.data if isinstance(component, BaseBuilder) else component
@@ -76,28 +92,24 @@ class LayoutBuilder(BaseBuilder[LayoutData]):
def add_option(self, key: str, value: Any) -> Self:
"""
为布局添加一个自定义选项,该选项会传递给模板。
为布局模板添加一个自定义选项。
例如,`add_option("padding", "30px")` 会在模板的 `data.options`
字典中添加 `{"padding": "30px"}`。
参数:
key: 选项的键名,用于在模板中引用。
value: 选项的值,可以是任意类型的数据。
key: 选项的键名。
value: 选项的值。
返回:
Self: 返回当前布局构建器实例,支持链式调用。
Self: 当前构建器实例,以支持链式调用。
"""
self._options[key] = value
return self
def build(self) -> LayoutData:
"""
[修改] 构建并返回 LayoutData 模型实例。
此方法现在是同步的,并且不执行渲染。
参数:
无
返回:
LayoutData: 配置好的布局数据模型。
构建并返回 LayoutData 模型实例。
"""
if not self._template_name:
raise ValueError(
@@ -106,4 +118,4 @@ class LayoutBuilder(BaseBuilder[LayoutData]):
self._data.options = self._options
self._data.layout_type = self._template_name.split("/")[-1]
return self._data
return super().build()
+31
View File
@@ -0,0 +1,31 @@
from typing_extensions import Self
from ...models.core.base import RenderableComponent
from ...models.core.list import ListData, ListItem
from ..base import BaseBuilder
class ListBuilder(BaseBuilder[ListData]):
"""链式构建通用列表的辅助类。"""
def __init__(self, ordered: bool = False):
data_model = ListData(ordered=ordered)
super().__init__(data_model, template_name="components/core/list")
def add_item(self, component: "BaseBuilder | RenderableComponent") -> Self:
"""
向列表中添加一个项目。
参数:
component: 一个 Builder 实例或一个 RenderableComponent 数据模型。
"""
component_data = (
component.build() if isinstance(component, BaseBuilder) else component
)
self._data.items.append(ListItem(component=component_data))
return self
def ordered(self, is_ordered: bool = True) -> Self:
"""设置列表是否为有序列表(带数字编号)。"""
self._data.ordered = is_ordered
return self
+14 -3
View File
@@ -4,6 +4,7 @@ from typing import Any
from ...models.core.markdown import (
CodeElement,
ComponentElement,
HeadingElement,
ImageElement,
ListElement,
@@ -12,6 +13,7 @@ from ...models.core.markdown import (
MarkdownElement,
QuoteElement,
RawHtmlElement,
RenderableComponent,
TableElement,
TextElement,
)
@@ -24,7 +26,7 @@ class MarkdownBuilder(BaseBuilder[MarkdownData]):
"""链式构建Markdown图片的辅助类,支持上下文管理和组合。"""
def __init__(self):
data_model = MarkdownData(markdown="", width=800, css_path=None)
data_model = MarkdownData(elements=[], width=800, css_path=None)
super().__init__(data_model, template_name="components/core/markdown")
self._parts: list[MarkdownElement] = []
self._width: int = 800
@@ -78,6 +80,16 @@ class MarkdownBuilder(BaseBuilder[MarkdownData]):
)
return self
def add_component(
self, component: "BaseBuilder | RenderableComponent"
) -> "MarkdownBuilder":
"""添加一个UI组件(如图表、卡片等)。"""
component_data = (
component.build() if isinstance(component, BaseBuilder) else component
)
self._append_element(ComponentElement(component=component_data))
return self
def add_builder(self, builder: "MarkdownBuilder") -> "MarkdownBuilder":
"""将另一个builder的内容组合进来。"""
if self._context_stack:
@@ -144,8 +156,7 @@ class MarkdownBuilder(BaseBuilder[MarkdownData]):
"""
构建并返回 MarkdownData 模型实例。
"""
final_markdown = "\n\n".join(part.to_markdown() for part in self._parts).strip()
self._data.markdown = final_markdown
self._data.elements = self._parts
self._data.width = self._width
self._data.css_path = self._css_path
return super().build()
+50 -3
View File
@@ -1,3 +1,5 @@
from typing import Literal
from ...models.core.table import TableCell, TableData
from ..base import BaseBuilder
@@ -12,16 +14,61 @@ class TableBuilder(BaseBuilder[TableData]):
super().__init__(data_model, template_name="components/core/table")
def set_headers(self, headers: list[str]) -> "TableBuilder":
"""设置表头"""
"""
设置表格的表头。
参数:
headers: 一个包含表头文本的字符串列表。
返回:
TableBuilder: 当前构建器实例,以支持链式调用。
"""
self._data.headers = headers
return self
def set_column_alignments(
self, alignments: list[Literal["left", "center", "right"]]
) -> "TableBuilder":
"""
设置表格每列的文本对齐方式。
参数:
alignments: 一个包含 'left', 'center', 'right' 的对齐方式列表。
返回:
TableBuilder: 当前构建器实例,以支持链式调用。
"""
self._data.column_alignments = alignments
return self
def set_column_widths(self, widths: list[str | int]) -> "TableBuilder":
"""设置每列的宽度"""
self._data.column_widths = widths
return self
def add_row(self, row: list[TableCell]) -> "TableBuilder":
"""添加单行数据"""
"""
向表格中添加一行数据。
参数:
row: 一个包含单元格数据的列表。单元格可以是字符串、数字或
`TextCell`, `ImageCell` 等模型实例。
返回:
TableBuilder: 当前构建器实例,以支持链式调用。
"""
self._data.rows.append(row)
return self
def add_rows(self, rows: list[list[TableCell]]) -> "TableBuilder":
"""批量添加多行数据"""
"""
向表格中批量添加多行数据。
参数:
rows: 一个包含多行数据的列表。
返回:
TableBuilder: 当前构建器实例,以支持链式调用。
"""
self._data.rows.extend(rows)
return self
+62
View File
@@ -0,0 +1,62 @@
from typing import Literal
from typing_extensions import Self
from ...models.core.text import TextData, TextSpan
from ..base import BaseBuilder
class TextBuilder(BaseBuilder[TextData]):
"""链式构建轻量级富文本组件的辅助类"""
def __init__(self, text: str = ""):
data_model = TextData(spans=[], align="left")
super().__init__(data_model, template_name="components/core/text")
if text:
self.add_span(text)
def set_alignment(self, align: Literal["left", "right", "center"]) -> Self:
"""设置整个文本块的对齐方式"""
self._data.align = align
return self
def add_span(
self,
text: str,
*,
bold: bool = False,
italic: bool = False,
underline: bool = False,
strikethrough: bool = False,
code: bool = False,
color: str | None = None,
font_size: str | int | None = None,
font_family: str | None = None,
) -> Self:
"""
添加一个带有样式的文本片段。
参数:
text: 文本内容。
bold: 是否加粗。
italic: 是否斜体。
underline: 是否有下划线。
strikethrough: 是否有删除线。
code: 是否渲染为代码样式。
color: 文本颜色 (e.g., '#ff0000', 'red')。
font_size: 字体大小 (e.g., 16, '1.2em', '12px')。
font_family: 字体族。
"""
font_size_str = f"{font_size}px" if isinstance(font_size, int) else font_size
span = TextSpan(
text=text,
bold=bold,
italic=italic,
underline=underline,
strikethrough=strikethrough,
code=code,
color=color,
font_size=font_size_str,
font_family=font_family,
)
self._data.spans.append(span)
return self
+1 -3
View File
@@ -3,12 +3,10 @@
包含预定义的UI组件构建器
"""
from .help_page import PluginHelpPageBuilder
from .info_card import InfoCardBuilder
from .plugin_help_page import PluginHelpPageBuilder
from .plugin_menu import PluginMenuBuilder
__all__ = [
"InfoCardBuilder",
"PluginHelpPageBuilder",
"PluginMenuBuilder",
]
-46
View File
@@ -1,46 +0,0 @@
from typing import Any
from ...models.presets.card import (
InfoCardData,
InfoCardMetadataItem,
InfoCardSection,
)
from ..base import BaseBuilder
__all__ = ["InfoCardBuilder"]
class InfoCardBuilder(BaseBuilder[InfoCardData]):
def __init__(self, title: str):
self._data = InfoCardData(title=title)
super().__init__(self._data, template_name="components/presets/info_card")
def add_metadata(self, label: str, value: str | int) -> "InfoCardBuilder":
self._data.metadata.append(InfoCardMetadataItem(label=label, value=value))
return self
def add_metadata_items(
self, items: list[tuple[str, Any]] | list[dict[str, Any]]
) -> "InfoCardBuilder":
for item in items:
if isinstance(item, tuple):
self.add_metadata(item[0], item[1])
elif isinstance(item, dict):
self.add_metadata(item.get("label", ""), item.get("value", ""))
return self
def add_section(self, title: str, content: str | list[str]) -> "InfoCardBuilder":
content_list = [content] if isinstance(content, str) else content
self._data.sections.append(InfoCardSection(title=title, content=content_list))
return self
def add_sections(
self, sections: list[tuple[str, str | list[str]]] | list[dict[str, Any]]
) -> "InfoCardBuilder":
for section in sections:
if isinstance(section, tuple):
self.add_section(section[0], section[1])
elif isinstance(section, dict):
self.add_section(section.get("title", ""), section.get("content", []))
return self
@@ -1,4 +1,4 @@
from ...models.presets.help_page import (
from ...models.presets.plugin_help_page import (
HelpCategory,
PluginHelpPageData,
)
@@ -13,7 +13,7 @@ class PluginHelpPageBuilder(BaseBuilder[PluginHelpPageData]):
bot_nickname=bot_nickname, page_title=page_title, categories=[]
)
super().__init__(self._data, template_name="pages/core/help_page")
super().__init__(self._data, template_name="pages/core/plugin_help_page")
def add_category(self, category: HelpCategory) -> "PluginHelpPageBuilder":
"""添加一个帮助分类"""
-14
View File
@@ -1,14 +0,0 @@
"""
小组件构建器模块
包含各种UI小组件的构建器
"""
from .badge import BadgeBuilder
from .progress_bar import ProgressBarBuilder
from .user_info_block import UserInfoBlockBuilder
__all__ = [
"BadgeBuilder",
"ProgressBarBuilder",
"UserInfoBlockBuilder",
]