From be664d7c011a473002c1b413bac8303f7905d160 Mon Sep 17 00:00:00 2001
From: dongyukun <1208714201@qq.com>
Date: 星期四, 29 五月 2025 14:26:36 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 src/views/infra/monitor/components/MonitorDisk.vue |  410 +++++++++++++++++++++++++++++++++++-----------------------
 1 files changed, 246 insertions(+), 164 deletions(-)

diff --git a/src/views/infra/monitor/components/MonitorDisk.vue b/src/views/infra/monitor/components/MonitorDisk.vue
index ffec629..3a0c954 100644
--- a/src/views/infra/monitor/components/MonitorDisk.vue
+++ b/src/views/infra/monitor/components/MonitorDisk.vue
@@ -8,41 +8,25 @@
       :inline="true"
       label-width="68px"
     >
-      <!--      <el-form-item label="主机名称" prop="hostName">-->
-      <!--        <el-input-->
-      <!--          v-model="queryParams.hostName"-->
-      <!--          placeholder="请输入主机名称"-->
-      <!--          clearable-->
-      <!--          @keyup.enter="handleQuery"-->
-      <!--          class="!w-240px"-->
-      <!--        />-->
-      <!--      </el-form-item>-->
-      <el-form-item label="服务器IP" prop="hostIp">
-        <el-input
-          v-model="queryParams.hostIp"
-          placeholder="请输入服务器IP"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-120px"
-        />
+      <el-form-item label="主机名称" prop="hostName">
+        <el-select v-model="queryParams.hostName" clearable placeholder="请选择" class="!w-180px" @change="getDataList">
+          <el-option
+            v-for="(host, index) in hosts"
+            :key="index"
+            :label="host"
+            :value="host"
+          />
+        </el-select>
       </el-form-item>
-      <!--      <el-form-item label="盘符" prop="disk">-->
-      <!--        <el-input-->
-      <!--          v-model="queryParams.disk"-->
-      <!--          placeholder="请输入盘符"-->
-      <!--          clearable-->
-      <!--          @keyup.enter="handleQuery"-->
-      <!--          class="!w-240px"-->
-      <!--        />-->
-      <!--      </el-form-item>-->
-      <el-form-item label="磁盘名" prop="diskName">
-        <el-input
-          v-model="queryParams.diskName"
-          placeholder="请输入磁盘名"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-120px"
-        />
+      <el-form-item label="服务器IP" prop="hostIp">
+        <el-select v-model="queryParams.hostIp" clearable placeholder="请选择" class="!w-160px" @change="getDataList">
+          <el-option
+            v-for="(ip, index) in ips"
+            :key="index"
+            :label="ip"
+            :value="ip"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item label="创建时间" prop="createTime">
         <el-date-picker
@@ -109,9 +93,8 @@
 
   <ContentWrap v-if="showType == 'chart'">
     <!-- 磁盘使用率折线图 -->
-    <el-skeleton :loading="echartsLoading" animated>
-      <Echart :height="320" :options="diskChartOptions"/>
-    </el-skeleton>
+    <div id="chartArea"></div>
+
     <!-- 磁盘使用率饼图 -->
     <h3 style="margin-top: 20px; margin-bottom: 10px">主机磁盘使用率</h3>
     <div v-for="host in hostList" :key="host.name" class="host">
@@ -131,19 +114,6 @@
         </el-skeleton>
       </div>
     </div>
-<!--    <div v-for="(host, hostIndex) in hostList" :key="hostIndex">-->
-<!--      <div style="margin-top: 10px">-->
-<!--        <el-skeleton :loading="echartsLoading" animated>-->
-<!--          {{ hostIndex }} &nbsp;&nbsp;&nbsp;&nbsp;主机名: {{ host.name }}&nbsp;&nbsp;&nbsp;&nbsp;-->
-<!--          服务器IP:{{ host.ip }}-->
-<!--          <div v-for="(disk, diskIndex) in host.disks" :key="diskIndex">-->
-<!--            <h3>{{ disk.name }}</h3>-->
-<!--            <div :ref="el => chartRefs[hostIndex][diskIndex] = el"-->
-<!--                 :style="{ width: '300px', height: '300px' }"></div>-->
-<!--          </div>-->
-<!--        </el-skeleton>-->
-<!--      </div>-->
-<!--    </div>-->
   </ContentWrap>
 
   <!-- 列表 -->
@@ -217,6 +187,8 @@
 
 const loading = ref(true) // 列表的加载中
 const list = ref<MonitorDiskVO[]>([]) // 列表的数据
+const hosts = ref<String[]>([]) // 主机列表
+const ips = ref<String[]>([]) // ip列表
 const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
@@ -236,26 +208,197 @@
 const echartsLoading = ref(true) // 图表加载中
 const showType = ref() //展示类型(chart-图例,data-数据)
 
-const hostList = ref([
-  {
-    name: 'Thinkpad-E14',
-    ip: '172.16.216.133',
-    disks: [
-      {disk: '磁盘C', used: 70, total: 200},
-      {disk: '磁盘D', used: 40, total: 60}
-    ]
-  },
-  {
-    name: 'Thinkpad-E16',
-    ip: '172.16.216.133',
-    disks: [
-      {disk: '磁盘C', used: 80, total: 500},
-      {disk: '磁盘D', used: 20, total: 500}
-    ]
-  }
-]);
+const hostList = ref([]);
 
-const chartRefs = ref([]);
+
+// 颜色集合(10个区分度较好的颜色)
+const colors = [
+  '#5470C6', '#91CC75', '#FAC858', '#EE6666', '#73C0DE',
+  '#3BA272', '#FC8452', '#9A60B4', '#EA7CCC', '#19DCDC'
+]
+const chartRefs = ref<HTMLElement[]>([])
+// 图表实例和容器管理
+const chartInstances = ref<{ instance: echarts.ECharts; container: HTMLDivElement }[]>([])
+
+// 清理所有图表资源
+const cleanCharts = () => {
+  // 1. 销毁所有图表实例
+  chartInstances.value.forEach(({ instance }) => {
+    instance.dispose()
+  })
+
+  // 2. 移除所有容器元素
+  chartInstances.value.forEach(({ container }) => {
+    container.remove()
+  })
+
+  // 3. 清空实例记录
+  chartInstances.value = []
+}
+
+
+// 处理接口数据
+const processServerData = (apiData: any[]) => {
+  return apiData.flatMap(serverObj => {
+    return Object.entries(serverObj).map(([serverName, dataPoints]) => ({
+      serverName,
+      data: (dataPoints as any[]).map(item => ({
+        createTime: item.createTime,
+        ...Object.fromEntries(
+          Object.entries(item)
+            .filter(([key]) => key !== 'createTime')
+            .map(([key, value]) => [key.replace(/[()/]/g, '_'), value]) // 清理特殊字符
+        )
+      })).sort((a, b) => a.createTime - b.createTime) // 按时间排序
+    }))
+  })
+}
+
+// 初始化图表
+const initCharts = async (apiData: any[]) => {
+  cleanCharts()
+  await nextTick()
+
+  const servers = processServerData(apiData)
+  const chartArea = document.querySelector('#chartArea')
+
+  servers.forEach((server, index) => {
+    // 创建容器
+    const container = document.createElement('div')
+    container.style.width = '100%'
+    container.style.height = '300px'
+    container.classList.add('chart-container')
+    chartArea?.appendChild(container)
+
+    // 初始化图表实例
+    const chart = echarts.init(container)
+    const dimensions = Object.keys(server.data[0]).filter(k => k !== 'createTime')
+
+    // 图表配置
+    const option: echarts.EChartsOption = {
+      title: {
+        text: `${server.serverName} 磁盘使用率趋势`,
+        left: 'center',
+        textStyle: {
+          fontSize: 16,
+          fontWeight: 'bold'
+        }
+      },
+      tooltip: {
+        trigger: 'axis',
+        valueFormatter: (value) => `${value}%`,
+        axisPointer: {
+          type: 'cross',
+          label: {
+            backgroundColor: '#6a7985'
+          }
+        }
+      },
+      legend: {
+        type: 'scroll',
+        top: 30,
+        pageIconColor: '#2c3e50',
+        pageTextStyle: {
+          color: '#666'
+        }
+      },
+      grid: {
+        top: 80,
+        left: 50,
+        right: 30,
+        bottom: 30,
+        containLabel: true
+      },
+      xAxis: {
+        type: 'time',
+        axisLabel: {
+          formatter: (value: number) => {
+            const date = new Date(value)
+            return `${date.getMonth() + 1}/${date.getDate()}`
+          },
+          interval: 0,
+          length: 6,
+          rotate: 45,
+          fontSize: 12
+        },
+        min: (value) => value.min - 86400000,
+        max: (value) => value.max + 86400000
+      },
+      yAxis: {
+        type: 'value',
+        min: 0,
+        max: 100,
+        axisLabel: {
+          formatter: '{value}%',
+          fontSize: 12
+        },
+        splitLine: {
+          show: true,
+          lineStyle: {
+            type: 'dashed'
+          }
+        }
+      },
+      toolbox: {
+        feature: {
+          dataZoom: {
+            type: 'inside',
+            filterMode: 'none', // 禁用数据过滤
+            yAxisIndex: false,
+            title: { zoom: '缩放', back: '还原' }
+          },
+          brush: {
+            type: ['lineX', 'clear'],
+            title: { lineX: '选择', clear: '清除' }
+          },
+          saveAsImage: {
+            name: '磁盘使用率图表',
+            title: '保存',
+            pixelRatio: 2
+          }
+        }
+      },
+      color: colors,
+      series: dimensions.map((dim, dimIndex) => ({
+        name: dim.replace(/_/g, ' '), // 还原清理的特殊字符
+        type: 'line',
+        // 关键配置项
+        progressive: 0,          // 禁用分片渲染
+        large: false,            // 禁用大数据模式
+        showAllSymbol: true,     // 显示所有数据点
+        sampling: 'none',        // 禁用采样
+        // 数据维度声明
+        dimensions: ['createTime', 'value'],
+        smooth: true,
+        symbol: 'none',
+        areaStyle: {
+          opacity: 0.3,
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: colors[dimIndex % colors.length] },
+            { offset: 1, color: 'rgba(255,255,255,0)' }
+          ])
+        },
+        lineStyle: {
+          width: 2,
+          color: colors[dimIndex % colors.length]
+        },
+        data: server.data.map(d => [d.createTime, d[dim]])
+      })),
+      dataZoom: [{
+        type: 'inside',
+        filterMode: 'none' // 禁用数据过滤
+        // type: 'inside',
+        // start: 0,
+        // end: 100,
+        // minValueSpan: 86400000 * 1 // 最小缩放范围为1天
+      }]
+    }
+
+    chart.setOption(option)
+    chartInstances.value.push({ instance: chart, container })
+  })
+}
+
 
 /** 查询列表 */
 const getList = async () => {
@@ -268,6 +411,19 @@
     loading.value = false
   }
 }
+
+/** 查询所有主机名 */
+const getAllHost = async () => {
+  const data = await MonitorDiskApi.getAllHost()
+  hosts.value = data
+}
+
+/** 查询所有ip */
+const getAllIp = async () => {
+  const data = await MonitorDiskApi.getAllIp()
+  ips.value = data
+}
+
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
@@ -321,98 +477,26 @@
   }
 }
 
-/** 堆叠面积图配置 */
-const diskChartOptions = reactive<EChartsOption>({
-  title: {
-    text: '磁盘空间折线图'
-  },
-  dataset: {
-    dimensions: [],
-    source: []
-  },
-  grid: {
-    left: 30,
-    right: 20,
-    bottom: 10,
-    top: 70,
-    containLabel: true
-  },
-  legend: {
-    top: 0
-  },
-  series: [
-    {
-      name: 'disk', type: 'line',
-      emphasis: {
-        focus: 'series'
-      }, smooth: false
-    }
-  ],
-  toolbox: {
-    feature: {
-      // 数据区域缩放
-      dataZoom: {
-        yAxisIndex: false // Y轴不缩放
-      },
-      brush: {
-        type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
-      },
-      saveAsImage: {show: true, name: '物理内存日志图片'} // 保存为图片
-    }
-  },
-  tooltip: {
-    trigger: 'axis',
-    axisPointer: {
-      type: 'cross',
-      label: {
-        backgroundColor: '#6a7985'
-      }
-    },
-    padding: [5, 10]
-  },
-  xAxis: {
-    type: 'category',
-    boundaryGap: false,
-    axisTick: {
-      show: false
-    }
-  },
-  yAxis: {
-    name: "单位(百分比)",
-    nameTextStyle: {
-      color: "#aaa",
-      nameLocation: "start",
-    },
-  },
-}) as EChartsOption
-
 /** 查询统计数据列表 */
 const getMonitorDiskDataList = async () => {
   const list = await MonitorDiskApi.getMonitorDiskList(queryParams)
-  if (list != null && list != undefined && list.length > 0) {
-    diskChartOptions.dataset['dimensions'] = Object.keys(list[0])
-    diskChartOptions.series = diskChartOptions.dataset['dimensions'].map(item => ({
-      name: item.name,
-      type: 'line',
-      emphasis: {
-        focus: 'series'
-      },
-      smooth: false
-    }));
-    diskChartOptions.series.splice(0, 1)
-    for (let item of list) {
-      item.createTime = formatTime(item.createTime, 'yyyy-MM-dd HH:mm:ss')
-    }
-  }
-  // 更新 Echarts 数据
-  diskChartOptions.dataset['source'] = list
   echartsLoading.value = false
+  await initCharts(list)
 }
 
 const usedDiskInstance = async () => {
   const list = await MonitorDiskApi.getMonitorDiskInfo(queryParams)
   hostList.value = list
-  // 仪表盘详情,用于显示数据。
+}
+
+/** 封装接口调用 */
+const getDataList = async () => {
+  if(showType.value == 'data') {
+    await getList()
+  } else {
+    await getMonitorDiskDataList()
+    await usedDiskInstance()
+  }
 }
 
 /** 切换展示方式 */
@@ -430,19 +514,16 @@
 /** 初始化 **/
 onMounted(() => {
   showType.value = 'data';
-  const currentDate = new Date();
-  const previousDate = new Date(currentDate);
-  previousDate.setDate(currentDate.getDate() - 1);
-  queryParams.createTime[0] = formatDate(previousDate, 'YYYY-MM-DD HH:mm:ss');
-  queryParams.createTime[1] = formatDate(currentDate, 'YYYY-MM-DD HH:mm:ss');
+  getAllHost();
+  getAllIp();
+  getDataList()
   intervalId = setInterval(() => {
-    if (showType.value == 'data') {
-      getList()
-    } else {
-      getMonitorDiskDataList()
-      usedDiskInstance()
-    }
-  }, 30000);
+    getDataList()
+  }, 300000);
+})
+
+onBeforeUnmount(() => {
+  cleanCharts()
 })
 
 onUnmounted(() => {
@@ -484,4 +565,5 @@
     font-size: 0.9em;
     color: #666;
   }
+
 </style>

--
Gitblit v1.9.3