feat(crm): 创建 CRM 插件 crate + 前端 tabs/tree 页面类型 + 动态菜单
- CRM WASM 插件:Cargo.toml + src/lib.rs + plugin.toml(5 实体 + 9 权限 + 6 页面) - 注册 erp-plugin-crm 到 workspace members - PluginTabsPage: 通用标签页容器,递归渲染子页面 - PluginTreePage: 通用树形页面,前端构建树结构 - App.tsx: 新增 /tabs/:pageLabel 和 /tree/:entityName 路由 - plugin store: 从 manifest pages 生成菜单(支持 tabs 聚合) - MainLayout: 动态图标映射(team/user/message/tags/apartment)
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import { create } from 'zustand';
|
||||
import type { PluginInfo, PluginStatus } from '../api/plugins';
|
||||
import { listPlugins } from '../api/plugins';
|
||||
import type { PluginInfo, PluginStatus, PluginPageSchema, PluginSchemaResponse } from '../api/plugins';
|
||||
import { listPlugins, getPluginSchema } from '../api/plugins';
|
||||
|
||||
export interface PluginMenuItem {
|
||||
key: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
pluginId: string;
|
||||
entity: string;
|
||||
entity?: string;
|
||||
pageType: 'crud' | 'tree' | 'tabs' | 'detail';
|
||||
menuGroup?: string;
|
||||
}
|
||||
|
||||
@@ -15,6 +16,7 @@ interface PluginStore {
|
||||
plugins: PluginInfo[];
|
||||
loading: boolean;
|
||||
pluginMenuItems: PluginMenuItem[];
|
||||
schemaCache: Record<string, PluginSchemaResponse>;
|
||||
fetchPlugins: (page?: number, status?: PluginStatus) => Promise<void>;
|
||||
refreshMenuItems: () => void;
|
||||
}
|
||||
@@ -23,12 +25,25 @@ export const usePluginStore = create<PluginStore>((set, get) => ({
|
||||
plugins: [],
|
||||
loading: false,
|
||||
pluginMenuItems: [],
|
||||
schemaCache: {},
|
||||
|
||||
fetchPlugins: async (page = 1, status?: PluginStatus) => {
|
||||
set({ loading: true });
|
||||
try {
|
||||
const result = await listPlugins(page, 100, status);
|
||||
set({ plugins: result.data });
|
||||
|
||||
// 预加载所有运行中插件的 schema
|
||||
const schemas: Record<string, PluginSchemaResponse> = {};
|
||||
for (const plugin of result.data) {
|
||||
if (plugin.status !== 'running' && plugin.status !== 'enabled') continue;
|
||||
try {
|
||||
schemas[plugin.id] = await getPluginSchema(plugin.id) as PluginSchemaResponse;
|
||||
} catch {
|
||||
// schema 加载失败跳过
|
||||
}
|
||||
}
|
||||
set({ schemaCache: schemas });
|
||||
get().refreshMenuItems();
|
||||
} finally {
|
||||
set({ loading: false });
|
||||
@@ -36,21 +51,59 @@ export const usePluginStore = create<PluginStore>((set, get) => ({
|
||||
},
|
||||
|
||||
refreshMenuItems: () => {
|
||||
const { plugins } = get();
|
||||
const { plugins, schemaCache } = get();
|
||||
const items: PluginMenuItem[] = [];
|
||||
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.status !== 'running' && plugin.status !== 'enabled') continue;
|
||||
|
||||
for (const entity of plugin.entities) {
|
||||
items.push({
|
||||
key: `/plugins/${plugin.id}/${entity.name}`,
|
||||
icon: 'AppstoreOutlined',
|
||||
label: entity.display_name || entity.name,
|
||||
pluginId: plugin.id,
|
||||
entity: entity.name,
|
||||
menuGroup: undefined,
|
||||
});
|
||||
const schema = schemaCache[plugin.id];
|
||||
const pages = (schema as { ui?: { pages: PluginPageSchema[] } })?.ui?.pages;
|
||||
|
||||
if (pages && pages.length > 0) {
|
||||
for (const page of pages) {
|
||||
if (page.type === 'tabs') {
|
||||
// tabs 类型聚合为一个菜单项
|
||||
items.push({
|
||||
key: `/plugins/${plugin.id}/tabs/${encodeURIComponent('label' in page ? page.label : '')}`,
|
||||
icon: ('icon' in page ? page.icon : 'AppstoreOutlined') || 'AppstoreOutlined',
|
||||
label: ('label' in page ? page.label : plugin.name) as string,
|
||||
pluginId: plugin.id,
|
||||
pageType: 'tabs',
|
||||
});
|
||||
} else if (page.type === 'tree') {
|
||||
items.push({
|
||||
key: `/plugins/${plugin.id}/tree/${page.entity}`,
|
||||
icon: ('icon' in page ? page.icon : 'ApartmentOutlined') || 'ApartmentOutlined',
|
||||
label: ('label' in page ? page.label : page.entity) as string,
|
||||
pluginId: plugin.id,
|
||||
entity: page.entity,
|
||||
pageType: 'tree',
|
||||
});
|
||||
} else if (page.type === 'crud') {
|
||||
items.push({
|
||||
key: `/plugins/${plugin.id}/${page.entity}`,
|
||||
icon: ('icon' in page ? page.icon : 'TableOutlined') || 'TableOutlined',
|
||||
label: ('label' in page ? page.label : page.entity) as string,
|
||||
pluginId: plugin.id,
|
||||
entity: page.entity,
|
||||
pageType: 'crud',
|
||||
});
|
||||
}
|
||||
// detail 类型不生成菜单项
|
||||
}
|
||||
} else {
|
||||
// 回退:从 entities 生成菜单
|
||||
for (const entity of plugin.entities) {
|
||||
items.push({
|
||||
key: `/plugins/${plugin.id}/${entity.name}`,
|
||||
icon: 'AppstoreOutlined',
|
||||
label: entity.display_name || entity.name,
|
||||
pluginId: plugin.id,
|
||||
entity: entity.name,
|
||||
pageType: 'crud',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user