feat(web): 完善插件前端页面 — 数据 API、筛选、视图切换和统计展示
- 新增 pluginData API 层:count/aggregate/stats 端点调用 - PluginCRUDPage 支持 visible_when 条件字段、筛选器下拉、视图切换 - PluginTabsPage 支持 tabs 布局和子实体 CRUD - PluginTreePage 实现树形数据加载和节点展开/收起 - PluginGraphPage 实现关系图谱可视化展示 - PluginDashboardPage 实现统计卡片和聚合数据展示 - PluginAdmin 状态显示优化 - plugin store 增强 schema 加载逻辑和菜单生成
This commit is contained in:
@@ -1,46 +1,67 @@
|
||||
import { useState } from 'react';
|
||||
import { Tabs } from 'antd';
|
||||
import {
|
||||
PluginPageSchema,
|
||||
PluginEntitySchema,
|
||||
PluginFieldSchema,
|
||||
} from '../api/plugins';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Tabs, Spin, message } from 'antd';
|
||||
import { getPluginSchema, type PluginPageSchema, type PluginSchemaResponse } from '../api/plugins';
|
||||
import PluginCRUDPage from './PluginCRUDPage';
|
||||
import { PluginTreePage } from './PluginTreePage';
|
||||
|
||||
interface PluginTabsPageProps {
|
||||
pluginId: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
tabs: PluginPageSchema[];
|
||||
entities: PluginEntitySchema[];
|
||||
}
|
||||
/**
|
||||
* 插件 Tabs 页面 — 通过路由参数自加载 schema
|
||||
* 路由: /plugins/:pluginId/tabs/:pageLabel
|
||||
*/
|
||||
export function PluginTabsPage() {
|
||||
const { pluginId, pageLabel } = useParams<{ pluginId: string; pageLabel: string }>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [tabs, setTabs] = useState<PluginPageSchema[]>([]);
|
||||
const [activeKey, setActiveKey] = useState('');
|
||||
|
||||
export function PluginTabsPage({ pluginId, label, tabs, entities }: PluginTabsPageProps) {
|
||||
const [activeKey, setActiveKey] = useState(tabs[0] && 'label' in tabs[0] ? tabs[0].label : '');
|
||||
useEffect(() => {
|
||||
if (!pluginId || !pageLabel) return;
|
||||
const abortController = new AbortController();
|
||||
|
||||
async function loadSchema() {
|
||||
try {
|
||||
const schema: PluginSchemaResponse = await getPluginSchema(pluginId!);
|
||||
const pages = schema.ui?.pages || [];
|
||||
const tabsPage = pages.find(
|
||||
(p): p is PluginPageSchema & { type: 'tabs' } =>
|
||||
p.type === 'tabs' && p.label === pageLabel,
|
||||
);
|
||||
if (tabsPage && 'tabs' in tabsPage) {
|
||||
setTabs(tabsPage.tabs);
|
||||
const firstLabel = tabsPage.tabs.find((t) => 'label' in t)?.label || '';
|
||||
setActiveKey(firstLabel);
|
||||
}
|
||||
} catch {
|
||||
message.warning('Schema 加载失败,部分功能不可用');
|
||||
} finally {
|
||||
if (!abortController.signal.aborted) setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadSchema();
|
||||
return () => abortController.abort();
|
||||
}, [pluginId, pageLabel]);
|
||||
|
||||
if (loading) {
|
||||
return <div style={{ padding: 24, textAlign: 'center' }}><Spin /></div>;
|
||||
}
|
||||
|
||||
const renderTabContent = (tab: PluginPageSchema) => {
|
||||
if (tab.type === 'crud') {
|
||||
// 懒加载 PluginCRUDPage 避免循环依赖
|
||||
const PluginCRUDPage = require('./PluginCRUDPage').default;
|
||||
return (
|
||||
<PluginCRUDPage
|
||||
pluginIdOverride={pluginId}
|
||||
entityOverride={tab.entity}
|
||||
enableSearch={tab.enable_search}
|
||||
enableViews={tab.enable_views}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (tab.type === 'tree') {
|
||||
const PluginTreePage = require('./PluginTreePage').PluginTreePage;
|
||||
const entity = entities.find((e) => e.name === tab.entity);
|
||||
return (
|
||||
<PluginTreePage
|
||||
pluginId={pluginId}
|
||||
entity={tab.entity}
|
||||
idField={tab.id_field}
|
||||
parentField={tab.parent_field}
|
||||
labelField={tab.label_field}
|
||||
fields={entity?.fields || []}
|
||||
pluginIdOverride={pluginId}
|
||||
entityOverride={tab.entity}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user