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 { OutfitPhotoGenerationPage } from './pages/OutfitPhotoGeneration';
|
||||||
import ComfyUIManagement from './pages/ComfyUIManagement';
|
import ComfyUIManagement from './pages/ComfyUIManagement';
|
||||||
import ComfyUIWorkflowTest from './pages/ComfyUIWorkflowTest';
|
import ComfyUIWorkflowTest from './pages/ComfyUIWorkflowTest';
|
||||||
|
import { ComfyUIV2Dashboard } from './pages/ComfyUIV2Dashboard';
|
||||||
import { WorkflowPage } from './pages/WorkflowPage';
|
import { WorkflowPage } from './pages/WorkflowPage';
|
||||||
// import CanvasTool from './pages/CanvasTool';
|
// import CanvasTool from './pages/CanvasTool';
|
||||||
|
|
||||||
|
|
@ -139,6 +140,13 @@ function App() {
|
||||||
<Route path="/outfit-photo-generation/:projectId/:modelId" element={<OutfitPhotoGenerationPage />} />
|
<Route path="/outfit-photo-generation/:projectId/:modelId" element={<OutfitPhotoGenerationPage />} />
|
||||||
|
|
||||||
<Route path="/workflows" element={<WorkflowPage />} />
|
<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-cluster" element={<ComfyUIManagement />} />
|
||||||
<Route path="/comfyui-node" element={<ComfyUIWorkflowTest />} />
|
<Route path="/comfyui-node" element={<ComfyUIWorkflowTest />} />
|
||||||
<Route path="/tools" element={<Tools />} />
|
<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 { Link, useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
FolderIcon,
|
FolderIcon,
|
||||||
|
|
@ -8,25 +8,48 @@ import {
|
||||||
WrenchScrewdriverIcon,
|
WrenchScrewdriverIcon,
|
||||||
SparklesIcon,
|
SparklesIcon,
|
||||||
Cog6ToothIcon,
|
Cog6ToothIcon,
|
||||||
|
RectangleStackIcon,
|
||||||
|
ServerIcon,
|
||||||
|
PlayIcon,
|
||||||
|
ChartBarIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
|
// 导航项类型定义
|
||||||
|
interface NavItem {
|
||||||
|
name: string;
|
||||||
|
href?: string;
|
||||||
|
icon: React.ComponentType<any>;
|
||||||
|
description: string;
|
||||||
|
children?: NavItem[];
|
||||||
|
}
|
||||||
|
|
||||||
const Navigation: React.FC = () => {
|
const Navigation: React.FC = () => {
|
||||||
const location = useLocation();
|
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: '项目',
|
name: '项目',
|
||||||
href: '/',
|
href: '/',
|
||||||
icon: FolderIcon,
|
icon: FolderIcon,
|
||||||
description: '管理项目和素材'
|
description: '管理项目和素材'
|
||||||
},
|
},
|
||||||
// AI画布暂时隐藏
|
|
||||||
// {
|
|
||||||
// name: 'AI画布',
|
|
||||||
// href: '/canvas-tool',
|
|
||||||
// icon: PaintBrushIcon,
|
|
||||||
// description: '可视化AI内容生成画布'
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
name: '模特',
|
name: '模特',
|
||||||
href: '/models',
|
href: '/models',
|
||||||
|
|
@ -51,6 +74,31 @@ const Navigation: React.FC = () => {
|
||||||
icon: SparklesIcon,
|
icon: SparklesIcon,
|
||||||
description: 'AI穿搭方案推荐与素材检索'
|
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工作流',
|
name: 'AI工作流',
|
||||||
href: '/workflows',
|
href: '/workflows',
|
||||||
|
|
@ -84,6 +132,16 @@ const Navigation: React.FC = () => {
|
||||||
return location.pathname.startsWith(href);
|
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 (
|
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">
|
<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">
|
<div className="px-4 sm:px-2 lg:px-4">
|
||||||
|
|
@ -100,16 +158,81 @@ const Navigation: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div ref={dropdownRef}>
|
||||||
<div className="ml-4 flex items-baseline space-x-2">
|
<div className="ml-4 flex items-baseline space-x-2">
|
||||||
{navItems.map((item) => {
|
{navItems.map((item) => {
|
||||||
const Icon = item.icon;
|
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 (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={item.name}
|
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 ${
|
className={`group flex items-center px-2 py-2 rounded-lg text-sm font-medium transition-all duration-200 relative overflow-hidden ${
|
||||||
active
|
active
|
||||||
? 'bg-gradient-to-r from-primary-100 to-primary-200 text-primary-700 shadow-sm'
|
? 'bg-gradient-to-r from-primary-100 to-primary-200 text-primary-700 shadow-sm'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue