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()
+}