Complete main UI #37

Merged
FatttSnake merged 192 commits from FatttSnake into dev 2024-02-23 16:31:17 +08:00
9 changed files with 119 additions and 39 deletions
Showing only changes of commit 6510567fdf - Show all commits

View File

@@ -186,14 +186,19 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-left: 4px; margin-left: 4px;
padding: 10px;
width: 36px; width: 36px;
height: 36px; height: 36px;
font-size: constants.$SIZE_ICON_XS; font-size: constants.$SIZE_ICON_XS;
border: 2px constants.$font-secondary-color solid; border: 2px constants.$font-secondary-color solid;
color: constants.$font-secondary-color; color: constants.$font-secondary-color;
border-radius: 50%; border-radius: 50%;
overflow: hidden;
cursor: pointer; cursor: pointer;
img {
width: 100%;
height: 100%;
}
} }
.text { .text {

View File

@@ -91,6 +91,13 @@
} }
} }
.verify {
a {
color: constants.$production-color;
font-weight: bolder;
}
}
.sign-up, .sign-in, .forget { .sign-up, .sign-in, .forget {
.footer { .footer {
a { a {

View File

@@ -433,6 +433,9 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
} }
useEffect(() => { useEffect(() => {
setTimeout(() => {
reloadScrollbar()
}, 500)
const resizeObserver = new ResizeObserver(() => { const resizeObserver = new ResizeObserver(() => {
reloadScrollbar() reloadScrollbar()
}) })

View File

@@ -1,8 +1,8 @@
import React from 'react' import React, { useState } from 'react'
import Icon from '@ant-design/icons' import Icon from '@ant-design/icons'
import { COLOR_ERROR } from '@/constants/common.constants' import { COLOR_ERROR } from '@/constants/common.constants'
import { getRedirectUrl } from '@/util/route' import { getRedirectUrl } from '@/util/route'
import { getLoginStatus, getNickname, removeToken } from '@/util/auth' import { getAvatar, getLoginStatus, getNickname, removeToken } from '@/util/auth'
import { r_auth_logout } from '@/services/auth' import { r_auth_logout } from '@/services/auth'
const SidebarFooter: React.FC = () => { const SidebarFooter: React.FC = () => {
@@ -12,6 +12,7 @@ const SidebarFooter: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [exiting, setExiting] = useState(false) const [exiting, setExiting] = useState(false)
const [nickname, setNickname] = useState('') const [nickname, setNickname] = useState('')
const [avatar, setAvatar] = useState('')
const handleClickAvatar = () => { const handleClickAvatar = () => {
if (getLoginStatus()) { if (getLoginStatus()) {
@@ -45,6 +46,10 @@ const SidebarFooter: React.FC = () => {
if (getLoginStatus()) { if (getLoginStatus()) {
void getNickname().then((nickname) => { void getNickname().then((nickname) => {
setNickname(nickname) setNickname(nickname)
void getAvatar().then((avatar) => {
setAvatar(`data:image/png;base64,${avatar}`)
})
}) })
} }
}, [loginStatus]) }, [loginStatus])
@@ -56,7 +61,11 @@ const SidebarFooter: React.FC = () => {
onClick={handleClickAvatar} onClick={handleClickAvatar}
title={getLoginStatus() ? '个人中心' : '登录'} title={getLoginStatus() ? '个人中心' : '登录'}
> >
<Icon component={IconFatwebUser} /> {avatar ? (
<img src={avatar} alt={'Avatar'} />
) : (
<Icon viewBox={'-20 0 1024 1024'} component={IconFatwebUser} />
)}
</span> </span>
<span hidden={getLoginStatus()} className={'text'}> <span hidden={getLoginStatus()} className={'text'}>
@@ -64,7 +73,7 @@ const SidebarFooter: React.FC = () => {
</NavLink> </NavLink>
</span> </span>
<span hidden={!getLoginStatus()} className={'text'}> <span hidden={!getLoginStatus()} className={'text'} title={nickname}>
{nickname} {nickname}
</span> </span>
<div <div

5
src/global.d.ts vendored
View File

@@ -199,6 +199,7 @@ interface UserAddEditParam {
id?: string id?: string
username: string username: string
password?: string password?: string
verified: boolean
locking?: boolean locking?: boolean
expiration?: string expiration?: string
credentialsExpiration?: string credentialsExpiration?: string
@@ -413,6 +414,10 @@ interface ActiveInfoVo {
time: string time: string
count: number count: number
}[] }[]
verifyHistory: {
time: string
count: number
}[]
} }
interface ActiveInfoGetParam { interface ActiveInfoGetParam {

View File

@@ -8,6 +8,7 @@ import {
PERMISSION_ACCOUNT_NEED_INIT, PERMISSION_ACCOUNT_NEED_INIT,
PERMISSION_LOGIN_SUCCESS, PERMISSION_LOGIN_SUCCESS,
PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR, PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR,
PERMISSION_NO_VERIFICATION_REQUIRED,
PERMISSION_REGISTER_SUCCESS, PERMISSION_REGISTER_SUCCESS,
PERMISSION_USER_DISABLE, PERMISSION_USER_DISABLE,
PERMISSION_USERNAME_NOT_FOUND PERMISSION_USERNAME_NOT_FOUND
@@ -201,7 +202,7 @@ const SignUp: React.FC = () => {
</> </>
) : ( ) : (
<div className={'retry'}> <div className={'retry'}>
<a onClick={handleOnResend}></a> <a onClick={handleOnResend}></a>
</div> </div>
)} )}
@@ -228,6 +229,7 @@ const Verify: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const [hasCode, setHasCode] = useState(true) const [hasCode, setHasCode] = useState(true)
const [needVerify, setNeedVerify] = useState(true)
const [isValid, setIsValid] = useState(true) const [isValid, setIsValid] = useState(true)
const [isSending, setIsSending] = useState(false) const [isSending, setIsSending] = useState(false)
const [isGettingAvatar, setIsGettingAvatar] = useState(false) const [isGettingAvatar, setIsGettingAvatar] = useState(false)
@@ -255,11 +257,17 @@ const Verify: React.FC = () => {
void r_auth_verify({ code }) void r_auth_verify({ code })
.then((res) => { .then((res) => {
const response = res.data const response = res.data
if (response.code === PERMISSION_ACCOUNT_NEED_INIT) { switch (response.code) {
case PERMISSION_ACCOUNT_NEED_INIT:
void getUserInfo().then((user) => { void getUserInfo().then((user) => {
setAvatar(user.userInfo.avatar) setAvatar(user.userInfo.avatar)
}) })
} else { break
case PERMISSION_NO_VERIFICATION_REQUIRED:
void message.success('无需验证')
setNeedVerify(false)
break
default:
setIsValid(false) setIsValid(false)
} }
}) })
@@ -347,7 +355,13 @@ const Verify: React.FC = () => {
<div className={'secondary'}>Verify account</div> <div className={'secondary'}>Verify account</div>
</div> </div>
<AntdForm className={'form'} onFinish={handleOnFinish}> <AntdForm className={'form'} onFinish={handleOnFinish}>
<div className={'verify-process'} hidden={!hasCode || !isValid}> <div className={'no-verify-need'} hidden={needVerify}>
&nbsp;<a href={'/'}></a>
</div>
<div
className={'verify-process'}
hidden={!needVerify || !hasCode || !isValid}
>
<div <div
style={{ style={{
display: 'flex', display: 'flex',
@@ -360,7 +374,7 @@ const Verify: React.FC = () => {
src={ src={
<img <img
src={`data:image/png;base64,${avatar}`} src={`data:image/png;base64,${avatar}`}
alt={'avatar'} alt={'Avatar'}
/> />
} }
size={100} size={100}

View File

@@ -6,6 +6,8 @@ import {
TooltipComponentOption, TooltipComponentOption,
GridComponent, GridComponent,
GridComponentOption, GridComponentOption,
LegendComponent,
LegendComponentOption,
ToolboxComponentOption, ToolboxComponentOption,
DataZoomComponentOption, DataZoomComponentOption,
ToolboxComponent, ToolboxComponent,
@@ -14,7 +16,7 @@ import {
import { BarChart, BarSeriesOption, LineChart, LineSeriesOption } from 'echarts/charts' import { BarChart, BarSeriesOption, LineChart, LineSeriesOption } from 'echarts/charts'
import { UniversalTransition } from 'echarts/features' import { UniversalTransition } from 'echarts/features'
import { SVGRenderer } from 'echarts/renderers' import { SVGRenderer } from 'echarts/renderers'
import { TopLevelFormatterParams } from 'echarts/types/dist/shared' import { CallbackDataParams } from 'echarts/types/dist/shared'
import '@/assets/css/pages/system/statistics.scss' import '@/assets/css/pages/system/statistics.scss'
import { useUpdatedEffect } from '@/util/hooks' import { useUpdatedEffect } from '@/util/hooks'
import { formatByteSize } from '@/util/common' import { formatByteSize } from '@/util/common'
@@ -38,6 +40,7 @@ echarts.use([
TooltipComponent, TooltipComponent,
ToolboxComponent, ToolboxComponent,
GridComponent, GridComponent,
LegendComponent,
DataZoomComponent, DataZoomComponent,
BarChart, BarChart,
LineChart, LineChart,
@@ -48,6 +51,7 @@ type EChartsOption = echarts.ComposeOption<
| TooltipComponentOption | TooltipComponentOption
| ToolboxComponentOption | ToolboxComponentOption
| GridComponentOption | GridComponentOption
| LegendComponentOption
| BarSeriesOption | BarSeriesOption
| DataZoomComponentOption | DataZoomComponentOption
| LineSeriesOption | LineSeriesOption
@@ -94,33 +98,26 @@ const barEChartsBaseOption: EChartsOption = {
} }
const getTooltipTimeFormatter = (format: string = 'yyyy-MM-DD HH:mm:ss') => { const getTooltipTimeFormatter = (format: string = 'yyyy-MM-DD HH:mm:ss') => {
return (params: TopLevelFormatterParams) => return (params: CallbackDataParams[]) =>
`${utcToLocalTime(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error // @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
params[0].data[0], `${utcToLocalTime(params[0].data[0], format)}<br>${params
format .map(
)}<br><span style="display: flex; justify-content: space-between"><span>${ (param) =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error // @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
params[0]['marker'] `<span style="display: flex; justify-content: space-between;"><span>${param.marker}${param.seriesName}</span><span style="font-weight: bold; margin-left: 16px;">${param.data[1]}</span></span>`
// eslint-disable-next-line @typescript-eslint/ban-ts-comment )
// @ts-expect-error .join('')}`
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
}${params[0]['seriesName']}</span><span style="font-weight: bold">${
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access
params[0].data[1]
}</span></span> `
} }
const lineEChartsBaseOption: EChartsOption = { const lineEChartsBaseOption: EChartsOption = {
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis'
}, },
legend: {},
toolbox: { toolbox: {
feature: { feature: {
dataZoom: { dataZoom: {
@@ -380,6 +377,19 @@ const ActiveInfo: React.FC = () => {
)?.count ?? 0 )?.count ?? 0
]) ])
: [] : []
const verifyList = data.verifyHistory.length
? getTimesBetweenTwoTimes(
data.verifyHistory[0].time,
data.verifyHistory[data.verifyHistory.length - 1].time,
'day'
).map((time) => [
time,
data.verifyHistory.find(
(value) =>
value.time.substring(0, 10) === time.substring(0, 10)
)?.count ?? 0
])
: []
activeInfoEChartsRef.current = echarts.init( activeInfoEChartsRef.current = echarts.init(
activeInfoDivRef.current, activeInfoDivRef.current,
@@ -417,6 +427,14 @@ const ActiveInfo: React.FC = () => {
symbol: 'none', symbol: 'none',
areaStyle: {}, areaStyle: {},
data: loginList data: loginList
},
{
name: '验证账号人数',
type: 'line',
smooth: true,
symbol: 'none',
areaStyle: {},
data: verifyList
} }
] ]
}) })

View File

@@ -91,7 +91,7 @@ const User: React.FC = () => {
<AntdImage <AntdImage
preview={{ mask: <Icon component={IconFatwebEye}></Icon> }} preview={{ mask: <Icon component={IconFatwebEye}></Icon> }}
src={`data:image/png;base64,${value}`} src={`data:image/png;base64,${value}`}
alt={'avatar'} alt={'Avatar'}
/> />
} }
style={{ background: COLOR_BACKGROUND }} style={{ background: COLOR_BACKGROUND }}
@@ -161,13 +161,21 @@ const User: React.FC = () => {
</> </>
} }
> >
{!record.locking && {!record.verify &&
!record.locking &&
(!record.expiration || !isPastTime(record.expiration)) && (!record.expiration || !isPastTime(record.expiration)) &&
(!record.credentialsExpiration || !isPastTime(record.credentialsExpiration)) && (!record.credentialsExpiration || !isPastTime(record.credentialsExpiration)) &&
record.enable ? ( record.enable ? (
<AntdTag color={'green'}></AntdTag> <AntdTag color={'green'}></AntdTag>
) : ( ) : (
<> <>
{record.verify ? (
<>
<AntdPopover content={record.verify} trigger={'click'}>
<AntdTag style={{ cursor: 'pointer' }}></AntdTag>
</AntdPopover>
</>
) : undefined}
{record.locking ? <AntdTag></AntdTag> : undefined} {record.locking ? <AntdTag></AntdTag> : undefined}
{record.expiration && isPastTime(record.expiration) ? ( {record.expiration && isPastTime(record.expiration) ? (
<AntdTag></AntdTag> <AntdTag></AntdTag>
@@ -430,6 +438,7 @@ const User: React.FC = () => {
form.setFieldValue('id', value.id) form.setFieldValue('id', value.id)
form.setFieldValue('username', value.username) form.setFieldValue('username', value.username)
form.setFieldValue('password', undefined) form.setFieldValue('password', undefined)
form.setFieldValue('verified', !value.verify?.length)
form.setFieldValue('locking', value.locking) form.setFieldValue('locking', value.locking)
form.setFieldValue('expiration', value.expiration) form.setFieldValue('expiration', value.expiration)
form.setFieldValue('credentialsExpiration', value.credentialsExpiration) form.setFieldValue('credentialsExpiration', value.credentialsExpiration)
@@ -727,6 +736,7 @@ const User: React.FC = () => {
if (!isDrawerEdit && formValues) { if (!isDrawerEdit && formValues) {
setNewFormValues({ setNewFormValues({
username: formValues.username, username: formValues.username,
verified: formValues.verified,
locking: formValues.locking, locking: formValues.locking,
expiration: formValues.expiration, expiration: formValues.expiration,
credentialsExpiration: formValues.credentialsExpiration, credentialsExpiration: formValues.credentialsExpiration,
@@ -771,7 +781,7 @@ const User: React.FC = () => {
src={`data:image/png;base64,${ src={`data:image/png;base64,${
isDrawerEdit ? formValues?.avatar : avatar isDrawerEdit ? formValues?.avatar : avatar
}`} }`}
alt={'avatar'} alt={'Avatar'}
/> />
} }
size={100} size={100}
@@ -840,6 +850,9 @@ const User: React.FC = () => {
}))} }))}
/> />
</AntdForm.Item> </AntdForm.Item>
<AntdForm.Item name={'verified'} label={'已验证'}>
<AntdSwitch />
</AntdForm.Item>
<AntdForm.Item <AntdForm.Item
valuePropName={'checked'} valuePropName={'checked'}
name={'locking'} name={'locking'}

View File

@@ -112,6 +112,12 @@ export const getNickname = async () => {
return user.userInfo.nickname return user.userInfo.nickname
} }
export const getAvatar = async () => {
const user = await getUserInfo()
return user.userInfo.avatar
}
export const getUsername = async () => { export const getUsername = async () => {
const user = await getUserInfo() const user = await getUserInfo()