mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-11 15:00:00 +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
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user