feat:接入mcp,为模型提供图表信息
This commit is contained in:
+2
-2
@@ -165,9 +165,9 @@ def plan_materials(state: QueryState) -> list[dict]:
|
||||
if not types:
|
||||
return []
|
||||
|
||||
# 按类型提取全部匹配物料,使用协议别名输出 schema 等字段
|
||||
# 按类型提取全部匹配物料,使用协议别名输出 schema 等字段;字段定义由查询工具按需提供
|
||||
return [
|
||||
item.model_dump(mode="json", by_alias=True)
|
||||
item.model_dump(mode="json", by_alias=True, exclude={"field_groups"})
|
||||
for item in state["component_resources"].components
|
||||
if item.type in types
|
||||
]
|
||||
|
||||
+35
-15
@@ -3,9 +3,37 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from app.actions.base import BaseActionHandler
|
||||
from app.resources.material_fields import editable_fields, known_prop_keys
|
||||
from app.schemas.actions import UpdateAction
|
||||
from app.schemas.screen import Screen
|
||||
from app.schemas.screen import Screen, ScreenNode
|
||||
|
||||
|
||||
def fit_existing_path(node: ScreenNode, action: UpdateAction) -> UpdateAction:
|
||||
"""字段定义允许的路径在组件中可能还没有中间对象(例如未开启过的数值标签)
|
||||
|
||||
前端只按已有路径赋值,这里把缺失的部分折叠进值里:改为更新最近的已有对象,
|
||||
值为补齐的嵌套对象;缺失的若是数组元素则无法补齐,保持原样交给执行时报错
|
||||
"""
|
||||
if action.section == "name":
|
||||
return action
|
||||
current = getattr(node, action.section) or {}
|
||||
for index, segment in enumerate(action.path[:-1]):
|
||||
if isinstance(current, dict) and isinstance(segment, str) and segment not in current:
|
||||
rest = action.path[index + 1 :]
|
||||
if not all(isinstance(item, str) for item in rest):
|
||||
return action
|
||||
value = action.value
|
||||
for item in reversed(rest):
|
||||
value = {item: value}
|
||||
return action.model_copy(
|
||||
update={"path": action.path[: index + 1], "value": value}
|
||||
)
|
||||
if isinstance(current, dict) and isinstance(segment, str):
|
||||
current = current[segment]
|
||||
elif isinstance(current, list) and type(segment) is int and segment < len(current):
|
||||
current = current[segment]
|
||||
else:
|
||||
return action
|
||||
return action
|
||||
|
||||
|
||||
class UpdateHandler(BaseActionHandler[UpdateAction]):
|
||||
@@ -13,23 +41,15 @@ class UpdateHandler(BaseActionHandler[UpdateAction]):
|
||||
# 确认目标存在,后续路径访问和赋值均在大屏副本中进行
|
||||
result, target = self.copy_target(screen, action.target_id)
|
||||
|
||||
# 组件名称是节点顶层字段,没有后续路径
|
||||
if action.section == "name":
|
||||
target.name = action.value
|
||||
return result
|
||||
|
||||
# 未设置的 style 视为空对象,只允许直接添加末级字段
|
||||
if action.section == "style" and target.style is None:
|
||||
target.style = {}
|
||||
|
||||
# props 第一层不能新造字段:渲染器不会读取约定外的字段,新造只会“成功”却没有效果
|
||||
if action.section == "props":
|
||||
known = known_prop_keys(target)
|
||||
if known is not None and action.path[0] not in known:
|
||||
labels = "、".join(
|
||||
f"{field['label']}={field['section']}.{field['path']}"
|
||||
for field in editable_fields(target.type)
|
||||
)
|
||||
raise ValueError(
|
||||
f"组件 {target.type} 的 props 没有字段 {action.path[0]!r},"
|
||||
f"不能新造组件级属性;可编辑字段:{labels or '无'}"
|
||||
)
|
||||
|
||||
# 获取动作指定的属性区域,作为路径遍历的起点
|
||||
current = getattr(target, action.section)
|
||||
# 逐段访问路径,只有到达末级时才写入新值
|
||||
|
||||
@@ -5,15 +5,15 @@ from fastapi import APIRouter
|
||||
from app.common.response import ApiResponse, success_response
|
||||
from app.core.exceptions import BizException
|
||||
from app.resources.component import get_catalog_cache
|
||||
from app.schemas.component_resource import ComponentCatalog
|
||||
from app.resources.material_fields import frontend_catalog
|
||||
|
||||
router = APIRouter(prefix="/materials", tags=["Material"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_materials() -> ApiResponse[ComponentCatalog]:
|
||||
# 直接返回启动时载入的内存缓存,不查库
|
||||
async def list_materials() -> ApiResponse[dict]:
|
||||
# 直接使用启动时载入的内存缓存,不查库;属性面板 setters 由字段定义派生
|
||||
catalog = get_catalog_cache()
|
||||
if catalog is None:
|
||||
raise BizException(503, "物料目录尚未加载")
|
||||
return success_response(catalog)
|
||||
return success_response(frontend_catalog(catalog))
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""物料字段 MCP 客户端:把字段查询工具交给模型使用
|
||||
|
||||
服务只监听本机,由应用启动时拉起;不可用时直接报错,不退回其他查询方式
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from langchain_core.tools import BaseTool
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from langchain_mcp_adapters.interceptors import MCPToolCallRequest
|
||||
from loguru import logger
|
||||
from mcp.types import CallToolResult
|
||||
|
||||
from app.config import settings
|
||||
from app.resources.component import get_catalog_cache
|
||||
|
||||
SERVER_NAME = "materials"
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
class MaterialFieldServiceError(RuntimeError):
|
||||
"""物料字段服务不可用,或与主进程的物料目录版本不一致"""
|
||||
|
||||
|
||||
async def _check_version(request: MCPToolCallRequest, handler):
|
||||
"""限制单次查询耗时;返回的目录版本须与主进程一致,否则模型查到的字段和校验依据不同"""
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
handler(request), timeout=settings.mcp_tool_timeout
|
||||
)
|
||||
except TimeoutError as error:
|
||||
raise MaterialFieldServiceError(f"物料字段查询超时: {request.name}") from error
|
||||
if isinstance(result, CallToolResult) and result.structuredContent:
|
||||
served = result.structuredContent.get("catalog_version")
|
||||
catalog = get_catalog_cache()
|
||||
expected = catalog.version if catalog is not None else None
|
||||
if served != expected:
|
||||
raise MaterialFieldServiceError(
|
||||
f"物料字段服务的目录版本 {served} 与主服务 {expected} 不一致,请重启服务"
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
_client = MultiServerMCPClient(
|
||||
{
|
||||
SERVER_NAME: {
|
||||
"transport": "streamable_http",
|
||||
"url": settings.mcp_material_url,
|
||||
}
|
||||
},
|
||||
tool_interceptors=[_check_version],
|
||||
# 未知组件类型、分组等查询错误返回给模型自行修正;连接失败等仍直接抛出
|
||||
handle_tool_errors=True,
|
||||
)
|
||||
_tools: list[BaseTool] | None = None
|
||||
|
||||
|
||||
async def get_material_tools() -> list[BaseTool]:
|
||||
"""字段查询工具;首次使用或此前连接失败时向服务拉取工具清单"""
|
||||
global _tools
|
||||
if _tools is None:
|
||||
try:
|
||||
_tools = await asyncio.wait_for(
|
||||
_client.get_tools(server_name=SERVER_NAME),
|
||||
timeout=settings.mcp_tool_timeout,
|
||||
)
|
||||
except Exception as error:
|
||||
raise MaterialFieldServiceError(
|
||||
f"物料字段服务不可用: {settings.mcp_material_url}"
|
||||
) from error
|
||||
return _tools
|
||||
|
||||
|
||||
def _port_open(port: int) -> bool:
|
||||
with socket.socket() as probe:
|
||||
probe.settimeout(0.2)
|
||||
return probe.connect_ex(("127.0.0.1", port)) == 0
|
||||
|
||||
|
||||
async def start_material_server() -> subprocess.Popen | None:
|
||||
"""本机地址且尚未运行时拉起服务,返回进程供退出时关闭;已运行则直接复用"""
|
||||
target = urlsplit(settings.mcp_material_url)
|
||||
if target.hostname not in {"localhost", "127.0.0.1"}:
|
||||
raise MaterialFieldServiceError("物料字段服务只能部署在本机,不对外暴露")
|
||||
port = target.port or 80
|
||||
if _port_open(port):
|
||||
return None
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, "-X", "utf8", "-m", "mcp_servers.material_server"],
|
||||
cwd=PROJECT_ROOT,
|
||||
env={**os.environ, "PORT": str(port)},
|
||||
)
|
||||
for _ in range(100):
|
||||
if process.poll() is not None:
|
||||
raise MaterialFieldServiceError("物料字段服务启动失败,请查看终端日志")
|
||||
if _port_open(port):
|
||||
logger.info("物料字段服务已启动: {}", settings.mcp_material_url)
|
||||
return process
|
||||
await asyncio.sleep(0.1)
|
||||
await stop_material_server(process)
|
||||
raise MaterialFieldServiceError("物料字段服务启动超时")
|
||||
|
||||
|
||||
async def stop_material_server(process: subprocess.Popen | None) -> None:
|
||||
if process is None or process.poll() is not None:
|
||||
return
|
||||
process.terminate()
|
||||
try:
|
||||
await asyncio.to_thread(process.wait, timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
await asyncio.to_thread(process.wait)
|
||||
@@ -58,6 +58,21 @@ class Settings:
|
||||
default_factory=lambda: int(os.getenv("MAX_ACTION_RETRIES", "2"))
|
||||
)
|
||||
|
||||
# 物料字段 MCP 服务:只给模型查询字段定义,只监听本机,随应用启动
|
||||
mcp_material_url: str = field(
|
||||
default_factory=lambda: (
|
||||
os.getenv("MCP_MATERIAL_URL") or "http://127.0.0.1:8201/mcp"
|
||||
)
|
||||
)
|
||||
# 单次字段查询的超时(秒)
|
||||
mcp_tool_timeout: float = field(
|
||||
default_factory=lambda: float(os.getenv("MCP_TOOL_TIMEOUT", "5"))
|
||||
)
|
||||
# 生成动作或修正建议时,最多几轮查询字段,之后必须给出结果
|
||||
max_field_tool_rounds: int = field(
|
||||
default_factory=lambda: int(os.getenv("MAX_FIELD_TOOL_ROUNDS", "4"))
|
||||
)
|
||||
|
||||
# 没有任何对话的空会话保留时长(秒),超时后视为不存在
|
||||
conversation_empty_ttl: int = field(
|
||||
default_factory=lambda: int(os.getenv("CONVERSATION_EMPTY_TTL", "7200"))
|
||||
@@ -113,6 +128,10 @@ class Settings:
|
||||
raise ValueError("CONVERSATION_HISTORY_TURNS 必须是非负整数")
|
||||
if type(self.max_action_retries) is not int or self.max_action_retries < 0:
|
||||
raise ValueError("MAX_ACTION_RETRIES 必须是非负整数")
|
||||
if self.mcp_tool_timeout <= 0:
|
||||
raise ValueError("MCP_TOOL_TIMEOUT 必须是正数")
|
||||
if self.max_field_tool_rounds < 0:
|
||||
raise ValueError("MAX_FIELD_TOOL_ROUNDS 必须是非负整数")
|
||||
if not 0 <= self.intent_min_probability <= 1:
|
||||
raise ValueError("INTENT_MIN_PROBABILITY 必须在 0 到 1 之间")
|
||||
if not 0 <= self.intent_qa_min_probability <= 1:
|
||||
|
||||
+19
-6
@@ -18,6 +18,11 @@ from app.core.logging import setup_logging
|
||||
async def lifespan(application: FastAPI):
|
||||
setup_logging()
|
||||
from app.clients.decision import close_decision_engine
|
||||
from app.clients.material_mcp import (
|
||||
MaterialFieldServiceError,
|
||||
start_material_server,
|
||||
stop_material_server,
|
||||
)
|
||||
from app.clients.user_center import close_user_center
|
||||
from app.observability.tracing import flush_tracing
|
||||
from app.processor.graph import build_graph
|
||||
@@ -28,9 +33,15 @@ async def lifespan(application: FastAPI):
|
||||
# 按配置选择 MongoDB 或进程内存,会话记录与图检查点使用同一种存储
|
||||
persistence = await open_persistence()
|
||||
application.state.conversation_store = persistence.store
|
||||
material_server = None
|
||||
try:
|
||||
# 物料目录启动时载入内存,agent 规划与前端接口共用,避免重复查询数据库
|
||||
await warm_material_cache(persistence.db)
|
||||
# 物料字段服务给模型查询字段;启动失败只记录,需要查字段的修改执行时会报错
|
||||
try:
|
||||
material_server = await start_material_server()
|
||||
except MaterialFieldServiceError as error:
|
||||
logger.error("{},修改组件属性和新增组件将无法执行", error)
|
||||
application.state.agent_service = AgentService(
|
||||
build_graph(persistence.checkpointer), persistence.store
|
||||
)
|
||||
@@ -38,19 +49,21 @@ async def lifespan(application: FastAPI):
|
||||
finally:
|
||||
# 共享客户端只在应用退出时关闭,追踪刷新放到线程避免阻塞事件循环
|
||||
try:
|
||||
await persistence.close()
|
||||
await stop_material_server(material_server)
|
||||
finally:
|
||||
try:
|
||||
await close_decision_engine()
|
||||
await persistence.close()
|
||||
finally:
|
||||
try:
|
||||
await close_user_center()
|
||||
await close_decision_engine()
|
||||
finally:
|
||||
try:
|
||||
await asyncio.to_thread(flush_tracing)
|
||||
await close_user_center()
|
||||
finally:
|
||||
await logger.complete()
|
||||
|
||||
try:
|
||||
await asyncio.to_thread(flush_tracing)
|
||||
finally:
|
||||
await logger.complete()
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
application = FastAPI(title="Screen Agent API", lifespan=lifespan)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
"""需要物料字段的模型调用:模型按需调用字段查询工具,最后按指定结构输出
|
||||
|
||||
动作生成和修正建议共用;字段查询走物料字段 MCP 服务,服务不可用时直接报错
|
||||
"""
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
from langchain.agents import create_agent
|
||||
from langchain.agents.middleware import wrap_model_call
|
||||
from langchain.agents.structured_output import ToolStrategy
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.clients.llm import get_llm
|
||||
from app.clients.material_mcp import get_material_tools
|
||||
from app.config import settings
|
||||
from app.processor.state import QueryState
|
||||
from app.resources.component import get_catalog_cache
|
||||
from app.resources.material_fields import list_field_groups
|
||||
|
||||
OutputModel = TypeVar("OutputModel", bound=BaseModel)
|
||||
|
||||
|
||||
@wrap_model_call
|
||||
async def _limit_field_queries(request, handler):
|
||||
"""查询轮数用尽后只保留输出工具,模型必须给出结果"""
|
||||
rounds = sum(isinstance(message, AIMessage) for message in request.messages)
|
||||
if rounds >= settings.max_field_tool_rounds:
|
||||
request = request.override(tools=[])
|
||||
return await handler(request)
|
||||
|
||||
|
||||
async def invoke_with_field_tools(
|
||||
schema: type[OutputModel], system_prompt: str, content: str
|
||||
) -> OutputModel | None:
|
||||
"""返回结构化结果;模型未给出结果时返回 None
|
||||
|
||||
结果不符合 schema 时抛出 StructuredOutputValidationError,由调用方转换为校验错误
|
||||
"""
|
||||
agent = create_agent(
|
||||
get_llm(),
|
||||
await get_material_tools(),
|
||||
system_prompt=system_prompt,
|
||||
# 不在调用内自动重试,结构错误交给后续校验和修正流程,与不查字段时一致
|
||||
response_format=ToolStrategy(schema, handle_errors=False),
|
||||
middleware=[_limit_field_queries],
|
||||
name=f"{schema.__name__}_with_fields",
|
||||
)
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content=content)]},
|
||||
# 每轮模型调用和工具执行各占一步,轮数由中间件限制,这里只防止异常循环
|
||||
config={"recursion_limit": 2 * settings.max_field_tool_rounds + 6},
|
||||
)
|
||||
return result.get("structured_response")
|
||||
|
||||
|
||||
def field_group_directory(state: QueryState) -> dict[str, dict]:
|
||||
"""本次相关组件的字段分组目录,按物料类型列出,省去模型先查目录的一轮调用"""
|
||||
nodes = {node.id: node.type for node in state["input_context"].screen.nodes}
|
||||
plan = state.get("screen_plan")
|
||||
if plan is None:
|
||||
types = [
|
||||
nodes[target]
|
||||
for change in state["requirement"].changes
|
||||
if change.target_scope == "nodes"
|
||||
for target in change.target_ids
|
||||
if target in nodes
|
||||
]
|
||||
else:
|
||||
types = [
|
||||
item.component_type
|
||||
if item.disposition in ("add", "replace")
|
||||
else nodes.get(item.target_id)
|
||||
for item in plan.components
|
||||
if item.disposition in ("add", "replace", "modify")
|
||||
]
|
||||
catalog = get_catalog_cache()
|
||||
directory = {}
|
||||
for component_type in dict.fromkeys(item for item in types if item):
|
||||
try:
|
||||
if catalog is None:
|
||||
raise ValueError("物料目录未载入")
|
||||
directory[component_type] = {
|
||||
key: value
|
||||
for key, value in list_field_groups(catalog, component_type).items()
|
||||
if key not in ("component_type", "catalog_version")
|
||||
}
|
||||
except ValueError as error:
|
||||
# 没有物料定义的组件不能修改属性,告诉模型原因
|
||||
directory[component_type] = {"error": str(error)}
|
||||
return directory
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
import json
|
||||
|
||||
from langchain.agents.structured_output import (
|
||||
StructuredOutputError,
|
||||
StructuredOutputValidationError,
|
||||
)
|
||||
from langchain_core.exceptions import OutputParserException
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from pydantic import ValidationError
|
||||
@@ -9,13 +13,18 @@ from pydantic import ValidationError
|
||||
from app.actions.plan import assemble_plan_actions, plan_block_reason, plan_materials
|
||||
from app.clients.llm import get_llm
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.material_agent import field_group_directory, invoke_with_field_tools
|
||||
from app.processor.state import QueryState
|
||||
from app.prompt.action import build_action_prompt
|
||||
from app.resources.material_fields import editable_fields
|
||||
from app.prompt.action import build_action_prompt, uses_material_fields
|
||||
from app.schemas.action_validation import ActionValidationError, schema_errors
|
||||
from app.schemas.actions import ActionBatch, AddAction
|
||||
from app.schemas.operations import is_direct_operation
|
||||
|
||||
GENERATION_ERROR = ActionValidationError(
|
||||
code="generation_error",
|
||||
message="模型输出无法解析为 ActionBatch,请按协议重新生成",
|
||||
)
|
||||
|
||||
|
||||
def can_generate_actions(state: QueryState) -> bool:
|
||||
# 没有阻塞原因时才允许生成动作,路由和节点入口复用同一套能力检查
|
||||
@@ -59,28 +68,6 @@ def action_block_reason(state: QueryState) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def update_fields(state: QueryState) -> dict[str, list[dict]]:
|
||||
"""按组件 ID 汇总可能被属性更新的已有组件的可编辑字段,没有字段的组件省略"""
|
||||
plan = state.get("screen_plan")
|
||||
if plan is None:
|
||||
target_ids = [
|
||||
target
|
||||
for change in state["requirement"].changes
|
||||
if change.target_scope == "nodes"
|
||||
for target in change.target_ids
|
||||
]
|
||||
else:
|
||||
target_ids = [
|
||||
item.target_id for item in plan.components if item.disposition == "modify"
|
||||
]
|
||||
nodes = {node.id: node for node in state["input_context"].screen.nodes}
|
||||
result = {}
|
||||
for target in dict.fromkeys(target_ids):
|
||||
if target in nodes and (fields := editable_fields(nodes[target].type)):
|
||||
result[target] = fields
|
||||
return result
|
||||
|
||||
|
||||
class ActionNode(BaseNode):
|
||||
name = "action_node"
|
||||
|
||||
@@ -115,10 +102,6 @@ class ActionNode(BaseNode):
|
||||
payload["screen_plan"] = state["screen_plan"].model_dump(mode="json")
|
||||
payload["component_materials"] = plan_materials(state)
|
||||
|
||||
# 提供待修改组件的属性面板字段,让模型把用户说的“标题”“颜色”等对应到真实路径
|
||||
if fields := update_fields(state):
|
||||
payload["editable_fields"] = fields
|
||||
|
||||
# 如果本轮是动作修正,提供修正建议、上一轮动作和校验错误供模型参考
|
||||
if state.get("repair_plan") is not None:
|
||||
payload.update(
|
||||
@@ -133,7 +116,17 @@ class ActionNode(BaseNode):
|
||||
],
|
||||
)
|
||||
|
||||
llm_client = get_llm(ActionBatch)
|
||||
system_prompt = build_action_prompt(
|
||||
state["requirement"],
|
||||
state.get("screen_plan"),
|
||||
repairing=state.get("repair_plan") is not None,
|
||||
)
|
||||
# 需要写组件属性时提供相关组件的字段分组,模型再按需查询字段定义
|
||||
with_fields = uses_material_fields(state["requirement"], state.get("screen_plan"))
|
||||
if with_fields:
|
||||
payload["field_groups"] = field_group_directory(state)
|
||||
# 将本次具体资料放入用户消息,保留中文以便阅读和排查
|
||||
content = json.dumps(payload, ensure_ascii=False)
|
||||
|
||||
# 旧动作及错误已放入模型输入,现在清空输出状态,避免本轮失败后残留旧结果
|
||||
updated_state["actions"] = None
|
||||
@@ -146,19 +139,12 @@ class ActionNode(BaseNode):
|
||||
# 捕获输出结构和解析错误,转换成统一校验结果供后续修正流程使用
|
||||
try:
|
||||
# 异步调用模型,根据需求、规划及是否正在修正选择对应的系统规则
|
||||
result = await llm_client.ainvoke(
|
||||
[
|
||||
SystemMessage(
|
||||
content=build_action_prompt(
|
||||
state["requirement"],
|
||||
state.get("screen_plan"),
|
||||
repairing=state.get("repair_plan") is not None,
|
||||
)
|
||||
),
|
||||
# 将本次具体资料放入用户消息,保留中文以便阅读和排查
|
||||
HumanMessage(content=json.dumps(payload, ensure_ascii=False)),
|
||||
]
|
||||
)
|
||||
if with_fields:
|
||||
result = await invoke_with_field_tools(ActionBatch, system_prompt, content)
|
||||
else:
|
||||
result = await get_llm(ActionBatch).ainvoke(
|
||||
[SystemMessage(content=system_prompt), HumanMessage(content=content)]
|
||||
)
|
||||
|
||||
# 响应中没有工具调用时解析器会返回 None
|
||||
if result is None:
|
||||
@@ -184,15 +170,19 @@ class ActionNode(BaseNode):
|
||||
updated_state["validation_passed"] = False
|
||||
updated_state["validation_errors"] = schema_errors(error)
|
||||
return updated_state
|
||||
except OutputParserException:
|
||||
except StructuredOutputValidationError as error:
|
||||
# 查询字段后的输出不符合结构,与直接输出时的处理一致
|
||||
updated_state["validation_passed"] = False
|
||||
updated_state["validation_errors"] = (
|
||||
schema_errors(error.source)
|
||||
if isinstance(error.source, ValidationError)
|
||||
else [GENERATION_ERROR]
|
||||
)
|
||||
return updated_state
|
||||
except (OutputParserException, StructuredOutputError):
|
||||
# 如果模型输出无法解析,记录生成错误,提示后续按 ActionBatch 协议重新生成
|
||||
updated_state["validation_passed"] = False
|
||||
updated_state["validation_errors"] = [
|
||||
ActionValidationError(
|
||||
code="generation_error",
|
||||
message="模型输出无法解析为 ActionBatch,请按协议重新生成",
|
||||
)
|
||||
]
|
||||
updated_state["validation_errors"] = [GENERATION_ERROR]
|
||||
return updated_state
|
||||
|
||||
# 保存生成的动作,实际目标、规划一致性及模拟执行结果仍由后续校验节点检查
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import json
|
||||
|
||||
from langchain.agents.structured_output import StructuredOutputError
|
||||
from langchain_core.exceptions import OutputParserException
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from pydantic import ValidationError
|
||||
@@ -9,7 +10,9 @@ from pydantic import ValidationError
|
||||
from app.actions.plan import plan_materials
|
||||
from app.clients.llm import get_llm
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.material_agent import field_group_directory, invoke_with_field_tools
|
||||
from app.processor.state import QueryState
|
||||
from app.prompt.action import FIELD_QUERY_PROMPT, uses_material_fields
|
||||
from app.schemas.capabilities import USER_FAILURE_MESSAGE
|
||||
from app.prompt.reflection import REFLECTION_SYSTEM_PROMPT
|
||||
from app.schemas.action_repair import ActionRepairPlan
|
||||
@@ -22,7 +25,6 @@ class ReflectionNode(BaseNode):
|
||||
# 浅拷贝状态
|
||||
updated_state: QueryState = state.copy()
|
||||
|
||||
llm_client = get_llm(ActionRepairPlan)
|
||||
# 汇总原始上下文、需求和失败信息,让模型基于实际错误提出修正建议
|
||||
payload = {
|
||||
"input_context": state["input_context"].model_dump(mode="json"),
|
||||
@@ -46,21 +48,30 @@ class ReflectionNode(BaseNode):
|
||||
payload["screen_plan"] = state["screen_plan"].model_dump(mode="json")
|
||||
payload["component_materials"] = plan_materials(state)
|
||||
|
||||
# 涉及组件属性时,修正建议须基于查询到的字段定义,与动作生成使用同一组工具
|
||||
with_fields = uses_material_fields(state["requirement"], state.get("screen_plan"))
|
||||
if with_fields:
|
||||
payload["field_groups"] = field_group_directory(state)
|
||||
# 用户消息承载本轮待分析的具体资料,序列化时保留中文
|
||||
content = json.dumps(payload, ensure_ascii=False)
|
||||
|
||||
# 只捕获输出结构和解析错误,配置、网络等异常继续向上抛出
|
||||
try:
|
||||
# 异步生成修正建议,本节点不直接修改动作
|
||||
plan = await llm_client.ainvoke(
|
||||
[
|
||||
# 系统消息提供稳定的失败分析和修正规则
|
||||
SystemMessage(content=REFLECTION_SYSTEM_PROMPT),
|
||||
# 用户消息承载本轮待分析的具体资料,序列化时保留中文
|
||||
HumanMessage(content=json.dumps(payload, ensure_ascii=False)),
|
||||
]
|
||||
)
|
||||
if with_fields:
|
||||
plan = await invoke_with_field_tools(
|
||||
ActionRepairPlan,
|
||||
REFLECTION_SYSTEM_PROMPT + "\n" + FIELD_QUERY_PROMPT,
|
||||
content,
|
||||
)
|
||||
else:
|
||||
plan = await get_llm(ActionRepairPlan).ainvoke(
|
||||
[SystemMessage(content=REFLECTION_SYSTEM_PROMPT), HumanMessage(content=content)]
|
||||
)
|
||||
# 响应中没有工具调用时解析器会返回 None
|
||||
if plan is None:
|
||||
raise OutputParserException("模型未返回结构化结果")
|
||||
except (ValidationError, OutputParserException):
|
||||
except (ValidationError, OutputParserException, StructuredOutputError):
|
||||
# 如果未能得到合法建议,清空旧修正计划,避免再次使用已失效的建议
|
||||
updated_state["repair_plan"] = None
|
||||
# 记录无法继续修正的原因,交给后续路由和失败处理流程
|
||||
|
||||
@@ -4,10 +4,18 @@ from pydantic import ValidationError
|
||||
|
||||
from app.actions.plan import validate_plan_actions, validate_plan_result
|
||||
from app.actions.registry import apply_action
|
||||
from app.actions.update import fit_existing_path
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.state import QueryState
|
||||
from app.resources.component import get_catalog_cache
|
||||
from app.resources.material_fields import check_update_field
|
||||
from app.schemas.action_validation import ActionValidationError, schema_errors
|
||||
from app.schemas.actions import ActionBatch, AddAction, UpdateCanvasAction
|
||||
from app.schemas.actions import (
|
||||
ActionBatch,
|
||||
AddAction,
|
||||
UpdateAction,
|
||||
UpdateCanvasAction,
|
||||
)
|
||||
from app.schemas.operations import is_canvas_operation
|
||||
|
||||
|
||||
@@ -121,8 +129,32 @@ class ValidatorNode(BaseNode):
|
||||
# 在副本上试执行,不改变用户传入的大屏,也不操作前端画布
|
||||
screen = state["input_context"].screen.model_copy(deep=True)
|
||||
|
||||
catalog = get_catalog_cache()
|
||||
# 保存补齐路径后的动作,前端按它执行
|
||||
actions = list(batch.actions)
|
||||
|
||||
# 按动作顺序逐项执行,每一步都使用上一步更新后的大屏副本
|
||||
for index, action in enumerate(batch.actions):
|
||||
for index, action in enumerate(actions):
|
||||
# 属性更新先按物料字段定义检查,目标不存在时交给处理器报错
|
||||
node = (
|
||||
next((item for item in screen.nodes if item.id == action.target_id), None)
|
||||
if isinstance(action, UpdateAction)
|
||||
else None
|
||||
)
|
||||
if node is not None:
|
||||
try:
|
||||
check_update_field(catalog, node.type, action)
|
||||
except ValueError as error:
|
||||
updated_state["validation_passed"] = False
|
||||
updated_state["validation_errors"] = [
|
||||
ActionValidationError(
|
||||
action_index=index, code="field_error", message=str(error)
|
||||
)
|
||||
]
|
||||
updated_state["simulated_screen"] = None
|
||||
return updated_state
|
||||
# 字段定义允许、但组件中还没有的中间对象,折叠进值里再执行
|
||||
action = actions[index] = fit_existing_path(node, action)
|
||||
try:
|
||||
# 根据动作类型调用对应处理逻辑,检查目标和参数并返回更新后的大屏
|
||||
screen = apply_action(screen, action)
|
||||
@@ -147,8 +179,8 @@ class ValidatorNode(BaseNode):
|
||||
updated_state["simulated_screen"] = None
|
||||
return updated_state
|
||||
|
||||
# 全部检查通过后,保存重新校验过的动作对象,统一后续读取的数据结构
|
||||
updated_state["actions"] = batch.actions
|
||||
# 全部检查通过后,保存重新校验并补齐路径的动作对象,统一后续读取的数据结构
|
||||
updated_state["actions"] = actions
|
||||
# 标记整批动作校验通过,并清空之前可能残留的错误
|
||||
updated_state["validation_passed"] = True
|
||||
updated_state["validation_errors"] = []
|
||||
|
||||
+48
-21
@@ -53,6 +53,46 @@ REPAIR_TASK_PROMPT = """# 当前阶段:修正重试
|
||||
"""
|
||||
|
||||
|
||||
FIELD_QUERY_PROMPT = """# 查询组件字段
|
||||
field_groups 按物料类型列出本次相关组件的字段分组,节点的 type 就是物料类型
|
||||
确定要改的字段:用用户原话中对属性的说法(input_context 中的提问,不用改写后的需求)调用 search_fields;
|
||||
返回多个字段时,依据字段说明判断用户指的是哪一个;用户看的是大屏画面,没有提到图层列表、属性面板等编辑器位置时,按画面上看得到的内容理解
|
||||
需要了解一组相关字段时用 get_fields,一次可查多个分组
|
||||
只能使用查询到的字段路径,值须符合字段的类型、可选值和取值范围;查不到对应字段说明不支持,不自行编造路径
|
||||
"""
|
||||
|
||||
# 这些动作会写组件属性,需要先查物料字段
|
||||
FIELD_ACTIONS = frozenset({"update", "add"})
|
||||
|
||||
|
||||
def action_types(requirement: Requirement, screen_plan: ScreenPlan | None) -> set[str]:
|
||||
"""本次需要的动作类型:简单修改按需求变更,规划按各组件的处理方式"""
|
||||
if screen_plan is None:
|
||||
return {
|
||||
"update_canvas" if change.target_scope == "canvas" else change.operation
|
||||
for change in requirement.changes
|
||||
}
|
||||
types = set()
|
||||
if screen_plan.canvas_changes:
|
||||
types.add("update_canvas")
|
||||
for item in screen_plan.components:
|
||||
if item.disposition in ("add", "replace"):
|
||||
types.add("add")
|
||||
elif item.disposition == "remove":
|
||||
types.add("delete")
|
||||
elif item.disposition == "modify":
|
||||
types.update(("move", "resize"))
|
||||
if item.design:
|
||||
types.add("update")
|
||||
return types
|
||||
|
||||
|
||||
def uses_material_fields(
|
||||
requirement: Requirement, screen_plan: ScreenPlan | None
|
||||
) -> bool:
|
||||
return bool(action_types(requirement, screen_plan) & FIELD_ACTIONS)
|
||||
|
||||
|
||||
def build_action_prompt(
|
||||
requirement: Requirement,
|
||||
screen_plan: ScreenPlan | None = None,
|
||||
@@ -60,40 +100,27 @@ def build_action_prompt(
|
||||
repairing: bool = False,
|
||||
) -> str:
|
||||
"""只组合本次任务需要的规则,不改变 Schema 或后端校验范围"""
|
||||
types = action_types(requirement, screen_plan)
|
||||
sections = [ACTION_SYSTEM_PROMPT]
|
||||
if screen_plan is None:
|
||||
sections.append(SIMPLE_TASK_PROMPT)
|
||||
action_types = {
|
||||
"update_canvas" if change.target_scope == "canvas" else change.operation
|
||||
for change in requirement.changes
|
||||
}
|
||||
else:
|
||||
sections.append(PLAN_TASK_PROMPT)
|
||||
action_types = set()
|
||||
dispositions = {item.disposition for item in screen_plan.components}
|
||||
if screen_plan.canvas_changes:
|
||||
sections.append(
|
||||
"## 调整画布\n将 canvas_changes 中的最终属性值转换为 update_canvas,完整覆盖且不添加其他画布字段"
|
||||
)
|
||||
action_types.add("update_canvas")
|
||||
if any(item.disposition == "add" for item in screen_plan.components):
|
||||
action_types.add("add")
|
||||
if any(item.disposition == "replace" for item in screen_plan.components):
|
||||
if "replace" in dispositions:
|
||||
sections.append(REPLACE_PLAN_PROMPT)
|
||||
action_types.add("add")
|
||||
if any(item.disposition == "remove" for item in screen_plan.components):
|
||||
action_types.add("delete")
|
||||
modifications = [
|
||||
item for item in screen_plan.components if item.disposition == "modify"
|
||||
]
|
||||
if modifications:
|
||||
if "modify" in dispositions:
|
||||
sections.append(MODIFY_PLAN_PROMPT)
|
||||
action_types.update(("move", "resize"))
|
||||
if any(item.design for item in modifications):
|
||||
action_types.add("update")
|
||||
if types & FIELD_ACTIONS:
|
||||
sections.append(FIELD_QUERY_PROMPT)
|
||||
|
||||
# 排序保证提示词顺序稳定,缺少动作说明时直接报错,避免静默遗漏
|
||||
sections.append("# 本次可用动作\n" + "、".join(sorted(action_types)))
|
||||
sections.extend(ACTION_RULES[name] for name in sorted(action_types))
|
||||
sections.append("# 本次可用动作\n" + "、".join(sorted(types)))
|
||||
sections.extend(ACTION_RULES[name] for name in sorted(types))
|
||||
if repairing:
|
||||
sections.append(REPAIR_TASK_PROMPT)
|
||||
return "\n\n".join(section.strip() for section in sections)
|
||||
|
||||
@@ -21,13 +21,11 @@ properties 只包含需要修改的画布顶层字段,宽高为最终绝对值
|
||||
不需要改变的尺寸省略,不改变组件位置
|
||||
""",
|
||||
"update": """## update:更新已有组件属性
|
||||
依据快照选择 props / style 下的字段路径,不猜测不存在的嵌套结构
|
||||
path 中字符串是对象键,整数是数组下标;中间路径必须存在,数组下标不能越界
|
||||
只替换指定字段,不做深度合并;null 表示赋值,不表示删除
|
||||
提供 editable_fields 时,它是该组件属性面板的字段:用户说的名称(如名字、标题、颜色)优先对应 label 相符的字段,使用其 section 和 path
|
||||
props 第一层只能使用组件已有或物料默认配置中的字段,不新造组件级属性;更深层的配置可以按需新增
|
||||
字段路径的第一段写入 section(name / props / style),其余各段写入 path,数字段写成整数下标;name 的 path 为空列表
|
||||
例如字段 props.option.title.text 写成 section=props、path=["option", "title", "text"]
|
||||
layout 字段不用 update:位置用 move,尺寸用 resize
|
||||
只替换指定字段,不做深度合并;组件中还没有的中间对象由后端补齐
|
||||
不通过修改属性改变组件类型(如把 series 的 type 改成其他图表类型),组件类型由节点 type 和物料决定
|
||||
style 为 null 时可用单层路径添加属性,保留未要求修改的配置
|
||||
""",
|
||||
"add": """## add:按物料新增组件
|
||||
每个 add 规划对应一个新增动作,用 plan_id 关联规划,component_key 选择 component_materials 中的物料
|
||||
|
||||
@@ -15,6 +15,7 @@ REFLECTION_SYSTEM_PROMPT = f"""## 任务
|
||||
需要用户补充、外部资源或尚未支持的能力时,retryable=false,说明缺少什么
|
||||
{DATA_BINDING_GUIDE}
|
||||
因数据绑定校验失败时,去掉 dataId 并保留物料示例数据即可修正,retryable=true
|
||||
字段错误(field_error)时,查询字段定义,建议改用查到的字段路径或合法取值;物料没有能满足需求的字段时,retryable=false
|
||||
|
||||
## 边界
|
||||
保持需求和规划范围,不通过遗漏需求、猜测字段或替换用户意图来绕过错误
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""查询前端组件目录:目录只来自启动时从 MongoDB 载入的进程内存缓存,没有文件回退"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from app.schemas.component_resource import (
|
||||
@@ -21,14 +23,44 @@ def get_catalog_cache() -> ComponentCatalog | None:
|
||||
return _cached_catalog
|
||||
|
||||
|
||||
def load_catalog_file(path: str | Path) -> ComponentCatalog:
|
||||
"""读取导出的目录文件,只供导入脚本使用,运行时不读取文件"""
|
||||
# 如果传入的是相对路径,以项目根目录为基准,避免受启动时工作目录影响
|
||||
target = Path(path)
|
||||
# 手写的物料定义目录,只供导入脚本读取后写入 MongoDB,运行时不读取
|
||||
MATERIALS_DIR = Path(__file__).resolve().parent / "materials"
|
||||
FIELD_GROUPS_FILE = "field_groups.json"
|
||||
|
||||
|
||||
def load_material_definitions(directory: str | Path = MATERIALS_DIR) -> ComponentCatalog:
|
||||
"""读取物料定义目录:共享字段分组一个文件,每个物料一个文件;不合法直接报错
|
||||
|
||||
version 为全部文件内容的摘要,定义有任何改动都会得到新版本
|
||||
"""
|
||||
target = Path(directory)
|
||||
if not target.is_absolute():
|
||||
target = Path(__file__).resolve().parents[2] / target
|
||||
# utf-8-sig 同时兼容带 BOM 和不带 BOM 的 UTF-8 文件;损坏或不合法直接报错
|
||||
return ComponentCatalog.model_validate_json(target.read_text(encoding="utf-8-sig"))
|
||||
files = sorted(target.glob("*.json"))
|
||||
if not any(path.name == FIELD_GROUPS_FILE for path in files):
|
||||
raise FileNotFoundError(f"物料定义目录缺少 {FIELD_GROUPS_FILE}: {target}")
|
||||
digest = hashlib.sha256()
|
||||
components, field_groups = [], []
|
||||
for path in files:
|
||||
# utf-8-sig 同时兼容带 BOM 和不带 BOM 的 UTF-8 文件
|
||||
text = path.read_text(encoding="utf-8-sig")
|
||||
digest.update(path.name.encode() + b"\0" + text.encode())
|
||||
if path.name == FIELD_GROUPS_FILE:
|
||||
field_groups = json.loads(text)
|
||||
else:
|
||||
components.append(json.loads(text))
|
||||
# 文件名排序与展示顺序无关,物料按定义中的 order 排列,未写时排在最后
|
||||
components.sort(key=lambda item: item.get("order", len(files)))
|
||||
return ComponentCatalog.model_validate(
|
||||
{
|
||||
"version": f"sha256:{digest.hexdigest()}",
|
||||
"components": [
|
||||
{key: value for key, value in item.items() if key != "order"}
|
||||
for item in components
|
||||
],
|
||||
"field_groups": field_groups,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ComponentProvider:
|
||||
|
||||
@@ -1,50 +1,206 @@
|
||||
"""从物料目录读取组件的可编辑字段,供动作生成理解字段含义,并供校验约束属性结构"""
|
||||
"""按物料展开字段定义:解析共享分组引用、取字段默认值、派生前端属性面板 setters"""
|
||||
|
||||
from app.resources.component import ComponentProvider
|
||||
from app.schemas.component_resource import ComponentDefinition, ComponentQuery
|
||||
from app.schemas.screen import ScreenNode
|
||||
from pydantic import JsonValue
|
||||
|
||||
# 属性面板的 key 以区域开头,只有这两个区域可以被 update 修改
|
||||
EDITABLE_SECTIONS = ("props", "style")
|
||||
from app.schemas.component_resource import ComponentCatalog, ComponentDefinition
|
||||
from app.schemas.material_field import (
|
||||
MISSING,
|
||||
PANEL_CONTROLS,
|
||||
FieldGroup,
|
||||
MaterialField,
|
||||
lookup,
|
||||
)
|
||||
|
||||
|
||||
def _materials(node_type: str) -> list[ComponentDefinition]:
|
||||
# 多个物料预设可以共用同一前端 type,按 type 取全部预设
|
||||
result = ComponentProvider().query(ComponentQuery(type=node_type))
|
||||
return result.components if result.status == "resolved" else []
|
||||
def resolve_field_groups(
|
||||
catalog: ComponentCatalog, component: ComponentDefinition
|
||||
) -> list[FieldGroup]:
|
||||
"""按物料声明的顺序展开字段分组,共享分组取目录中的定义;返回副本,不污染目录"""
|
||||
shared = {group.key: group for group in catalog.field_groups}
|
||||
return [
|
||||
(shared[group] if isinstance(group, str) else group).model_copy(deep=True)
|
||||
for group in component.field_groups
|
||||
]
|
||||
|
||||
|
||||
def _split_key(key: str) -> tuple[str, list[str | int]] | None:
|
||||
# 前端点路径中的纯数字段是数组下标,转换为与 UpdateAction.path 一致的整数
|
||||
section, *path = key.split(".")
|
||||
if section not in EDITABLE_SECTIONS or not path:
|
||||
return None
|
||||
return section, [int(part) if part.isdigit() else part for part in path]
|
||||
def field_default(component: ComponentDefinition, field: MaterialField) -> JsonValue:
|
||||
"""物料默认配置中有该路径时取默认配置的值,否则取字段声明的 default"""
|
||||
value = lookup(component.template(), field.segments)
|
||||
return field.default if value is MISSING else value
|
||||
|
||||
|
||||
def editable_fields(node_type: str) -> list[dict]:
|
||||
"""返回组件属性面板中的字段:用户可见名称、区域、路径及可选值,按出现顺序去重"""
|
||||
fields: dict[str, dict] = {}
|
||||
for material in _materials(node_type):
|
||||
for setter in material.setters:
|
||||
parsed = _split_key(setter.key)
|
||||
if parsed is None or setter.key in fields:
|
||||
def panel_setters(groups: list[FieldGroup]) -> list[dict]:
|
||||
"""派生前端属性面板的 setters,格式与前端 SetterSchema 一致"""
|
||||
setters = []
|
||||
for group in groups:
|
||||
for field in group.fields:
|
||||
if not field.panel:
|
||||
continue
|
||||
section, path = parsed
|
||||
field = {"label": setter.label, "section": section, "path": path}
|
||||
if options := setter.props.get("options"):
|
||||
field["options"] = options
|
||||
fields[setter.key] = field
|
||||
return list(fields.values())
|
||||
props = {}
|
||||
if field.options:
|
||||
props["options"] = [
|
||||
{"label": option.label, "value": option.value} for option in field.options
|
||||
]
|
||||
setter = {
|
||||
"type": PANEL_CONTROLS[field.value_type],
|
||||
"label": field.label,
|
||||
"key": field.path,
|
||||
"props": props,
|
||||
}
|
||||
if field.span is not None:
|
||||
setter["span"] = field.span
|
||||
setters.append(setter)
|
||||
return setters
|
||||
|
||||
|
||||
def known_prop_keys(node: ScreenNode) -> set[str] | None:
|
||||
"""props 第一层是组件与渲染器的约定,只能是节点已有或物料默认配置中的字段;
|
||||
目录中找不到该类型时约定未知,返回 None 表示不做限制"""
|
||||
materials = _materials(node.type)
|
||||
if not materials:
|
||||
return None
|
||||
keys = set(node.props)
|
||||
for material in materials:
|
||||
keys.update(material.schema_.props)
|
||||
return keys
|
||||
def frontend_catalog(catalog: ComponentCatalog) -> dict:
|
||||
"""前端物料接口的数据:在保存的定义上附加由字段定义派生的 setters"""
|
||||
data = catalog.model_dump(mode="json", by_alias=True)
|
||||
for item, component in zip(data["components"], catalog.components, strict=True):
|
||||
item["setters"] = panel_setters(resolve_field_groups(catalog, component))
|
||||
return data
|
||||
|
||||
|
||||
# ---- 字段查询:MCP 工具、动作提示词和动作校验共用同一套逻辑 ----
|
||||
|
||||
|
||||
def find_material(catalog: ComponentCatalog, component_type: str) -> ComponentDefinition:
|
||||
"""按节点 type 找物料;找不到或对应多个物料时报错,不猜测"""
|
||||
matches = [item for item in catalog.components if item.type == component_type]
|
||||
if len(matches) != 1:
|
||||
known = "、".join(item.type for item in catalog.components)
|
||||
reason = "没有对应的物料" if not matches else "对应多个物料"
|
||||
raise ValueError(f"组件类型 {component_type} {reason},可用类型:{known}")
|
||||
return matches[0]
|
||||
|
||||
|
||||
def describe_field(
|
||||
component: ComponentDefinition, group: FieldGroup, field: MaterialField
|
||||
) -> dict:
|
||||
"""给模型看的字段说明:省略空值和属性面板专用的信息"""
|
||||
data = field.model_dump(
|
||||
mode="json",
|
||||
exclude={"default", "panel", "span", "editable"},
|
||||
exclude_none=True,
|
||||
exclude_defaults=True,
|
||||
)
|
||||
data["default"] = field_default(component, field)
|
||||
if not field.editable:
|
||||
data["editable"] = False
|
||||
data["group"] = group.key
|
||||
return data
|
||||
|
||||
|
||||
def list_field_groups(catalog: ComponentCatalog, component_type: str) -> dict:
|
||||
"""物料的字段分组目录:每组的说明和字段名称,模型据此决定查看哪几组"""
|
||||
component = find_material(catalog, component_type)
|
||||
return {
|
||||
"component_type": component.type,
|
||||
"name": component.name,
|
||||
"catalog_version": catalog.version,
|
||||
"groups": [
|
||||
{
|
||||
"key": group.key,
|
||||
"label": group.label,
|
||||
"description": group.description,
|
||||
"fields": [field.label for field in group.fields],
|
||||
}
|
||||
for group in resolve_field_groups(catalog, component)
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def get_fields(
|
||||
catalog: ComponentCatalog, component_type: str, group_keys: list[str]
|
||||
) -> dict:
|
||||
"""指定分组的完整字段定义"""
|
||||
component = find_material(catalog, component_type)
|
||||
groups = {group.key: group for group in resolve_field_groups(catalog, component)}
|
||||
if unknown := [key for key in group_keys if key not in groups]:
|
||||
raise ValueError(
|
||||
f"{component.type} 没有字段分组 {unknown},可用分组:{list(groups)}"
|
||||
)
|
||||
return {
|
||||
"component_type": component.type,
|
||||
"catalog_version": catalog.version,
|
||||
"fields": [
|
||||
describe_field(component, groups[key], field)
|
||||
for key in dict.fromkeys(group_keys)
|
||||
for field in groups[key].fields
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _match_score(field: MaterialField, query: str) -> int:
|
||||
"""名称、别名完全一致最相关,其次是互相包含、路径末段一致,最后是说明中出现"""
|
||||
names = [name.casefold() for name in (field.label, *field.aliases)]
|
||||
if query in names:
|
||||
return 4
|
||||
if any(query in name or name in query for name in names):
|
||||
return 3
|
||||
if query == field.path.rsplit(".", 1)[-1].casefold():
|
||||
return 2
|
||||
if query in field.description.casefold() or query in field.path.casefold():
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def search_fields(
|
||||
catalog: ComponentCatalog, component_type: str, query: str, limit: int = 8
|
||||
) -> dict:
|
||||
"""按名称、别名、路径和说明搜索字段,按相关度返回"""
|
||||
component = find_material(catalog, component_type)
|
||||
keyword = query.strip().casefold()
|
||||
if not keyword:
|
||||
raise ValueError("搜索内容不能为空")
|
||||
scored = [
|
||||
(score, index, group, field)
|
||||
for index, (group, field) in enumerate(
|
||||
(group, field)
|
||||
for group in resolve_field_groups(catalog, component)
|
||||
for field in group.fields
|
||||
)
|
||||
if (score := _match_score(field, keyword))
|
||||
]
|
||||
scored.sort(key=lambda item: (-item[0], item[1]))
|
||||
return {
|
||||
"component_type": component.type,
|
||||
"catalog_version": catalog.version,
|
||||
"query": query,
|
||||
"fields": [
|
||||
describe_field(component, group, field)
|
||||
for _, _, group, field in scored[:limit]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def check_update_field(catalog: ComponentCatalog | None, node_type: str, action) -> None:
|
||||
"""update 只能修改字段定义中的可编辑字段,值须符合字段类型和取值约束
|
||||
|
||||
不合法时抛 ValueError,路径不存在时附上可能对应的字段供修正参考
|
||||
"""
|
||||
if catalog is None:
|
||||
raise ValueError("物料目录未载入,无法确认组件有哪些字段")
|
||||
component = find_material(catalog, node_type)
|
||||
fields = {
|
||||
field.path: field
|
||||
for group in resolve_field_groups(catalog, component)
|
||||
for field in group.fields
|
||||
}
|
||||
path = action.field_path
|
||||
field = fields.get(path)
|
||||
if field is None:
|
||||
message = f"{node_type} 没有字段 {path},只能修改字段定义中的路径"
|
||||
keyword = next(
|
||||
(str(item) for item in reversed(action.path) if isinstance(item, str)),
|
||||
action.section,
|
||||
)
|
||||
if similar := search_fields(catalog, node_type, keyword, limit=3)["fields"]:
|
||||
message += ";可能是:" + "、".join(
|
||||
f"{item['label']}({item['path']})" for item in similar
|
||||
)
|
||||
raise ValueError(message)
|
||||
if not field.editable:
|
||||
raise ValueError(f"字段 {path}({field.label})不允许修改")
|
||||
if error := field.value_error(action.value):
|
||||
raise ValueError(f"字段 {path}({field.label}){error}")
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"order": 2,
|
||||
"key": "area-chart",
|
||||
"type": "area-chart",
|
||||
"name": "面积图",
|
||||
"description": "折线下方填充面积,展示数值随时间的变化趋势并突出总量",
|
||||
"tags": [
|
||||
"charts",
|
||||
"面积图",
|
||||
"area-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:graph-new-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-grid",
|
||||
"chart-axis",
|
||||
"chart-line-series",
|
||||
{
|
||||
"key": "area-fill",
|
||||
"label": "面积",
|
||||
"description": "折线下方填充区域",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.areaStyle.color",
|
||||
"label": "面积色",
|
||||
"description": "折线下方填充区域的颜色,通常带透明度,例如 rgba(64, 158, 255, 0.25)",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"填充色",
|
||||
"面积颜色",
|
||||
"阴影颜色"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-xy"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "area-chart",
|
||||
"name": "面积图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "成交额趋势",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "axis"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"date": "周一",
|
||||
"revenue": 120
|
||||
},
|
||||
{
|
||||
"date": "周二",
|
||||
"revenue": 200
|
||||
},
|
||||
{
|
||||
"date": "周三",
|
||||
"revenue": 150
|
||||
},
|
||||
{
|
||||
"date": "周四",
|
||||
"revenue": 260
|
||||
},
|
||||
{
|
||||
"date": "周五",
|
||||
"revenue": 330
|
||||
},
|
||||
{
|
||||
"date": "周六",
|
||||
"revenue": 420
|
||||
},
|
||||
{
|
||||
"date": "周日",
|
||||
"revenue": 510
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 86,
|
||||
"right": 24,
|
||||
"bottom": 32,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "category",
|
||||
"boundaryGap": false,
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "成交额",
|
||||
"type": "line",
|
||||
"smooth": true,
|
||||
"symbolSize": 8,
|
||||
"encode": {
|
||||
"x": "date",
|
||||
"y": "revenue"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": 3,
|
||||
"color": "#409eff"
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#409eff"
|
||||
},
|
||||
"areaStyle": {
|
||||
"color": "rgba(64, 158, 255, 0.25)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
{
|
||||
"order": 0,
|
||||
"key": "bar-chart",
|
||||
"type": "bar-chart",
|
||||
"name": "柱状图",
|
||||
"description": "按类目对比数值大小,适合月度、地区、产品等分类数据的对比",
|
||||
"tags": [
|
||||
"charts",
|
||||
"柱状图",
|
||||
"bar-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:chart-2-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-grid",
|
||||
"chart-axis",
|
||||
{
|
||||
"key": "bar-series",
|
||||
"label": "柱形",
|
||||
"description": "柱子的颜色、宽度、圆角、系列名称和数值标签",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.itemStyle.color",
|
||||
"label": "柱颜色",
|
||||
"description": "柱子的填充颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"柱子颜色",
|
||||
"主色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.name",
|
||||
"label": "系列名称",
|
||||
"description": "这组柱子的名称,显示在图例和鼠标悬停提示中;不是图表标题",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"图例名称",
|
||||
"系列名",
|
||||
"数据名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.barWidth",
|
||||
"label": "柱宽",
|
||||
"description": "柱子宽度,百分比表示占每个类目宽度的比例,例如 45%",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"柱子宽度",
|
||||
"柱子粗细"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.itemStyle.borderRadius",
|
||||
"label": "柱圆角",
|
||||
"description": "柱子四个角的圆角半径,顺序为左上、右上、右下、左下",
|
||||
"value_type": "array",
|
||||
"item_type": "number",
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"圆角"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.show",
|
||||
"label": "数值标签",
|
||||
"description": "是否在每根柱子上显示数值",
|
||||
"value_type": "boolean",
|
||||
"default": false,
|
||||
"aliases": [
|
||||
"显示数值",
|
||||
"数据标签",
|
||||
"标签"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.position",
|
||||
"label": "标签位置",
|
||||
"description": "数值标签相对柱子的位置,开启数值标签后生效",
|
||||
"value_type": "enum",
|
||||
"default": "inside",
|
||||
"options": [
|
||||
{
|
||||
"value": "top",
|
||||
"label": "柱顶上方"
|
||||
},
|
||||
{
|
||||
"value": "inside",
|
||||
"label": "柱内居中"
|
||||
},
|
||||
{
|
||||
"value": "insideTop",
|
||||
"label": "柱内顶端"
|
||||
},
|
||||
{
|
||||
"value": "insideBottom",
|
||||
"label": "柱内底端"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"数值位置"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-xy"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "bar-chart",
|
||||
"name": "柱状图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "月度销售额",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"month": "一月",
|
||||
"sales": 120
|
||||
},
|
||||
{
|
||||
"month": "二月",
|
||||
"sales": 200
|
||||
},
|
||||
{
|
||||
"month": "三月",
|
||||
"sales": 150
|
||||
},
|
||||
{
|
||||
"month": "四月",
|
||||
"sales": 80
|
||||
},
|
||||
{
|
||||
"month": "五月",
|
||||
"sales": 170
|
||||
},
|
||||
{
|
||||
"month": "六月",
|
||||
"sales": 240
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 86,
|
||||
"right": 24,
|
||||
"bottom": 32,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "category",
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "销售额",
|
||||
"type": "bar",
|
||||
"barWidth": "45%",
|
||||
"encode": {
|
||||
"x": "month",
|
||||
"y": "sales"
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#409eff",
|
||||
"borderRadius": [
|
||||
4,
|
||||
4,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
{
|
||||
"order": 11,
|
||||
"key": "bar-line-chart",
|
||||
"type": "bar-line-chart",
|
||||
"name": "柱线混合图",
|
||||
"description": "柱子展示数量、折线展示比率,左右两个纵轴分别对应两种数值",
|
||||
"tags": [
|
||||
"charts",
|
||||
"柱线混合图",
|
||||
"bar-line-chart",
|
||||
"双轴",
|
||||
"混合",
|
||||
"增长率"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:chart-2-linear",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作",
|
||||
"柱系列使用左轴,折线系列 yAxisIndex=1 使用右轴",
|
||||
"两个系列 encode.x 需保持一致"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-grid",
|
||||
"chart-palette",
|
||||
{
|
||||
"key": "bar-line-series",
|
||||
"label": "系列",
|
||||
"description": "柱系列和折线系列的颜色、名称和样式",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.color.0",
|
||||
"label": "柱颜色",
|
||||
"description": "柱子的颜色",
|
||||
"value_type": "color",
|
||||
"span": 12,
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"主色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.color.1",
|
||||
"label": "线颜色",
|
||||
"description": "折线的颜色",
|
||||
"value_type": "color",
|
||||
"span": 12,
|
||||
"aliases": [
|
||||
"折线颜色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.1.smooth",
|
||||
"label": "平滑曲线",
|
||||
"description": "开启后折线为平滑曲线,关闭后为折角直线",
|
||||
"value_type": "boolean",
|
||||
"aliases": [
|
||||
"曲线",
|
||||
"平滑"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.name",
|
||||
"label": "柱系列名称",
|
||||
"description": "柱子系列的名称,显示在图例和鼠标悬停提示中",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"柱子名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.1.name",
|
||||
"label": "线系列名称",
|
||||
"description": "折线系列的名称,显示在图例和鼠标悬停提示中",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"折线名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.barWidth",
|
||||
"label": "柱宽",
|
||||
"description": "柱子宽度,百分比表示占每个类目宽度的比例,例如 40%",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"柱子宽度"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "bar-line-axis",
|
||||
"label": "坐标轴",
|
||||
"description": "左纵轴对应柱子数值,右纵轴对应折线数值",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.yAxis.0.name",
|
||||
"label": "左轴名称",
|
||||
"description": "左侧纵轴(柱子数值)顶部显示的名称",
|
||||
"value_type": "string",
|
||||
"span": 12,
|
||||
"aliases": [
|
||||
"左轴标题"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.yAxis.1.name",
|
||||
"label": "右轴名称",
|
||||
"description": "右侧纵轴(折线数值)顶部显示的名称",
|
||||
"value_type": "string",
|
||||
"span": 12,
|
||||
"aliases": [
|
||||
"右轴标题"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.yAxis.1.axisLabel.formatter",
|
||||
"label": "右轴格式",
|
||||
"description": "右侧纵轴刻度的显示格式,{value} 代表刻度值,例如 {value}%",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"右轴单位"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.xAxis.axisLabel.color",
|
||||
"label": "X轴文字色",
|
||||
"description": "横轴类目文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"横轴文字颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "bar-line-data",
|
||||
"label": "数据映射",
|
||||
"description": "柱线混合图从数据集(dataset.source)中取哪些列",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.encode.x",
|
||||
"label": "X字段",
|
||||
"description": "横轴类目使用数据集中的哪一列;两个系列必须相同,修改时同时修改折线的X字段",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"横轴字段",
|
||||
"类目字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.1.encode.x",
|
||||
"label": "折线X字段",
|
||||
"description": "须与X字段保持一致,修改X字段时同步修改",
|
||||
"value_type": "string",
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.encode.y",
|
||||
"label": "柱数值字段",
|
||||
"description": "柱子高度使用数据集中的哪一列",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"柱子字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.1.encode.y",
|
||||
"label": "线数值字段",
|
||||
"description": "折线数值使用数据集中的哪一列",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"折线字段"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "bar-line-chart",
|
||||
"name": "柱线混合图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"color": [
|
||||
"#409eff",
|
||||
"#e6a23c"
|
||||
],
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "销售额与增长率",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "axis"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"month": "一月",
|
||||
"sales": 120,
|
||||
"growth": 12.5
|
||||
},
|
||||
{
|
||||
"month": "二月",
|
||||
"sales": 200,
|
||||
"growth": 18.2
|
||||
},
|
||||
{
|
||||
"month": "三月",
|
||||
"sales": 150,
|
||||
"growth": -4.3
|
||||
},
|
||||
{
|
||||
"month": "四月",
|
||||
"sales": 80,
|
||||
"growth": -20.1
|
||||
},
|
||||
{
|
||||
"month": "五月",
|
||||
"sales": 170,
|
||||
"growth": 9.6
|
||||
},
|
||||
{
|
||||
"month": "六月",
|
||||
"sales": 240,
|
||||
"growth": 21.4
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 86,
|
||||
"right": 48,
|
||||
"bottom": 32,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "category",
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"yAxis": [
|
||||
{
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
},
|
||||
"name": "销售额",
|
||||
"nameTextStyle": {
|
||||
"color": "#a3a6ad"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc",
|
||||
"formatter": "{value}%"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false
|
||||
},
|
||||
"name": "增长率",
|
||||
"nameTextStyle": {
|
||||
"color": "#a3a6ad"
|
||||
}
|
||||
}
|
||||
],
|
||||
"series": [
|
||||
{
|
||||
"name": "销售额",
|
||||
"type": "bar",
|
||||
"barWidth": "40%",
|
||||
"encode": {
|
||||
"x": "month",
|
||||
"y": "sales"
|
||||
},
|
||||
"itemStyle": {
|
||||
"borderRadius": [
|
||||
4,
|
||||
4,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "增长率",
|
||||
"type": "line",
|
||||
"yAxisIndex": 1,
|
||||
"smooth": true,
|
||||
"symbolSize": 6,
|
||||
"encode": {
|
||||
"x": "month",
|
||||
"y": "growth"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
[
|
||||
{
|
||||
"key": "node-base",
|
||||
"label": "基础",
|
||||
"description": "所有组件共有的名称、位置和尺寸",
|
||||
"fields": [
|
||||
{
|
||||
"path": "name",
|
||||
"label": "组件名称",
|
||||
"description": "组件在编辑器图层列表和属性面板标题栏中的名称,用于编辑时识别组件,不显示在大屏画面上;图表画面上显示的名字是标题",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"组件名",
|
||||
"图层名",
|
||||
"图层名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "layout.x",
|
||||
"label": "X",
|
||||
"description": "组件左上角距画布左边缘的水平距离",
|
||||
"value_type": "number",
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"横坐标",
|
||||
"水平位置",
|
||||
"左边距离"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "layout.y",
|
||||
"label": "Y",
|
||||
"description": "组件左上角距画布上边缘的垂直距离",
|
||||
"value_type": "number",
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"纵坐标",
|
||||
"垂直位置",
|
||||
"上边距离"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "layout.width",
|
||||
"label": "宽度",
|
||||
"description": "组件的宽度",
|
||||
"value_type": "number",
|
||||
"minimum": 1,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"宽",
|
||||
"横向尺寸"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "layout.height",
|
||||
"label": "高度",
|
||||
"description": "组件的高度",
|
||||
"value_type": "number",
|
||||
"minimum": 1,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"高",
|
||||
"纵向尺寸"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-title",
|
||||
"label": "标题",
|
||||
"description": "图表上方显示的标题文字及其颜色、字号、对齐",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.title.text",
|
||||
"label": "标题",
|
||||
"description": "显示在图表上方的标题文字,是用户在大屏上看到的图表名字",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"名字",
|
||||
"名称",
|
||||
"图表名称",
|
||||
"图表标题",
|
||||
"标题文字"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.title.textStyle.color",
|
||||
"label": "标题色",
|
||||
"description": "标题文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"标题颜色",
|
||||
"标题文字颜色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.title.left",
|
||||
"label": "对齐",
|
||||
"description": "标题在组件内的水平位置",
|
||||
"value_type": "enum",
|
||||
"options": [
|
||||
{
|
||||
"value": "left",
|
||||
"label": "左对齐"
|
||||
},
|
||||
{
|
||||
"value": "center",
|
||||
"label": "居中"
|
||||
},
|
||||
{
|
||||
"value": "right",
|
||||
"label": "右对齐"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"标题对齐",
|
||||
"标题位置"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.title.textStyle.fontSize",
|
||||
"label": "标题字号",
|
||||
"description": "标题文字的大小",
|
||||
"value_type": "number",
|
||||
"minimum": 8,
|
||||
"maximum": 72,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"标题大小",
|
||||
"标题字体大小"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.title.show",
|
||||
"label": "显示标题",
|
||||
"description": "是否显示标题,关闭后整个标题隐藏",
|
||||
"value_type": "boolean",
|
||||
"default": true,
|
||||
"aliases": [
|
||||
"隐藏标题",
|
||||
"去掉标题"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-legend",
|
||||
"label": "图例",
|
||||
"description": "标题下方列出各系列名称和颜色的图例",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.legend.show",
|
||||
"label": "图例显示",
|
||||
"description": "是否显示图例;图例列出各系列的名称和颜色",
|
||||
"value_type": "boolean",
|
||||
"aliases": [
|
||||
"图例",
|
||||
"显示图例",
|
||||
"隐藏图例"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.legend.textStyle.color",
|
||||
"label": "图例文字色",
|
||||
"description": "图例中系列名称文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"图例颜色",
|
||||
"图例文字颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-grid",
|
||||
"label": "边距",
|
||||
"description": "绘图区(坐标轴所在区域)与组件四边的距离",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.grid.top",
|
||||
"label": "上边距",
|
||||
"description": "绘图区距组件上边缘的距离;默认为标题和图例留出位置,调小可能与它们重叠",
|
||||
"value_type": "number",
|
||||
"minimum": 0,
|
||||
"unit": "px",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.grid.right",
|
||||
"label": "右边距",
|
||||
"description": "绘图区距组件右边缘的距离",
|
||||
"value_type": "number",
|
||||
"minimum": 0,
|
||||
"unit": "px",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.grid.bottom",
|
||||
"label": "下边距",
|
||||
"description": "绘图区距组件下边缘的距离,横轴文字显示在这里",
|
||||
"value_type": "number",
|
||||
"minimum": 0,
|
||||
"unit": "px",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.grid.left",
|
||||
"label": "左边距",
|
||||
"description": "绘图区距组件左边缘的距离,纵轴文字显示在这里",
|
||||
"value_type": "number",
|
||||
"minimum": 0,
|
||||
"unit": "px",
|
||||
"span": 12
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-axis",
|
||||
"label": "坐标轴",
|
||||
"description": "直角坐标图表的横轴、纵轴和网格线颜色",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.xAxis.axisLabel.color",
|
||||
"label": "X轴文字色",
|
||||
"description": "横轴刻度文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"横轴文字颜色",
|
||||
"X轴标签颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.xAxis.axisLine.lineStyle.color",
|
||||
"label": "X轴线颜色",
|
||||
"description": "横轴轴线的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"横轴颜色",
|
||||
"坐标轴颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.yAxis.axisLabel.color",
|
||||
"label": "Y轴文字色",
|
||||
"description": "纵轴刻度文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"纵轴文字颜色",
|
||||
"Y轴标签颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.yAxis.splitLine.lineStyle.color",
|
||||
"label": "网格线颜色",
|
||||
"description": "绘图区内横向分隔线的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"分隔线颜色",
|
||||
"背景线颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-palette",
|
||||
"label": "配色",
|
||||
"description": "各系列或数据项按顺序依次取用的颜色列表",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.color",
|
||||
"label": "调色盘",
|
||||
"description": "颜色列表,第 1 个系列或数据项用第 1 个颜色,依次类推;整体换一套配色时修改它,只改某一项时用对应的单项颜色字段",
|
||||
"value_type": "array",
|
||||
"item_type": "color",
|
||||
"aliases": [
|
||||
"配色",
|
||||
"配色方案",
|
||||
"整体颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-encode-xy",
|
||||
"label": "数据映射",
|
||||
"description": "图表从数据集(dataset.source)中取哪一列作为横轴和纵轴",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.encode.x",
|
||||
"label": "X字段",
|
||||
"description": "横轴使用数据集中的哪一列,必须是 dataset.source 中存在的字段名",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"横轴字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.encode.y",
|
||||
"label": "Y字段",
|
||||
"description": "纵轴使用数据集中的哪一列,必须是 dataset.source 中存在的字段名",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"纵轴字段"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-encode-item",
|
||||
"label": "数据映射",
|
||||
"description": "图表从数据集(dataset.source)中取哪一列作为数据项名称和数值",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.encode.itemName",
|
||||
"label": "名称字段",
|
||||
"description": "每个数据项(扇区、层级等)的名称使用数据集中的哪一列,必须是 dataset.source 中存在的字段名",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"类目字段",
|
||||
"分类字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.encode.value",
|
||||
"label": "数值字段",
|
||||
"description": "每个数据项的数值使用数据集中的哪一列,必须是 dataset.source 中存在的字段名",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"值字段"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-line-series",
|
||||
"label": "线条",
|
||||
"description": "折线的颜色、粗细、平滑度、数据点和数值标签",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.lineStyle.color",
|
||||
"label": "线颜色",
|
||||
"description": "折线的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"折线颜色",
|
||||
"主色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.name",
|
||||
"label": "系列名称",
|
||||
"description": "这条折线的名称,显示在图例和鼠标悬停提示中;不是图表标题",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"图例名称",
|
||||
"系列名",
|
||||
"数据名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.smooth",
|
||||
"label": "平滑曲线",
|
||||
"description": "开启后折线为平滑曲线,关闭后为折角直线",
|
||||
"value_type": "boolean",
|
||||
"aliases": [
|
||||
"曲线",
|
||||
"平滑"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.lineStyle.width",
|
||||
"label": "线宽",
|
||||
"description": "折线的粗细",
|
||||
"value_type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 12,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"线条粗细",
|
||||
"线粗细"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.itemStyle.color",
|
||||
"label": "数据点颜色",
|
||||
"description": "折线上每个数据点(拐点)的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"拐点颜色",
|
||||
"圆点颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.symbolSize",
|
||||
"label": "数据点大小",
|
||||
"description": "折线上数据点(拐点)的直径",
|
||||
"value_type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 30,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"拐点大小",
|
||||
"圆点大小"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.show",
|
||||
"label": "数值标签",
|
||||
"description": "是否在每个数据点上方显示数值",
|
||||
"value_type": "boolean",
|
||||
"default": false,
|
||||
"aliases": [
|
||||
"显示数值",
|
||||
"数据标签",
|
||||
"标签"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-item-series",
|
||||
"label": "数据项",
|
||||
"description": "饼图、漏斗图等按数据项着色的图表的主色、系列名称和数据项标签",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.color.0",
|
||||
"label": "主色",
|
||||
"description": "调色盘的第一个颜色,用于第一个数据项(扇区或层级);其余数据项依次取调色盘后面的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"第一个颜色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.name",
|
||||
"label": "系列名称",
|
||||
"description": "这组数据的名称,显示在鼠标悬停提示中;不是图表标题",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"系列名",
|
||||
"数据名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.show",
|
||||
"label": "数据项标签",
|
||||
"description": "是否在每个数据项旁显示名称标签",
|
||||
"value_type": "boolean",
|
||||
"default": true,
|
||||
"aliases": [
|
||||
"标签",
|
||||
"显示标签",
|
||||
"扇区标签"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.color",
|
||||
"label": "标签文字色",
|
||||
"description": "数据项名称标签的文字颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"标签颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "chart-gauge-value",
|
||||
"label": "数值",
|
||||
"description": "仪表类图表的量程、进度颜色和中心数值显示",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.progress.itemStyle.color",
|
||||
"label": "进度色",
|
||||
"description": "进度弧的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"主色",
|
||||
"进度条颜色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.min",
|
||||
"label": "最小值",
|
||||
"description": "量程起点,进度从这个值开始计算",
|
||||
"value_type": "number",
|
||||
"span": 12,
|
||||
"aliases": [
|
||||
"量程最小值",
|
||||
"起始值"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.max",
|
||||
"label": "最大值",
|
||||
"description": "量程终点,数值达到它时进度为满",
|
||||
"value_type": "number",
|
||||
"span": 12,
|
||||
"aliases": [
|
||||
"量程最大值",
|
||||
"目标值",
|
||||
"满值"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.detail.formatter",
|
||||
"label": "数值格式",
|
||||
"description": "中心数值的显示格式,{value} 代表当前值,例如 {value}%",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"数值单位",
|
||||
"显示格式"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.detail.color",
|
||||
"label": "数值颜色",
|
||||
"description": "中心数值文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"数字颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.detail.fontSize",
|
||||
"label": "数值字号",
|
||||
"description": "中心数值文字的大小",
|
||||
"value_type": "number",
|
||||
"minimum": 8,
|
||||
"maximum": 96,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"数字大小"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,201 @@
|
||||
{
|
||||
"order": 8,
|
||||
"key": "funnel-chart",
|
||||
"type": "funnel-chart",
|
||||
"name": "漏斗图",
|
||||
"description": "展示流程各阶段的数量和逐级转化情况",
|
||||
"tags": [
|
||||
"charts",
|
||||
"漏斗图",
|
||||
"funnel-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:filter-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-palette",
|
||||
"chart-item-series",
|
||||
{
|
||||
"key": "funnel-shape",
|
||||
"label": "漏斗",
|
||||
"description": "漏斗的排序和层间距",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.sort",
|
||||
"label": "排序",
|
||||
"description": "各层按数值排列的方向",
|
||||
"value_type": "enum",
|
||||
"options": [
|
||||
{
|
||||
"value": "descending",
|
||||
"label": "从大到小(正漏斗)"
|
||||
},
|
||||
{
|
||||
"value": "ascending",
|
||||
"label": "从小到大(倒漏斗)"
|
||||
},
|
||||
{
|
||||
"value": "none",
|
||||
"label": "按数据顺序"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"方向",
|
||||
"顺序"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.gap",
|
||||
"label": "层间距",
|
||||
"description": "相邻两层之间的间隔",
|
||||
"value_type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 40,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"间隔",
|
||||
"间距"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-item"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "funnel-chart",
|
||||
"name": "漏斗图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 280
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"color": [
|
||||
"#409eff",
|
||||
"#67c23a",
|
||||
"#e6a23c",
|
||||
"#f56c6c",
|
||||
"#909399"
|
||||
],
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "转化漏斗",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "item"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"stage": "访问",
|
||||
"value": 100
|
||||
},
|
||||
{
|
||||
"stage": "咨询",
|
||||
"value": 72
|
||||
},
|
||||
{
|
||||
"stage": "下单",
|
||||
"value": 48
|
||||
},
|
||||
{
|
||||
"stage": "成交",
|
||||
"value": 26
|
||||
}
|
||||
]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "转化",
|
||||
"type": "funnel",
|
||||
"top": 72,
|
||||
"bottom": 16,
|
||||
"left": "12%",
|
||||
"width": "76%",
|
||||
"minSize": "16%",
|
||||
"maxSize": "100%",
|
||||
"sort": "descending",
|
||||
"gap": 4,
|
||||
"label": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"itemStyle": {
|
||||
"borderColor": "transparent"
|
||||
},
|
||||
"encode": {
|
||||
"itemName": "stage",
|
||||
"value": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"order": 9,
|
||||
"key": "gauge-chart",
|
||||
"type": "gauge-chart",
|
||||
"name": "仪表盘",
|
||||
"description": "用指针和进度弧展示单个指标的当前值或完成率",
|
||||
"tags": [
|
||||
"charts",
|
||||
"仪表盘",
|
||||
"gauge-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:speedometer-middle-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-gauge-value",
|
||||
{
|
||||
"key": "gauge-dial",
|
||||
"label": "表盘",
|
||||
"description": "仪表盘的指针、刻度和表盘宽度",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.pointer.show",
|
||||
"label": "显示指针",
|
||||
"description": "是否显示指针",
|
||||
"value_type": "boolean",
|
||||
"default": true,
|
||||
"aliases": [
|
||||
"指针"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.pointer.itemStyle.color",
|
||||
"label": "指针颜色",
|
||||
"description": "指针的颜色",
|
||||
"value_type": "color",
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.axisLabel.color",
|
||||
"label": "刻度文字色",
|
||||
"description": "表盘刻度数字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"刻度颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.axisLine.lineStyle.width",
|
||||
"label": "表盘宽度",
|
||||
"description": "表盘底弧的粗细",
|
||||
"value_type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 60,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"底弧宽度"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.progress.width",
|
||||
"label": "进度宽度",
|
||||
"description": "进度弧的粗细",
|
||||
"value_type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 60,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"进度条宽度"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-item"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "gauge-chart",
|
||||
"name": "仪表盘",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 320,
|
||||
"height": 280
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"title": {
|
||||
"text": "销售目标完成率",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "item"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"name": "销售目标完成率",
|
||||
"value": 72
|
||||
}
|
||||
]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"type": "gauge",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"startAngle": 210,
|
||||
"endAngle": -30,
|
||||
"center": [
|
||||
"50%",
|
||||
"58%"
|
||||
],
|
||||
"radius": "80%",
|
||||
"progress": {
|
||||
"show": true,
|
||||
"width": 12,
|
||||
"itemStyle": {
|
||||
"color": "#409eff"
|
||||
}
|
||||
},
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"width": 12,
|
||||
"color": [
|
||||
[
|
||||
1,
|
||||
"rgba(255, 255, 255, 0.1)"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": false
|
||||
},
|
||||
"splitLine": {
|
||||
"length": 8,
|
||||
"lineStyle": {
|
||||
"width": 1,
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc",
|
||||
"distance": 16
|
||||
},
|
||||
"pointer": {
|
||||
"itemStyle": {
|
||||
"color": "#e5eaf3"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"offsetCenter": [
|
||||
0,
|
||||
"72%"
|
||||
],
|
||||
"color": "#cfd3dc",
|
||||
"fontSize": 13
|
||||
},
|
||||
"detail": {
|
||||
"valueAnimation": true,
|
||||
"offsetCenter": [
|
||||
0,
|
||||
"46%"
|
||||
],
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 28,
|
||||
"formatter": "{value}%"
|
||||
},
|
||||
"encode": {
|
||||
"itemName": "name",
|
||||
"value": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
{
|
||||
"order": 1,
|
||||
"key": "hbar-chart",
|
||||
"type": "hbar-chart",
|
||||
"name": "条形图",
|
||||
"description": "横向条形对比各类目数值,类目名称较长或类目较多时比柱状图更易读",
|
||||
"tags": [
|
||||
"charts",
|
||||
"条形图",
|
||||
"hbar-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:align-left-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-grid",
|
||||
{
|
||||
"key": "hbar-axis",
|
||||
"label": "坐标轴",
|
||||
"description": "条形图的横轴为数值轴、纵轴为类目轴",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.xAxis.axisLabel.color",
|
||||
"label": "X轴文字色",
|
||||
"description": "横轴(数值)刻度文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"横轴文字颜色",
|
||||
"数值轴文字颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.xAxis.splitLine.lineStyle.color",
|
||||
"label": "网格线颜色",
|
||||
"description": "绘图区内纵向分隔线的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"分隔线颜色",
|
||||
"背景线颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.yAxis.axisLabel.color",
|
||||
"label": "Y轴文字色",
|
||||
"description": "纵轴类目名称文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"纵轴文字颜色",
|
||||
"类目文字颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.yAxis.axisLine.lineStyle.color",
|
||||
"label": "Y轴线颜色",
|
||||
"description": "纵轴轴线的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"纵轴颜色",
|
||||
"坐标轴颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "hbar-series",
|
||||
"label": "条形",
|
||||
"description": "条形的颜色、粗细、圆角、系列名称和数值标签",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.itemStyle.color",
|
||||
"label": "条颜色",
|
||||
"description": "条形的填充颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"条形颜色",
|
||||
"主色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.name",
|
||||
"label": "系列名称",
|
||||
"description": "这组条形的名称,显示在图例和鼠标悬停提示中;不是图表标题",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"图例名称",
|
||||
"系列名",
|
||||
"数据名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.barWidth",
|
||||
"label": "条宽",
|
||||
"description": "条形粗细,百分比表示占每个类目高度的比例,例如 45%",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"条形宽度",
|
||||
"条形粗细"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.itemStyle.borderRadius",
|
||||
"label": "条圆角",
|
||||
"description": "条形四个角的圆角半径,顺序为左上、右上、右下、左下,默认只有右端圆角",
|
||||
"value_type": "array",
|
||||
"item_type": "number",
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"圆角"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.show",
|
||||
"label": "数值标签",
|
||||
"description": "是否在每个条形上显示数值",
|
||||
"value_type": "boolean",
|
||||
"default": false,
|
||||
"aliases": [
|
||||
"显示数值",
|
||||
"数据标签",
|
||||
"标签"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.position",
|
||||
"label": "标签位置",
|
||||
"description": "数值标签相对条形的位置,开启数值标签后生效",
|
||||
"value_type": "enum",
|
||||
"default": "inside",
|
||||
"options": [
|
||||
{
|
||||
"value": "right",
|
||||
"label": "条形右侧"
|
||||
},
|
||||
{
|
||||
"value": "inside",
|
||||
"label": "条内居中"
|
||||
},
|
||||
{
|
||||
"value": "insideRight",
|
||||
"label": "条内右端"
|
||||
},
|
||||
{
|
||||
"value": "insideLeft",
|
||||
"label": "条内左端"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"数值位置"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-xy"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "hbar-chart",
|
||||
"name": "条形图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "地区销售额",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"region": "华东",
|
||||
"sales": 320
|
||||
},
|
||||
{
|
||||
"region": "华南",
|
||||
"sales": 240
|
||||
},
|
||||
{
|
||||
"region": "华北",
|
||||
"sales": 210
|
||||
},
|
||||
{
|
||||
"region": "西南",
|
||||
"sales": 180
|
||||
},
|
||||
{
|
||||
"region": "西北",
|
||||
"sales": 130
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 86,
|
||||
"right": 24,
|
||||
"bottom": 24,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "category",
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "销售额",
|
||||
"type": "bar",
|
||||
"barWidth": "45%",
|
||||
"encode": {
|
||||
"x": "sales",
|
||||
"y": "region"
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#409eff",
|
||||
"borderRadius": [
|
||||
0,
|
||||
4,
|
||||
4,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
{
|
||||
"order": 14,
|
||||
"key": "heatmap-chart",
|
||||
"type": "heatmap-chart",
|
||||
"name": "热力图",
|
||||
"description": "用格子颜色深浅展示两个类目维度交叉处的数值大小",
|
||||
"tags": [
|
||||
"charts",
|
||||
"热力图",
|
||||
"heatmap-chart",
|
||||
"热力",
|
||||
"分布",
|
||||
"时段"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:widget-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作",
|
||||
"数据源每行一个格子,三列分别对应 X、Y、数值,数值列需放在最后一列(visualMap 默认映射最后一个维度)",
|
||||
"visualMap.min / max 需按实际数据范围调整",
|
||||
"label.formatter 用 {@字段名} 引用数值列,修改数值字段时需同步修改"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-grid",
|
||||
{
|
||||
"key": "heatmap-axis",
|
||||
"label": "坐标轴",
|
||||
"description": "热力图的横轴和纵轴都是类目轴",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.xAxis.axisLabel.color",
|
||||
"label": "X轴文字色",
|
||||
"description": "横轴类目文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"横轴文字颜色"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.yAxis.axisLabel.color",
|
||||
"label": "Y轴文字色",
|
||||
"description": "纵轴类目文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"纵轴文字颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "heatmap-visual",
|
||||
"label": "色阶",
|
||||
"description": "数值到格子颜色的映射,数值越大颜色越接近高值颜色",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.visualMap.min",
|
||||
"label": "最小值",
|
||||
"description": "色阶的最小值,需按实际数据范围调整",
|
||||
"value_type": "number",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.visualMap.max",
|
||||
"label": "最大值",
|
||||
"description": "色阶的最大值,需按实际数据范围调整",
|
||||
"value_type": "number",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.visualMap.inRange.color.0",
|
||||
"label": "低值颜色",
|
||||
"description": "最小值对应的格子颜色",
|
||||
"value_type": "color",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.visualMap.inRange.color.4",
|
||||
"label": "高值颜色",
|
||||
"description": "最大值对应的格子颜色",
|
||||
"value_type": "color",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.visualMap.inRange.color",
|
||||
"label": "色阶颜色",
|
||||
"description": "从低到高的 5 个颜色,整体换配色时修改它",
|
||||
"value_type": "array",
|
||||
"item_type": "color",
|
||||
"aliases": [
|
||||
"配色",
|
||||
"渐变色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "heatmap-label",
|
||||
"label": "数值标签",
|
||||
"description": "格子内显示的数值",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.label.show",
|
||||
"label": "显示数值",
|
||||
"description": "是否在每个格子内显示数值",
|
||||
"value_type": "boolean",
|
||||
"aliases": [
|
||||
"数值标签",
|
||||
"标签"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.formatter",
|
||||
"label": "数值格式",
|
||||
"description": "格子内数值的显示格式,用 {@字段名} 引用数值列,例如 {@value};修改数值字段时需同步修改",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"标签格式"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.label.color",
|
||||
"label": "数值文字色",
|
||||
"description": "格子内数值文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"标签颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-xy",
|
||||
{
|
||||
"key": "heatmap-value",
|
||||
"label": "数值映射",
|
||||
"description": "格子颜色使用的数值列",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.encode.value",
|
||||
"label": "数值字段",
|
||||
"description": "格子数值使用数据集中的哪一列,必须是 dataset.source 中的最后一列;修改后同步修改数值格式",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"值字段"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "heatmap-chart",
|
||||
"name": "热力图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 480,
|
||||
"height": 300
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"title": {
|
||||
"text": "访问时段分布",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"position": "top"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"slot": "0-4点",
|
||||
"day": "周一",
|
||||
"value": 8
|
||||
},
|
||||
{
|
||||
"slot": "4-8点",
|
||||
"day": "周一",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"slot": "8-12点",
|
||||
"day": "周一",
|
||||
"value": 46
|
||||
},
|
||||
{
|
||||
"slot": "12-16点",
|
||||
"day": "周一",
|
||||
"value": 62
|
||||
},
|
||||
{
|
||||
"slot": "16-20点",
|
||||
"day": "周一",
|
||||
"value": 76
|
||||
},
|
||||
{
|
||||
"slot": "20-24点",
|
||||
"day": "周一",
|
||||
"value": 84
|
||||
},
|
||||
{
|
||||
"slot": "0-4点",
|
||||
"day": "周二",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"slot": "4-8点",
|
||||
"day": "周二",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"slot": "8-12点",
|
||||
"day": "周二",
|
||||
"value": 42
|
||||
},
|
||||
{
|
||||
"slot": "12-16点",
|
||||
"day": "周二",
|
||||
"value": 58
|
||||
},
|
||||
{
|
||||
"slot": "16-20点",
|
||||
"day": "周二",
|
||||
"value": 72
|
||||
},
|
||||
{
|
||||
"slot": "20-24点",
|
||||
"day": "周二",
|
||||
"value": 80
|
||||
},
|
||||
{
|
||||
"slot": "0-4点",
|
||||
"day": "周三",
|
||||
"value": 11
|
||||
},
|
||||
{
|
||||
"slot": "4-8点",
|
||||
"day": "周三",
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"slot": "8-12点",
|
||||
"day": "周三",
|
||||
"value": 49
|
||||
},
|
||||
{
|
||||
"slot": "12-16点",
|
||||
"day": "周三",
|
||||
"value": 65
|
||||
},
|
||||
{
|
||||
"slot": "16-20点",
|
||||
"day": "周三",
|
||||
"value": 68
|
||||
},
|
||||
{
|
||||
"slot": "20-24点",
|
||||
"day": "周三",
|
||||
"value": 76
|
||||
},
|
||||
{
|
||||
"slot": "0-4点",
|
||||
"day": "周四",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"slot": "4-8点",
|
||||
"day": "周四",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"slot": "8-12点",
|
||||
"day": "周四",
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"slot": "12-16点",
|
||||
"day": "周四",
|
||||
"value": 61
|
||||
},
|
||||
{
|
||||
"slot": "16-20点",
|
||||
"day": "周四",
|
||||
"value": 75
|
||||
},
|
||||
{
|
||||
"slot": "20-24点",
|
||||
"day": "周四",
|
||||
"value": 83
|
||||
},
|
||||
{
|
||||
"slot": "0-4点",
|
||||
"day": "周五",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"slot": "4-8点",
|
||||
"day": "周五",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"slot": "8-12点",
|
||||
"day": "周五",
|
||||
"value": 52
|
||||
},
|
||||
{
|
||||
"slot": "12-16点",
|
||||
"day": "周五",
|
||||
"value": 57
|
||||
},
|
||||
{
|
||||
"slot": "16-20点",
|
||||
"day": "周五",
|
||||
"value": 71
|
||||
},
|
||||
{
|
||||
"slot": "20-24点",
|
||||
"day": "周五",
|
||||
"value": 79
|
||||
},
|
||||
{
|
||||
"slot": "0-4点",
|
||||
"day": "周六",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"slot": "4-8点",
|
||||
"day": "周六",
|
||||
"value": 11
|
||||
},
|
||||
{
|
||||
"slot": "8-12点",
|
||||
"day": "周六",
|
||||
"value": 61
|
||||
},
|
||||
{
|
||||
"slot": "12-16点",
|
||||
"day": "周六",
|
||||
"value": 81
|
||||
},
|
||||
{
|
||||
"slot": "16-20点",
|
||||
"day": "周六",
|
||||
"value": 98
|
||||
},
|
||||
{
|
||||
"slot": "20-24点",
|
||||
"day": "周六",
|
||||
"value": 97
|
||||
},
|
||||
{
|
||||
"slot": "0-4点",
|
||||
"day": "周日",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"slot": "4-8点",
|
||||
"day": "周日",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"slot": "8-12点",
|
||||
"day": "周日",
|
||||
"value": 57
|
||||
},
|
||||
{
|
||||
"slot": "12-16点",
|
||||
"day": "周日",
|
||||
"value": 77
|
||||
},
|
||||
{
|
||||
"slot": "16-20点",
|
||||
"day": "周日",
|
||||
"value": 94
|
||||
},
|
||||
{
|
||||
"slot": "20-24点",
|
||||
"day": "周日",
|
||||
"value": 104
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 48,
|
||||
"right": 24,
|
||||
"bottom": 64,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "category",
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "category",
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"inverse": true,
|
||||
"splitArea": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
"visualMap": {
|
||||
"min": 0,
|
||||
"max": 120,
|
||||
"calculable": true,
|
||||
"orient": "horizontal",
|
||||
"left": "center",
|
||||
"bottom": 4,
|
||||
"itemHeight": 120,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"inRange": {
|
||||
"color": [
|
||||
"#1d2b44",
|
||||
"#337ecc",
|
||||
"#79bbff",
|
||||
"#e6a23c",
|
||||
"#f56c6c"
|
||||
]
|
||||
}
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "访问量",
|
||||
"type": "heatmap",
|
||||
"label": {
|
||||
"show": true,
|
||||
"formatter": "{@value}",
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 11
|
||||
},
|
||||
"itemStyle": {
|
||||
"borderColor": "#141414",
|
||||
"borderWidth": 2
|
||||
},
|
||||
"encode": {
|
||||
"x": "slot",
|
||||
"y": "day",
|
||||
"value": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"order": 3,
|
||||
"key": "line-chart",
|
||||
"type": "line-chart",
|
||||
"name": "折线图",
|
||||
"description": "展示数值随时间或有序类目的变化趋势",
|
||||
"tags": [
|
||||
"charts",
|
||||
"折线图",
|
||||
"line-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:graph-up-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-grid",
|
||||
"chart-axis",
|
||||
"chart-line-series",
|
||||
"chart-encode-xy"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "line-chart",
|
||||
"name": "折线图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "访问量趋势",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "axis"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"date": "周一",
|
||||
"visits": 820
|
||||
},
|
||||
{
|
||||
"date": "周二",
|
||||
"visits": 932
|
||||
},
|
||||
{
|
||||
"date": "周三",
|
||||
"visits": 901
|
||||
},
|
||||
{
|
||||
"date": "周四",
|
||||
"visits": 934
|
||||
},
|
||||
{
|
||||
"date": "周五",
|
||||
"visits": 1290
|
||||
},
|
||||
{
|
||||
"date": "周六",
|
||||
"visits": 1330
|
||||
},
|
||||
{
|
||||
"date": "周日",
|
||||
"visits": 1320
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 86,
|
||||
"right": 24,
|
||||
"bottom": 32,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "category",
|
||||
"boundaryGap": false,
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "访问量",
|
||||
"type": "line",
|
||||
"smooth": true,
|
||||
"symbolSize": 8,
|
||||
"encode": {
|
||||
"x": "date",
|
||||
"y": "visits"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": 3,
|
||||
"color": "#409eff"
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#409eff"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
{
|
||||
"order": 13,
|
||||
"key": "pictorial-bar-chart",
|
||||
"type": "pictorial-bar-chart",
|
||||
"name": "象形柱图",
|
||||
"description": "用重复的图形堆叠成柱子对比各类目数值,视觉上更形象",
|
||||
"tags": [
|
||||
"charts",
|
||||
"象形柱图",
|
||||
"pictorial-bar-chart",
|
||||
"象形",
|
||||
"分段",
|
||||
"装饰"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:battery-charge-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-grid",
|
||||
"chart-axis",
|
||||
{
|
||||
"key": "pictorial-series",
|
||||
"label": "图形",
|
||||
"description": "组成柱子的图形样式",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.itemStyle.color",
|
||||
"label": "图形颜色",
|
||||
"description": "图形的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"主色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.symbol",
|
||||
"label": "图形",
|
||||
"description": "组成柱子的图形形状",
|
||||
"value_type": "enum",
|
||||
"options": [
|
||||
{
|
||||
"value": "rect",
|
||||
"label": "矩形"
|
||||
},
|
||||
{
|
||||
"value": "roundRect",
|
||||
"label": "圆角矩形"
|
||||
},
|
||||
{
|
||||
"value": "circle",
|
||||
"label": "圆形"
|
||||
},
|
||||
{
|
||||
"value": "triangle",
|
||||
"label": "三角形"
|
||||
},
|
||||
{
|
||||
"value": "diamond",
|
||||
"label": "菱形"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"形状"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.symbolSize.0",
|
||||
"label": "图形宽度",
|
||||
"description": "单个图形的宽度",
|
||||
"value_type": "number",
|
||||
"span": 12,
|
||||
"minimum": 1,
|
||||
"unit": "px"
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.symbolSize.1",
|
||||
"label": "图形高度",
|
||||
"description": "单个图形的高度",
|
||||
"value_type": "number",
|
||||
"span": 12,
|
||||
"minimum": 1,
|
||||
"unit": "px"
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.symbolMargin",
|
||||
"label": "图形间距",
|
||||
"description": "重复图形之间的间隔",
|
||||
"value_type": "number",
|
||||
"span": 12,
|
||||
"minimum": 0,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"间距"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.symbolRepeat",
|
||||
"label": "重复图形",
|
||||
"description": "开启后用多个小图形重复堆叠成柱子,关闭后用一个拉伸的图形",
|
||||
"value_type": "boolean",
|
||||
"span": 12,
|
||||
"aliases": [
|
||||
"重复"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.name",
|
||||
"label": "系列名称",
|
||||
"description": "这组柱子的名称,显示在鼠标悬停提示中;不是图表标题",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"系列名",
|
||||
"数据名称"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-xy"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "pictorial-bar-chart",
|
||||
"name": "象形柱图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"title": {
|
||||
"text": "各仓库库存量",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "axis",
|
||||
"axisPointer": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"warehouse": "上海仓",
|
||||
"stock": 86
|
||||
},
|
||||
{
|
||||
"warehouse": "广州仓",
|
||||
"stock": 72
|
||||
},
|
||||
{
|
||||
"warehouse": "北京仓",
|
||||
"stock": 64
|
||||
},
|
||||
{
|
||||
"warehouse": "成都仓",
|
||||
"stock": 45
|
||||
},
|
||||
{
|
||||
"warehouse": "武汉仓",
|
||||
"stock": 38
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 48,
|
||||
"right": 24,
|
||||
"bottom": 32,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "category",
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "库存",
|
||||
"type": "pictorialBar",
|
||||
"symbol": "rect",
|
||||
"symbolRepeat": true,
|
||||
"symbolSize": [
|
||||
24,
|
||||
6
|
||||
],
|
||||
"symbolMargin": 2,
|
||||
"symbolClip": true,
|
||||
"itemStyle": {
|
||||
"color": "#409eff"
|
||||
},
|
||||
"encode": {
|
||||
"x": "warehouse",
|
||||
"y": "stock"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
{
|
||||
"order": 4,
|
||||
"key": "pie-chart",
|
||||
"type": "pie-chart",
|
||||
"name": "饼图",
|
||||
"description": "展示各部分占整体的比例,适合类目不多的占比数据",
|
||||
"tags": [
|
||||
"charts",
|
||||
"饼图",
|
||||
"pie-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:pie-chart-2-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-palette",
|
||||
"chart-item-series",
|
||||
"chart-encode-item"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "pie-chart",
|
||||
"name": "饼图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"color": [
|
||||
"#409eff",
|
||||
"#67c23a",
|
||||
"#e6a23c",
|
||||
"#f56c6c",
|
||||
"#909399"
|
||||
],
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "访问来源占比",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "item"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"channel": "搜索引擎",
|
||||
"value": 1048
|
||||
},
|
||||
{
|
||||
"channel": "直接访问",
|
||||
"value": 735
|
||||
},
|
||||
{
|
||||
"channel": "社交媒体",
|
||||
"value": 484
|
||||
},
|
||||
{
|
||||
"channel": "广告投放",
|
||||
"value": 300
|
||||
}
|
||||
]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "访问来源",
|
||||
"type": "pie",
|
||||
"center": [
|
||||
"50%",
|
||||
"62%"
|
||||
],
|
||||
"avoidLabelOverlap": true,
|
||||
"label": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"labelLine": {
|
||||
"lineStyle": {
|
||||
"color": "#a3a6ad"
|
||||
}
|
||||
},
|
||||
"encode": {
|
||||
"itemName": "channel",
|
||||
"value": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"order": 15,
|
||||
"key": "progress-ring-chart",
|
||||
"type": "progress-ring-chart",
|
||||
"name": "进度环",
|
||||
"description": "用环形进度条展示单个指标的完成百分比",
|
||||
"tags": [
|
||||
"charts",
|
||||
"进度环",
|
||||
"progress-ring-chart",
|
||||
"进度",
|
||||
"完成率",
|
||||
"百分比"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:pie-chart-3-linear",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作",
|
||||
"基于 gauge 系列,数据源取第一行,前端按 encode 转成 series.data"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-gauge-value",
|
||||
{
|
||||
"key": "progress-ring-shape",
|
||||
"label": "环形",
|
||||
"description": "进度环和底环的粗细与端点",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.progress.width",
|
||||
"label": "环宽度",
|
||||
"description": "进度环的粗细",
|
||||
"value_type": "number",
|
||||
"span": 12,
|
||||
"minimum": 1,
|
||||
"maximum": 60,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"进度环宽度",
|
||||
"环的粗细"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.axisLine.lineStyle.width",
|
||||
"label": "底环宽度",
|
||||
"description": "进度环下方灰色底环的粗细,通常与环宽度相同",
|
||||
"value_type": "number",
|
||||
"span": 12,
|
||||
"minimum": 1,
|
||||
"maximum": 60,
|
||||
"unit": "px"
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.progress.roundCap",
|
||||
"label": "圆角端点",
|
||||
"description": "开启后进度环两端为圆角",
|
||||
"value_type": "boolean",
|
||||
"aliases": [
|
||||
"圆角"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-item"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "progress-ring-chart",
|
||||
"name": "进度环",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 260,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"title": {
|
||||
"text": "年度目标完成率",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "item",
|
||||
"formatter": "{b}: {c}%"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"name": "完成率",
|
||||
"value": 68
|
||||
}
|
||||
]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"type": "gauge",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"startAngle": 90,
|
||||
"endAngle": -270,
|
||||
"center": [
|
||||
"50%",
|
||||
"56%"
|
||||
],
|
||||
"radius": "72%",
|
||||
"pointer": {
|
||||
"show": false
|
||||
},
|
||||
"progress": {
|
||||
"show": true,
|
||||
"roundCap": true,
|
||||
"width": 14,
|
||||
"itemStyle": {
|
||||
"color": "#409eff"
|
||||
}
|
||||
},
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"width": 14,
|
||||
"color": [
|
||||
[
|
||||
1,
|
||||
"rgba(255, 255, 255, 0.1)"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": false
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": false
|
||||
},
|
||||
"title": {
|
||||
"offsetCenter": [
|
||||
0,
|
||||
"28%"
|
||||
],
|
||||
"color": "#cfd3dc",
|
||||
"fontSize": 13
|
||||
},
|
||||
"detail": {
|
||||
"valueAnimation": true,
|
||||
"offsetCenter": [
|
||||
0,
|
||||
"-8%"
|
||||
],
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 30,
|
||||
"fontWeight": "bold",
|
||||
"formatter": "{value}%"
|
||||
},
|
||||
"encode": {
|
||||
"itemName": "name",
|
||||
"value": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
{
|
||||
"order": 7,
|
||||
"key": "radar-chart",
|
||||
"type": "radar-chart",
|
||||
"name": "雷达图",
|
||||
"description": "在多个维度上对比一个或多个对象的得分",
|
||||
"tags": [
|
||||
"charts",
|
||||
"雷达图",
|
||||
"radar-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:radar-2-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-palette",
|
||||
{
|
||||
"key": "radar-series",
|
||||
"label": "线条",
|
||||
"description": "各对象雷达线的颜色和填充",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.color.0",
|
||||
"label": "第一条线颜色",
|
||||
"description": "第一个对象(数据集第一行)的雷达线颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"主色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.color.1",
|
||||
"label": "第二条线颜色",
|
||||
"description": "第二个对象(数据集第二行)的雷达线颜色",
|
||||
"value_type": "color"
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.lineStyle.width",
|
||||
"label": "线宽",
|
||||
"description": "雷达线的粗细",
|
||||
"value_type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 12,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"线条粗细"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.areaStyle.opacity",
|
||||
"label": "填充透明度",
|
||||
"description": "雷达线围成区域的填充透明度,0 为不填充,1 为不透明",
|
||||
"value_type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"aliases": [
|
||||
"填充",
|
||||
"透明度"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "radar-axis",
|
||||
"label": "维度",
|
||||
"description": "雷达图的各个维度及其文字样式",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.radar.indicator",
|
||||
"label": "维度",
|
||||
"description": "雷达图的维度列表,每项为 {name: 维度名称, max: 满分},顺序与数值字段一一对应",
|
||||
"value_type": "array",
|
||||
"item_type": "object",
|
||||
"aliases": [
|
||||
"指标",
|
||||
"维度名称",
|
||||
"满分"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.radar.axisName.color",
|
||||
"label": "维度文字色",
|
||||
"description": "维度名称文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"维度颜色",
|
||||
"指标文字颜色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "radar-data",
|
||||
"label": "数据映射",
|
||||
"description": "雷达图从数据集(dataset.source)中取哪些列",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.encode.itemName",
|
||||
"label": "名称字段",
|
||||
"description": "每个对象(每条雷达线)的名称使用数据集中的哪一列",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"对象字段",
|
||||
"类目字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.encode.value",
|
||||
"label": "数值字段",
|
||||
"description": "各维度的得分依次使用数据集中的哪些列,顺序与维度列表一一对应",
|
||||
"value_type": "array",
|
||||
"item_type": "string",
|
||||
"aliases": [
|
||||
"维度字段",
|
||||
"得分字段"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "radar-chart",
|
||||
"name": "雷达图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 280
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"color": [
|
||||
"#409eff",
|
||||
"#67c23a",
|
||||
"#e6a23c",
|
||||
"#f56c6c",
|
||||
"#909399"
|
||||
],
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "店铺服务评分",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {},
|
||||
"radar": {
|
||||
"center": [
|
||||
"50%",
|
||||
"62%"
|
||||
],
|
||||
"radius": "58%",
|
||||
"indicator": [
|
||||
{
|
||||
"name": "商品质量",
|
||||
"max": 100
|
||||
},
|
||||
{
|
||||
"name": "物流速度",
|
||||
"max": 100
|
||||
},
|
||||
{
|
||||
"name": "服务态度",
|
||||
"max": 100
|
||||
},
|
||||
{
|
||||
"name": "描述相符",
|
||||
"max": 100
|
||||
},
|
||||
{
|
||||
"name": "售后保障",
|
||||
"max": 100
|
||||
}
|
||||
],
|
||||
"axisName": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.12)"
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(255, 255, 255, 0.03)",
|
||||
"rgba(255, 255, 255, 0.06)"
|
||||
]
|
||||
}
|
||||
},
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.16)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"name": "店铺A",
|
||||
"sales": 80,
|
||||
"market": 70,
|
||||
"develop": 90,
|
||||
"support": 85,
|
||||
"tech": 75
|
||||
},
|
||||
{
|
||||
"name": "店铺B",
|
||||
"sales": 65,
|
||||
"market": 88,
|
||||
"develop": 72,
|
||||
"support": 78,
|
||||
"tech": 92
|
||||
}
|
||||
]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "评分",
|
||||
"type": "radar",
|
||||
"encode": {
|
||||
"itemName": "name",
|
||||
"value": [
|
||||
"sales",
|
||||
"market",
|
||||
"develop",
|
||||
"support",
|
||||
"tech"
|
||||
]
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": 2
|
||||
},
|
||||
"areaStyle": {
|
||||
"opacity": 0.18
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"order": 5,
|
||||
"key": "ring-chart",
|
||||
"type": "ring-chart",
|
||||
"name": "环形图",
|
||||
"description": "中间留空的饼图,展示各部分占整体的比例",
|
||||
"tags": [
|
||||
"charts",
|
||||
"环形图",
|
||||
"ring-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:pie-chart-3-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-palette",
|
||||
"chart-item-series",
|
||||
{
|
||||
"key": "ring-shape",
|
||||
"label": "环形",
|
||||
"description": "环形的内外半径",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.radius",
|
||||
"label": "内外半径",
|
||||
"description": "两个百分比依次为内半径和外半径,相对组件较短边的一半,例如 [\"42%\", \"68%\"];内半径越大环越细",
|
||||
"value_type": "array",
|
||||
"item_type": "string",
|
||||
"aliases": [
|
||||
"环宽",
|
||||
"半径",
|
||||
"环的粗细"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-item"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "ring-chart",
|
||||
"name": "环形图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"color": [
|
||||
"#409eff",
|
||||
"#67c23a",
|
||||
"#e6a23c",
|
||||
"#f56c6c",
|
||||
"#909399"
|
||||
],
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "设备占比",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "item"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"device": "桌面端",
|
||||
"value": 1048
|
||||
},
|
||||
{
|
||||
"device": "移动端",
|
||||
"value": 735
|
||||
},
|
||||
{
|
||||
"device": "平板",
|
||||
"value": 380
|
||||
},
|
||||
{
|
||||
"device": "其他",
|
||||
"value": 184
|
||||
}
|
||||
]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "设备",
|
||||
"type": "pie",
|
||||
"radius": [
|
||||
"42%",
|
||||
"68%"
|
||||
],
|
||||
"center": [
|
||||
"50%",
|
||||
"62%"
|
||||
],
|
||||
"avoidLabelOverlap": true,
|
||||
"label": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"labelLine": {
|
||||
"lineStyle": {
|
||||
"color": "#a3a6ad"
|
||||
}
|
||||
},
|
||||
"encode": {
|
||||
"itemName": "device",
|
||||
"value": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
{
|
||||
"order": 12,
|
||||
"key": "rose-chart",
|
||||
"type": "rose-chart",
|
||||
"name": "玫瑰图",
|
||||
"description": "用扇区半径展示各类目数值大小的饼图变体,差异比普通饼图更醒目",
|
||||
"tags": [
|
||||
"charts",
|
||||
"玫瑰图",
|
||||
"rose-chart",
|
||||
"南丁格尔",
|
||||
"占比"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:pie-chart-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-palette",
|
||||
"chart-item-series",
|
||||
{
|
||||
"key": "rose-shape",
|
||||
"label": "玫瑰",
|
||||
"description": "玫瑰图的类型和半径",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.roseType",
|
||||
"label": "玫瑰类型",
|
||||
"description": "扇区如何体现数值",
|
||||
"value_type": "enum",
|
||||
"options": [
|
||||
{
|
||||
"value": "area",
|
||||
"label": "面积模式",
|
||||
"description": "各扇区圆心角相同,半径体现数值"
|
||||
},
|
||||
{
|
||||
"value": "radius",
|
||||
"label": "半径模式",
|
||||
"description": "圆心角和半径都体现数值"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"模式"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.radius",
|
||||
"label": "内外半径",
|
||||
"description": "两个百分比依次为内半径和外半径,相对组件较短边的一半,例如 [\"12%\", \"62%\"]",
|
||||
"value_type": "array",
|
||||
"item_type": "string",
|
||||
"aliases": [
|
||||
"半径"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-item"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "rose-chart",
|
||||
"name": "玫瑰图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"color": [
|
||||
"#409eff",
|
||||
"#67c23a",
|
||||
"#e6a23c",
|
||||
"#f56c6c",
|
||||
"#909399",
|
||||
"#b37feb"
|
||||
],
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "区域销售分布",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "item"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"region": "华东",
|
||||
"value": 40
|
||||
},
|
||||
{
|
||||
"region": "华南",
|
||||
"value": 33
|
||||
},
|
||||
{
|
||||
"region": "华北",
|
||||
"value": 28
|
||||
},
|
||||
{
|
||||
"region": "西南",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"region": "华中",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"region": "西北",
|
||||
"value": 15
|
||||
}
|
||||
]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "区域",
|
||||
"type": "pie",
|
||||
"roseType": "area",
|
||||
"radius": [
|
||||
"12%",
|
||||
"62%"
|
||||
],
|
||||
"center": [
|
||||
"50%",
|
||||
"62%"
|
||||
],
|
||||
"itemStyle": {
|
||||
"borderRadius": 4
|
||||
},
|
||||
"label": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"labelLine": {
|
||||
"lineStyle": {
|
||||
"color": "#a3a6ad"
|
||||
}
|
||||
},
|
||||
"encode": {
|
||||
"itemName": "region",
|
||||
"value": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
{
|
||||
"order": 6,
|
||||
"key": "scatter-chart",
|
||||
"type": "scatter-chart",
|
||||
"name": "散点图",
|
||||
"description": "用点的横纵位置展示两个数值指标之间的关系和分布",
|
||||
"tags": [
|
||||
"charts",
|
||||
"散点图",
|
||||
"scatter-chart"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:chart-square-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-grid",
|
||||
"chart-axis",
|
||||
{
|
||||
"key": "scatter-series",
|
||||
"label": "散点",
|
||||
"description": "散点的颜色、大小和系列名称",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.itemStyle.color",
|
||||
"label": "点颜色",
|
||||
"description": "散点的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"散点颜色",
|
||||
"主色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.symbolSize",
|
||||
"label": "点大小",
|
||||
"description": "散点的直径",
|
||||
"value_type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 60,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"散点大小",
|
||||
"点的大小"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.name",
|
||||
"label": "系列名称",
|
||||
"description": "这组散点的名称,显示在图例和鼠标悬停提示中;不是图表标题",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"图例名称",
|
||||
"系列名",
|
||||
"数据名称"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"chart-encode-xy"
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "scatter-chart",
|
||||
"name": "散点图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "访客数与销售额",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "item"
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"users": 12,
|
||||
"sales": 32
|
||||
},
|
||||
{
|
||||
"users": 28,
|
||||
"sales": 48
|
||||
},
|
||||
{
|
||||
"users": 35,
|
||||
"sales": 41
|
||||
},
|
||||
{
|
||||
"users": 47,
|
||||
"sales": 72
|
||||
},
|
||||
{
|
||||
"users": 58,
|
||||
"sales": 66
|
||||
},
|
||||
{
|
||||
"users": 69,
|
||||
"sales": 88
|
||||
},
|
||||
{
|
||||
"users": 81,
|
||||
"sales": 94
|
||||
},
|
||||
{
|
||||
"users": 90,
|
||||
"sales": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 86,
|
||||
"right": 24,
|
||||
"bottom": 32,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "value",
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "店铺",
|
||||
"type": "scatter",
|
||||
"symbolSize": 12,
|
||||
"encode": {
|
||||
"x": "users",
|
||||
"y": "sales"
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#409eff"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
{
|
||||
"order": 10,
|
||||
"key": "stacked-bar-chart",
|
||||
"type": "stacked-bar-chart",
|
||||
"name": "堆叠柱状图",
|
||||
"description": "多个系列在同一柱子上堆叠,同时展示总量和各部分构成",
|
||||
"tags": [
|
||||
"charts",
|
||||
"堆叠柱状图",
|
||||
"stacked-bar-chart",
|
||||
"堆叠",
|
||||
"构成"
|
||||
],
|
||||
"renderer": "echarts",
|
||||
"group": "charts",
|
||||
"icon": "solar:chart-bold",
|
||||
"sample_data": true,
|
||||
"usage_notes": [
|
||||
"默认图表数据仅用于演示,不是真实业务数据",
|
||||
"通过顶层 dataId 关联已有数据源,当前后端尚未实现绑定操作",
|
||||
"三个系列共用 stack: total,X 字段需在三个系列的 encode.x 中保持一致"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
"chart-title",
|
||||
"chart-legend",
|
||||
"chart-grid",
|
||||
"chart-axis",
|
||||
"chart-palette",
|
||||
{
|
||||
"key": "stacked-series",
|
||||
"label": "系列",
|
||||
"description": "三个堆叠系列的颜色和名称,从下往上依次为系列1、2、3",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.color.0",
|
||||
"label": "系列1颜色",
|
||||
"description": "第 1 个系列(从下往上第 1 段)的颜色",
|
||||
"value_type": "color",
|
||||
"span": 12,
|
||||
"aliases": [
|
||||
"颜色",
|
||||
"主色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.color.1",
|
||||
"label": "系列2颜色",
|
||||
"description": "第 2 个系列(从下往上第 2 段)的颜色",
|
||||
"value_type": "color",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.color.2",
|
||||
"label": "系列3颜色",
|
||||
"description": "第 3 个系列(从下往上第 3 段)的颜色",
|
||||
"value_type": "color",
|
||||
"span": 12
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.name",
|
||||
"label": "系列1名称",
|
||||
"description": "第 1 个系列的名称,显示在图例和鼠标悬停提示中",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"图例名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.1.name",
|
||||
"label": "系列2名称",
|
||||
"description": "第 2 个系列的名称,显示在图例和鼠标悬停提示中",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"图例名称"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.2.name",
|
||||
"label": "系列3名称",
|
||||
"description": "第 3 个系列的名称,显示在图例和鼠标悬停提示中",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"图例名称"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "stacked-data",
|
||||
"label": "数据映射",
|
||||
"description": "堆叠柱状图从数据集(dataset.source)中取哪些列",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.option.series.0.encode.x",
|
||||
"label": "X字段",
|
||||
"description": "横轴类目使用数据集中的哪一列;三个系列必须相同,修改时同时修改系列2、系列3的X字段",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"横轴字段",
|
||||
"类目字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.1.encode.x",
|
||||
"label": "系列2X字段",
|
||||
"description": "须与X字段保持一致,修改X字段时同步修改",
|
||||
"value_type": "string",
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.2.encode.x",
|
||||
"label": "系列3X字段",
|
||||
"description": "须与X字段保持一致,修改X字段时同步修改",
|
||||
"value_type": "string",
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.0.encode.y",
|
||||
"label": "系列1字段",
|
||||
"description": "第 1 个系列的数值使用数据集中的哪一列",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"系列1数值字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.1.encode.y",
|
||||
"label": "系列2字段",
|
||||
"description": "第 2 个系列的数值使用数据集中的哪一列",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"系列2数值字段"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "props.option.series.2.encode.y",
|
||||
"label": "系列3字段",
|
||||
"description": "第 3 个系列的数值使用数据集中的哪一列",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"系列3数值字段"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "stacked-bar-chart",
|
||||
"name": "堆叠柱状图",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 260
|
||||
},
|
||||
"props": {
|
||||
"option": {
|
||||
"color": [
|
||||
"#409eff",
|
||||
"#67c23a",
|
||||
"#e6a23c",
|
||||
"#f56c6c",
|
||||
"#909399"
|
||||
],
|
||||
"legend": {
|
||||
"top": 38,
|
||||
"left": "center",
|
||||
"itemWidth": 12,
|
||||
"itemHeight": 8,
|
||||
"show": true,
|
||||
"textStyle": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"text": "各渠道月度销售额",
|
||||
"top": 8,
|
||||
"left": "center",
|
||||
"textStyle": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "axis",
|
||||
"axisPointer": {
|
||||
"type": "shadow"
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"source": [
|
||||
{
|
||||
"month": "一月",
|
||||
"online": 120,
|
||||
"offline": 220,
|
||||
"agent": 150
|
||||
},
|
||||
{
|
||||
"month": "二月",
|
||||
"online": 132,
|
||||
"offline": 182,
|
||||
"agent": 212
|
||||
},
|
||||
{
|
||||
"month": "三月",
|
||||
"online": 101,
|
||||
"offline": 191,
|
||||
"agent": 201
|
||||
},
|
||||
{
|
||||
"month": "四月",
|
||||
"online": 134,
|
||||
"offline": 234,
|
||||
"agent": 154
|
||||
},
|
||||
{
|
||||
"month": "五月",
|
||||
"online": 90,
|
||||
"offline": 290,
|
||||
"agent": 190
|
||||
},
|
||||
{
|
||||
"month": "六月",
|
||||
"online": 230,
|
||||
"offline": 330,
|
||||
"agent": 330
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"top": 86,
|
||||
"right": 24,
|
||||
"bottom": 32,
|
||||
"left": 48,
|
||||
"containLabel": true
|
||||
},
|
||||
"xAxis": {
|
||||
"type": "category",
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#4c4d4f"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
}
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "value",
|
||||
"axisLabel": {
|
||||
"color": "#cfd3dc"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(255, 255, 255, 0.08)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "线上",
|
||||
"type": "bar",
|
||||
"stack": "total",
|
||||
"barWidth": "45%",
|
||||
"encode": {
|
||||
"x": "month",
|
||||
"y": "online"
|
||||
},
|
||||
"itemStyle": {}
|
||||
},
|
||||
{
|
||||
"name": "门店",
|
||||
"type": "bar",
|
||||
"stack": "total",
|
||||
"barWidth": "45%",
|
||||
"encode": {
|
||||
"x": "month",
|
||||
"y": "offline"
|
||||
},
|
||||
"itemStyle": {}
|
||||
},
|
||||
{
|
||||
"name": "代理",
|
||||
"type": "bar",
|
||||
"stack": "total",
|
||||
"barWidth": "45%",
|
||||
"encode": {
|
||||
"x": "month",
|
||||
"y": "agent"
|
||||
},
|
||||
"itemStyle": {
|
||||
"borderRadius": [
|
||||
4,
|
||||
4,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"style": null,
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"order": 16,
|
||||
"key": "text",
|
||||
"type": "text",
|
||||
"name": "文本",
|
||||
"description": "显示一段文字,常用作大屏标题、说明或标签",
|
||||
"tags": [
|
||||
"info",
|
||||
"文本",
|
||||
"text"
|
||||
],
|
||||
"renderer": "text",
|
||||
"group": "info",
|
||||
"icon": "solar:text-bold",
|
||||
"sample_data": false,
|
||||
"usage_notes": [
|
||||
"events 中代码只作为资料保存,不由后端执行"
|
||||
],
|
||||
"field_groups": [
|
||||
"node-base",
|
||||
{
|
||||
"key": "text-content",
|
||||
"label": "内容",
|
||||
"description": "文本组件显示的文字",
|
||||
"fields": [
|
||||
{
|
||||
"path": "props.content",
|
||||
"label": "内容",
|
||||
"description": "文本组件显示的文字",
|
||||
"value_type": "string",
|
||||
"aliases": [
|
||||
"文字",
|
||||
"文本内容",
|
||||
"标题文字",
|
||||
"名字"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "text-style",
|
||||
"label": "文字样式",
|
||||
"description": "文字的颜色、大小、粗细和对齐",
|
||||
"fields": [
|
||||
{
|
||||
"path": "style.color",
|
||||
"label": "颜色",
|
||||
"description": "文字的颜色",
|
||||
"value_type": "color",
|
||||
"aliases": [
|
||||
"文字颜色",
|
||||
"字体颜色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "style.fontSize",
|
||||
"label": "字号",
|
||||
"description": "文字的大小",
|
||||
"value_type": "number",
|
||||
"minimum": 8,
|
||||
"maximum": 200,
|
||||
"unit": "px",
|
||||
"aliases": [
|
||||
"字体大小",
|
||||
"文字大小"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "style.textAlign",
|
||||
"label": "水平对齐",
|
||||
"description": "文字在组件内的水平位置",
|
||||
"value_type": "enum",
|
||||
"options": [
|
||||
{
|
||||
"value": "left",
|
||||
"label": "左对齐"
|
||||
},
|
||||
{
|
||||
"value": "center",
|
||||
"label": "居中"
|
||||
},
|
||||
{
|
||||
"value": "right",
|
||||
"label": "右对齐"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"对齐"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "style.alignItems",
|
||||
"label": "垂直对齐",
|
||||
"description": "文字在组件内的垂直位置",
|
||||
"value_type": "enum",
|
||||
"options": [
|
||||
{
|
||||
"value": "flex-start",
|
||||
"label": "顶部"
|
||||
},
|
||||
{
|
||||
"value": "center",
|
||||
"label": "居中"
|
||||
},
|
||||
{
|
||||
"value": "flex-end",
|
||||
"label": "底部"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "style.fontWeight",
|
||||
"label": "字重",
|
||||
"description": "文字粗细",
|
||||
"value_type": "enum",
|
||||
"default": "normal",
|
||||
"options": [
|
||||
{
|
||||
"value": "normal",
|
||||
"label": "常规"
|
||||
},
|
||||
{
|
||||
"value": "bold",
|
||||
"label": "加粗"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"加粗",
|
||||
"粗细"
|
||||
],
|
||||
"panel": false
|
||||
},
|
||||
{
|
||||
"path": "style.backgroundColor",
|
||||
"label": "背景色",
|
||||
"description": "文本组件的背景颜色,默认透明",
|
||||
"value_type": "color",
|
||||
"default": "transparent",
|
||||
"aliases": [
|
||||
"背景颜色",
|
||||
"底色"
|
||||
],
|
||||
"panel": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"eventOptions": [
|
||||
{
|
||||
"label": "点击事件",
|
||||
"value": "click"
|
||||
},
|
||||
{
|
||||
"label": "双击事件",
|
||||
"value": "dblclick"
|
||||
},
|
||||
{
|
||||
"label": "鼠标进入事件",
|
||||
"value": "mouseenter"
|
||||
},
|
||||
{
|
||||
"label": "鼠标离开事件",
|
||||
"value": "mouseleave"
|
||||
},
|
||||
{
|
||||
"label": "鼠标按下事件",
|
||||
"value": "mousedown"
|
||||
},
|
||||
{
|
||||
"label": "鼠标抬起事件",
|
||||
"value": "mouseup"
|
||||
},
|
||||
{
|
||||
"label": "鼠标移动事件",
|
||||
"value": "mousemove"
|
||||
},
|
||||
{
|
||||
"label": "鼠标滚轮事件",
|
||||
"value": "mousewheel"
|
||||
},
|
||||
{
|
||||
"label": "生命周期事件",
|
||||
"value": "vnodeMounted"
|
||||
}
|
||||
],
|
||||
"schema": {
|
||||
"type": "text",
|
||||
"name": "文本",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 200,
|
||||
"height": 50
|
||||
},
|
||||
"props": {
|
||||
"content": "电商运营数据看板"
|
||||
},
|
||||
"style": {
|
||||
"color": "#e5eaf3",
|
||||
"fontSize": 16,
|
||||
"textAlign": "center",
|
||||
"alignItems": "center"
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"type": "click",
|
||||
"title": "点击事件",
|
||||
"name": "fn",
|
||||
"code": "console.log(123)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,9 @@ class ActionValidationError(BaseModel):
|
||||
action_index: int | None = Field(
|
||||
default=None, ge=0, description="失败动作的下标,从 0 开始,整批错误为 null"
|
||||
)
|
||||
code: Literal["schema_error", "generation_error", "scope_error", "execution_error"]
|
||||
code: Literal[
|
||||
"schema_error", "generation_error", "scope_error", "field_error", "execution_error"
|
||||
]
|
||||
message: str
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""精确更新组件 props 或 style 下的一个字段"""
|
||||
"""精确更新组件名称,或 props / style 下的一个字段"""
|
||||
|
||||
from typing import Annotated, Literal
|
||||
from typing import Annotated, Literal, Self
|
||||
|
||||
from pydantic import Field, JsonValue
|
||||
from pydantic import Field, JsonValue, model_validator
|
||||
|
||||
from app.schemas.base import NonBlankString
|
||||
|
||||
@@ -13,10 +13,21 @@ PathSegment = NonBlankString | Annotated[int, Field(ge=0)]
|
||||
|
||||
class UpdateAction(BaseAction):
|
||||
type: Literal["update"] = "update"
|
||||
section: Literal["props", "style"] = Field(description="允许更新的组件属性区域")
|
||||
section: Literal["name", "props", "style"] = Field(
|
||||
description="字段路径的第一段:name 为组件名称,props / style 为组件属性区域"
|
||||
)
|
||||
path: list[PathSegment] = Field(
|
||||
min_length=1, description="区域内的字段路径,字符串为对象键,整数为数组下标"
|
||||
)
|
||||
value: JsonValue = Field(
|
||||
description="替换该路径的值,支持任意 JSON;null 是赋值,不表示删除"
|
||||
description="字段路径第一段之后的各段,字符串为对象键,整数为数组下标;name 没有后续路径,传空列表"
|
||||
)
|
||||
value: JsonValue = Field(description="替换该字段的值,必须符合字段定义的类型和取值")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def path_matches_section(self) -> Self:
|
||||
if (self.section == "name") != (not self.path):
|
||||
raise ValueError("section 为 name 时 path 为空,props / style 至少一段路径")
|
||||
return self
|
||||
|
||||
@property
|
||||
def field_path(self) -> str:
|
||||
"""与物料字段定义一致的点路径,例如 props.option.title.text"""
|
||||
return ".".join([self.section, *map(str, self.path)])
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Literal, Self
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from app.schemas.base import ExtensibleModel, JsonObject, NonBlankString
|
||||
from app.schemas.material_field import MISSING, FieldGroup, lookup
|
||||
from app.schemas.screen import Layout, PositiveNumber
|
||||
|
||||
|
||||
@@ -26,15 +27,6 @@ class MaterialTemplate(ExtensibleModel):
|
||||
events: list[JsonObject] = Field(default_factory=list)
|
||||
|
||||
|
||||
class MaterialSetter(ExtensibleModel):
|
||||
type: NonBlankString = Field(description="前端属性控件类型,不是完整的值类型约束")
|
||||
label: NonBlankString
|
||||
key: NonBlankString = Field(
|
||||
description="前端原始点路径,例如 props.option.series.0.itemStyle.color"
|
||||
)
|
||||
props: JsonObject = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ComponentDefinition(ExtensibleModel):
|
||||
model_config = ConfigDict(serialize_by_alias=True)
|
||||
key: NonBlankString = Field(description="目录中的唯一编号,不是 Screen 节点 ID")
|
||||
@@ -52,22 +44,26 @@ class ComponentDefinition(ExtensibleModel):
|
||||
schema_: MaterialTemplate = Field(
|
||||
alias="schema", description="完整默认物料配置,不包含实例 ID"
|
||||
)
|
||||
setters: list[MaterialSetter] = Field(default_factory=list)
|
||||
field_groups: list[NonBlankString | FieldGroup] = Field(
|
||||
default_factory=list,
|
||||
description="字段分组:字符串引用目录的共享分组,对象为本物料独有的分组;"
|
||||
"前端属性面板的 setters 由它派生,不单独保存",
|
||||
)
|
||||
eventOptions: list[JsonObject] = Field(default_factory=list)
|
||||
sample_data: bool = Field(
|
||||
default=False, description="默认配置是否含示例数据,不能视为真实业务数据"
|
||||
)
|
||||
props_schema: JsonObject | None = Field(
|
||||
default=None,
|
||||
description="可选的 props JSON Schema,未提供表示未知,不表示无限制",
|
||||
)
|
||||
style_schema: JsonObject | None = Field(
|
||||
default=None, description="可选的 style JSON Schema,仅保存定义,当前不执行校验"
|
||||
)
|
||||
usage_notes: list[NonBlankString] = Field(
|
||||
default_factory=list, description="配置、数据绑定及使用限制说明"
|
||||
)
|
||||
|
||||
def template(self) -> JsonObject:
|
||||
"""默认配置的 JSON;style 为 null 表示未设置,字段默认值按路径不存在处理"""
|
||||
data = self.schema_.model_dump(mode="json")
|
||||
if data.get("style") is None:
|
||||
data.pop("style", None)
|
||||
return data
|
||||
|
||||
@model_validator(mode="after")
|
||||
def consistent_type(self) -> Self:
|
||||
if self.type != self.schema_.type:
|
||||
@@ -77,17 +73,73 @@ class ComponentDefinition(ExtensibleModel):
|
||||
return self
|
||||
|
||||
|
||||
def _default_problem(field, template: JsonObject) -> str | None:
|
||||
"""默认值只有一个来源:默认配置有该路径时取它,没有时字段必须声明 default"""
|
||||
try:
|
||||
current = lookup(template, field.segments)
|
||||
except ValueError:
|
||||
return "的路径与默认配置结构冲突"
|
||||
if current is MISSING:
|
||||
if field.default is None:
|
||||
return "在默认配置中不存在,需要声明 default"
|
||||
error = field.value_error(field.default)
|
||||
return f"的 default {error}" if error else None
|
||||
if field.default is not None:
|
||||
return "在默认配置中已有值,不能再声明 default"
|
||||
error = field.value_error(current)
|
||||
return f"的默认配置值 {error}" if error else None
|
||||
|
||||
|
||||
class ComponentCatalog(ExtensibleModel):
|
||||
version: NonBlankString = Field(
|
||||
description="前端组件目录版本,后续用于检查是否与前端匹配"
|
||||
)
|
||||
components: list[ComponentDefinition]
|
||||
field_groups: list[FieldGroup] = Field(
|
||||
default_factory=list, description="多个物料共用的字段分组,物料按 key 引用"
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def unique_keys(self) -> Self:
|
||||
keys = [component.key for component in self.components]
|
||||
if len(keys) != len(set(keys)):
|
||||
raise ValueError("组件目录 key 不能重复")
|
||||
shared = [group.key for group in self.field_groups]
|
||||
if len(shared) != len(set(shared)):
|
||||
raise ValueError("共享字段分组 key 不能重复")
|
||||
for component in self.components:
|
||||
groups = [
|
||||
group.key if isinstance(group, FieldGroup) else group
|
||||
for group in component.field_groups
|
||||
]
|
||||
if len(groups) != len(set(groups)):
|
||||
raise ValueError(f"物料 {component.key} 的字段分组 key 重复")
|
||||
if missing := [
|
||||
group
|
||||
for group in component.field_groups
|
||||
if isinstance(group, str) and group not in shared
|
||||
]:
|
||||
raise ValueError(f"物料 {component.key} 引用了不存在的共享分组: {missing}")
|
||||
if shadowed := [
|
||||
group.key
|
||||
for group in component.field_groups
|
||||
if isinstance(group, FieldGroup) and group.key in shared
|
||||
]:
|
||||
raise ValueError(f"物料 {component.key} 的独有分组与共享分组重名: {shadowed}")
|
||||
by_key = {group.key: group for group in self.field_groups}
|
||||
fields = [
|
||||
field
|
||||
for group in component.field_groups
|
||||
for field in (by_key[group] if isinstance(group, str) else group).fields
|
||||
]
|
||||
paths = [field.path for field in fields]
|
||||
if duplicated := sorted({path for path in paths if paths.count(path) > 1}):
|
||||
raise ValueError(f"物料 {component.key} 的字段路径重复: {duplicated}")
|
||||
template = component.template()
|
||||
for field in fields:
|
||||
problem = _default_problem(field, template)
|
||||
if problem:
|
||||
raise ValueError(f"物料 {component.key} 字段 {field.path} {problem}")
|
||||
return self
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
"""物料字段定义:分组 → 字段,每个字段带路径、含义、值类型和取值约束
|
||||
|
||||
字段定义是“组件有哪些可改属性”的唯一来源:前端属性面板的 setters、
|
||||
agent 查询字段、动作校验都由它派生,不再各自维护一份
|
||||
"""
|
||||
|
||||
from typing import Literal, Self
|
||||
|
||||
from pydantic import Field, JsonValue, model_validator
|
||||
|
||||
from app.schemas.base import ExtensibleModel, NonBlankString
|
||||
|
||||
# 字段路径的第一段:节点名称、布局、组件属性、组件样式
|
||||
FIELD_ROOTS = ("name", "layout", "props", "style")
|
||||
|
||||
ValueType = Literal[
|
||||
"string", "number", "integer", "boolean", "color", "enum", "array", "object"
|
||||
]
|
||||
# 数组元素只用基础类型,枚举和嵌套数组没有需要的场景
|
||||
ItemType = Literal["string", "number", "integer", "boolean", "color", "object"]
|
||||
|
||||
# 值类型对应的前端属性控件;array / object 没有通用控件,只能不在面板显示
|
||||
PANEL_CONTROLS: dict[str, str] = {
|
||||
"string": "input",
|
||||
"number": "number",
|
||||
"integer": "number",
|
||||
"boolean": "checkbox",
|
||||
"color": "color",
|
||||
"enum": "select",
|
||||
}
|
||||
|
||||
|
||||
# 默认配置中没有该路径时的标记,区别于值为 null
|
||||
MISSING = object()
|
||||
|
||||
# 值类型对应的 JSON 类型,用于检查默认配置里的现值
|
||||
_JSON_TYPES: dict[str, type | tuple[type, ...]] = {
|
||||
"string": str,
|
||||
"color": str,
|
||||
"number": (int, float),
|
||||
"integer": int,
|
||||
"boolean": bool,
|
||||
"array": list,
|
||||
"object": dict,
|
||||
}
|
||||
|
||||
|
||||
def _type_error(value_type: str, value: JsonValue) -> str | None:
|
||||
# bool 是 int 的子类,数值类型需要单独排除
|
||||
if not isinstance(value, _JSON_TYPES[value_type]) or (
|
||||
value_type in ("number", "integer") and isinstance(value, bool)
|
||||
):
|
||||
return f"类型必须是 {value_type}"
|
||||
return None
|
||||
|
||||
|
||||
def lookup(value: JsonValue, segments: list[str | int]):
|
||||
"""按路径取值;路径不存在返回 MISSING,路径中途遇到标量时抛 ValueError"""
|
||||
for segment in segments:
|
||||
if isinstance(value, dict) and isinstance(segment, str):
|
||||
if segment not in value:
|
||||
return MISSING
|
||||
value = value[segment]
|
||||
elif isinstance(value, list) and isinstance(segment, int):
|
||||
if segment >= len(value):
|
||||
return MISSING
|
||||
value = value[segment]
|
||||
else:
|
||||
raise ValueError(f"路径无法继续访问: {segments}")
|
||||
return value
|
||||
|
||||
|
||||
class FieldOption(ExtensibleModel):
|
||||
value: JsonValue
|
||||
label: NonBlankString
|
||||
description: NonBlankString | None = None
|
||||
|
||||
|
||||
class MaterialField(ExtensibleModel):
|
||||
path: NonBlankString = Field(
|
||||
description="点路径,与前端 setter key 一致,数字段为数组下标,例如 props.option.series.0.itemStyle.color"
|
||||
)
|
||||
label: NonBlankString = Field(description="字段名称,属性面板显示的文字")
|
||||
description: NonBlankString = Field(
|
||||
description="字段含义、在画面上的效果,以及用户通常怎么称呼它"
|
||||
)
|
||||
value_type: ValueType
|
||||
options: list[FieldOption] = Field(
|
||||
default_factory=list, description="enum 的可选值,每个值可带说明"
|
||||
)
|
||||
item_type: ItemType | None = Field(
|
||||
default=None, description="array 的元素类型"
|
||||
)
|
||||
minimum: int | float | None = None
|
||||
maximum: int | float | None = None
|
||||
unit: NonBlankString | None = Field(default=None, description="数值单位,例如 px")
|
||||
default: JsonValue = Field(
|
||||
default=None,
|
||||
description="物料默认配置中没有该路径时的默认值;有该路径时以默认配置为准,不在这里重复",
|
||||
)
|
||||
aliases: list[NonBlankString] = Field(
|
||||
default_factory=list, description="用户可能的叫法,供字段搜索使用"
|
||||
)
|
||||
editable: bool = Field(default=True, description="是否允许修改")
|
||||
panel: bool = Field(default=True, description="是否显示在前端属性面板")
|
||||
span: int | None = Field(default=None, description="属性面板栅格宽度,24 为整行")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def consistent(self) -> Self:
|
||||
if self.path.split(".")[0] not in FIELD_ROOTS:
|
||||
raise ValueError(f"字段路径必须以 {'/'.join(FIELD_ROOTS)} 开头: {self.path}")
|
||||
if (self.value_type == "enum") != bool(self.options):
|
||||
raise ValueError(f"只有 enum 字段提供 options,且 enum 必须提供: {self.path}")
|
||||
if (self.value_type == "array") != (self.item_type is not None):
|
||||
raise ValueError(f"只有 array 字段提供 item_type,且 array 必须提供: {self.path}")
|
||||
if self.panel and self.value_type not in PANEL_CONTROLS:
|
||||
raise ValueError(f"{self.value_type} 字段没有属性面板控件,panel 必须为 false: {self.path}")
|
||||
return self
|
||||
|
||||
def value_error(self, value: JsonValue) -> str | None:
|
||||
"""检查值是否符合字段类型和取值约束,符合返回 None"""
|
||||
if self.value_type == "enum":
|
||||
allowed = [option.value for option in self.options]
|
||||
return None if value in allowed else f"取值必须是 {allowed} 之一"
|
||||
if error := _type_error(self.value_type, value):
|
||||
return error
|
||||
if self.item_type is not None:
|
||||
for element in value:
|
||||
if error := _type_error(self.item_type, element):
|
||||
return f"数组元素{error}"
|
||||
if self.value_type in ("number", "integer"):
|
||||
if self.minimum is not None and value < self.minimum:
|
||||
return f"不能小于 {self.minimum}"
|
||||
if self.maximum is not None and value > self.maximum:
|
||||
return f"不能大于 {self.maximum}"
|
||||
return None
|
||||
|
||||
@property
|
||||
def segments(self) -> list[str | int]:
|
||||
# 数字段视为数组下标,与前端 setter key 的解析方式一致
|
||||
return [int(part) if part.isdigit() else part for part in self.path.split(".")]
|
||||
|
||||
|
||||
class FieldGroup(ExtensibleModel):
|
||||
key: NonBlankString = Field(description="分组编号,共享分组在整个目录内唯一")
|
||||
label: NonBlankString
|
||||
description: NonBlankString = Field(description="这一组字段管什么,供模型先看目录再查字段")
|
||||
fields: list[MaterialField]
|
||||
|
||||
@model_validator(mode="after")
|
||||
def unique_paths(self) -> Self:
|
||||
paths = [field.path for field in self.fields]
|
||||
if len(paths) != len(set(paths)):
|
||||
raise ValueError(f"分组 {self.key} 中字段路径重复")
|
||||
return self
|
||||
@@ -30,7 +30,7 @@ class RequirementChange(ExtensibleModel):
|
||||
operation: NonBlankString = Field(description=f"需求操作。{OPERATION_GUIDE}")
|
||||
parameters: JsonObject = Field(
|
||||
default_factory=dict,
|
||||
description="需求参数,例如 delta_x=50;保留用户要求的单位与相对变化语义",
|
||||
description="需求参数,例如 delta_x=50;保留用户要求的单位与相对变化语义;属性修改用用户的说法描述改什么、改成什么,例如 {\"名字\": \"月度销售\"},不推断字段路径",
|
||||
)
|
||||
|
||||
@field_validator("operation")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""物料目录的 MongoDB 存储:每个组件一条记录,目录版本放在元数据记录中"""
|
||||
"""物料目录的 MongoDB 存储:每个组件一条记录,目录版本放在元数据记录中,
|
||||
多个物料共用的字段分组单独存放,每个分组一条记录"""
|
||||
|
||||
from loguru import logger
|
||||
from pymongo.asynchronous.database import AsyncDatabase
|
||||
@@ -14,6 +15,7 @@ class MongoMaterialRepository:
|
||||
def __init__(self, db: AsyncDatabase, prefix: str = ""):
|
||||
# 集合名前缀用于测试隔离
|
||||
self._materials = db.get_collection(f"{prefix}materials")
|
||||
self._field_groups = db.get_collection(f"{prefix}material_field_groups")
|
||||
|
||||
async def load_all(self) -> ComponentCatalog | None:
|
||||
# 没有元数据记录视为尚未导入,由调用方决定回退方式
|
||||
@@ -21,33 +23,42 @@ class MongoMaterialRepository:
|
||||
if meta is None:
|
||||
return None
|
||||
documents = await self._materials.find({"_id": {"$ne": META_ID}}).to_list()
|
||||
components = [
|
||||
{key: value for key, value in item.items() if key not in ("_id", "order")}
|
||||
for item in sorted(documents, key=lambda item: item.get("order", 0))
|
||||
]
|
||||
groups = await self._field_groups.find({}).to_list()
|
||||
return ComponentCatalog.model_validate(
|
||||
{
|
||||
"version": meta["version"],
|
||||
"components": components,
|
||||
"components": _ordered(documents),
|
||||
"field_groups": _ordered(groups),
|
||||
}
|
||||
)
|
||||
|
||||
async def replace_all(self, catalog: ComponentCatalog) -> None:
|
||||
# 整体替换目录,供导入脚本使用;order 保持导出文件里的组件顺序
|
||||
documents = [
|
||||
{
|
||||
**component.model_dump(mode="json", by_alias=True),
|
||||
"_id": component.key,
|
||||
"order": index,
|
||||
}
|
||||
for index, component in enumerate(catalog.components)
|
||||
]
|
||||
await self._materials.delete_many({})
|
||||
if documents:
|
||||
await self._materials.insert_many(documents)
|
||||
# 整体替换目录,供导入脚本使用;order 保持源文件中的顺序
|
||||
for collection, items in (
|
||||
(self._materials, catalog.components),
|
||||
(self._field_groups, catalog.field_groups),
|
||||
):
|
||||
await collection.delete_many({})
|
||||
if items:
|
||||
await collection.insert_many(_documents(items))
|
||||
await self._materials.insert_one({"_id": META_ID, "version": catalog.version})
|
||||
|
||||
|
||||
def _documents(items) -> list[dict]:
|
||||
# 记录 _id 为物料或分组的 key
|
||||
return [
|
||||
{**item.model_dump(mode="json", by_alias=True), "_id": item.key, "order": index}
|
||||
for index, item in enumerate(items)
|
||||
]
|
||||
|
||||
|
||||
def _ordered(documents: list[dict]) -> list[dict]:
|
||||
return [
|
||||
{key: value for key, value in item.items() if key not in ("_id", "order")}
|
||||
for item in sorted(documents, key=lambda item: item.get("order", 0))
|
||||
]
|
||||
|
||||
|
||||
async def warm_material_cache(db: AsyncDatabase | None) -> ComponentCatalog | None:
|
||||
"""启动时把物料目录从 MongoDB 载入进程缓存;未启用或尚未导入时目录为空,不回退读文件"""
|
||||
catalog = await MongoMaterialRepository(db).load_all() if db is not None else None
|
||||
|
||||
Reference in New Issue
Block a user