This commit is contained in:
mei
2026-08-11 10:03:40 +08:00
parent 2086bd79cc
commit fb326279fa
25 changed files with 2590 additions and 2379 deletions
+124 -7
View File
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'
import { Layout, Menu, theme, Button, Space, Dropdown, message, Drawer, Avatar } from 'antd'
import { Layout, Menu, theme, Button, Space, Dropdown, message, Drawer, Avatar, Modal, Form, Input, Badge, Tooltip } from 'antd'
import {
DashboardOutlined,
DesktopOutlined,
@@ -10,7 +10,10 @@ import {
MenuOutlined,
LogoutOutlined,
UserOutlined,
DownOutlined
DownOutlined,
UserSwitchOutlined,
KeyOutlined,
MessageOutlined
} from '@ant-design/icons'
import axios from 'axios'
import Login from './pages/Login'
@@ -19,6 +22,8 @@ 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'
@@ -42,6 +47,9 @@ function App() {
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 }
@@ -69,6 +77,23 @@ function App() {
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')
@@ -96,20 +121,20 @@ function App() {
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: '人员管理' },
...(user?.role === 'admin' ? [{ key: 'accounts', icon: <UserSwitchOutlined />, label: '账户管理' }] : []),
{ key: 'notifications', icon: <BellOutlined />, label: '通知管理' }
]
// 移动端底部导航(4个核心入口)
// 移动端底部导航(5个核心入口)
const bottomNavItems = [
{ key: 'dashboard', icon: <DashboardOutlined />, label: '首页' },
{ key: 'devices', icon: <DesktopOutlined />, label: '设备' },
{ key: 'personnel', icon: <TeamOutlined />, label: '人员' },
{ key: 'notifications', icon: <BellOutlined />, label: '通知' }
{ key: 'notifications', icon: <BellOutlined />, label: '通知' },
{ key: 'messages', icon: <MessageOutlined />, label: '消息' }
]
const renderPage = () => {
@@ -119,7 +144,9 @@ function App() {
case 'guide': return <Guide />
case 'devices': return <DeviceManagement user={user} />
case 'personnel': return <PersonnelManagement user={user} />
case 'accounts': return user?.role === 'admin' ? <AccountManagement user={user} /> : <Dashboard onNavigate={setCurrentPage} />
case 'notifications': return <NotificationManagement user={user} />
case 'messages': return <Messages user={user} />
default: return <Dashboard onNavigate={setCurrentPage} />
}
}
@@ -127,14 +154,82 @@ function App() {
const getPageTitle = () => {
const titles = {
dashboard: '系统首页', about: '软件简介', guide: '使用说明',
devices: '设备管理', personnel: '人员管理', notifications: '通知管理'
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 = (
<Modal
title="修改密码"
open={pwdModalVisible}
onOk={handleChangePassword}
onCancel={() => setPwdModalVisible(false)}
okText="确定"
cancelText="取消"
width={420}
>
<Form form={pwdForm} layout="vertical" style={{ marginTop: 12 }}>
<Form.Item name="oldPassword" label="原密码" rules={[{ required: true, message: '请输入原密码' }]}>
<Input.Password placeholder="请输入原密码" />
</Form.Item>
<Form.Item
name="newPassword"
label="新密码"
rules={[
{ required: true, message: '请输入新密码' },
{ min: 6, message: '密码不少于6位' }
]}
>
<Input.Password placeholder="请输入新密码" />
</Form.Item>
<Form.Item
name="confirmPassword"
label="确认新密码"
dependencies={['newPassword']}
rules={[
{ required: true, message: '请再次输入新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) return Promise.resolve()
return Promise.reject(new Error('两次输入的密码不一致'))
}
})
]}
>
<Input.Password placeholder="请再次输入新密码" />
</Form.Item>
</Form>
</Modal>
)
const userMenuItems = [
{ key: 'role', label: `角色:${user?.role === 'admin' ? '管理员' : '普通用户'}`, icon: <UserOutlined /> },
{ type: 'divider' },
{ key: 'changePwd', label: '修改密码', icon: <KeyOutlined />, onClick: () => setPwdModalVisible(true) },
{ key: 'logout', label: '退出登录', icon: <LogoutOutlined />, danger: true, onClick: () => handleLogout() }
]
@@ -204,6 +299,8 @@ function App() {
</button>
))}
</nav>
{pwdModal}
</Layout>
)
}
@@ -226,6 +323,21 @@ function App() {
items={menuItems}
onClick={({ key }) => setCurrentPage(key)}
/>
<div className="sider-footer">
<Tooltip title={collapsed ? '消息对话' : ''} placement="right">
<Button
type="text"
block
className={currentPage === 'messages' ? 'sider-footer-active' : ''}
onClick={() => setCurrentPage('messages')}
>
<Badge count={unreadTotal} size="small" offset={collapsed ? [12, -8] : [8, 0]}>
<MessageOutlined style={{ fontSize: 16 }} />
</Badge>
{!collapsed && <span style={{ marginLeft: 10 }}>消息对话</span>}
</Button>
</Tooltip>
</div>
</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)' }}>
@@ -233,6 +345,9 @@ function App() {
<Space size="large" align="center">
<Weather />
<div className="greeting-text">{user.display_name || user.username},{getGreeting()}!</div>
<Button type="text" icon={<KeyOutlined />} onClick={() => setPwdModalVisible(true)}>
修改密码
</Button>
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
<Button type="text" icon={<UserOutlined />} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
{user.display_name || user.username}
@@ -245,6 +360,8 @@ function App() {
{renderPage()}
</Content>
</Layout>
{pwdModal}
</Layout>
)
}