first commit
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Layout, Menu, theme, Button, Space, Dropdown, message, Drawer, Avatar } from 'antd'
|
||||
import {
|
||||
DashboardOutlined,
|
||||
DesktopOutlined,
|
||||
TeamOutlined,
|
||||
BellOutlined,
|
||||
InfoCircleOutlined,
|
||||
BookOutlined,
|
||||
MenuOutlined,
|
||||
LogoutOutlined,
|
||||
UserOutlined,
|
||||
DownOutlined
|
||||
} from '@ant-design/icons'
|
||||
import axios from 'axios'
|
||||
import Login from './pages/Login'
|
||||
import Weather from './components/Weather'
|
||||
import Dashboard from './pages/Dashboard'
|
||||
import DeviceManagement from './pages/DeviceManagement'
|
||||
import PersonnelManagement from './pages/PersonnelManagement'
|
||||
import NotificationManagement from './pages/NotificationManagement'
|
||||
import About from './pages/About'
|
||||
import Guide from './pages/Guide'
|
||||
|
||||
const { Header, Sider, Content } = Layout
|
||||
|
||||
// 移动端检测 Hook
|
||||
function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768)
|
||||
useEffect(() => {
|
||||
const onResize = () => setIsMobile(window.innerWidth < 768)
|
||||
window.addEventListener('resize', onResize)
|
||||
return () => window.removeEventListener('resize', onResize)
|
||||
}, [])
|
||||
return isMobile
|
||||
}
|
||||
|
||||
function App() {
|
||||
const isMobile = useIsMobile()
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [drawerVisible, setDrawerVisible] = useState(false)
|
||||
const [currentPage, setCurrentPage] = useState('dashboard')
|
||||
const [user, setUser] = useState(null)
|
||||
const [checkingAuth, setCheckingAuth] = useState(true)
|
||||
|
||||
const {
|
||||
token: { colorBgContainer, borderRadiusLG }
|
||||
} = theme.useToken()
|
||||
|
||||
// 初始化时恢复登录态
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token')
|
||||
const savedUser = localStorage.getItem('user')
|
||||
if (token && savedUser) {
|
||||
axios.get('/api/auth/me')
|
||||
.then(res => {
|
||||
if (res.data.success) setUser(res.data.data)
|
||||
else handleLogout(false)
|
||||
})
|
||||
.catch(() => handleLogout(false))
|
||||
.finally(() => setCheckingAuth(false))
|
||||
} else {
|
||||
setCheckingAuth(false)
|
||||
}
|
||||
|
||||
// 监听登录过期
|
||||
const onExpired = () => handleLogout(false)
|
||||
window.addEventListener('auth-expired', onExpired)
|
||||
return () => window.removeEventListener('auth-expired', onExpired)
|
||||
}, [])
|
||||
|
||||
const handleLogin = (userData) => {
|
||||
setUser(userData)
|
||||
setCurrentPage('dashboard')
|
||||
}
|
||||
|
||||
const handleLogout = async (callApi = true) => {
|
||||
if (callApi) {
|
||||
try { await axios.post('/api/auth/logout') } catch (err) { /* ignore */ }
|
||||
}
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('user')
|
||||
setUser(null)
|
||||
message.success('已安全退出')
|
||||
}
|
||||
|
||||
const getGreeting = () => {
|
||||
const hour = new Date().getHours()
|
||||
if (hour < 6) return '晚上好'
|
||||
if (hour < 9) return '早上好'
|
||||
if (hour < 12) return '上午好'
|
||||
if (hour < 14) return '中午好'
|
||||
if (hour < 18) return '下午好'
|
||||
return '晚上好'
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{ key: 'dashboard', icon: <DashboardOutlined />, label: '系统首页' },
|
||||
{ key: 'about', icon: <InfoCircleOutlined />, label: '软件简介' },
|
||||
{ key: 'guide', icon: <BookOutlined />, label: '使用说明' },
|
||||
{ type: 'divider' },
|
||||
{ key: 'devices', icon: <DesktopOutlined />, label: '设备管理' },
|
||||
{ key: 'personnel', icon: <TeamOutlined />, label: '人员管理' },
|
||||
{ key: 'notifications', icon: <BellOutlined />, label: '通知管理' }
|
||||
]
|
||||
|
||||
// 移动端底部导航(4个核心入口)
|
||||
const bottomNavItems = [
|
||||
{ key: 'dashboard', icon: <DashboardOutlined />, label: '首页' },
|
||||
{ key: 'devices', icon: <DesktopOutlined />, label: '设备' },
|
||||
{ key: 'personnel', icon: <TeamOutlined />, label: '人员' },
|
||||
{ key: 'notifications', icon: <BellOutlined />, label: '通知' }
|
||||
]
|
||||
|
||||
const renderPage = () => {
|
||||
switch (currentPage) {
|
||||
case 'dashboard': return <Dashboard onNavigate={setCurrentPage} />
|
||||
case 'about': return <About />
|
||||
case 'guide': return <Guide />
|
||||
case 'devices': return <DeviceManagement user={user} />
|
||||
case 'personnel': return <PersonnelManagement user={user} />
|
||||
case 'notifications': return <NotificationManagement user={user} />
|
||||
default: return <Dashboard onNavigate={setCurrentPage} />
|
||||
}
|
||||
}
|
||||
|
||||
const getPageTitle = () => {
|
||||
const titles = {
|
||||
dashboard: '系统首页', about: '软件简介', guide: '使用说明',
|
||||
devices: '设备管理', personnel: '人员管理', notifications: '通知管理'
|
||||
}
|
||||
return titles[currentPage] || '系统首页'
|
||||
}
|
||||
|
||||
const userMenuItems = [
|
||||
{ key: 'role', label: `角色:${user?.role === 'admin' ? '管理员' : '普通用户'}`, icon: <UserOutlined /> },
|
||||
{ type: 'divider' },
|
||||
{ key: 'logout', label: '退出登录', icon: <LogoutOutlined />, danger: true, onClick: () => handleLogout() }
|
||||
]
|
||||
|
||||
if (checkingAuth) {
|
||||
return <div style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>加载中...</div>
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return <Login onLogin={handleLogin} />
|
||||
}
|
||||
|
||||
const menuContent = (
|
||||
<>
|
||||
<div className="logo-area">
|
||||
<img src="/assets/logo-white.png" alt="影视星河" className="logo-img" />
|
||||
</div>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
selectedKeys={[currentPage]}
|
||||
items={menuItems}
|
||||
onClick={({ key }) => { setCurrentPage(key); setDrawerVisible(false) }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
// ============ 移动端布局 ============
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Header className="mobile-header">
|
||||
<Button type="text" icon={<MenuOutlined />} onClick={() => setDrawerVisible(true)} style={{ color: '#fff' }} />
|
||||
<span className="mobile-title">{getPageTitle()}</span>
|
||||
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight" trigger={['click']}>
|
||||
<Avatar style={{ backgroundColor: '#7c3aed' }} icon={<UserOutlined />} />
|
||||
</Dropdown>
|
||||
</Header>
|
||||
|
||||
<div className="mobile-info-bar">
|
||||
<span className="mobile-greeting">{user.display_name || user.username},{getGreeting()}!</span>
|
||||
<Weather />
|
||||
</div>
|
||||
|
||||
<Drawer
|
||||
placement="left"
|
||||
open={drawerVisible}
|
||||
onClose={() => setDrawerVisible(false)}
|
||||
width={260}
|
||||
styles={{ body: { padding: 0, background: '#001529' }, header: { display: 'none' } }}
|
||||
>
|
||||
<div style={{ background: '#001529', height: '100%' }}>{menuContent}</div>
|
||||
</Drawer>
|
||||
|
||||
<Content className="mobile-content">
|
||||
{renderPage()}
|
||||
</Content>
|
||||
|
||||
<nav className="bottom-nav">
|
||||
{bottomNavItems.map(item => (
|
||||
<button
|
||||
key={item.key}
|
||||
className={`bottom-nav-item ${currentPage === item.key ? 'active' : ''}`}
|
||||
onClick={() => setCurrentPage(item.key)}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
// ============ 桌面端布局 ============
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Sider trigger={null} collapsible collapsed={collapsed} theme="dark">
|
||||
<div className="logo-area">
|
||||
{collapsed ? (
|
||||
<img src="/assets/logo-icon.png" alt="logo" className="logo-icon" />
|
||||
) : (
|
||||
<img src="/assets/logo-white.png" alt="影视星河" className="logo-img" />
|
||||
)}
|
||||
</div>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
selectedKeys={[currentPage]}
|
||||
items={menuItems}
|
||||
onClick={({ key }) => setCurrentPage(key)}
|
||||
/>
|
||||
</Sider>
|
||||
<Layout>
|
||||
<Header style={{ padding: '0 24px', background: colorBgContainer, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, boxShadow: '0 1px 4px rgba(0,0,0,0.08)' }}>
|
||||
<span style={{ fontSize: 18, fontWeight: 500 }}>{getPageTitle()}</span>
|
||||
<Space size="large" align="center">
|
||||
<Weather />
|
||||
<div className="greeting-text">{user.display_name || user.username},{getGreeting()}!</div>
|
||||
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
|
||||
<Button type="text" icon={<UserOutlined />} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
{user.display_name || user.username}
|
||||
<DownOutlined style={{ fontSize: 10 }} />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Header>
|
||||
<Content style={{ margin: 24, padding: 24, background: colorBgContainer, borderRadius: borderRadiusLG, minHeight: 280 }}>
|
||||
{renderPage()}
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
Reference in New Issue
Block a user