- 新增 AppHeader 组件(Logo + 台入口 + 用户状态) - 新增 UserDropdown 组件(用户下拉菜单) - 新增 AppLayout 布局组件 - 移除 SidebarBrand 和 SidebarUser 组件 - 修改各台页面,移除侧边栏中的品牌区和用户区 - 修改 HomePage,移除独立 header/footer - 修改 Layout 组件,简化为 sidebar + content - 账户设置改为弹框形式,不中断用户操作 - 更新 README.md 布局系统说明 - 同步 delta specs 到主 specs
219 lines
8.4 KiB
JavaScript
219 lines
8.4 KiB
JavaScript
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; |