feat(plugin): 集成 WASM 插件系统到主服务并修复链路问题

- 新增 erp-plugin crate:插件管理、WASM 运行时、动态表、数据 CRUD
- 新增前端插件管理页面(PluginAdmin/PluginCRUDPage)和 API 层
- 新增插件数据迁移(plugins/plugin_entities/plugin_event_subscriptions)
- 新增权限补充迁移(为已有租户补充 plugin.admin/plugin.list 权限)
- 修复 PluginAdmin 页面 InstallOutlined 图标不存在的崩溃问题
- 修复 settings 唯一索引迁移顺序错误(先去重再建索引)
- 更新 wiki 和 CLAUDE.md 反映插件系统集成状态
- 新增 dev.ps1 一键启动脚本
This commit is contained in:
iven
2026-04-15 23:32:02 +08:00
parent 7e8fabb095
commit ff352a4c24
46 changed files with 6723 additions and 19 deletions

View File

@@ -1,4 +1,4 @@
import { useCallback, memo } from 'react';
import { useCallback, memo, useEffect } from 'react';
import { Layout, Avatar, Space, Dropdown, Tooltip, theme } from 'antd';
import {
HomeOutlined,
@@ -14,10 +14,12 @@ import {
SearchOutlined,
BulbOutlined,
BulbFilled,
AppstoreOutlined,
} from '@ant-design/icons';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAppStore } from '../stores/app';
import { useAuthStore } from '../stores/auth';
import { usePluginStore } from '../stores/plugin';
import NotificationPanel from '../components/NotificationPanel';
const { Header, Sider, Content, Footer } = Layout;
@@ -42,6 +44,7 @@ const bizMenuItems: MenuItem[] = [
const sysMenuItems: MenuItem[] = [
{ key: '/settings', icon: <SettingOutlined />, label: '系统设置' },
{ key: '/plugins/admin', icon: <AppstoreOutlined />, label: '插件管理' },
];
const routeTitleMap: Record<string, string> = {
@@ -52,6 +55,7 @@ const routeTitleMap: Record<string, string> = {
'/workflow': '工作流',
'/messages': '消息中心',
'/settings': '系统设置',
'/plugins/admin': '插件管理',
};
// 侧边栏菜单项 - 提取为独立组件避免重复渲染
@@ -82,11 +86,17 @@ const SidebarMenuItem = memo(function SidebarMenuItem({
export default function MainLayout({ children }: { children: React.ReactNode }) {
const { sidebarCollapsed, toggleSidebar, theme: themeMode, setTheme } = useAppStore();
const { user, logout } = useAuthStore();
const { pluginMenuItems, fetchPlugins } = usePluginStore();
theme.useToken();
const navigate = useNavigate();
const location = useLocation();
const currentPath = location.pathname || '/';
// 加载插件菜单
useEffect(() => {
fetchPlugins(1, 'running');
}, [fetchPlugins]);
const handleLogout = useCallback(async () => {
await logout();
navigate('/login');
@@ -159,6 +169,28 @@ export default function MainLayout({ children }: { children: React.ReactNode })
))}
</div>
{/* 菜单组:插件 */}
{pluginMenuItems.length > 0 && (
<>
{!sidebarCollapsed && <div className="erp-sidebar-group"></div>}
<div className="erp-sidebar-menu">
{pluginMenuItems.map((item) => (
<SidebarMenuItem
key={item.key}
item={{
key: item.key,
icon: <AppstoreOutlined />,
label: item.label,
}}
isActive={currentPath === item.key}
collapsed={sidebarCollapsed}
onClick={() => navigate(item.key)}
/>
))}
</div>
</>
)}
{/* 菜单组:系统 */}
{!sidebarCollapsed && <div className="erp-sidebar-group"></div>}
<div className="erp-sidebar-menu">
@@ -187,7 +219,9 @@ export default function MainLayout({ children }: { children: React.ReactNode })
{sidebarCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
</div>
<span className={`erp-header-title ${isDark ? 'erp-text-dark' : 'erp-text-light'}`}>
{routeTitleMap[currentPath] || '页面'}
{routeTitleMap[currentPath] ||
pluginMenuItems.find((p) => p.key === currentPath)?.label ||
'页面'}
</span>
</Space>