mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-15 09:10: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
+101
-42
@@ -1,63 +1,122 @@
|
||||
from typing import Literal
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Literal
|
||||
import uuid
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from zhenxun.utils.pydantic_compat import model_dump
|
||||
|
||||
from .core.base import RenderableComponent
|
||||
|
||||
|
||||
class BaseChartData(RenderableComponent):
|
||||
class EChartsTitle(BaseModel):
|
||||
text: str
|
||||
left: Literal["left", "center", "right"] = "center"
|
||||
|
||||
|
||||
class EChartsAxis(BaseModel):
|
||||
type: Literal["category", "value", "time", "log"]
|
||||
data: list[Any] | None = None
|
||||
show: bool = True
|
||||
|
||||
|
||||
class EChartsSeries(BaseModel):
|
||||
type: str
|
||||
data: list[Any]
|
||||
name: str | None = None
|
||||
label: dict[str, Any] | None = None
|
||||
itemStyle: dict[str, Any] | None = None
|
||||
barMaxWidth: int | None = None
|
||||
smooth: bool | None = None
|
||||
|
||||
|
||||
class EChartsTooltip(BaseModel):
|
||||
trigger: Literal["item", "axis", "none"] = "item"
|
||||
|
||||
|
||||
class EChartsGrid(BaseModel):
|
||||
left: str | None = None
|
||||
right: str | None = None
|
||||
top: str | None = None
|
||||
bottom: str | None = None
|
||||
containLabel: bool = True
|
||||
|
||||
|
||||
class BaseChartData(RenderableComponent, ABC):
|
||||
"""所有图表数据模型的基类"""
|
||||
|
||||
style_name: str | None = None
|
||||
title: str
|
||||
chart_id: str = Field(default_factory=lambda: f"chart-{uuid.uuid4().hex}")
|
||||
|
||||
echarts_options: dict[str, Any] | None = None
|
||||
|
||||
@abstractmethod
|
||||
def build_option(self) -> dict[str, Any]:
|
||||
"""将 Pydantic 模型序列化为 ECharts 的 option 字典。"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_render_data(self) -> dict[str, Any]:
|
||||
"""为图表组件定制渲染数据,动态构建最终的 option 对象。"""
|
||||
dumped_data = model_dump(self, exclude={"template_path"})
|
||||
if hasattr(self, "build_option"):
|
||||
dumped_data["option"] = self.build_option()
|
||||
return dumped_data
|
||||
|
||||
def get_required_scripts(self) -> list[str]:
|
||||
"""声明此组件需要 ECharts 库。"""
|
||||
return ["js/echarts.min.js"]
|
||||
|
||||
|
||||
class BarChartData(BaseChartData):
|
||||
"""柱状图(支持横向和竖向)的数据模型"""
|
||||
class EChartsData(BaseChartData):
|
||||
"""统一的 ECharts 图表数据模型"""
|
||||
|
||||
category_data: list[str]
|
||||
data: list[int | float]
|
||||
direction: Literal["horizontal", "vertical"] = "horizontal"
|
||||
background_image: str | None = None
|
||||
template_path: str = Field(..., exclude=True)
|
||||
title_model: EChartsTitle | None = Field(None, alias="title")
|
||||
grid_model: EChartsGrid | None = Field(None, alias="grid")
|
||||
tooltip_model: EChartsTooltip | None = Field(None, alias="tooltip")
|
||||
x_axis_model: EChartsAxis | None = Field(None, alias="xAxis")
|
||||
y_axis_model: EChartsAxis | None = Field(None, alias="yAxis")
|
||||
series_models: list[EChartsSeries] = Field(default_factory=list, alias="series")
|
||||
legend_model: dict[str, Any] | None = Field(default_factory=dict, alias="legend")
|
||||
raw_options: dict[str, Any] = Field(
|
||||
default_factory=dict, description="用于 set_option 的原始覆盖选项"
|
||||
)
|
||||
|
||||
background_image: str | None = Field(
|
||||
None, description="【兼容】用于横向柱状图的背景图片"
|
||||
)
|
||||
|
||||
def build_option(self) -> dict[str, Any]:
|
||||
"""将 Pydantic 模型序列化为 ECharts 的 option 字典。"""
|
||||
option: dict[str, Any] = {}
|
||||
key_map = {
|
||||
"title": "title_model",
|
||||
"grid": "grid_model",
|
||||
"tooltip": "tooltip_model",
|
||||
"xAxis": "x_axis_model",
|
||||
"yAxis": "y_axis_model",
|
||||
"series": "series_models",
|
||||
"legend": "legend_model",
|
||||
}
|
||||
for echarts_key, model_attr in key_map.items():
|
||||
model_instance = getattr(self, model_attr, None)
|
||||
if model_instance:
|
||||
if isinstance(model_instance, list):
|
||||
option[echarts_key] = [
|
||||
model_dump(m, exclude_none=True) for m in model_instance
|
||||
]
|
||||
elif isinstance(model_instance, BaseModel):
|
||||
option[echarts_key] = model_dump(model_instance, exclude_none=True)
|
||||
else:
|
||||
option[echarts_key] = model_instance
|
||||
option.update(self.raw_options)
|
||||
return option
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
"""为模板提供一个简单的字符串标题,保持向后兼容性。"""
|
||||
return self.title_model.text if self.title_model else ""
|
||||
|
||||
@property
|
||||
def template_name(self) -> str:
|
||||
return "components/charts/bar_chart"
|
||||
|
||||
|
||||
class PieChartDataItem(BaseModel):
|
||||
name: str
|
||||
value: int | float
|
||||
|
||||
|
||||
class PieChartData(BaseChartData):
|
||||
"""饼图的数据模型"""
|
||||
|
||||
data: list[PieChartDataItem]
|
||||
|
||||
@property
|
||||
def template_name(self) -> str:
|
||||
return "components/charts/pie_chart"
|
||||
|
||||
|
||||
class LineChartSeries(BaseModel):
|
||||
name: str
|
||||
data: list[int | float]
|
||||
smooth: bool = False
|
||||
|
||||
|
||||
class LineChartData(BaseChartData):
|
||||
"""折线图的数据模型"""
|
||||
|
||||
category_data: list[str]
|
||||
series: list[LineChartSeries]
|
||||
|
||||
@property
|
||||
def template_name(self) -> str:
|
||||
return "components/charts/line_chart"
|
||||
return self.template_path
|
||||
|
||||
Reference in New Issue
Block a user