Files
GrandClaw-prototype/src/pages/ConsolePage.jsx
lanyuanxiaoyao 7f493aa921 feat: 统一全局 Header 结构
- 新增 AppHeader 组件(Logo + 台入口 + 用户状态)
- 新增 UserDropdown 组件(用户下拉菜单)
- 新增 AppLayout 布局组件
- 移除 SidebarBrand 和 SidebarUser 组件
- 修改各台页面,移除侧边栏中的品牌区和用户区
- 修改 HomePage,移除独立 header/footer
- 修改 Layout 组件,简化为 sidebar + content
- 账户设置改为弹框形式,不中断用户操作
- 更新 README.md 布局系统说明
- 同步 delta specs 到主 specs
2026-03-27 12:27:38 +08:00

219 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { FiPlus, FiClock, FiList, FiUsers, FiBox } from 'react-icons/fi';
import { FaPuzzlePiece } from 'react-icons/fa';
import Layout from '../components/Layout.jsx';
import SidebarNavItem from '../components/layout/SidebarNavItem.jsx';
import usePageState from '../hooks/usePageState.js';
import { CONSOLE_PAGES } from '../constants/pages.js';
import { CONSOLE_KEYS } from '../constants/storageKeys.js';
import api from '../services/api.js';
import ChatPage from './console/ChatPage.jsx';
import SkillsPage from './console/SkillsPage.jsx';
import SkillDetailPage from './console/SkillDetailPage.jsx';
import MySkillsPage from './console/MySkillsPage.jsx';
import SkillConfigPage from './console/SkillConfigPage.jsx';
import LogsPage from './console/LogsPage.jsx';
import TasksPage from './console/TasksPage.jsx';
import TaskDetailPage from './console/TaskDetailPage.jsx';
import ProjectsPage from './console/ProjectsPage.jsx';
import MemberConfigPage from './console/MemberConfigPage.jsx';
import AddMemberPage from './console/AddMemberPage.jsx';
function ConsolePage() {
const location = useLocation();
const navigate = useNavigate();
// 使用 usePageState 管理 currentPage不使用其返回的 getPageTitle因为需要访问组件局部变量
const { currentPage, setCurrentPage } = usePageState({
storageKey: CONSOLE_KEYS.CURRENT_PAGE,
defaultPage: 'chat',
pageTitles: CONSOLE_PAGES,
});
// 保留额外的状态scene 和 skillId 等需要特殊处理)
const [currentScene, setCurrentScene] = useState(() => {
return localStorage.getItem(CONSOLE_KEYS.CURRENT_SCENE) || 'welcome';
});
const [currentSkillId, setCurrentSkillId] = useState(null);
const [currentTaskId, setCurrentTaskId] = useState(null);
const [currentSubscriptionId, setCurrentSubscriptionId] = useState(null);
// 处理主页跳转重置
useEffect(() => {
if (location.state?.fromHome) {
setCurrentPage('chat');
setCurrentScene('welcome');
navigate('.', { replace: true, state: {} });
}
}, [location.state, navigate, setCurrentPage, setCurrentScene]);
// 同步 currentScene 到 localStorage
useEffect(() => {
localStorage.setItem(CONSOLE_KEYS.CURRENT_SCENE, currentScene);
}, [currentScene]);
const switchPage = (pageId, data = {}) => {
setCurrentPage(pageId);
if (data.skillId !== undefined) {
setCurrentSkillId(data.skillId);
}
if (data.subscriptionId !== undefined) {
setCurrentSubscriptionId(data.subscriptionId);
}
};
const handleSkillClick = (skillId) => {
switchPage('skillDetail', { skillId });
};
const handleBack = () => {
switchPage('skills');
};
const switchChatScene = (scene) => {
setCurrentScene(scene);
if (currentPage !== 'chat') {
setCurrentPage('chat');
}
};
const createNewChat = () => {
setCurrentScene('welcome');
setCurrentPage('chat');
};
const activeScene = currentPage === 'chat' ? currentScene : null;
const renderPage = () => {
switch (currentPage) {
case 'chat':
return <ChatPage scene={currentScene} />;
case 'skills':
return <SkillsPage onSkillClick={handleSkillClick} />;
case 'skillDetail':
return <SkillDetailPage skillId={currentSkillId} onBack={handleBack} />;
case 'mySkills':
return <MySkillsPage
onConfig={(subscriptionId) => switchPage('skillConfig', { subscriptionId })}
onBack={() => switchPage('skills')}
/>;
case 'skillConfig':
return <SkillConfigPage
subscriptionId={currentSubscriptionId}
onBack={() => switchPage('mySkills')}
/>;
case 'logs':
return <LogsPage />;
case 'scheduledTasks':
return <TasksPage
onViewDetail={(taskId) => {
setCurrentTaskId(taskId);
switchPage('taskDetail');
}}
/>;
case 'taskDetail':
return <TaskDetailPage
taskId={currentTaskId}
onBack={() => switchPage('scheduledTasks')}
/>;
case 'projects':
return <ProjectsPage onAddMember={() => switchPage('addMember')} />;
case 'memberConfig':
return <MemberConfigPage onBack={() => switchPage('projects')} />;
case 'addMember':
return <AddMemberPage onBack={() => switchPage('projects')} />;
default:
return <div>Page not found</div>;
}
};
const getPageTitle = () => {
let title = CONSOLE_PAGES[currentPage]?.title || '';
if (currentPage === 'chat') {
const conv = api.conversations.list().find(c => c.scene === currentScene);
title = conv?.title || '智能助手';
}
if (currentPage === 'skillDetail' && currentSkillId) {
const skill = api.skills.getById(currentSkillId);
title = skill?.name || '技能详情';
}
return title;
};
const sidebar = (
<>
<div className="chat-sidebar-header">
<button className="btn btn-primary" style={{ width: '100%' }} onClick={createNewChat}>
<span style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px' }}>
<FiPlus /> 新建对话
</span>
</button>
</div>
<div className="chat-sidebar-content">
{api.conversations.list().map(conv => (
<div
key={conv.id}
className={`conversation-item ${conv.scene === activeScene ? 'active' : ''}`}
onClick={() => switchChatScene(conv.scene)}
>
<div className="conversation-title">{conv.title}</div>
<div className="conversation-time">{conv.time}</div>
</div>
))}
</div>
<div className="chat-sidebar-project">
<label className="chat-sidebar-project-label">当前项目</label>
<select className="form-control chat-sidebar-project-select">
<option>企业 AI 智算平台</option>
<option>知识库管理系统</option>
<option>数据分析平台</option>
</select>
</div>
<div className="chat-sidebar-nav">
<SidebarNavItem
icon={<FaPuzzlePiece />}
label="技能市场"
active={currentPage === 'skills'}
onClick={() => switchPage('skills')}
/>
<SidebarNavItem
icon={<FiBox />}
label="我的技能"
active={currentPage === 'mySkills'}
onClick={() => switchPage('mySkills')}
/>
<SidebarNavItem
icon={<FiClock />}
label="定时任务"
active={currentPage === 'scheduledTasks'}
onClick={() => switchPage('scheduledTasks')}
/>
<SidebarNavItem
icon={<FiList />}
label="日志查询"
active={currentPage === 'logs'}
onClick={() => switchPage('logs')}
/>
<SidebarNavItem
icon={<FiUsers />}
label="项目管理"
active={currentPage === 'projects'}
onClick={() => switchPage('projects')}
/>
</div>
</>
);
return (
<Layout
sidebar={sidebar}
sidebarClassName="chat-sidebar"
contentClassName={currentPage === 'chat' ? 'page-content-full' : ''}
>
{renderPage()}
</Layout>
);
}
export default ConsolePage;