Add login and logout notification
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"match-sorter": "^6.3.1",
|
"match-sorter": "^6.3.1",
|
||||||
|
"moment": "^2.29.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router": "^6.15.0",
|
"react-router": "^6.15.0",
|
||||||
@@ -4885,6 +4886,14 @@
|
|||||||
"ufo": "^1.3.0"
|
"ufo": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.29.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz",
|
||||||
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"match-sorter": "^6.3.1",
|
"match-sorter": "^6.3.1",
|
||||||
|
"moment": "^2.29.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router": "^6.15.0",
|
"react-router": "^6.15.0",
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
$origin-color: white;
|
$origin-color: white;
|
||||||
$main-color: #4E47BB;
|
$production-color: #4E47BB;
|
||||||
|
$main-color: $production-color;
|
||||||
$secondary-color: #BAB8E5;
|
$secondary-color: #BAB8E5;
|
||||||
$error-color: #ff4d4f;
|
$error-color: #FF4D4F;
|
||||||
$error-secondary-color: #ff7875;
|
$error-secondary-color: #FF7875;
|
||||||
$blue-color: #1677ff;
|
$blue-color: #1677FF;
|
||||||
$active-color: #EBECFFD;
|
$active-color: #EBECFD;
|
||||||
$background-color: #F5F5F5;
|
$background-color: #F5F5F5;
|
||||||
$font-main-color: #4D4D4D;
|
$font-main-color: #4D4D4D;
|
||||||
$font-secondary-color: #9E9E9E;
|
$font-secondary-color: #9E9E9E;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Icon from '@ant-design/icons'
|
import Icon from '@ant-design/icons'
|
||||||
import { getLoginStatus, logout } from '@/utils/auth'
|
import { getLoginStatus, getUsername, logout } from '@/utils/auth'
|
||||||
import { getRedirectUrl } from '@/utils/common'
|
import { getRedirectUrl } from '@/utils/common'
|
||||||
|
import { notification } from 'antd'
|
||||||
|
import { COLOR_ERROR } from '@/constants/common.constants.ts'
|
||||||
|
|
||||||
const SidebarFooter: React.FC = () => {
|
const SidebarFooter: React.FC = () => {
|
||||||
const matches = useMatches()
|
const matches = useMatches()
|
||||||
@@ -9,6 +11,8 @@ const SidebarFooter: React.FC = () => {
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [exiting, setExiting] = useState(false)
|
const [exiting, setExiting] = useState(false)
|
||||||
|
const [username, setUsername] = useState('')
|
||||||
|
|
||||||
const handleClickAvatar = () => {
|
const handleClickAvatar = () => {
|
||||||
if (getLoginStatus()) {
|
if (getLoginStatus()) {
|
||||||
navigate('/user')
|
navigate('/user')
|
||||||
@@ -24,12 +28,26 @@ const SidebarFooter: React.FC = () => {
|
|||||||
|
|
||||||
setExiting(true)
|
setExiting(true)
|
||||||
void logout().finally(() => {
|
void logout().finally(() => {
|
||||||
|
notification.info({
|
||||||
|
message: '已退出登录',
|
||||||
|
icon: <Icon component={IconFatwebExit} style={{ color: COLOR_ERROR }} />
|
||||||
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}, 1500)
|
}, 1500)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loginStatus = getLoginStatus()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getLoginStatus()) {
|
||||||
|
void getUsername().then((username) => {
|
||||||
|
setUsername(username)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [loginStatus])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'footer'}>
|
<div className={'footer'}>
|
||||||
<span className={'icon-user'} onClick={handleClickAvatar}>
|
<span className={'icon-user'} onClick={handleClickAvatar}>
|
||||||
@@ -42,7 +60,7 @@ const SidebarFooter: React.FC = () => {
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
</span>
|
</span>
|
||||||
<span hidden={!getLoginStatus()} className={'text'}>
|
<span hidden={!getLoginStatus()} className={'text'}>
|
||||||
已登录
|
{username}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
hidden={!getLoginStatus()}
|
hidden={!getLoginStatus()}
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
export const PRODUCTION_NAME = 'FatWeb'
|
export const PRODUCTION_NAME = 'FatWeb'
|
||||||
export const STORAGE_TOKEN_KEY = 'JWT_TOKEN'
|
export const STORAGE_TOKEN_KEY = 'JWT_TOKEN'
|
||||||
export const STORAGE_USER_INFO_KEY = 'USER_INFO'
|
export const STORAGE_USER_INFO_KEY = 'USER_INFO'
|
||||||
export const COLOR_PRODUCTION = '#00D4FF'
|
export const COLOR_ORIGIN = 'white'
|
||||||
|
export const COLOR_PRODUCTION = '#4E47BB'
|
||||||
|
export const COLOR_MAIN = COLOR_PRODUCTION
|
||||||
|
export const COLOR_SECONDARY = '#BAB8E5'
|
||||||
|
export const COLOR_ERROR = '#FF4D4F'
|
||||||
|
export const COLOR_ERROR_SECONDARY = '#FF7875'
|
||||||
|
export const COLOR_BLUE = '#1677FF'
|
||||||
|
export const COLOR_ACTIVE = '#EBECFD'
|
||||||
export const COLOR_BACKGROUND = '#F5F5F5'
|
export const COLOR_BACKGROUND = '#F5F5F5'
|
||||||
export const COLOR_TOP = 'rgba(234,46,13,0.85)'
|
export const COLOR_TOP = 'rgba(234,46,13,0.85)'
|
||||||
export const COLOR_FONT_MAIN = '#4D4D4D'
|
export const COLOR_FONT_MAIN = '#4D4D4D'
|
||||||
export const COLOR_FONT_SECONDARY = '#9E9E9E'
|
export const COLOR_FONT_SECONDARY = '#9E9E9E'
|
||||||
|
export const COLOR_FOCUS = '#DDDDDD'
|
||||||
export const SIZE_ICON_XS = '16px'
|
export const SIZE_ICON_XS = '16px'
|
||||||
export const SIZE_ICON_SM = '20px'
|
export const SIZE_ICON_SM = '20px'
|
||||||
export const SIZE_ICON_MD = '24px'
|
export const SIZE_ICON_MD = '24px'
|
||||||
@@ -33,3 +41,12 @@ export const SYSTEM_REQUEST_ILLEGAL = 10040
|
|||||||
export const SYSTEM_ARGUMENT_NOT_VALID = 10041
|
export const SYSTEM_ARGUMENT_NOT_VALID = 10041
|
||||||
export const SYSTEM_ERROR = 10050
|
export const SYSTEM_ERROR = 10050
|
||||||
export const SYSTEM_TIMEOUT = 10051
|
export const SYSTEM_TIMEOUT = 10051
|
||||||
|
|
||||||
|
export const DATABASE_SELECT_SUCCESS = 20000
|
||||||
|
export const DATABASE_SELECT_FAILED = 20005
|
||||||
|
export const DATABASE_INSERT_SUCCESS = 20010
|
||||||
|
export const DATABASE_INSERT_FAILED = 20015
|
||||||
|
export const DATABASE_UPDATE_SUCCESS = 20020
|
||||||
|
export const DATABASE_UPDATE_FILED = 20025
|
||||||
|
export const DATABASE_DELETE_SUCCESS = 20030
|
||||||
|
export const DATABASE_DELETE_FILED = 20035
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export const URL_API_LOGIN = '/login'
|
export const URL_API_LOGIN = '/login'
|
||||||
export const URL_API_TOKEN = '/token'
|
export const URL_API_TOKEN = '/token'
|
||||||
export const URL_API_LOGOUT = '/logout'
|
export const URL_API_LOGOUT = '/logout'
|
||||||
|
export const URL_API_USER_INFO = '/system/user/info'
|
||||||
|
|||||||
41
src/global.d.ts
vendored
41
src/global.d.ts
vendored
@@ -47,14 +47,49 @@ type Captcha = {
|
|||||||
base64Src: string
|
base64Src: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Token = {
|
type TokenVo = {
|
||||||
token: string
|
token: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type User = {
|
type UserVo = {
|
||||||
id: number
|
id: string
|
||||||
username: string
|
username: string
|
||||||
|
locking: boolean
|
||||||
|
expiration: Date
|
||||||
|
credentialsExpiration: Date
|
||||||
enable: number
|
enable: number
|
||||||
|
lastLoginTime: Date
|
||||||
|
lastLoginIp: string
|
||||||
|
createTime: Date
|
||||||
|
updateTime: Date
|
||||||
|
menus: MenuVo[]
|
||||||
|
elements: ElementVo[]
|
||||||
|
operations: OperationVo[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuVo = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
powerId: number
|
||||||
|
parentId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ElementVo = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
powerId: number
|
||||||
|
parentId: number
|
||||||
|
menuId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type OperationVo = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
code: string
|
||||||
|
powerId: number
|
||||||
|
parentId: number
|
||||||
|
elementId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginForm = {
|
type LoginForm = {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import {
|
|||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
import '@/assets/css/pages/login.scss'
|
import '@/assets/css/pages/login.scss'
|
||||||
import { setToken } from '@/utils/common'
|
import { setToken } from '@/utils/common'
|
||||||
import { login } from '@/utils/auth'
|
import { getUserInfo, login } from '@/utils/auth'
|
||||||
|
import { notification } from 'antd'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
@@ -31,6 +33,27 @@ const Login: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
navigate('/')
|
navigate('/')
|
||||||
}
|
}
|
||||||
|
void getUserInfo().then((user) => {
|
||||||
|
notification.success({
|
||||||
|
message: '欢迎回来',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
你好 <strong>{user.username}</strong>
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span>
|
||||||
|
上次登录:
|
||||||
|
{moment(user.lastLoginTime).format(
|
||||||
|
'yyyy-MM-DD HH:mm:ssZ'
|
||||||
|
)}
|
||||||
|
【{user.lastLoginIp}】
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
placement: 'topRight'
|
||||||
|
})
|
||||||
|
})
|
||||||
}, 1500)
|
}, 1500)
|
||||||
break
|
break
|
||||||
case SYSTEM_USERNAME_NOT_FOUND:
|
case SYSTEM_USERNAME_NOT_FOUND:
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ service.interceptors.request.use(
|
|||||||
.get(import.meta.env.VITE_API_TOKEN_URL, {
|
.get(import.meta.env.VITE_API_TOKEN_URL, {
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
})
|
})
|
||||||
.then((value: AxiosResponse<_Response<Token>>) => {
|
.then((value: AxiosResponse<_Response<TokenVo>>) => {
|
||||||
const response = value.data
|
const response = value.data
|
||||||
if (response.code === SYSTEM_TOKEN_RENEW_SUCCESS) {
|
if (response.code === SYSTEM_TOKEN_RENEW_SUCCESS) {
|
||||||
setToken(response.data?.token ?? '')
|
setToken(response.data?.token ?? '')
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { getCaptcha, getLocalStorage, removeToken, setLocalStorage } from './common'
|
import { getCaptcha, getLocalStorage, removeToken, setLocalStorage } from './common'
|
||||||
import { SYSTEM_OK, STORAGE_TOKEN_KEY, STORAGE_USER_INFO_KEY } from '@/constants/common.constants'
|
import {
|
||||||
|
STORAGE_TOKEN_KEY,
|
||||||
|
STORAGE_USER_INFO_KEY,
|
||||||
|
DATABASE_SELECT_SUCCESS
|
||||||
|
} from '@/constants/common.constants'
|
||||||
import request from '@/services'
|
import request from '@/services'
|
||||||
import { URL_API_LOGIN, URL_API_LOGOUT } from '@/constants/urls.constants'
|
import { URL_API_LOGIN, URL_API_LOGOUT, URL_API_USER_INFO } from '@/constants/urls.constants'
|
||||||
|
|
||||||
let captcha: Captcha
|
let captcha: Captcha
|
||||||
|
|
||||||
export const login = async (username: string, password: string) => {
|
export const login = async (username: string, password: string) => {
|
||||||
return await request.post<Token>(URL_API_LOGIN, {
|
return await request.post<TokenVo>(URL_API_LOGIN, {
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
})
|
})
|
||||||
@@ -22,27 +26,27 @@ export const getLoginStatus = () => {
|
|||||||
return getLocalStorage(STORAGE_TOKEN_KEY) !== null
|
return getLocalStorage(STORAGE_TOKEN_KEY) !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getUser = async (): Promise<User> => {
|
export const getUserInfo = async (): Promise<UserVo> => {
|
||||||
if (getLocalStorage(STORAGE_USER_INFO_KEY) !== null) {
|
if (getLocalStorage(STORAGE_USER_INFO_KEY) !== null) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
resolve(JSON.parse(getLocalStorage(STORAGE_USER_INFO_KEY) as string) as User)
|
resolve(JSON.parse(getLocalStorage(STORAGE_USER_INFO_KEY) as string) as UserVo)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return requestUser()
|
return requestUserInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestUser = async () => {
|
export const requestUserInfo = async () => {
|
||||||
let user: User | null
|
let user: UserVo | null
|
||||||
|
|
||||||
await request.get<User>('/user/info').then((value) => {
|
await request.get<UserVo>(URL_API_USER_INFO).then((value) => {
|
||||||
const response = value.data
|
const response = value.data
|
||||||
if (response.code === SYSTEM_OK) {
|
if (response.code === DATABASE_SELECT_SUCCESS) {
|
||||||
user = response.data
|
user = response.data
|
||||||
setLocalStorage(STORAGE_USER_INFO_KEY, JSON.stringify(user))
|
setLocalStorage(STORAGE_USER_INFO_KEY, JSON.stringify(user))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return new Promise<User>((resolve, reject) => {
|
return new Promise<UserVo>((resolve, reject) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
resolve(user)
|
resolve(user)
|
||||||
}
|
}
|
||||||
@@ -51,7 +55,7 @@ export const requestUser = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getUsername = async () => {
|
export const getUsername = async () => {
|
||||||
const user = await getUser()
|
const user = await getUserInfo()
|
||||||
|
|
||||||
return user.username
|
return user.username
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user