Files
GrandClaw-prototype/src/pages/DeveloperPage.jsx
lanyuanxiaoyao fb9616a10f feat: 实现技能审核全流程 - 新增审核管理模块、技能状态机、版本审核机制
- 新增审核管理页面:版本审核列表、下架审核列表、审核详情页
- 完善技能状态机:开发中/已上架/下架审核中/已下架四种状态
- 实现版本审核机制:审核中/通过/拒绝/撤销四种状态
- 更新 README:详细记录技能开发流程与审核机制
- 优化技能详情页:根据状态展示不同操作按钮
- 完善我的技能列表:状态筛选与操作限制
- 新增上传新版本页面:分离版本上传与基本信息编辑
- 更新 openspec 规范:技能审核流程与状态定义
2026-03-20 17:54:51 +08:00

194 lines
7.3 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, FiHome } 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 DevOverviewPage from './developer/DevOverviewPage.jsx';
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';
import UpdateSkillInfoPage from './developer/UpdateSkillInfoPage.jsx';
import UploadVersionPage from './developer/UploadVersionPage.jsx';
const skillStatusMap = {
dev: { text: '开发中', className: 'status-stopped' },
published: { text: '已上架', className: 'status-running' },
unlisting: { text: '下架审核中', className: 'status-warning' },
unlisted: { text: '已下架', className: 'status-stopped' }
};
function DeveloperPage() {
const location = useLocation();
const navigate = useNavigate();
// 使用 usePageState 管理页面状态
const { currentPage, setCurrentPage } = usePageState({
storageKey: DEVELOPER_KEYS.CURRENT_PAGE,
defaultPage: 'overview',
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('overview');
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 openUpdateInfoPage = (skillId) => {
setCurrentSkillId(skillId);
setCurrentPage('updateInfo');
};
const handleBack = () => {
setCurrentPage('mySkills');
setCurrentSkillId(null);
};
const handleEditorBack = () => {
setCurrentPage('skillEditor');
setNewVersionSkillName('');
};
const renderPage = () => {
switch (currentPage) {
case 'overview':
return <DevOverviewPage onSkillClick={openSkillEditor} />;
case 'mySkills':
return <MySkillsPage onSkillClick={openSkillEditor} />;
case 'uploadSkill':
return <UploadSkillPage onBack={() => switchPage('mySkills')} />;
case 'devDocs':
return <DevDocsPage />;
case 'devAccount':
return <DevAccountPage />;
case 'skillEditor':
return <SkillEditorPage
skillId={currentSkillId}
onBack={handleBack}
onUploadNewVersion={openNewVersionPage}
onUpdateInfo={openUpdateInfoPage}
/>;
case 'newVersion':
return <UploadVersionPage skillName={newVersionSkillName} onBack={handleEditorBack} />;
case 'updateInfo':
return <UpdateSkillInfoPage
skill={api.developer.getSkillById(currentSkillId)}
onBack={handleEditorBack}
/>;
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">
<span className={`status ${skillStatusMap[skill.status]?.className || 'status-stopped'}`}>
{skillStatusMap[skill.status]?.text || skill.status}
</span>
</div>
</div>
))}
</div>
<div className="chat-sidebar-nav">
<SidebarNavItem
icon={<FiHome />}
label="总览"
active={currentPage === 'overview'}
onClick={() => switchPage('overview')}
/>
<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;