diff --git a/src/assets/svg/analysis.svg b/src/assets/svg/analysis.svg new file mode 100644 index 0000000..50f98c7 --- /dev/null +++ b/src/assets/svg/analysis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/constants/urls.constants.ts b/src/constants/urls.constants.ts index ab5a1e2..75c2374 100644 --- a/src/constants/urls.constants.ts +++ b/src/constants/urls.constants.ts @@ -17,6 +17,7 @@ export const URL_SYS_STATISTIC_HARDWARE = `${URL_SYS_STATISTIC}/hardware` export const URL_SYS_STATISTIC_CPU = `${URL_SYS_STATISTIC}/cpu` export const URL_SYS_STATISTIC_STORAGE = `${URL_SYS_STATISTIC}/storage` export const URL_SYS_STATISTIC_ONLINE = `${URL_SYS_STATISTIC}/online` +export const URL_SYS_STATISTIC_ACTIVE = `${URL_SYS_STATISTIC}/active` export const URL_API_V1 = '/api/v1' export const URL_API_V1_AVATAR_RANDOM_BASE64 = `${URL_API_V1}/avatar/base64` diff --git a/src/global.d.ts b/src/global.d.ts index 6856688..28204d2 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -389,3 +389,18 @@ interface OnlineInfoVo { interface OnlineInfoGetParam { scope: string } + +interface ActiveInfoVo { + registerHistory: { + time: string + count: number + }[] + loginHistory: { + time: string + count: number + }[] +} + +interface ActiveInfoGetParam { + scope: string +} diff --git a/src/pages/system/index.tsx b/src/pages/system/index.tsx index 16d688c..566a518 100644 --- a/src/pages/system/index.tsx +++ b/src/pages/system/index.tsx @@ -14,11 +14,13 @@ import { import { BarChart, BarSeriesOption, LineChart, LineSeriesOption } from 'echarts/charts' import { UniversalTransition } from 'echarts/features' import { SVGRenderer } from 'echarts/renderers' +import { TopLevelFormatterParams } from 'echarts/types/dist/shared' import '@/assets/css/pages/system/index.scss' import { useUpdatedEffect } from '@/util/hooks' import { formatByteSize } from '@/util/common' import { utcToLocalTime } from '@/util/datetime' import { + r_sys_statistic_active, r_sys_statistic_cpu, r_sys_statistic_hardware, r_sys_statistic_online, @@ -90,6 +92,30 @@ const barEChartsBaseOption: EChartsOption = { } } +const getTooltipTimeFormatter = (format: string = 'yyyy-MM-DD HH:mm:ss') => { + return (params: TopLevelFormatterParams) => + `${utcToLocalTime( + // 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[0], + format + )}
${ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + params[0]['marker'] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + }${params[0]['seriesName']}${ + // 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] + } ` +} + const lineEChartsBaseOption: EChartsOption = { tooltip: { trigger: 'axis' @@ -114,11 +140,8 @@ const lineEChartsBaseOption: EChartsOption = { { type: 'inside', start: 0, - end: 100 - }, - { - start: 0, - end: 100 + end: 100, + minValueSpan: 2 * 60 * 60 * 1000 } ], series: [{}] @@ -201,7 +224,6 @@ const OnlineInfo: React.FC = () => { setTimeout(() => { const dataList = data.history.map((value) => [value.time, value.record]) - onlineInfoEChartsRef.current = echarts.init( onlineInfoDivRef.current, null, @@ -210,6 +232,13 @@ const OnlineInfo: React.FC = () => { onlineInfoEChartsRef.current?.setOption({ ...lineEChartsBaseOption, + tooltip: { + ...lineEChartsBaseOption.tooltip, + formatter: getTooltipTimeFormatter('yyyy-MM-DD HH:mm') + }, + xAxis: { + ...lineEChartsBaseOption.xAxis + }, series: [ { name: '在线人数', @@ -267,6 +296,142 @@ const OnlineInfo: React.FC = () => { ) } +const ActiveInfo: React.FC = () => { + const activeInfoDivRef = useRef(null) + const activeInfoEChartsRef = useRef(null) + const [isLoading, setIsLoading] = useState(false) + const [scope, setScope] = useState('WEAK') + + useUpdatedEffect(() => { + const chartResizeObserver = new ResizeObserver(() => { + activeInfoEChartsRef.current?.resize() + }) + + activeInfoDivRef.current && chartResizeObserver.observe(activeInfoDivRef.current) + + return () => { + activeInfoDivRef.current && chartResizeObserver.unobserve(activeInfoDivRef.current) + } + }, [isLoading]) + + useUpdatedEffect(() => { + getActiveInfo() + }, []) + + const handleOnScopeChange = (value: string) => { + setScope(value) + getActiveInfo(value) + } + + const handleOnRefresh = () => { + getActiveInfo() + } + + const getActiveInfo = (_scope: string = scope) => { + if (isLoading) { + return + } + + setIsLoading(true) + + void r_sys_statistic_active({ scope: _scope }).then((res) => { + const response = res.data + if (response.success) { + const data = response.data + if (data) { + setIsLoading(false) + + setTimeout(() => { + const registerList = data.registerHistory.map((value) => [ + value.time, + value.count + ]) + const loginList = data.loginHistory.map((value) => [ + value.time, + value.count + ]) + + activeInfoEChartsRef.current = echarts.init( + activeInfoDivRef.current, + null, + { renderer: 'svg' } + ) + + activeInfoEChartsRef.current?.setOption({ + ...lineEChartsBaseOption, + tooltip: { + ...lineEChartsBaseOption.tooltip, + formatter: getTooltipTimeFormatter('yyyy-MM-DD') + }, + dataZoom: [ + { + type: 'inside', + start: 0, + end: 100, + minValueSpan: 2 * 24 * 60 * 60 * 1000 + } + ], + series: [ + { + name: '注册人数', + type: 'line', + smooth: true, + symbol: 'none', + areaStyle: {}, + data: registerList + }, + { + name: '登录人数', + type: 'line', + smooth: true, + symbol: 'none', + areaStyle: {}, + data: loginList + } + ] + }) + }) + } + } + }) + } + + return ( + + + 用户活跃 + + + } + loading={isLoading} + expand={ + <> + + 最近7天 + 最近30天 + 最近3个月 + 最近12个月 + 最近2年 + 最近3年 + 最近5年 + 全部 + + + + + + } + > + +
+ + + ) +} + const SoftwareInfo: React.FC = () => { const [softwareInfoData, setSoftwareInfoData] = useState() @@ -748,6 +913,7 @@ const System: React.FC = () => { + diff --git a/src/services/system.tsx b/src/services/system.tsx index 8686e6e..9d8133e 100644 --- a/src/services/system.tsx +++ b/src/services/system.tsx @@ -13,7 +13,8 @@ import { URL_SYS_STATISTIC_HARDWARE, URL_SYS_STATISTIC_CPU, URL_SYS_STATISTIC_STORAGE, - URL_SYS_STATISTIC_ONLINE + URL_SYS_STATISTIC_ONLINE, + URL_SYS_STATISTIC_ACTIVE } from '@/constants/urls.constants' import request from '@/services/index' @@ -90,3 +91,6 @@ export const r_sys_statistic_storage = () => request.get(URL_SYS_ export const r_sys_statistic_online = (param: OnlineInfoGetParam) => request.get(URL_SYS_STATISTIC_ONLINE, param) + +export const r_sys_statistic_active = (param: ActiveInfoGetParam) => + request.get(URL_SYS_STATISTIC_ACTIVE, param) diff --git a/src/util/datetime.tsx b/src/util/datetime.tsx index b88ade6..ef94672 100644 --- a/src/util/datetime.tsx +++ b/src/util/datetime.tsx @@ -28,3 +28,11 @@ export const localTimeToUtc = (localTime: string) => { export const isPastTime = (utcTime: string) => { return moment.utc(utcTime).isBefore(moment.now()) } + +export const utcToMillisecond = (utcTime: string) => { + return moment.utc(utcTime).valueOf() +} + +export const millisecondToUtc = (millisecond: number) => { + return moment(millisecond).toISOString() +}