feat: 集成ComfyUI界面到顶部导航栏
- 更新Navigation组件支持下拉菜单功能 - 添加ComfyUI子菜单,包含V2仪表板、集群管理、工作流测试 - 在App.tsx中添加ComfyUI相关页面路由配置 - 保持旧路由兼容性 - 优化导航交互体验,支持点击外部关闭下拉菜单 功能特性: - 支持下拉菜单的导航系统 - ComfyUI功能模块化组织 - 响应式设计和动画效果 - 路由状态高亮显示
This commit is contained in:
parent
589808d15a
commit
bae02e6141
|
|
@ -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 />} />
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in New Issue