mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-14 00:20:06 +08:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b50dacec00 | ||
|
|
5bc5feca78 | ||
|
|
3afedc8e3c | ||
|
|
a4decd8524 | ||
|
|
61279f8b3d | ||
|
|
86c748534c | ||
|
|
bba90e62db |
@@ -1,128 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
|
|
||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from nonebot.rule import to_me
|
|
||||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.services.page_template import PageTemplateConfig, template_manager
|
|
||||||
from zhenxun.services.page_template.components import (
|
|
||||||
Button,
|
|
||||||
ButtonProps,
|
|
||||||
Col,
|
|
||||||
ColProps,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormItemProps,
|
|
||||||
FormProps,
|
|
||||||
Row,
|
|
||||||
RowProps,
|
|
||||||
)
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="web测试",
|
|
||||||
description="想要更加了解真寻吗",
|
|
||||||
usage="""
|
|
||||||
指令:
|
|
||||||
关于
|
|
||||||
""".strip(),
|
|
||||||
extra=PluginExtraData(author="HibiKier", version="0.1", menu_type="其他").to_dict(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_matcher = on_alconna(Alconna("test"), priority=5, block=True, rule=to_me())
|
|
||||||
|
|
||||||
|
|
||||||
@_matcher.handle()
|
|
||||||
async def _(session: Uninfo, arparma: Arparma):
|
|
||||||
logger.info("1")
|
|
||||||
|
|
||||||
|
|
||||||
def temp(a: dict[str, Any]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UserFormData(BaseModel):
|
|
||||||
username: str = Field(..., min_length=3, max_length=20)
|
|
||||||
email: str
|
|
||||||
age: int | None = None
|
|
||||||
|
|
||||||
|
|
||||||
def register_user_form_template():
|
|
||||||
# 使用 list[Any] 避免 list 协变导致的类型告警
|
|
||||||
layout: list[Any] = [
|
|
||||||
Row(
|
|
||||||
props=RowProps(gutter=16),
|
|
||||||
children=[
|
|
||||||
Col(
|
|
||||||
props=ColProps(span=12),
|
|
||||||
children=[
|
|
||||||
Form(
|
|
||||||
props=FormProps(label_width="100px", inline=True),
|
|
||||||
children=[
|
|
||||||
FormItem(
|
|
||||||
props=FormItemProps(
|
|
||||||
label="用户名", prop="username"
|
|
||||||
),
|
|
||||||
children=None,
|
|
||||||
bind_field="username",
|
|
||||||
),
|
|
||||||
FormItem(
|
|
||||||
props=FormItemProps(label="邮箱", prop="email"),
|
|
||||||
children=None,
|
|
||||||
bind_field="email",
|
|
||||||
),
|
|
||||||
FormItem(
|
|
||||||
props=FormItemProps(label="年龄", prop="age"),
|
|
||||||
children=None,
|
|
||||||
bind_field="age",
|
|
||||||
),
|
|
||||||
FormItem(
|
|
||||||
props=FormItemProps(label=""),
|
|
||||||
children=[
|
|
||||||
Button(
|
|
||||||
props=ButtonProps(
|
|
||||||
text="提交",
|
|
||||||
type="primary",
|
|
||||||
action="submit",
|
|
||||||
confirm=True,
|
|
||||||
confirm_text="确认提交吗?",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Button(
|
|
||||||
props=ButtonProps(
|
|
||||||
text="重置",
|
|
||||||
type="default",
|
|
||||||
action="reset", # 前端重置表单
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Button(
|
|
||||||
props=ButtonProps(
|
|
||||||
text="取消",
|
|
||||||
type="danger",
|
|
||||||
action="cancel", # 前端自行关闭/返回
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
bind_field=None,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
config = PageTemplateConfig(
|
|
||||||
template_id="user_form",
|
|
||||||
title="用户表单示例",
|
|
||||||
description="包含提交/重置/取消按钮的示例表单",
|
|
||||||
layout=layout,
|
|
||||||
callback_handler=temp,
|
|
||||||
)
|
|
||||||
|
|
||||||
template_manager.register(config, data_model=UserFormData)
|
|
||||||
@@ -7,7 +7,6 @@ Zhenxun Bot - 核心服务模块
|
|||||||
- LLM服务 (llm): 提供与大语言模型交互的统一API。
|
- LLM服务 (llm): 提供与大语言模型交互的统一API。
|
||||||
- 插件生命周期管理 (plugin_init): 支持插件安装和卸载时的钩子函数。
|
- 插件生命周期管理 (plugin_init): 支持插件安装和卸载时的钩子函数。
|
||||||
- 定时任务调度器 (scheduler): 提供持久化的、可管理的定时任务服务。
|
- 定时任务调度器 (scheduler): 提供持久化的、可管理的定时任务服务。
|
||||||
- 页面模板服务 (page_template_service): 用于构建前端页面(表格、表单等)并处理数据提交。
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
@@ -45,15 +44,6 @@ from .llm import (
|
|||||||
set_global_default_model_name,
|
set_global_default_model_name,
|
||||||
)
|
)
|
||||||
from .log import logger
|
from .log import logger
|
||||||
from .page_template import (
|
|
||||||
ColumnAlign,
|
|
||||||
FieldConfig,
|
|
||||||
FieldType,
|
|
||||||
PageTemplateConfig,
|
|
||||||
PageTemplateManager,
|
|
||||||
PageTemplateService,
|
|
||||||
template_manager,
|
|
||||||
)
|
|
||||||
from .plugin_init import PluginInit, PluginInitManager
|
from .plugin_init import PluginInit, PluginInitManager
|
||||||
from .renderer import renderer_service
|
from .renderer import renderer_service
|
||||||
from .scheduler import (
|
from .scheduler import (
|
||||||
@@ -66,19 +56,13 @@ from .scheduler import (
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"AI",
|
"AI",
|
||||||
"AIConfig",
|
"AIConfig",
|
||||||
"ColumnAlign",
|
|
||||||
"CommonOverrides",
|
"CommonOverrides",
|
||||||
"ExecutionPolicy",
|
"ExecutionPolicy",
|
||||||
"FieldConfig",
|
|
||||||
"FieldType",
|
|
||||||
"LLMContentPart",
|
"LLMContentPart",
|
||||||
"LLMException",
|
"LLMException",
|
||||||
"LLMGenerationConfig",
|
"LLMGenerationConfig",
|
||||||
"LLMMessage",
|
"LLMMessage",
|
||||||
"Model",
|
"Model",
|
||||||
"PageTemplateConfig",
|
|
||||||
"PageTemplateManager",
|
|
||||||
"PageTemplateService",
|
|
||||||
"PluginInit",
|
"PluginInit",
|
||||||
"PluginInitManager",
|
"PluginInitManager",
|
||||||
"ScheduleContext",
|
"ScheduleContext",
|
||||||
@@ -102,6 +86,5 @@ __all__ = [
|
|||||||
"scheduler_manager",
|
"scheduler_manager",
|
||||||
"search",
|
"search",
|
||||||
"set_global_default_model_name",
|
"set_global_default_model_name",
|
||||||
"template_manager",
|
|
||||||
"with_db_timeout",
|
"with_db_timeout",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ from .types import (
|
|||||||
ToolChoice,
|
ToolChoice,
|
||||||
)
|
)
|
||||||
from .types.exceptions import get_user_friendly_error_message
|
from .types.exceptions import get_user_friendly_error_message
|
||||||
from .types.models import GeminiGoogleSearch
|
|
||||||
from .utils import create_multimodal_message
|
from .utils import create_multimodal_message
|
||||||
|
|
||||||
T = TypeVar("T", bound=BaseModel)
|
T = TypeVar("T", bound=BaseModel)
|
||||||
@@ -183,7 +182,7 @@ async def embed_documents(
|
|||||||
|
|
||||||
|
|
||||||
async def generate_structured(
|
async def generate_structured(
|
||||||
message: str | UniMessage | LLMMessage | list[LLMContentPart],
|
message: str | LLMMessage | list[LLMContentPart],
|
||||||
response_model: type[T],
|
response_model: type[T],
|
||||||
*,
|
*,
|
||||||
model: ModelName = None,
|
model: ModelName = None,
|
||||||
@@ -403,5 +402,4 @@ async def search(
|
|||||||
model=model,
|
model=model,
|
||||||
instruction=instruction,
|
instruction=instruction,
|
||||||
config=final_config,
|
config=final_config,
|
||||||
tools=[GeminiGoogleSearch()],
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -445,12 +445,11 @@ class AI:
|
|||||||
instruction=instruction,
|
instruction=instruction,
|
||||||
template_vars=template_vars,
|
template_vars=template_vars,
|
||||||
config=search_config,
|
config=search_config,
|
||||||
tools=[GeminiGoogleSearch()],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def generate_structured(
|
async def generate_structured(
|
||||||
self,
|
self,
|
||||||
message: str | UniMessage | LLMMessage | list[LLMContentPart] | None,
|
message: str | LLMMessage | list[LLMContentPart] | None,
|
||||||
response_model: type[T],
|
response_model: type[T],
|
||||||
*,
|
*,
|
||||||
model: ModelName = None,
|
model: ModelName = None,
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
"""
|
|
||||||
页面模板服务模块
|
|
||||||
|
|
||||||
提供页面模板配置、字段定义和数据验证功能。
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .components import (
|
|
||||||
Button,
|
|
||||||
ButtonProps,
|
|
||||||
Card,
|
|
||||||
CardProps,
|
|
||||||
Col,
|
|
||||||
ColProps,
|
|
||||||
Component,
|
|
||||||
ComponentType,
|
|
||||||
Divider,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormItemProps,
|
|
||||||
FormProps,
|
|
||||||
Row,
|
|
||||||
RowProps,
|
|
||||||
Space,
|
|
||||||
Table,
|
|
||||||
TableProps,
|
|
||||||
Text,
|
|
||||||
TextProps,
|
|
||||||
)
|
|
||||||
from .service import PageTemplateConfig, PageTemplateManager, PageTemplateService
|
|
||||||
|
|
||||||
# 创建全局模板管理器实例
|
|
||||||
template_manager = PageTemplateManager()
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"Button",
|
|
||||||
"ButtonProps",
|
|
||||||
"Card",
|
|
||||||
"CardProps",
|
|
||||||
"Col",
|
|
||||||
"ColProps",
|
|
||||||
"Component",
|
|
||||||
"ComponentType",
|
|
||||||
"Divider",
|
|
||||||
"Form",
|
|
||||||
"FormItem",
|
|
||||||
"FormItemProps",
|
|
||||||
"FormProps",
|
|
||||||
"PageTemplateConfig",
|
|
||||||
"PageTemplateManager",
|
|
||||||
"PageTemplateService",
|
|
||||||
"Row",
|
|
||||||
"RowProps",
|
|
||||||
"Space",
|
|
||||||
"Table",
|
|
||||||
"TableProps",
|
|
||||||
"Text",
|
|
||||||
"TextProps",
|
|
||||||
"template_manager",
|
|
||||||
]
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
"""
|
|
||||||
前端布局组件模型集合
|
|
||||||
|
|
||||||
用于以数据形式描述页面布局,标签与前端 Element 组件保持一致:
|
|
||||||
- el-row, el-col
|
|
||||||
- el-text
|
|
||||||
- el-button
|
|
||||||
- el-card, el-divider, el-space, el-form, el-form-item, el-table
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .layout import (
|
|
||||||
Button,
|
|
||||||
ButtonProps,
|
|
||||||
Card,
|
|
||||||
CardProps,
|
|
||||||
Col,
|
|
||||||
ColProps,
|
|
||||||
Component,
|
|
||||||
ComponentType,
|
|
||||||
Divider,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormItemProps,
|
|
||||||
FormProps,
|
|
||||||
Row,
|
|
||||||
RowProps,
|
|
||||||
Space,
|
|
||||||
Table,
|
|
||||||
TableProps,
|
|
||||||
Text,
|
|
||||||
TextProps,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"Button",
|
|
||||||
"ButtonProps",
|
|
||||||
"Card",
|
|
||||||
"CardProps",
|
|
||||||
"Col",
|
|
||||||
"ColProps",
|
|
||||||
"Component",
|
|
||||||
"ComponentType",
|
|
||||||
"Divider",
|
|
||||||
"Form",
|
|
||||||
"FormItem",
|
|
||||||
"FormItemProps",
|
|
||||||
"FormProps",
|
|
||||||
"Row",
|
|
||||||
"RowProps",
|
|
||||||
"Space",
|
|
||||||
"Table",
|
|
||||||
"TableProps",
|
|
||||||
"Text",
|
|
||||||
"TextProps",
|
|
||||||
]
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class ComponentType(str, Enum):
|
|
||||||
"""组件类型,名称与前端 tag 保持一致"""
|
|
||||||
|
|
||||||
ROW = "row"
|
|
||||||
COL = "col"
|
|
||||||
TEXT = "text"
|
|
||||||
BUTTON = "button"
|
|
||||||
CARD = "card"
|
|
||||||
DIVIDER = "divider"
|
|
||||||
SPACE = "space"
|
|
||||||
FORM = "form"
|
|
||||||
FORM_ITEM = "form_item"
|
|
||||||
TABLE = "table"
|
|
||||||
|
|
||||||
|
|
||||||
class RowProps(BaseModel):
|
|
||||||
"""行组件属性"""
|
|
||||||
|
|
||||||
gutter: int | None = None # 行间距
|
|
||||||
justify: str | None = None # 主轴对齐方式
|
|
||||||
align: str | None = None # 交叉轴对齐方式
|
|
||||||
|
|
||||||
|
|
||||||
class ColProps(BaseModel):
|
|
||||||
"""列组件属性"""
|
|
||||||
|
|
||||||
span: int | None = None # 栅格占比
|
|
||||||
offset: int | None = None # 左侧偏移
|
|
||||||
push: int | None = None # 向右移动
|
|
||||||
pull: int | None = None # 向左移动
|
|
||||||
|
|
||||||
|
|
||||||
class TextProps(BaseModel):
|
|
||||||
"""文本组件属性"""
|
|
||||||
|
|
||||||
content: str = "" # 文本内容
|
|
||||||
tag: str | None = None # HTML 标签,如 h1/h2/p/span
|
|
||||||
type: str | None = None # 文本类型,对应 el-text 的 type
|
|
||||||
size: str | None = None # 文本大小
|
|
||||||
truncated: bool = False # 是否截断
|
|
||||||
line_clamp: int | None = None # 最多显示行数
|
|
||||||
|
|
||||||
|
|
||||||
class ButtonProps(BaseModel):
|
|
||||||
"""按钮组件属性"""
|
|
||||||
|
|
||||||
text: str # 按钮文本(必填)
|
|
||||||
type: str | None = None # 按钮类型 primary/success/warning/danger/info/default
|
|
||||||
size: str | None = None # 按钮尺寸 large/default/small
|
|
||||||
plain: bool = False # 朴素按钮
|
|
||||||
round: bool = False # 圆角按钮
|
|
||||||
circle: bool = False # 圆形按钮
|
|
||||||
link: bool = False # 文字按钮
|
|
||||||
icon: str | None = None # 图标名称
|
|
||||||
action: str | None = None # 按钮行为:submit/reset/cancel/custom
|
|
||||||
confirm: bool = False # 是否需要二次确认
|
|
||||||
confirm_text: str | None = None # 确认提示文案
|
|
||||||
api: str | None = None # 自定义调用的后端 API(action=custom 时使用)
|
|
||||||
api_method: str = "POST" # 自定义 API 的 HTTP 方法
|
|
||||||
|
|
||||||
|
|
||||||
class CardProps(BaseModel):
|
|
||||||
"""卡片组件属性"""
|
|
||||||
|
|
||||||
header: str | None = None # 卡片标题
|
|
||||||
shadow: str | None = None # 阴影类型
|
|
||||||
body_style: dict[str, Any] | None = None # 卡片主体样式
|
|
||||||
|
|
||||||
|
|
||||||
class FormProps(BaseModel):
|
|
||||||
"""表单组件属性"""
|
|
||||||
|
|
||||||
label_width: str | None = None # 标签宽度
|
|
||||||
inline: bool = False # 行内表单
|
|
||||||
size: str | None = None # 表单尺寸
|
|
||||||
|
|
||||||
|
|
||||||
class FormItemProps(BaseModel):
|
|
||||||
"""表单项组件属性"""
|
|
||||||
|
|
||||||
label: str | None = None # 标签文本
|
|
||||||
prop: str | None = None # 绑定字段名
|
|
||||||
required: bool = False # 是否必填
|
|
||||||
|
|
||||||
|
|
||||||
class TableProps(BaseModel):
|
|
||||||
"""表格组件属性"""
|
|
||||||
|
|
||||||
columns: list[dict[str, Any]] | None = None # 列配置
|
|
||||||
data: list[dict[str, Any]] | None = None # 数据源
|
|
||||||
|
|
||||||
|
|
||||||
class BaseComponent(BaseModel):
|
|
||||||
# 这些字段在 BaseModel 中有默认值,调用时都不是必传
|
|
||||||
# 用 Any 以便可以直接传 TextProps / ButtonProps / FormProps 等模型实例
|
|
||||||
props: Any | None
|
|
||||||
# 用 Any 避免 list 协变问题
|
|
||||||
children: list[Any] | None = None
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
|
||||||
def __init__(self, **data: Any):
|
|
||||||
super().__init__(**data)
|
|
||||||
_validate_children_impl(self.__dict__)
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_children_impl(values: dict[str, Any]) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
限定哪些组件可以拥有 children:
|
|
||||||
- 允许 children: row, col, card, space, form, form_item, table
|
|
||||||
- 不允许 children: text, button, divider
|
|
||||||
"""
|
|
||||||
t = values.get("type")
|
|
||||||
children = values.get("children") or []
|
|
||||||
no_children = {
|
|
||||||
ComponentType.TEXT.value,
|
|
||||||
ComponentType.BUTTON.value,
|
|
||||||
ComponentType.DIVIDER.value,
|
|
||||||
}
|
|
||||||
if t in no_children and children:
|
|
||||||
raise ValueError(f"组件 '{t}' 不允许包含 children")
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
class Row(BaseComponent):
|
|
||||||
"""行组件(对应 el-row)"""
|
|
||||||
|
|
||||||
type = ComponentType.ROW.value
|
|
||||||
props: RowProps # 必填
|
|
||||||
|
|
||||||
|
|
||||||
class Col(BaseComponent):
|
|
||||||
"""列组件(对应 el-col)"""
|
|
||||||
|
|
||||||
type = ComponentType.COL.value
|
|
||||||
props: ColProps # 必填
|
|
||||||
|
|
||||||
|
|
||||||
class Text(BaseComponent):
|
|
||||||
"""文本组件(对应 el-text 或基础标签)"""
|
|
||||||
|
|
||||||
type = ComponentType.TEXT.value
|
|
||||||
props: TextProps # 必填
|
|
||||||
|
|
||||||
|
|
||||||
class Button(BaseComponent):
|
|
||||||
"""按钮组件(对应 el-button)"""
|
|
||||||
|
|
||||||
type = ComponentType.BUTTON.value
|
|
||||||
props: ButtonProps # 必填
|
|
||||||
|
|
||||||
|
|
||||||
class Card(BaseComponent):
|
|
||||||
"""卡片组件(对应 el-card)"""
|
|
||||||
|
|
||||||
type = ComponentType.CARD.value
|
|
||||||
props: CardProps # 必填
|
|
||||||
|
|
||||||
|
|
||||||
class Divider(BaseComponent):
|
|
||||||
"""分割线组件(对应 el-divider)"""
|
|
||||||
|
|
||||||
type = ComponentType.DIVIDER.value
|
|
||||||
|
|
||||||
|
|
||||||
class Space(BaseComponent):
|
|
||||||
"""间距组件(对应 el-space)"""
|
|
||||||
|
|
||||||
type = ComponentType.SPACE.value
|
|
||||||
|
|
||||||
|
|
||||||
class Form(BaseComponent):
|
|
||||||
"""表单组件(对应 el-form)"""
|
|
||||||
|
|
||||||
type = ComponentType.FORM.value
|
|
||||||
props: FormProps # 必填
|
|
||||||
|
|
||||||
|
|
||||||
class FormItem(BaseComponent):
|
|
||||||
"""表单项组件(对应 el-form-item)"""
|
|
||||||
|
|
||||||
type = ComponentType.FORM_ITEM.value
|
|
||||||
props: FormItemProps # 必填
|
|
||||||
children: list[Any] | None = None
|
|
||||||
bind_field: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class Table(BaseComponent):
|
|
||||||
"""表格组件(对应 el-table)"""
|
|
||||||
|
|
||||||
type = ComponentType.TABLE.value
|
|
||||||
|
|
||||||
|
|
||||||
Component = Row | Col | Text | Button | Card | Divider | Space | Form | FormItem | Table
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
BaseComponent.model_rebuild()
|
|
||||||
except AttributeError:
|
|
||||||
BaseComponent.update_forward_refs()
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
"""
|
|
||||||
页面模板服务 FastAPI 路由
|
|
||||||
|
|
||||||
提供统一的API接口,通过template_id来获取配置和处理数据提交。
|
|
||||||
"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Body, Depends, Query
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
|
|
||||||
from zhenxun.builtin_plugins.web_ui.base_model import Result
|
|
||||||
from zhenxun.builtin_plugins.web_ui.utils import authentication
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.services.page_template import template_manager
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/page_template")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/template_config",
|
|
||||||
dependencies=[Depends(authentication())],
|
|
||||||
response_model=Result[dict],
|
|
||||||
response_class=JSONResponse,
|
|
||||||
description="获取页面模板配置(表格配置)",
|
|
||||||
)
|
|
||||||
async def get_template_config(template_id: str = Query(..., description="模板ID")):
|
|
||||||
"""获取页面模板配置"""
|
|
||||||
try:
|
|
||||||
config = template_manager.get_template_config(template_id)
|
|
||||||
if config is None:
|
|
||||||
return Result.fail(f"模板ID '{template_id}' 不存在")
|
|
||||||
return Result.ok(config, "获取配置成功")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{router.prefix}/template_config 调用错误", "PageTemplate", e=e)
|
|
||||||
return Result.fail(f"获取配置失败: {type(e)}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/form_config",
|
|
||||||
dependencies=[Depends(authentication())],
|
|
||||||
response_model=Result[dict],
|
|
||||||
response_class=JSONResponse,
|
|
||||||
description="获取表单配置",
|
|
||||||
)
|
|
||||||
async def get_form_config(template_id: str = Query(..., description="模板ID")):
|
|
||||||
"""获取表单配置"""
|
|
||||||
try:
|
|
||||||
config = template_manager.get_form_config(template_id)
|
|
||||||
if config is None:
|
|
||||||
return Result.fail(f"模板ID '{template_id}' 不存在")
|
|
||||||
return Result.ok(config, "获取表单配置成功")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{router.prefix}/form_config 调用错误", "PageTemplate", e=e)
|
|
||||||
return Result.fail(f"获取表单配置失败: {type(e)}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/submit",
|
|
||||||
dependencies=[Depends(authentication())],
|
|
||||||
response_model=Result,
|
|
||||||
response_class=JSONResponse,
|
|
||||||
description="提交数据",
|
|
||||||
)
|
|
||||||
async def submit_data(
|
|
||||||
template_id: str = Query(..., description="模板ID"),
|
|
||||||
data: dict = Body(..., description="提交的数据"),
|
|
||||||
):
|
|
||||||
"""处理数据提交"""
|
|
||||||
try:
|
|
||||||
success, message, result = await template_manager.process_submit(
|
|
||||||
template_id, data
|
|
||||||
)
|
|
||||||
if not success:
|
|
||||||
return Result.fail(message)
|
|
||||||
return Result.ok(info=message, data=result)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{router.prefix}/submit 调用错误", "PageTemplate", e=e)
|
|
||||||
return Result.fail(f"提交数据失败: {type(e)}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/list",
|
|
||||||
dependencies=[Depends(authentication())],
|
|
||||||
response_model=Result[list[str]],
|
|
||||||
response_class=JSONResponse,
|
|
||||||
description="获取所有已注册的模板ID列表",
|
|
||||||
)
|
|
||||||
async def list_templates():
|
|
||||||
"""获取所有已注册的模板ID列表"""
|
|
||||||
try:
|
|
||||||
template_ids = template_manager.list_templates()
|
|
||||||
return Result.ok(template_ids, "获取模板列表成功")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{router.prefix}/list 调用错误", "PageTemplate", e=e)
|
|
||||||
return Result.fail(f"获取模板列表失败: {type(e)}: {e}")
|
|
||||||
@@ -1,314 +0,0 @@
|
|||||||
"""
|
|
||||||
页面模板服务
|
|
||||||
|
|
||||||
用于构建前端页面(如表格、表单等),支持字段绑定和数据提交处理。
|
|
||||||
"""
|
|
||||||
|
|
||||||
from collections.abc import Callable
|
|
||||||
from typing import Any, Generic, TypeVar
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, ValidationError
|
|
||||||
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.services.page_template.components import Component
|
|
||||||
|
|
||||||
T = TypeVar("T", bound=BaseModel)
|
|
||||||
|
|
||||||
|
|
||||||
class PageTemplateConfig(BaseModel):
|
|
||||||
"""页面模板配置"""
|
|
||||||
|
|
||||||
template_id: str = Field(..., description="模板ID")
|
|
||||||
"""模板ID"""
|
|
||||||
title: str = Field(..., description="页面标题")
|
|
||||||
"""页面标题"""
|
|
||||||
description: str | None = Field(None, description="页面描述")
|
|
||||||
"""页面描述"""
|
|
||||||
callback_handler: Callable[[dict[str, Any]], Any] | None = Field(
|
|
||||||
None,
|
|
||||||
description="数据提交后的回调处理方法(异步或同步函数,接收验证后的数据字典)",
|
|
||||||
)
|
|
||||||
"""数据提交后的回调处理方法(异步或同步函数,接收验证后的数据字典)"""
|
|
||||||
layout: list[Component] = Field(
|
|
||||||
default_factory=list,
|
|
||||||
description="页面布局组件树,使用 row/col/text/button 等组件描述前端结构",
|
|
||||||
)
|
|
||||||
"""页面布局组件树"""
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
|
||||||
|
|
||||||
class PageTemplateService(Generic[T]):
|
|
||||||
"""页面模板服务"""
|
|
||||||
|
|
||||||
def __init__(self, config: PageTemplateConfig, data_model: type[T] | None = None):
|
|
||||||
"""
|
|
||||||
初始化页面模板服务
|
|
||||||
|
|
||||||
参数:
|
|
||||||
config: 页面模板配置
|
|
||||||
data_model: 数据模型类(可选,用于数据验证)
|
|
||||||
"""
|
|
||||||
self.config = config
|
|
||||||
self.data_model = data_model
|
|
||||||
|
|
||||||
def get_table_config(self) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
获取表格配置(用于前端渲染表格)
|
|
||||||
|
|
||||||
返回:
|
|
||||||
包含表格配置的字典
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"template_id": self.config.template_id,
|
|
||||||
"title": self.config.title,
|
|
||||||
"description": self.config.description,
|
|
||||||
"layout": self.get_layout_config(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_form_config(self) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
获取表单配置(用于前端渲染表单)
|
|
||||||
|
|
||||||
返回:
|
|
||||||
包含表单配置的字典
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"template_id": self.config.template_id,
|
|
||||||
"title": self.config.title,
|
|
||||||
"description": self.config.description,
|
|
||||||
"layout": self.get_layout_config(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_layout_config(self) -> list[dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
获取页面布局配置(组件树)
|
|
||||||
|
|
||||||
返回:
|
|
||||||
布局组件的列表(字典形式,适合前端直接渲染)
|
|
||||||
"""
|
|
||||||
from zhenxun.utils.pydantic_compat import model_dump
|
|
||||||
|
|
||||||
return [model_dump(node, exclude_none=True) for node in self.config.layout]
|
|
||||||
|
|
||||||
def validate_data(self, data: dict[str, Any]) -> tuple[bool, str | None, T | None]:
|
|
||||||
"""
|
|
||||||
验证提交的数据
|
|
||||||
|
|
||||||
参数:
|
|
||||||
data: 待验证的数据字典
|
|
||||||
|
|
||||||
返回:
|
|
||||||
元组 (是否有效, 错误信息, 验证后的数据模型实例)
|
|
||||||
"""
|
|
||||||
# 如果提供了数据模型,使用Pydantic验证
|
|
||||||
if self.data_model:
|
|
||||||
try:
|
|
||||||
validated_data = self.data_model(**data)
|
|
||||||
return True, None, validated_data
|
|
||||||
except ValidationError as e:
|
|
||||||
error_messages = []
|
|
||||||
for error in e.errors():
|
|
||||||
field_name = ".".join(str(loc) for loc in error["loc"])
|
|
||||||
error_messages.append(f"{field_name}: {error['msg']}")
|
|
||||||
return False, "; ".join(error_messages), None
|
|
||||||
|
|
||||||
return True, None, None
|
|
||||||
|
|
||||||
def process_submit_data(
|
|
||||||
self, data: dict[str, Any]
|
|
||||||
) -> tuple[bool, str, dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
处理提交的数据(验证并返回处理后的数据)
|
|
||||||
|
|
||||||
参数:
|
|
||||||
data: 提交的数据字典
|
|
||||||
|
|
||||||
返回:
|
|
||||||
元组 (是否成功, 消息, 处理后的数据)
|
|
||||||
"""
|
|
||||||
is_valid, error_msg, validated_model = self.validate_data(data)
|
|
||||||
|
|
||||||
if not is_valid:
|
|
||||||
return False, error_msg or "数据验证失败", {}
|
|
||||||
|
|
||||||
# 如果验证成功且有模型,返回模型数据
|
|
||||||
if validated_model:
|
|
||||||
from zhenxun.utils.pydantic_compat import model_dump
|
|
||||||
|
|
||||||
return True, "数据验证成功", model_dump(validated_model)
|
|
||||||
|
|
||||||
# 否则返回原始数据(已通过验证)
|
|
||||||
return True, "数据验证成功", data
|
|
||||||
|
|
||||||
|
|
||||||
class PageTemplateManager:
|
|
||||||
"""页面模板管理器(全局单例)"""
|
|
||||||
|
|
||||||
_instance: "PageTemplateManager | None" = None
|
|
||||||
_templates: dict[str, PageTemplateService[Any]]
|
|
||||||
|
|
||||||
def __new__(cls) -> "PageTemplateManager":
|
|
||||||
"""单例模式"""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super().__new__(cls)
|
|
||||||
cls._instance._templates = {}
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def register(
|
|
||||||
self,
|
|
||||||
config: PageTemplateConfig,
|
|
||||||
data_model: type[T] | None = None,
|
|
||||||
) -> PageTemplateService[T]:
|
|
||||||
"""
|
|
||||||
注册页面模板
|
|
||||||
|
|
||||||
参数:
|
|
||||||
config: 页面模板配置
|
|
||||||
data_model: 数据模型类(可选,用于数据验证)
|
|
||||||
|
|
||||||
返回:
|
|
||||||
PageTemplateService实例
|
|
||||||
|
|
||||||
异常:
|
|
||||||
ValueError: 如果template_id已存在
|
|
||||||
"""
|
|
||||||
if config.template_id in self._templates:
|
|
||||||
raise ValueError(
|
|
||||||
f"模板ID '{config.template_id}' 已存在,请使用不同的template_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
service = PageTemplateService(config, data_model)
|
|
||||||
self._templates[config.template_id] = service
|
|
||||||
logger.info(f"已注册页面模板: {config.template_id} - {config.title}")
|
|
||||||
return service
|
|
||||||
|
|
||||||
def get(self, template_id: str) -> PageTemplateService[Any] | None:
|
|
||||||
"""
|
|
||||||
获取页面模板服务
|
|
||||||
|
|
||||||
参数:
|
|
||||||
template_id: 模板ID
|
|
||||||
|
|
||||||
返回:
|
|
||||||
PageTemplateService实例,如果不存在则返回None
|
|
||||||
"""
|
|
||||||
return self._templates.get(template_id)
|
|
||||||
|
|
||||||
def unregister(self, template_id: str) -> bool:
|
|
||||||
"""
|
|
||||||
注销页面模板
|
|
||||||
|
|
||||||
参数:
|
|
||||||
template_id: 模板ID
|
|
||||||
|
|
||||||
返回:
|
|
||||||
是否成功注销
|
|
||||||
"""
|
|
||||||
if template_id in self._templates:
|
|
||||||
del self._templates[template_id]
|
|
||||||
logger.info(f"已注销页面模板: {template_id}")
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def list_templates(self) -> list[str]:
|
|
||||||
"""
|
|
||||||
列出所有已注册的模板ID
|
|
||||||
|
|
||||||
返回:
|
|
||||||
模板ID列表
|
|
||||||
"""
|
|
||||||
return list(self._templates.keys())
|
|
||||||
|
|
||||||
def get_template_config(self, template_id: str) -> dict[str, Any] | None:
|
|
||||||
"""
|
|
||||||
获取模板配置(表格配置)
|
|
||||||
|
|
||||||
参数:
|
|
||||||
template_id: 模板ID
|
|
||||||
|
|
||||||
返回:
|
|
||||||
表格配置字典,如果模板不存在则返回None
|
|
||||||
"""
|
|
||||||
service = self.get(template_id)
|
|
||||||
return service.get_table_config() if service else None
|
|
||||||
|
|
||||||
def get_form_config(self, template_id: str) -> dict[str, Any] | None:
|
|
||||||
"""
|
|
||||||
获取表单配置
|
|
||||||
|
|
||||||
参数:
|
|
||||||
template_id: 模板ID
|
|
||||||
|
|
||||||
返回:
|
|
||||||
表单配置字典,如果模板不存在则返回None
|
|
||||||
"""
|
|
||||||
service = self.get(template_id)
|
|
||||||
return service.get_form_config() if service else None
|
|
||||||
|
|
||||||
async def process_submit(
|
|
||||||
self, template_id: str, data: dict[str, Any]
|
|
||||||
) -> tuple[bool, str, dict[str, Any] | None]:
|
|
||||||
"""
|
|
||||||
处理数据提交
|
|
||||||
|
|
||||||
参数:
|
|
||||||
template_id: 模板ID
|
|
||||||
data: 提交的数据字典
|
|
||||||
|
|
||||||
返回:
|
|
||||||
元组 (是否成功, 消息, 处理后的数据或回调结果)
|
|
||||||
"""
|
|
||||||
service = self.get(template_id)
|
|
||||||
if not service:
|
|
||||||
return False, f"模板ID '{template_id}' 不存在", None
|
|
||||||
|
|
||||||
# 验证数据
|
|
||||||
success, message, processed_data = service.process_submit_data(data)
|
|
||||||
if not success:
|
|
||||||
return False, message, None
|
|
||||||
|
|
||||||
# 如果有回调处理器,执行回调
|
|
||||||
if service.config.callback_handler:
|
|
||||||
try:
|
|
||||||
result = await self._call_handler(
|
|
||||||
service.config.callback_handler, processed_data
|
|
||||||
)
|
|
||||||
return True, message, result
|
|
||||||
except Exception as e:
|
|
||||||
handler_name = getattr(
|
|
||||||
service.config.callback_handler, "__name__", "unknown"
|
|
||||||
)
|
|
||||||
logger.error(
|
|
||||||
f"执行回调处理器失败: {handler_name}",
|
|
||||||
"PageTemplate",
|
|
||||||
e=e,
|
|
||||||
)
|
|
||||||
return False, f"执行回调处理器失败: {e!s}", None
|
|
||||||
|
|
||||||
return True, message, processed_data
|
|
||||||
|
|
||||||
async def _call_handler(
|
|
||||||
self, handler: Callable[[dict[str, Any]], Any], data: dict[str, Any]
|
|
||||||
) -> Any:
|
|
||||||
"""
|
|
||||||
调用回调处理器
|
|
||||||
|
|
||||||
参数:
|
|
||||||
handler: 回调处理函数
|
|
||||||
data: 要传递的数据
|
|
||||||
|
|
||||||
返回:
|
|
||||||
处理器的返回值
|
|
||||||
"""
|
|
||||||
if not callable(handler):
|
|
||||||
raise ValueError("回调处理器必须是一个可调用对象")
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
# 检查是否是异步函数
|
|
||||||
if inspect.iscoroutinefunction(handler):
|
|
||||||
return await handler(data)
|
|
||||||
else:
|
|
||||||
return handler(data)
|
|
||||||
Reference in New Issue
Block a user