@@ -1,10 +1,25 @@
< template >
< Card title = "监控信息" style = "width: 100%; height: 30vh; margin: 0; padding: 0;" >
< template # extra >
< div class = "status-filter-container" >
< div class = "status-filter" >
< span
v-for = "item in statusOptions"
:key = "item.value"
: class = "['status-item', { active: currentStatus === item.value }]"
@click ="handleStatusChange(item.value)"
>
{ { item . label } }
< / span >
< / div >
< / div >
< / template >
< div ref = "chartDom" style = "width: 100%; height: calc(100% - 32px - 4px); padding: 2px;" > < / div >
< / Card >
< / template >
< script lang = "ts" setup name = "Monitor" >
import { ref , onMounted , watch , onUnmounted } from 'vue' ;
import { ref , onMounted , watch , onUnmounted , nextTick } from 'vue' ;
import { Card } from 'ant-design-vue' ;
import { BizResourceMonitor , bizResourceMonitorListAll } from '@jeesite/biz/api/biz/resourceMonitor' ;
import * as echarts from 'echarts' ;
@@ -17,15 +32,48 @@ const monitorList = ref<BizResourceMonitor[]>([]);
const chartInstance = ref < echarts .ECharts | null > ( null ) ;
const chartDom = ref < HTMLDivElement | null > ( null ) ;
// 状态选项配置
const statusOptions = ref ( [
{ value : '-12' , label : '12小时' } ,
{ value : '-24' , label : '24小时' } ,
{ value : '-36' , label : '36小时' } ,
{ value : '-72' , label : '72小时' } ,
] ) ;
const currentStatus = ref < string > ( '-12' ) ;
// 防抖处理resize, 避免频繁触发
const resizeHandler = ( ) => {
chartInstance . value ? . resize ( ) ;
if ( chartInstance . value ) {
// 使用nextTick确保DOM更新完成后再调整大小
nextTick ( ( ) => {
chartInstance . value ? . resize ( {
animation : { duration : 200 }
} ) ;
} ) ;
}
} ;
const handleStatusChange = ( status : string ) => {
if ( currentStatus . value === status ) return ;
currentStatus . value = status ;
fetchList ( props . formParams ) ;
} ;
const fetchList = async ( params : Record < string , any > ) => {
try {
const result = await bizResourceMonitorListAll ( params ) ;
const reqParams = {
... params ,
afterHours : currentStatus . value
} ;
const result = await bizResourceMonitorListAll ( reqParams ) ;
monitorList . value = result || [ ] ;
renderChart ( ) ;
// 确保图表实例初始化完成后再渲染
if ( chartInstance . value ) {
renderChart ( ) ;
} else {
// 如果实例未初始化,先初始化再渲染
initChart ( ) . then ( ( ) => renderChart ( ) ) ;
}
} catch ( error ) {
console . error ( '获取数据列表失败:' , error ) ;
monitorList . value = [ ] ;
@@ -33,26 +81,91 @@ const fetchList = async (params: Record<string, any>) => {
}
} ;
const initChart = ( ) => {
// 改为异步函数,确保初始化完成
const initChart = async ( ) => {
if ( ! chartDom . value ) return ;
// 先销毁旧实例(如果存在)
if ( chartInstance . value ) {
chartInstance . value . dispose ( ) ;
chartInstance . value = null ;
}
// 等待DOM更新完成
await nextTick ( ) ;
// 初始化图表实例
chartInstance . value = echarts . init ( chartDom . value ) ;
// 移除重复的事件监听
window . removeEventListener ( 'resize' , resizeHandler ) ;
window . addEventListener ( 'resize' , resizeHandler ) ;
// 监听图例点击事件,增加错误捕获
chartInstance . value . on ( 'legendselectchanged' , ( params ) => {
try {
// 可以在这里自定义图例点击逻辑,也可以留空仅做错误捕获
console . log ( '图例选中状态变更:' , params ) ;
} catch ( e ) {
console . error ( '图例点击事件处理异常:' , e ) ;
}
} ) ;
} ;
const renderChart = ( ) => {
// 核心保护:实例不存在时直接返回
if ( ! chartInstance . value ) return ;
// 空数据时的完整配置,避免交互报错
if ( monitorList . value . length === 0 ) {
// 空数据时不直接隐藏tooltip, 仅清空数据( 避免影响tooltip初始化)
chartInstance . value . setOption ( {
series : [ { data : [ ] } , { data : [ ] } ] ,
xAxis : { data : [ ] }
legend : {
data : [ 'CPU使用率' , '内存使用率' ] ,
top : 5 ,
left : 'center' ,
itemGap : 15 ,
textStyle : { fontSize : 11 }
} ,
grid : {
left : '2%' ,
right : '2%' ,
bottom : '8%' ,
top : '18%' ,
containLabel : true
} ,
xAxis : {
type : 'category' ,
data : [ ] ,
axisLabel : { rotate : 20 , fontSize : 10 , margin : 5 } ,
axisTick : { alignWithLabel : true }
} ,
yAxis : {
type : 'value' ,
name : '使用率(%)' ,
nameTextStyle : { fontSize : 10 } ,
min : 0 ,
max : 100 ,
interval : 20 ,
axisLabel : { formatter : '{value}%' , fontSize : 10 , margin : 5 }
} ,
tooltip : {
trigger : 'axis' ,
textStyle : { fontSize : 10 }
} ,
series : [
{
name : 'CPU使用率' ,
type : 'line' ,
data : [ ] ,
smooth : true
} ,
{
name : '内存使用率' ,
type : 'line' ,
data : [ ] ,
smooth : true
}
]
} ) ;
return ;
}
@@ -67,21 +180,10 @@ const renderChart = () => {
return Math . max ( 0 , Math . min ( 100 , value ) ) ;
} ) ;
const allData = [ ... cpuData , ... memoryData ] ;
const minValue = Math . min ( ... allData ) ;
const maxValue = Math . max ( ... allData ) ;
const yMin = Math . max ( 0 , minValue - 5 ) ;
const yMax = Math . min ( 100 , maxValue + 5 ) ;
const yMin = 0 ;
const yMax = 100 ;
const yInterval = 20 ;
const getInterval = ( min : number , max : number ) => {
const range = max - min ;
if ( range <= 10 ) return 2 ;
if ( range <= 20 ) return 5 ;
if ( range <= 50 ) return 10 ;
return 20 ;
} ;
const yInterval = getInterval ( yMin , yMax ) ;
const option = {
title : { left : 'center' } ,
legend : {
@@ -91,9 +193,18 @@ const renderChart = () => {
itemGap : 15 ,
textStyle : { fontSize : 11 }
} ,
tooltip : {
trigger : 'axis' ,
textStyle : { fontSize : 10 } ,
// 增加tooltip配置, 避免交互时的默认行为报错
axisPointer : {
type : 'line' ,
lineStyle : { width : 1 , color : '#ccc' }
}
} ,
grid : {
left : '2%' , // 从1%改为5%,扩大触发区域
right : '2%' , // 从1%改为5%,扩大触发区域
left : '2%' ,
right : '2%' ,
bottom : '8%' ,
top : '18%' ,
containLabel : true
@@ -153,9 +264,10 @@ const renderChart = () => {
} ) ;
} ;
onMounted ( ( ) => {
initChart ( ) ;
fetchList ( props . formParams ) ;
onMounted ( async ( ) => {
// 关键修复:先初始化图表,再获取数据渲染
await initChart ( ) ;
await fetchList ( props . formParams ) ;
} ) ;
watch (
@@ -167,8 +279,11 @@ watch(
) ;
onUnmounted ( ( ) => {
// 移除所有事件监听
window . removeEventListener ( 'resize' , resizeHandler ) ;
if ( chartInstance . value ) {
// 移除图例点击事件监听
chartInstance . value . off ( 'legendselectchanged' ) ;
chartInstance . value . dispose ( ) ;
chartInstance . value = null ;
}
@@ -176,6 +291,55 @@ onUnmounted(() => {
< / script >
< style scoped >
/* extra插槽容器: 状态标签 + 更多按钮 横向排列 */
. status - filter - container {
display : flex ;
align - items : center ;
gap : 16 px ;
}
/* 状态筛选栏(非按钮样式) */
. status - filter {
display : flex ;
align - items : center ;
flex - wrap : wrap ;
gap : 12 px ; /* 标签间距 */
font - size : 14 px ;
}
/* 状态项样式(纯文本标签) */
. status - item {
cursor : pointer ;
color : # 666 ;
position : relative ;
padding - bottom : 2 px ;
transition : all 0.2 s ease ;
user - select : none ;
}
/* 选中状态样式(下划线 + 高亮色) */
. status - item . active {
color : # 1890 ff ;
font - weight : 500 ;
}
/* 选中状态下划线 */
. status - item . active : : after {
content : '' ;
position : absolute ;
bottom : 0 ;
left : 0 ;
width : 100 % ;
height : 2 px ;
background - color : # 1890 ff ;
border - radius : 1 px ;
}
/* 悬浮效果 */
. status - item : not ( . active ) : hover {
color : # 40 a9ff ;
}
: deep ( . ant - card ) {
border - radius : 4 px ;
box - shadow : 0 1 px 2 px rgba ( 0 , 0 , 0 , 0.05 ) ;
@@ -199,4 +363,4 @@ onUnmounted(() => {
margin : 0 ;
height : calc ( 100 % - 32 px ) ;
}
< / style >
< / style >