Add login and logout notification

This commit is contained in:
2023-10-30 00:26:19 +08:00
parent 5d71433d63
commit fcd4fd532c
10 changed files with 134 additions and 25 deletions

9
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"match-sorter": "^6.3.1",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.15.0",
@@ -4885,6 +4886,14 @@
"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": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",

View File

@@ -21,6 +21,7 @@
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"match-sorter": "^6.3.1",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.15.0",

View File

@@ -1,10 +1,11 @@
$origin-color: white;
$main-color: #4E47BB;
$production-color: #4E47BB;
$main-color: $production-color;
$secondary-color: #BAB8E5;
$error-color: #ff4d4f;
$error-secondary-color: #ff7875;
$blue-color: #1677ff;
$active-color: #EBECFFD;
$error-color: #FF4D4F;
$error-secondary-color: #FF7875;
$blue-color: #1677FF;
$active-color: #EBECFD;
$background-color: #F5F5F5;
$font-main-color: #4D4D4D;
$font-secondary-color: #9E9E9E;

View File

@@ -1,7 +1,9 @@
import React from 'react'
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 { notification } from 'antd'
import { COLOR_ERROR } from '@/constants/common.constants.ts'
const SidebarFooter: React.FC = () => {
const matches = useMatches()
@@ -9,6 +11,8 @@ const SidebarFooter: React.FC = () => {
const location = useLocation()
const navigate = useNavigate()
const [exiting, setExiting] = useState(false)
const [username, setUsername] = useState('')
const handleClickAvatar = () => {
if (getLoginStatus()) {
navigate('/user')
@@ -24,12 +28,26 @@ const SidebarFooter: React.FC = () => {
setExiting(true)
void logout().finally(() => {
notification.info({
message: '已退出登录',
icon: <Icon component={IconFatwebExit} style={{ color: COLOR_ERROR }} />
})
setTimeout(() => {
window.location.reload()
}, 1500)
})
}
const loginStatus = getLoginStatus()
useEffect(() => {
if (getLoginStatus()) {
void getUsername().then((username) => {
setUsername(username)
})
}
}, [loginStatus])
return (
<div className={'footer'}>
<span className={'icon-user'} onClick={handleClickAvatar}>
@@ -42,7 +60,7 @@ const SidebarFooter: React.FC = () => {
</NavLink>
</span>
<span hidden={!getLoginStatus()} className={'text'}>
{username}
</span>
<div
hidden={!getLoginStatus()}

View File

@@ -1,11 +1,19 @@
export const PRODUCTION_NAME = 'FatWeb'
export const STORAGE_TOKEN_KEY = 'JWT_TOKEN'
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_TOP = 'rgba(234,46,13,0.85)'
export const COLOR_FONT_MAIN = '#4D4D4D'
export const COLOR_FONT_SECONDARY = '#9E9E9E'
export const COLOR_FOCUS = '#DDDDDD'
export const SIZE_ICON_XS = '16px'
export const SIZE_ICON_SM = '20px'
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_ERROR = 10050
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

View File

@@ -1,3 +1,4 @@
export const URL_API_LOGIN = '/login'
export const URL_API_TOKEN = '/token'
export const URL_API_LOGOUT = '/logout'
export const URL_API_USER_INFO = '/system/user/info'

41
src/global.d.ts vendored
View File

@@ -47,14 +47,49 @@ type Captcha = {
base64Src: string
}
type Token = {
type TokenVo = {
token: string
}
type User = {
id: number
type UserVo = {
id: string
username: string
locking: boolean
expiration: Date
credentialsExpiration: Date
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 = {

View File

@@ -7,7 +7,9 @@ import {
} from '@/constants/common.constants'
import '@/assets/css/pages/login.scss'
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 [messageApi, contextHolder] = message.useMessage()
@@ -31,6 +33,27 @@ const Login: React.FC = () => {
} else {
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)
break
case SYSTEM_USERNAME_NOT_FOUND:

View File

@@ -44,7 +44,7 @@ service.interceptors.request.use(
.get(import.meta.env.VITE_API_TOKEN_URL, {
headers: { Authorization: `Bearer ${token}` }
})
.then((value: AxiosResponse<_Response<Token>>) => {
.then((value: AxiosResponse<_Response<TokenVo>>) => {
const response = value.data
if (response.code === SYSTEM_TOKEN_RENEW_SUCCESS) {
setToken(response.data?.token ?? '')

View File

@@ -1,12 +1,16 @@
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 { 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
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,
password
})
@@ -22,27 +26,27 @@ export const getLoginStatus = () => {
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) {
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 () => {
let user: User | null
export const requestUserInfo = async () => {
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
if (response.code === SYSTEM_OK) {
if (response.code === DATABASE_SELECT_SUCCESS) {
user = response.data
setLocalStorage(STORAGE_USER_INFO_KEY, JSON.stringify(user))
}
})
return new Promise<User>((resolve, reject) => {
return new Promise<UserVo>((resolve, reject) => {
if (user) {
resolve(user)
}
@@ -51,7 +55,7 @@ export const requestUser = async () => {
}
export const getUsername = async () => {
const user = await getUser()
const user = await getUserInfo()
return user.username
}