Files
GrandClaw-prototype/src/pages/DeveloperPage.jsx
lanyuanxiaoyao 56c08a34ff refactor: 代码架构重构 - 提取组件、统一状态管理和数据访问层
- 新增布局组件(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目录
2026-03-20 10:19:31 +08:00

156 lines
5.8 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, 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;