feat: 集成ComfyUI界面到顶部导航栏

- 更新Navigation组件支持下拉菜单功能
- 添加ComfyUI子菜单,包含V2仪表板、集群管理、工作流测试
- 在App.tsx中添加ComfyUI相关页面路由配置
- 保持旧路由兼容性
- 优化导航交互体验,支持点击外部关闭下拉菜单

功能特性:
- 支持下拉菜单的导航系统
- ComfyUI功能模块化组织
- 响应式设计和动画效果
- 路由状态高亮显示
This commit is contained in:
杨明明 2025-08-08 16:04:52 +08:00
parent 589808d15a
commit bae02e6141
2 changed files with 144 additions and 13 deletions

View File

@ -38,6 +38,7 @@ import VideoGeneration from './pages/VideoGeneration';
import { OutfitPhotoGenerationPage } from './pages/OutfitPhotoGeneration';
import ComfyUIManagement from './pages/ComfyUIManagement';
import ComfyUIWorkflowTest from './pages/ComfyUIWorkflowTest';
import { ComfyUIV2Dashboard } from './pages/ComfyUIV2Dashboard';
import { WorkflowPage } from './pages/WorkflowPage';
// import CanvasTool from './pages/CanvasTool';
@ -139,6 +140,13 @@ function App() {
<Route path="/outfit-photo-generation/:projectId/:modelId" element={<OutfitPhotoGenerationPage />} />
<Route path="/workflows" element={<WorkflowPage />} />
{/* ComfyUI 相关路由 */}
<Route path="/comfyui-v2-dashboard" element={<ComfyUIV2Dashboard />} />
<Route path="/comfyui-management" element={<ComfyUIManagement />} />
<Route path="/comfyui-workflow-test" element={<ComfyUIWorkflowTest />} />
{/* 保持旧路由兼容性 */}
<Route path="/comfyui-cluster" element={<ComfyUIManagement />} />
<Route path="/comfyui-node" element={<ComfyUIWorkflowTest />} />
<Route path="/tools" element={<Tools />} />

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import {
FolderIcon,
@ -8,25 +8,48 @@ import {
WrenchScrewdriverIcon,
SparklesIcon,
Cog6ToothIcon,
RectangleStackIcon,
ServerIcon,
PlayIcon,
ChartBarIcon,
ChevronDownIcon,
} from '@heroicons/react/24/outline';
// 导航项类型定义
interface NavItem {
name: string;
href?: string;
icon: React.ComponentType<any>;
description: string;
children?: NavItem[];
}
const Navigation: React.FC = () => {
const location = useLocation();
const [openDropdown, setOpenDropdown] = useState<string | null>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
const navItems = [
// 点击外部关闭下拉菜单
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setOpenDropdown(null);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const navItems: NavItem[] = [
{
name: '项目',
href: '/',
icon: FolderIcon,
description: '管理项目和素材'
},
// AI画布暂时隐藏
// {
// name: 'AI画布',
// href: '/canvas-tool',
// icon: PaintBrushIcon,
// description: '可视化AI内容生成画布'
// },
{
name: '模特',
href: '/models',
@ -51,6 +74,31 @@ const Navigation: React.FC = () => {
icon: SparklesIcon,
description: 'AI穿搭方案推荐与素材检索'
},
{
name: 'ComfyUI',
icon: RectangleStackIcon,
description: 'AI工作流管理平台',
children: [
{
name: 'V2 仪表板',
href: '/comfyui-v2-dashboard',
icon: ChartBarIcon,
description: '现代化AI工作流管理仪表板'
},
{
name: '集群管理',
href: '/comfyui-management',
icon: ServerIcon,
description: '分布式ComfyUI集群管理'
},
{
name: '工作流测试',
href: '/comfyui-workflow-test',
icon: PlayIcon,
description: '工作流测试和调试'
}
]
},
{
name: 'AI工作流',
href: '/workflows',
@ -84,6 +132,16 @@ const Navigation: React.FC = () => {
return location.pathname.startsWith(href);
};
// 检查下拉菜单项是否有激活的子项
const hasActiveChild = (children: NavItem[]) => {
return children.some(child => child.href && isActive(child.href));
};
// 切换下拉菜单
const toggleDropdown = (itemName: string) => {
setOpenDropdown(openDropdown === itemName ? null : itemName);
};
return (
<nav className="bg-white/95 backdrop-blur-sm shadow-sm border-b border-gray-200/50 sticky top-0 z-50 flex-shrink-0">
<div className="px-4 sm:px-2 lg:px-4">
@ -100,16 +158,81 @@ const Navigation: React.FC = () => {
</div>
</div>
<div>
<div ref={dropdownRef}>
<div className="ml-4 flex items-baseline space-x-2">
{navItems.map((item) => {
const Icon = item.icon;
const active = isActive(item.href);
// 如果有子菜单,渲染下拉菜单
if (item.children) {
const hasActive = hasActiveChild(item.children);
const isOpen = openDropdown === item.name;
return (
<div key={item.name} className="relative">
<button
onClick={() => toggleDropdown(item.name)}
className={`group flex items-center px-2 py-2 rounded-lg text-sm font-medium transition-all duration-200 relative overflow-hidden ${
hasActive
? 'bg-gradient-to-r from-primary-100 to-primary-200 text-primary-700 shadow-sm'
: 'text-gray-600 hover:text-gray-900 hover:bg-gradient-to-r hover:from-gray-50 hover:to-gray-100'
}`}
title={item.description}
>
{hasActive && (
<div className="absolute inset-0 bg-gradient-to-r from-primary-500/10 to-primary-600/10"></div>
)}
<Icon className={`mr-2 h-5 w-5 relative z-10 transition-all duration-200 ${
hasActive ? 'text-primary-600' : 'text-gray-400 group-hover:text-gray-600 group-hover:scale-110'
}`} />
<span className="relative z-10">{item.name}</span>
<ChevronDownIcon className={`ml-1 h-4 w-4 relative z-10 transition-transform duration-200 ${
isOpen ? 'rotate-180' : ''
} ${hasActive ? 'text-primary-600' : 'text-gray-400 group-hover:text-gray-600'}`} />
</button>
{/* 下拉菜单 */}
{isOpen && (
<div className="absolute top-full left-0 mt-1 w-56 bg-white rounded-lg shadow-lg border border-gray-200 py-1 z-50">
{item.children.map((child) => {
const ChildIcon = child.icon;
const childActive = child.href ? isActive(child.href) : false;
return (
<Link
key={child.name}
to={child.href || '#'}
className={`group flex items-center px-4 py-2 text-sm transition-all duration-200 ${
childActive
? 'bg-primary-50 text-primary-700'
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900'
}`}
title={child.description}
onClick={() => setOpenDropdown(null)}
>
<ChildIcon className={`mr-3 h-4 w-4 transition-all duration-200 ${
childActive ? 'text-primary-600' : 'text-gray-400 group-hover:text-gray-600'
}`} />
<div>
<div className="font-medium">{child.name}</div>
<div className="text-xs text-gray-500 mt-0.5">{child.description}</div>
</div>
</Link>
);
})}
</div>
)}
</div>
);
}
// 普通导航项
const active = item.href ? isActive(item.href) : false;
return (
<Link
key={item.name}
to={item.href}
to={item.href || '#'}
className={`group flex items-center px-2 py-2 rounded-lg text-sm font-medium transition-all duration-200 relative overflow-hidden ${
active
? 'bg-gradient-to-r from-primary-100 to-primary-200 text-primary-700 shadow-sm'