- 新增布局组件(SidebarBrand、SidebarUser、SidebarNavItem) - 新增通用UI组件(EmptyState、StatusBadge、TagInput、SearchBar) - 新增全局状态管理(UserContext) - 新增自定义Hooks(usePageState、useNavigation) - 新增统一数据访问层(src/services/api.js) - 新增常量配置(constants/pages.js、constants/storageKeys.js) - 样式文件模块化,拆分页面特定样式 - 更新README文档,添加组件和使用说明 - 同步OpenSpec规范到主specs目录
156 lines
5.8 KiB
JavaScript
156 lines
5.8 KiB
JavaScript
import { useState, useEffect } from 'react';
|
||
import { useLocation, useNavigate } from 'react-router-dom';
|
||
import { FiPlus, FiTerminal } from 'react-icons/fi';
|
||
import { FaPuzzlePiece } from 'react-icons/fa';
|
||
import Layout from '../components/Layout.jsx';
|
||
import SidebarBrand from '../components/layout/SidebarBrand.jsx';
|
||
import SidebarUser from '../components/layout/SidebarUser.jsx';
|
||
import SidebarNavItem from '../components/layout/SidebarNavItem.jsx';
|
||
import usePageState from '../hooks/usePageState.js';
|
||
import { DEVELOPER_PAGES } from '../constants/pages.js';
|
||
import { DEVELOPER_KEYS } from '../constants/storageKeys.js';
|
||
import api from '../services/api.js';
|
||
import MySkillsPage from './developer/MySkillsPage.jsx';
|
||
import UploadSkillPage from './developer/UploadSkillPage.jsx';
|
||
import NewVersionPage from './developer/NewVersionPage.jsx';
|
||
import DevDocsPage from './developer/DevDocsPage.jsx';
|
||
import DevAccountPage from './developer/DevAccountPage.jsx';
|
||
import SkillEditorPage from './developer/SkillEditorPage.jsx';
|
||
|
||
function DeveloperPage() {
|
||
const location = useLocation();
|
||
const navigate = useNavigate();
|
||
|
||
// 使用 usePageState 管理页面状态
|
||
const { currentPage, setCurrentPage } = usePageState({
|
||
storageKey: DEVELOPER_KEYS.CURRENT_PAGE,
|
||
defaultPage: 'mySkills',
|
||
pageTitles: DEVELOPER_PAGES,
|
||
});
|
||
|
||
// 保留额外的状态(currentSkillId 需要持久化到 localStorage)
|
||
const [currentSkillId, setCurrentSkillId] = useState(() => {
|
||
const saved = localStorage.getItem(DEVELOPER_KEYS.CURRENT_SKILL_ID);
|
||
return saved ? JSON.parse(saved) : null;
|
||
});
|
||
const [newVersionSkillName, setNewVersionSkillName] = useState('');
|
||
|
||
useEffect(() => {
|
||
if (location.state?.fromHome) {
|
||
setCurrentPage('mySkills');
|
||
setCurrentSkillId(null);
|
||
navigate('.', { replace: true, state: {} });
|
||
}
|
||
}, [location.state, navigate, setCurrentPage, setCurrentSkillId]);
|
||
|
||
useEffect(() => {
|
||
localStorage.setItem(DEVELOPER_KEYS.CURRENT_SKILL_ID, JSON.stringify(currentSkillId));
|
||
}, [DEVELOPER_KEYS.CURRENT_SKILL_ID, currentSkillId]);
|
||
|
||
const switchPage = (pageId, data = {}) => {
|
||
setCurrentPage(pageId);
|
||
if (data.skillId !== undefined) {
|
||
setCurrentSkillId(data.skillId);
|
||
}
|
||
};
|
||
|
||
const openSkillEditor = (skillId) => {
|
||
setCurrentSkillId(skillId);
|
||
setCurrentPage('skillEditor');
|
||
};
|
||
|
||
const createNewProject = () => {
|
||
setCurrentPage('uploadSkill');
|
||
};
|
||
|
||
const openNewVersionPage = (skillName) => {
|
||
setNewVersionSkillName(skillName);
|
||
setCurrentPage('newVersion');
|
||
};
|
||
|
||
const handleBack = () => {
|
||
setCurrentPage('mySkills');
|
||
setCurrentSkillId(null);
|
||
};
|
||
|
||
const handleNewVersionBack = () => {
|
||
setCurrentPage('skillEditor');
|
||
setNewVersionSkillName('');
|
||
};
|
||
|
||
const renderPage = () => {
|
||
switch (currentPage) {
|
||
case 'mySkills':
|
||
return <MySkillsPage onSkillClick={openSkillEditor} />;
|
||
case 'uploadSkill':
|
||
return <UploadSkillPage />;
|
||
case 'devDocs':
|
||
return <DevDocsPage />;
|
||
case 'devAccount':
|
||
return <DevAccountPage />;
|
||
case 'skillEditor':
|
||
return <SkillEditorPage skillId={currentSkillId} onBack={handleBack} onUploadNewVersion={openNewVersionPage} />;
|
||
case 'newVersion':
|
||
return <NewVersionPage skillName={newVersionSkillName} onBack={handleNewVersionBack} />;
|
||
default:
|
||
return <div>Page not found</div>;
|
||
}
|
||
};
|
||
|
||
const getPageTitle = () => {
|
||
return DEVELOPER_PAGES[currentPage]?.title || '';
|
||
};
|
||
|
||
const sidebar = (
|
||
<>
|
||
<div className="chat-sidebar-header">
|
||
<SidebarBrand subtitle="技能开发台" />
|
||
<div className="sidebar-divider"></div>
|
||
<button className="btn btn-primary" style={{ width: '100%' }} onClick={createNewProject}>
|
||
<span style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px' }}>
|
||
<FiPlus /> 创建技能
|
||
</span>
|
||
</button>
|
||
</div>
|
||
<div className="chat-sidebar-content">
|
||
{api.developer.getMySkills().map(skill => (
|
||
<div
|
||
key={skill.id}
|
||
className={`conversation-item ${currentSkillId === skill.id && currentPage === 'skillEditor' ? 'active' : ''}`}
|
||
onClick={() => openSkillEditor(skill.id)}
|
||
>
|
||
<div className="conversation-title">{skill.name}</div>
|
||
<div className="conversation-time">{skill.status === 'published' ? '已发布' : '草稿'}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="chat-sidebar-nav">
|
||
<SidebarNavItem
|
||
icon={<FaPuzzlePiece />}
|
||
label="我的技能"
|
||
active={currentPage === 'mySkills'}
|
||
onClick={() => switchPage('mySkills')}
|
||
/>
|
||
<SidebarNavItem
|
||
icon={<FiTerminal />}
|
||
label="开发文档"
|
||
active={currentPage === 'devDocs'}
|
||
onClick={() => switchPage('devDocs')}
|
||
/>
|
||
</div>
|
||
<SidebarUser onClick={() => switchPage('devAccount')} />
|
||
</>
|
||
);
|
||
|
||
return (
|
||
<Layout
|
||
sidebar={sidebar}
|
||
headerTitle={getPageTitle()}
|
||
sidebarClassName="chat-sidebar"
|
||
>
|
||
{renderPage()}
|
||
</Layout>
|
||
);
|
||
}
|
||
|
||
export default DeveloperPage; |