- 新增 PluginKanbanPage 看板页面,支持 dnd-kit 拖拽 - 支持泳道分组、卡片标题/副标题/标签展示 - 乐观更新 UI,失败自动回滚 - 路由入口 /plugins/:pluginId/kanban/:entityName 自加载 schema - PluginTabsPage 新增 kanban 页面类型支持 - PluginStore 新增 kanban 菜单项和路由生成 - 安装 @dnd-kit/core + @dnd-kit/sortable
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
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';
|
|
import { PluginKanbanPageFromConfig } from './PluginKanbanPage';
|
|
|
|
/**
|
|
* 插件 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('');
|
|
|
|
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') {
|
|
return (
|
|
<PluginCRUDPage
|
|
pluginIdOverride={pluginId}
|
|
entityOverride={tab.entity}
|
|
enableViews={tab.enable_views}
|
|
/>
|
|
);
|
|
}
|
|
if (tab.type === 'tree') {
|
|
return (
|
|
<PluginTreePage
|
|
pluginIdOverride={pluginId}
|
|
entityOverride={tab.entity}
|
|
/>
|
|
);
|
|
}
|
|
if (tab.type === 'kanban') {
|
|
return (
|
|
<PluginKanbanPageFromConfig
|
|
pluginId={pluginId!}
|
|
page={tab}
|
|
/>
|
|
);
|
|
}
|
|
return <div>不支持的页面类型: {tab.type}</div>;
|
|
};
|
|
|
|
const items = tabs.map((tab) => ({
|
|
key: 'label' in tab ? tab.label : '',
|
|
label: 'label' in tab ? tab.label : '',
|
|
children: renderTabContent(tab),
|
|
}));
|
|
|
|
return (
|
|
<div style={{ padding: 24 }}>
|
|
<Tabs activeKey={activeKey} onChange={setActiveKey} items={items} />
|
|
</div>
|
|
);
|
|
}
|