update
This commit is contained in:
+124
-7
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user