From 4e2d13c0644986b82f05676e4dc158e6d431e2c6 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 8 Dec 2023 15:08:22 +0800 Subject: [PATCH] Finish cpu statistics information --- src/assets/css/pages/system/index.scss | 5 +- src/components/common/echarts/EChartReact.tsx | 181 ----------------- src/global.d.ts | 1 + src/pages/system/index.tsx | 185 +++++++++++++----- src/util/hooks.tsx | 2 +- 5 files changed, 141 insertions(+), 233 deletions(-) delete mode 100644 src/components/common/echarts/EChartReact.tsx diff --git a/src/assets/css/pages/system/index.scss b/src/assets/css/pages/system/index.scss index 619ef30..b603847 100644 --- a/src/assets/css/pages/system/index.scss +++ b/src/assets/css/pages/system/index.scss @@ -35,7 +35,7 @@ padding: 0 10px; gap: 10px; - .key { + .key, .value-percent { flex: 0 0 auto; color: constants.$font-main-color; } @@ -52,7 +52,10 @@ } .value-chart { + justify-content: space-around; + width: 0; >div { + max-height: 12px; height: 12px; >* { transform: translateY(1px); diff --git a/src/components/common/echarts/EChartReact.tsx b/src/components/common/echarts/EChartReact.tsx deleted file mode 100644 index 174f2b0..0000000 --- a/src/components/common/echarts/EChartReact.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import React, { CSSProperties, useState } from 'react' -import echarts, { EChartsCoreOption, EChartsType } from 'echarts/core' -import { RendererType } from 'echarts/types/src/util/types' -import { LocaleOption } from 'echarts/types/src/core/locale' -import { bind, clear } from 'size-sensor' -import isEqual from 'fast-deep-equal' -import { usePrevious } from '@/util/hooks' - -interface EChartsInitOpts { - locale?: string | LocaleOption - renderer?: RendererType - devicePixelRatio?: number - useDirtyRect?: boolean - useCoarsePointer?: boolean - pointerSize?: number - ssr?: boolean - width?: number | string - height?: number | string -} - -interface EChartsReactProps { - echarts: typeof echarts - className?: string - style?: CSSProperties - option: EChartsCoreOption - theme?: string | object | null - notMerge?: boolean - lazyUpdate?: boolean - showLoading?: boolean - loadingOption?: object - opts?: EChartsInitOpts - onChartReady?: (instance: EChartsType) => void - onEvents?: Record void> - shouldSetOption?: (prevProps: EChartsReactProps, props: EChartsReactProps) => boolean -} - -const EChartReact: React.FC = (props) => { - const elementRef = useRef(null) - const prevProps = usePrevious(props) - const [echarts] = useState(props.echarts) - const [isInitialResize, setIsInitialResize] = useState(true) - - const { style, className = '', theme, opts } = props - - const renderNewECharts = () => { - const { onEvents, onChartReady } = props - - const eChartsInstance = updateEChartsOption() - - bindEvents(eChartsInstance, onEvents || {}) - - if (typeof onChartReady === 'function') { - onChartReady(eChartsInstance) - } - - if (elementRef.current) { - bind(elementRef.current, () => { - resize() - }) - } - - return () => { - dispose() - } - } - - const updateEChartsOption = () => { - const { option, notMerge = false, lazyUpdate = false, showLoading, loadingOption } = props - - const eChartsInstance = getEChartsInstance() - eChartsInstance.setOption(option, notMerge, lazyUpdate) - if (showLoading) { - eChartsInstance.showLoading(loadingOption) - } else { - eChartsInstance.hideLoading() - } - - return eChartsInstance - } - - const getEChartsInstance = () => - (elementRef.current && echarts.getInstanceByDom(elementRef.current)) || - echarts.init(elementRef.current, theme, opts) - - const bindEvents = (instance: EChartsType, events: EChartsReactProps['onEvents']) => { - const _bindEvents = ( - eventName: string, - func: (param: unknown, instance: EChartsType) => void - ) => { - if (typeof func === 'function') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call - instance.on(eventName, (param: unknown) => { - func(param, instance) - }) - } - } - - for (const eventName in events) { - if (Object.prototype.hasOwnProperty.call(events, eventName)) { - _bindEvents(eventName, events[eventName]) - } - } - } - - const resize = () => { - const eChartsInstance = getEChartsInstance() - - if (!isInitialResize) { - try { - eChartsInstance.resize() - } catch (e) { - console.warn(e) - } - } - - setIsInitialResize(false) - } - - const dispose = () => { - if (elementRef.current) { - try { - clear(elementRef.current) - } catch (e) { - console.warn(e) - } - echarts.dispose(elementRef.current) - } - } - - const pick = (obj: EChartsReactProps | undefined, keys: string[]): Record => { - const r = {} - keys.forEach((key) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - r[key] = obj[key] - }) - return r - } - - useEffect(() => { - renderNewECharts() - }) - - useEffect(() => { - const { shouldSetOption } = props - if ( - typeof shouldSetOption === 'function' && - prevProps && - !shouldSetOption(prevProps, props) - ) { - return - } - - if ( - !isEqual(prevProps?.theme, props.theme) || - !isEqual(prevProps?.opts, props.opts) || - !isEqual(prevProps?.onEvents, props.onEvents) - ) { - dispose() - renderNewECharts() - return - } - - const pickKeys = ['option', 'notMerge', 'lazyUpdate', 'showLoading', 'loadingOption'] - if (!isEqual(pick(props, pickKeys), pick(prevProps, pickKeys))) { - updateEChartsOption() - } - - if ( - !isEqual(prevProps?.style, props.style) || - !isEqual(prevProps?.className, props.className) - ) { - resize() - } - }, [props]) - - return
-} - -export default EChartReact diff --git a/src/global.d.ts b/src/global.d.ts index 0524f23..e05f83a 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -361,6 +361,7 @@ interface CpuInfoVo { irq: number softirq: number steal: number + total: number processors: CpuInfoVo[] } diff --git a/src/pages/system/index.tsx b/src/pages/system/index.tsx index 65d601a..ec76b2e 100644 --- a/src/pages/system/index.tsx +++ b/src/pages/system/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import Icon from '@ant-design/icons' import * as echarts from 'echarts/core' import { @@ -8,7 +8,7 @@ import { GridComponentOption } from 'echarts/components' import { BarChart, BarSeriesOption } from 'echarts/charts' -import { CanvasRenderer } from 'echarts/renderers' +import { SVGRenderer } from 'echarts/renderers' import '@/assets/css/pages/system/index.scss' import { useUpdatedEffect } from '@/util/hooks' import { utcToLocalTime } from '@/util/datetime' @@ -22,12 +22,12 @@ import FlexBox from '@/components/common/FlexBox' import FitFullScreen from '@/components/common/FitFullScreen' import HideScrollbar from '@/components/common/HideScrollbar' import LoadingMask from '@/components/common/LoadingMask' -import EChartReact from '@/components/common/echarts/EChartReact' -echarts.use([TooltipComponent, GridComponent, BarChart, CanvasRenderer]) +echarts.use([TooltipComponent, GridComponent, BarChart, SVGRenderer]) type EChartsOption = echarts.ComposeOption< TooltipComponentOption | GridComponentOption | BarSeriesOption > + interface CommonCardProps extends React.PropsWithChildren { icon: IconComponent title: string @@ -174,47 +174,54 @@ const HardwareInfo: React.FC = () => { } const CPUInfo: React.FC = () => { - const [cpuInfoData, setCpuInfoData] = useState() + const keyDivRef = useRef(null) + const percentDivRef = useRef(null) + const cpuInfoDivRef = useRef(null) + const cpuInfoEChatsRef = useRef([]) + const [isLoading, setIsLoading] = useState(true) + const [cpuInfoEChartsOption, setCpuInfoEChartsOption] = useState([]) const defaultSeriesOption: BarSeriesOption = { type: 'bar', stack: 'total', - emphasis: { - focus: 'series' + itemStyle: { + color: (params) => { + switch (params.seriesName) { + case 'idle': + return '#F5F5F5' + default: + return params.color ?? echarts.color.random() + } + } + }, + tooltip: { + valueFormatter: (value) => `${((value as number) * 100).toFixed(2)}%` } } useUpdatedEffect(() => { - setInterval( - () => - r_sys_statistics_cpu().then((res) => { - const response = res.data - if (response.success) { - const data = response.data - if (data) { - const cpuInfoData = Object.entries(data) - .filter(([key]) => key !== 'processors') - .map(([key, value]) => ({ - ...defaultSeriesOption, - name: key, - data: [value as number] - })) - console.log(cpuInfoData) - setCpuInfoData(cpuInfoData) - } - } - }), - 5000 - ) + const intervalId = setInterval(() => getCpuInfo(), 2000) + + const handleOnWindowResize = () => { + setTimeout(() => { + cpuInfoEChatsRef.current.forEach((value) => value.resize()) + }, 50) + } + + window.addEventListener('resize', handleOnWindowResize) + + return () => { + clearInterval(intervalId) + window.removeEventListener('resize', handleOnWindowResize) + } }, []) - const option: EChartsOption = { + const cpuInfoEChartBaseOption: EChartsOption = { tooltip: {}, xAxis: { show: false }, yAxis: { - data: ['总使用'], axisLine: { show: false }, @@ -230,30 +237,109 @@ const CPUInfo: React.FC = () => { axisPointer: { show: false } - }, - series: cpuInfoData + } } + const getCpuInfo = () => { + void r_sys_statistics_cpu().then((res) => { + const response = res.data + if (response.success) { + const data = response.data + if (data) { + if (isLoading) { + setIsLoading(false) + } + + setTimeout(() => { + const dataList = data.processors.map((value) => + cpuInfoVoToCpuInfoData(value) + ) + dataList.unshift(cpuInfoVoToCpuInfoData(data)) + + setCpuInfoEChartsOption( + dataList.map((value, index) => ({ + ...cpuInfoEChartBaseOption, + yAxis: { + ...cpuInfoEChartBaseOption.yAxis, + data: [index === 0 ? '总占用' : `CPU ${index - 1}`] + }, + series: value + })) + ) + + if (percentDivRef.current) { + percentDivRef.current.innerHTML = '' + dataList.forEach((value) => { + const percentElement = document.createElement('div') + const idle = value.find((item) => item.name === 'idle')?.data[0] + percentElement.innerText = + idle !== undefined + ? `${((1 - idle) * 100).toFixed(2)}%` + : 'Unknown' + percentDivRef.current?.appendChild(percentElement) + }) + } + + if (cpuInfoDivRef.current?.childElementCount !== dataList.length) { + keyDivRef.current && (keyDivRef.current.innerHTML = '') + cpuInfoDivRef.current && (cpuInfoDivRef.current.innerHTML = '') + for (let i = 0; i < dataList.length; i++) { + const keyElement = document.createElement('div') + keyElement.innerText = i === 0 ? '总占用' : `CPU ${i - 1}` + keyDivRef.current?.appendChild(keyElement) + + const valueElement = document.createElement('div') + cpuInfoDivRef.current?.appendChild(valueElement) + cpuInfoEChatsRef.current.push( + echarts.init(valueElement, null, { renderer: 'svg' }) + ) + } + } + }) + } + } + }) + } + + const cpuInfoVoToCpuInfoData = (cpuInfoVo: CpuInfoVo) => + Object.entries(cpuInfoVo) + .filter(([key]) => !['total', 'processors'].includes(key)) + .map(([key, value]) => ({ + ...defaultSeriesOption, + name: key, + data: [(value as number) / cpuInfoVo.total] + })) + .sort((a, b) => { + const order = [ + 'steal', + 'irq', + 'softirq', + 'iowait', + 'system', + 'nice', + 'user', + 'idle' + ] + return order.indexOf(a.name) - order.indexOf(b.name) + }) + + useEffect(() => { + cpuInfoEChatsRef.current?.forEach((value, index) => { + try { + value.setOption(cpuInfoEChartsOption[index]) + } catch (e) { + /* empty */ + } + }) + }, [cpuInfoEChartsOption]) + return ( <> - + - -
总占用
-
总占用
-
总占用
-
- -
- -
-
-
-
+ + +
@@ -287,7 +373,6 @@ const System: React.FC = () => { -
diff --git a/src/util/hooks.tsx b/src/util/hooks.tsx index 4667408..6152d2a 100644 --- a/src/util/hooks.tsx +++ b/src/util/hooks.tsx @@ -10,7 +10,7 @@ export const useUpdatedEffect = ( if (isFirstRender.current) { isFirstRender.current = false } else { - effect() + return effect() } }, dependencies) }