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目录
This commit is contained in:
2026-03-20 10:19:31 +08:00
parent f2e0ec047e
commit 56c08a34ff
27 changed files with 1812 additions and 199 deletions

View File

@@ -2,6 +2,12 @@ import { useState, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { FiHome, FiBarChart2, FiUsers, FiList } from 'react-icons/fi';
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 { ADMIN_PAGES } from '../constants/pages.js';
import { ADMIN_KEYS } from '../constants/storageKeys.js';
import OverviewPage from './admin/OverviewPage.jsx';
import DepartmentsPage from './admin/DepartmentsPage.jsx';
import UsersPage from './admin/UsersPage.jsx';
@@ -13,21 +19,14 @@ import AddProjectPage from './admin/AddProjectPage.jsx';
function AdminPage() {
const location = useLocation();
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(() => {
return localStorage.getItem('admin_currentPage') || 'overview';
// 使用 usePageState 管理页面状态
const { currentPage, setCurrentPage } = usePageState({
storageKey: ADMIN_KEYS.CURRENT_PAGE,
defaultPage: 'overview',
pageTitles: ADMIN_PAGES,
});
useEffect(() => {
if (location.state?.fromHome) {
setCurrentPage('overview');
navigate('.', { replace: true, state: {} });
}
}, [location.state, navigate, setCurrentPage]);
useEffect(() => {
localStorage.setItem('admin_currentPage', currentPage);
}, [currentPage]);
const renderPage = () => {
switch (currentPage) {
case 'overview':
@@ -50,69 +49,59 @@ function AdminPage() {
};
const getPageTitle = () => {
const titles = {
overview: '总览',
departments: '部门管理',
users: '用户管理',
projects: '项目管理',
addDepartment: '新增部门',
addUser: '新增用户',
addProject: '新增项目'
};
return titles[currentPage] || '';
return ADMIN_PAGES[currentPage]?.title || '';
};
const sidebar = (
<>
<div className="admin-sidebar-header">
<div className="sidebar-brand">
<div className="sidebar-logo-icon">
<span></span>
<span></span>
</div>
<div className="sidebar-brand-text">
<div className="sidebar-logo">GrandClaw</div>
<div className="sidebar-subtitle">运营管理台</div>
</div>
</div>
<SidebarBrand subtitle="运营管理台" />
</div>
<nav className="admin-sidebar-nav">
<div
className={`admin-nav-item ${currentPage === 'overview' ? 'active' : ''}`}
<SidebarNavItem
icon={<FiHome />}
label="总览"
active={currentPage === 'overview'}
onClick={() => setCurrentPage('overview')}
>
<span className="admin-nav-icon"><FiHome /></span>
<span className="admin-nav-text">总览</span>
</div>
<div
className={`admin-nav-item ${currentPage === 'departments' ? 'active' : ''}`}
itemClassName="admin-nav-item"
iconClassName="admin-nav-icon"
textClassName="admin-nav-text"
/>
<SidebarNavItem
icon={<FiBarChart2 />}
label="部门管理"
active={currentPage === 'departments'}
onClick={() => setCurrentPage('departments')}
>
<span className="admin-nav-icon"><FiBarChart2 /></span>
<span className="admin-nav-text">部门管理</span>
</div>
<div
className={`admin-nav-item ${currentPage === 'users' ? 'active' : ''}`}
itemClassName="admin-nav-item"
iconClassName="admin-nav-icon"
textClassName="admin-nav-text"
/>
<SidebarNavItem
icon={<FiUsers />}
label="用户管理"
active={currentPage === 'users'}
onClick={() => setCurrentPage('users')}
>
<span className="admin-nav-icon"><FiUsers /></span>
<span className="admin-nav-text">用户管理</span>
</div>
<div
className={`admin-nav-item ${currentPage === 'projects' ? 'active' : ''}`}
itemClassName="admin-nav-item"
iconClassName="admin-nav-icon"
textClassName="admin-nav-text"
/>
<SidebarNavItem
icon={<FiList />}
label="项目管理"
active={currentPage === 'projects'}
onClick={() => setCurrentPage('projects')}
>
<span className="admin-nav-icon"><FiList /></span>
<span className="admin-nav-text">项目管理</span>
</div>
itemClassName="admin-nav-item"
iconClassName="admin-nav-icon"
textClassName="admin-nav-text"
/>
</nav>
<div className="admin-sidebar-user">
<div className="user-avatar"></div>
<div className="admin-sidebar-user-info">
<div className="admin-sidebar-user-name">张三</div>
<div className="admin-sidebar-user-role">系统管理员</div>
</div>
</div>
<SidebarUser
onClick={() => {}}
wrapperClassName="admin-sidebar-user"
infoClassName="admin-sidebar-user-info"
nameClassName="admin-sidebar-user-name"
roleClassName="admin-sidebar-user-role"
/>
</>
);