Initial commit: ERP system with advance verification fixes
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
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
|
||||
Reference in New Issue
Block a user