Files
yunhaifinance/backups/20260325_002423/frontend/src/components/layout/MainLayout.tsx
T

415 lines
10 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect } from 'react'
import { Outlet, useNavigate, useLocation } from 'react-router-dom'
import {
Layout,
Menu,
Button,
Avatar,
Dropdown,
Typography,
Space,
Badge,
Drawer,
Modal,
theme
} from 'antd'
import {
DashboardOutlined,
ProjectOutlined,
DollarOutlined,
FileTextOutlined,
BarChartOutlined,
UserOutlined,
LogoutOutlined,
SettingOutlined,
BellOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
CalculatorOutlined,
ToolOutlined,
WalletOutlined,
MoneyCollectOutlined,
AuditOutlined,
FileSearchOutlined,
ShoppingCartOutlined,
TeamOutlined,
ShopOutlined,
SolutionOutlined,
HomeOutlined,
SafetyOutlined,
FileDoneOutlined,
AppstoreOutlined,
CheckCircleOutlined
} from '@ant-design/icons'
import { useAuthStore } from '../../store/authStore'
import { useLanguageStore } from '../../store/languageStore'
import CompanyLogo from '../common/CompanyLogo'
import LanguageSelector from '../common/LanguageSelector'
const { Header, Sider, Content } = Layout
const { Text } = Typography
const MainLayout: React.FC = () => {
const navigate = useNavigate()
const location = useLocation()
const [collapsed, setCollapsed] = useState(false)
const [isMobile, setIsMobile] = useState(false)
const [mobileMenuVisible, setMobileMenuVisible] = useState(false)
const [settingsVisible, setSettingsVisible] = useState(false)
const { user, logout } = useAuthStore()
const { t } = useLanguageStore()
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken()
// 检测屏幕尺寸
useEffect(() => {
const checkMobile = () => {
const mobile = window.innerWidth <= 768
setIsMobile(mobile)
if (mobile) {
setCollapsed(true)
}
}
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
// 完整菜单项
const menuItems = [
{
key: '/dashboard',
icon: <DashboardOutlined />,
label: '工作台'
},
{
key: '/projects',
icon: <ProjectOutlined />,
label: '项目管理'
},
{
key: '/budget-projects',
icon: <CalculatorOutlined />,
label: '预算报价'
},
{
key: '/construction',
icon: <ToolOutlined />,
label: '施工管理'
},
{
key: 'approval',
icon: <SolutionOutlined />,
label: '审批管理',
children: [
{
key: '/approval',
icon: <CheckCircleOutlined />,
label: '待审批'
},
{
key: '/execution',
icon: <DollarOutlined />,
label: '待执行'
}
]
},
{
key: 'finance-docs',
icon: <FileDoneOutlined />,
label: '财务申请',
children: [
{
key: '/advances',
icon: <WalletOutlined />,
label: '预支申请'
},
{
key: '/reimbursements',
icon: <FileTextOutlined />,
label: '报销申请'
},
{
key: '/payment-requests',
icon: <MoneyCollectOutlined />,
label: '付款申请'
},
{
key: '/verification',
icon: <AuditOutlined />,
label: '核销申请'
}
]
},
{
key: 'finance-group',
icon: <BarChartOutlined />,
label: '财务管理',
children: [
{
key: '/finance',
label: '财务概览'
},
{
key: '/exchange-rates',
icon: <DollarOutlined />,
label: '汇率管理'
}
]
},
{
key: '/reports',
icon: <FileSearchOutlined />,
label: '报表分析'
},
{
key: 'procurement',
icon: <ShoppingCartOutlined />,
label: '采购管理',
children: [
{
key: '/products',
icon: <AppstoreOutlined />,
label: '商品管理'
}
]
},
{
key: 'partners',
icon: <TeamOutlined />,
label: '合作伙伴',
children: [
{
key: '/suppliers',
icon: <ShopOutlined />,
label: '供应商管理'
},
{
key: '/subcontractors',
icon: <SolutionOutlined />,
label: '分包商管理'
},
{
key: '/customers',
icon: <HomeOutlined />,
label: '客户管理'
}
]
}
]
// 用户下拉菜单
const userMenuItems = [
{
key: 'profile',
icon: <UserOutlined />,
label: '个人信息'
},
{
key: 'settings',
icon: <SettingOutlined />,
label: '系统设置'
},
{
type: 'divider' as const
},
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录'
}
]
// 处理菜单点击
const handleMenuClick = ({ key }: { key: string }) => {
if (key === 'logout') {
logout()
navigate('/login')
} else if (key === 'settings') {
setSettingsVisible(true)
} else if (key.startsWith('/')) {
navigate(key)
if (isMobile) {
setMobileMenuVisible(false)
}
}
}
// 获取当前选中的菜单项
const getSelectedKey = () => {
return location.pathname
}
// 获取当前展开的菜单项
const getOpenKeys = () => {
const path = location.pathname
if (path.startsWith('/suppliers') ||
path.startsWith('/subcontractors') ||
path.startsWith('/customers')) {
return ['partners']
}
if (path.startsWith('/advances') ||
path.startsWith('/reimbursements') ||
path.startsWith('/payment-requests') ||
path.startsWith('/verification')) {
return ['finance-docs']
}
if (path.startsWith('/approval') || path.startsWith('/execution')) {
return ['approval']
}
if (path.startsWith('/products')) {
return ['procurement']
}
return []
}
return (
<Layout style={{ minHeight: '100vh' }}>
{/* 桌面端侧边栏 */}
{!isMobile && (
<Sider
trigger={null}
collapsible
collapsed={collapsed}
style={{
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
background: colorBgContainer,
borderRight: '1px solid #f0f0f0'
}}
width={220}
collapsedWidth={80}
>
{/* Logo */}
<div style={{
height: 64,
display: 'flex',
alignItems: 'center',
justifyContent: collapsed ? 'center' : 'flex-start',
padding: collapsed ? 0 : '0 20px',
borderBottom: '1px solid #f0f0f0'
}}>
<CompanyLogo collapsed={collapsed} />
</div>
{/* 菜单 */}
<Menu
mode="inline"
selectedKeys={[getSelectedKey()]}
defaultOpenKeys={getOpenKeys()}
items={menuItems}
onClick={handleMenuClick}
style={{ borderRight: 0 }}
/>
{/* 折叠按钮 */}
<div style={{
position: 'absolute',
bottom: 0,
width: '100%',
padding: 16,
borderTop: '1px solid #f0f0f0'
}}>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
style={{ width: '100%' }}
>
{!collapsed && '收起菜单'}
</Button>
</div>
</Sider>
)}
{/* 移动端抽屉菜单 */}
{isMobile && (
<Drawer
placement="left"
onClose={() => setMobileMenuVisible(false)}
open={mobileMenuVisible}
width={280}
styles={{ body: { padding: 0 } }}
>
<div style={{ height: 64, padding: '0 20px', display: 'flex', alignItems: 'center', borderBottom: '1px solid #f0f0f0' }}>
<CompanyLogo collapsed={false} />
</div>
<Menu
mode="inline"
selectedKeys={[getSelectedKey()]}
defaultOpenKeys={getOpenKeys()}
items={menuItems}
onClick={handleMenuClick}
style={{ borderRight: 0 }}
/>
</Drawer>
)}
<Layout style={{ marginLeft: isMobile ? 0 : (collapsed ? 80 : 220), transition: 'margin-left 0.2s' }}>
{/* 顶部导航 */}
<Header style={{
padding: '0 24px',
background: colorBgContainer,
position: 'sticky',
top: 0,
zIndex: 1,
borderBottom: '1px solid #f0f0f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
{/* 移动端菜单按钮 */}
{isMobile && (
<Button
type="text"
icon={<MenuFoldOutlined />}
onClick={() => setMobileMenuVisible(true)}
/>
)}
<div style={{ flex: 1 }} />
<Space size="middle">
<LanguageSelector />
<Dropdown menu={{ items: userMenuItems, onClick: handleMenuClick }} placement="bottomRight">
<Space style={{ cursor: 'pointer' }}>
<Avatar icon={<UserOutlined />} style={{ backgroundColor: '#1890ff' }} />
{!isMobile && <Text>{user?.name || user?.username || '用户'}</Text>}
</Space>
</Dropdown>
</Space>
</Header>
{/* 内容区域 */}
<Content style={{
margin: 0,
minHeight: 280,
background: '#f5f5f5'
}}>
<Outlet />
</Content>
</Layout>
{/* 设置弹窗 */}
<Modal
title="系统设置"
open={settingsVisible}
onCancel={() => setSettingsVisible(false)}
footer={null}
>
<p>系统设置功能开发中...</p>
</Modal>
</Layout>
)
}
export default MainLayout