import React, { useState, useEffect } from 'react' import { Layout, Menu, theme, Button, Space, Dropdown, message, Drawer, Avatar, Modal, Form, Input, Badge, Tooltip } from 'antd' import { DashboardOutlined, DesktopOutlined, TeamOutlined, BellOutlined, InfoCircleOutlined, BookOutlined, MenuOutlined, LogoutOutlined, UserOutlined, DownOutlined, UserSwitchOutlined, KeyOutlined, MessageOutlined } 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 AccountManagement from './pages/AccountManagement' import Messages from './pages/Messages' import About from './pages/About' import Guide from './pages/Guide' import Download from './pages/Download' 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 [pwdModalVisible, setPwdModalVisible] = useState(false) const [pwdForm] = Form.useForm() const [unreadTotal, setUnreadTotal] = useState(0) 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) }, []) // 轮询消息未读总数(侧边栏徽标) useEffect(() => { if (!user) return const fetchUnread = () => { axios.get('/api/chat/conversations') .then(res => { if (res.data.success) { setUnreadTotal(res.data.data.reduce((s, c) => s + (c.unread || 0), 0)) } }) .catch(() => {}) } fetchUnread() const timer = setInterval(fetchUnread, 10000) return () => clearInterval(timer) }, [user?.id]) 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: , label: '系统首页' }, { type: 'divider' }, { key: 'devices', icon: , label: '设备管理' }, { key: 'personnel', icon: , label: '人员管理' }, ...(user?.role === 'admin' ? [{ key: 'accounts', icon: , label: '账户管理' }] : []), { key: 'notifications', icon: , label: '通知管理' }, { key: 'download', icon: , label: '软件下载' } ] // 移动端底部导航(5个核心入口) const bottomNavItems = [ { key: 'dashboard', icon: , label: '首页' }, { key: 'devices', icon: , label: '设备' }, { key: 'personnel', icon: , label: '人员' }, { key: 'notifications', icon: , label: '通知' }, { key: 'messages', icon: , label: '消息' } ] const renderPage = () => { switch (currentPage) { case 'dashboard': return case 'about': return case 'guide': return case 'devices': return case 'personnel': return case 'accounts': return user?.role === 'admin' ? : case 'notifications': return case 'messages': return case 'download': return default: return } } const getPageTitle = () => { const titles = { dashboard: '系统首页', about: '软件简介', guide: '使用说明',download: '软件下载', devices: '设备管理', personnel: '人员管理', accounts: '账户管理', notifications: '通知管理', messages: '消息对话' } return titles[currentPage] || '系统首页' } // 修改自己的密码(所有用户可用,成功后需用新密码重新登录) const handleChangePassword = async () => { try { const values = await pwdForm.validateFields() const res = await axios.patch('/api/auth/password', { oldPassword: values.oldPassword, newPassword: values.newPassword }) if (res.data.success) { setPwdModalVisible(false) pwdForm.resetFields() // 后端已使当前会话失效,清除本地登录态回到登录页 localStorage.removeItem('token') localStorage.removeItem('user') setUser(null) message.success(res.data.message) } } catch (err) { if (err.response) message.error(err.response.data?.message || '修改失败') } } const pwdModal = ( setPwdModalVisible(false)} okText="确定" cancelText="取消" width={420} >
({ validator(_, value) { if (!value || getFieldValue('newPassword') === value) return Promise.resolve() return Promise.reject(new Error('两次输入的密码不一致')) } }) ]} >
) const userMenuItems = [ { key: 'role', label: `角色:${user?.role === 'admin' ? '管理员' : '普通用户'}`, icon: }, { type: 'divider' }, { key: 'changePwd', label: '修改密码', icon: , onClick: () => setPwdModalVisible(true) }, { key: 'logout', label: '退出登录', icon: , danger: true, onClick: () => handleLogout() } ] if (checkingAuth) { return
加载中...
} if (!user) { return } const menuContent = ( <>
影视星河
{ setCurrentPage(key); setDrawerVisible(false) }} /> ) // ============ 移动端布局 ============ if (isMobile) { return (
{user.display_name || user.username},{getGreeting()}!
setDrawerVisible(false)} width={260} styles={{ body: { padding: 0, background: '#001529' }, header: { display: 'none' } }} >
{menuContent}
{renderPage()} {pwdModal}
) } // ============ 桌面端布局 ============ return (
{collapsed ? ( logo ) : ( 影视星河 )}
setCurrentPage(key)} />
{getPageTitle()}
{user.display_name || user.username},{getGreeting()}!
{renderPage()}
{pwdModal} ) } export default App