liriming
2025-03-03 8bb7160c9c4fd7ce5893ee673647b13cc35410ae
提交 | 用户 | 时间
6bf4f9 1 <template>
H 2   <ContentWrap>
3     <!-- 搜索工作栏 -->
4     <el-form
5       class="-mb-15px"
6       :model="queryParams"
7       ref="queryFormRef"
8       :inline="true"
9       label-width="68px"
10     >
11 <!--      <el-form-item label="主机名称" prop="hostName">-->
12 <!--        <el-input-->
13 <!--          v-model="queryParams.hostName"-->
14 <!--          placeholder="请输入主机名称"-->
15 <!--          clearable-->
16 <!--          @keyup.enter="handleQuery"-->
17 <!--          class="!w-120px"-->
18 <!--        />-->
19 <!--      </el-form-item>-->
20       <el-form-item label="服务器IP" prop="hostIp">
21         <el-input
22           v-model="queryParams.hostIp"
23           placeholder="请输入服务器IP"
24           clearable
25           @keyup.enter="handleQuery"
26           class="!w-120px"
27         />
28       </el-form-item>
29       <el-form-item label="服务名" prop="serverName">
30         <el-input
31           v-model="queryParams.serverName"
32           placeholder="请输入服务名"
33           clearable
34           @keyup.enter="handleQuery"
35           class="!w-120px"
36         />
37       </el-form-item>
38       <el-form-item label="创建时间" prop="createTime">
39         <el-date-picker
40           v-model="queryParams.createTime"
41           value-format="YYYY-MM-DD HH:mm:ss"
42           type="datetimerange"
43           start-placeholder="开始日期"
44           end-placeholder="结束日期"
45           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
46           class="!w-360px"
47         />
48       </el-form-item>
49       <el-form-item>
50         <el-button @click="handleQuery">
51           <Icon icon="ep:search" class="mr-5px"/>
52           搜索
53         </el-button>
54         <el-button @click="resetQuery">
55           <Icon icon="ep:refresh" class="mr-5px"/>
56           重置
57         </el-button>
58         <el-button
59           type="primary"
60           plain
61           @click="openForm('create')"
62           v-hasPermi="['infra:monitor-mem:create']"
63         >
64           <Icon icon="ep:plus" class="mr-5px"/>
65           新增
66         </el-button>
67         <el-button
68           type="success"
69           plain
70           @click="handleExport"
71           :loading="exportLoading"
72           v-hasPermi="['infra:monitor-mem:export']"
73         >
74           <Icon icon="ep:download" class="mr-5px"/>
75           导出
76         </el-button>
77       </el-form-item>
78       <el-form-item style="float: right">
79         <el-button
80           v-if="showType == 'chart'"
81           type="warning"
82           style="font-weight: bold"
83           plain
84           @click="switchShow('data')">
85           <Icon icon="fa-solid:th-list" class="mr-5px"/>
86           列表展示
87         </el-button>
88         <el-button
89           v-if="showType == 'data'"
90           type="danger"
91           style="font-weight: bold"
92           plain
93           @click="switchShow('chart')">
94           <Icon icon="fa-solid:chart-pie" class="mr-5px"/>
95           图例展示
96         </el-button>
97       </el-form-item>
98     </el-form>
99   </ContentWrap>
100
101   <ContentWrap v-if="showType == 'chart'">
102     <!-- 物理内存折线图 -->
103     <el-skeleton :loading="echartsLoading" animated>
104       <Echart :height="320" :options="physicalChartOptions"/>
105     </el-skeleton>
106     <!-- JVM内存折线图 -->
107     <el-skeleton :loading="echartsLoading" animated>
108       <Echart style="margin-top: 20px" :height="320" :options="JVMChartOptions"/>
109     </el-skeleton>
110   </ContentWrap>
111
112   <!-- 列表 -->
113   <ContentWrap v-if="showType == 'data'">
114     <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
115       <el-table-column label="主机名称" align="center" prop="hostName"/>
116       <el-table-column label="服务器ip" align="center" prop="hostIp"/>
117       <el-table-column label="服务名" align="center" prop="serverName" width="120"/>
118       <el-table-column label="总内存" align="center" prop="physicalTotal"/>
119       <el-table-column label="已用内存" align="center" prop="physicalUsed"/>
120       <el-table-column label="空闲内存" align="center" prop="physicalFree"/>
121       <el-table-column label="内存使用率" align="center" prop="physicalUsage" width="100"/>
122       <el-table-column label="JVM占用内存" align="center" prop="runtimeTotal"/>
123       <el-table-column label="JVM最大内存" align="center" prop="runtimeMax"/>
124       <el-table-column label="JVM可用内存" align="center" prop="runtimeUsed"/>
125       <el-table-column label="JVM空闲内存" align="center" prop="runtimeFree"/>
126       <el-table-column label="JVM内存使用率" align="center" prop="runtimeUsage"/>
127       <el-table-column
128         label="创建时间"
129         align="center"
130         prop="createTime"
131         :formatter="dateFormatter"
132         width="180px"
133       />
134       <el-table-column label="操作" align="center">
135         <template #default="scope">
136           <el-button
137             link
138             type="primary"
139             @click="openForm('update', scope.row.id)"
140             v-hasPermi="['infra:monitor-mem:query']"
141           >
142             详情
143           </el-button>
144           <el-button
145             link
146             type="danger"
147             @click="handleDelete(scope.row.id)"
148             v-hasPermi="['infra:monitor-mem:delete']"
149           >
150             删除
151           </el-button>
152         </template>
153       </el-table-column>
154     </el-table>
155     <!-- 分页 -->
156     <Pagination
157       :total="total"
158       v-model:page="queryParams.pageNo"
159       v-model:limit="queryParams.pageSize"
160       @pagination="getList"
161     />
162   </ContentWrap>
163
164   <!-- 表单弹窗:添加/修改 -->
165   <MonitorMemForm ref="formRef" @success="getList"/>
166 </template>
167
168 <script setup lang="ts">
169 import {dateFormatter} from '@/utils/formatTime'
170 import download from '@/utils/download'
171 import {MonitorMemApi, MonitorMemVO} from '@/api/infra/monitormem'
172 import MonitorMemForm from './MonitorMemForm.vue'
173 import {EChartsOption} from "echarts";
174 import {formatTime} from "@/utils";
175 import {formatDate} from "@vueuse/core";
176
177 /** 内存监控日志 列表 */
178 defineOptions({name: 'MonitorMem'})
179
180 const message = useMessage() // 消息弹窗
181 const {t} = useI18n() // 国际化
182
183 const loading = ref(true) // 列表的加载中
184 const list = ref<MonitorMemVO[]>([]) // 列表的数据
185 const total = ref(0) // 列表的总页数
186 const queryParams = reactive({
187   pageNo: 1,
188   pageSize: 10,
189   hostName: undefined,
190   hostIp: undefined,
191   serverName: undefined,
192   physicalTotal: undefined,
193   physicalUsed: undefined,
194   physicalFree: undefined,
195   physicalUsage: undefined,
196   runtimeTotal: undefined,
197   runtimeMax: undefined,
198   runtimeUsed: undefined,
199   runtimeFree: undefined,
200   runtimeUsage: undefined,
201   createTime: [],
202 })
203 const queryFormRef = ref() // 搜索的表单
204 const exportLoading = ref(false) // 导出的加载中
205 const echartsLoading = ref(true) // 图表加载中
206 const showType = ref() //展示类型(chart-图例,data-数据)
207
208 /** 查询列表 */
209 const getList = async () => {
210   loading.value = true
211   try {
212     const data = await MonitorMemApi.getMonitorMemPage(queryParams)
213     list.value = data.list
214     total.value = data.total
215   } finally {
216     loading.value = false
217   }
218 }
219
220 /** 搜索按钮操作 */
221 const handleQuery = () => {
222   queryParams.pageNo = 1
223   if(showType.value == 'data') {
224     getList()
225   } else {
226     getMonitorMemDataList()
227   }
228 }
229
230 /** 重置按钮操作 */
231 const resetQuery = () => {
232   queryFormRef.value.resetFields()
233   handleQuery()
234 }
235
236 /** 添加/修改操作 */
237 const formRef = ref()
238 const openForm = (type: string, id?: number) => {
239   formRef.value.open(type, id)
240 }
241
242 /** 删除按钮操作 */
243 const handleDelete = async (id: number) => {
244   try {
245     // 删除的二次确认
246     await message.delConfirm()
247     // 发起删除
248     await MonitorMemApi.deleteMonitorMem(id)
249     message.success(t('common.delSuccess'))
250     // 刷新列表
251     await getList()
252   } catch {
253   }
254 }
255
256 /** 堆叠面积图配置 */
257 const physicalChartOptions = reactive<EChartsOption>({
258   title: {
259     text: '物理内存折线图'
260   },
261   dataset: {
262     dimensions: ['createTime', 'physicalTotal', 'physicalUsed', 'physicalFree'],
263     source: []
264   },
265   grid: {
266     left: 20,
267     right: 20,
268     bottom: 10,
269     top: 70,
270     containLabel: true
271   },
272   legend: {
273     top: 0
274   },
275   series: [
276     {
277       name: '总物理内存', type: 'line',
278       emphasis: {
279         focus: 'series'
280       }, smooth: false
281     },
282     {
283       name: '已用物理内存', type: 'line', areaStyle: {},
284       emphasis: {
285         focus: 'series'
286       }, smooth: false
287     },
288     {
289       name: '剩余物理内存', type: 'line', areaStyle: {},
290       emphasis: {
291         focus: 'series'
292       }, smooth: false
293     }
294   ],
295   toolbox: {
296     feature: {
297       // 数据区域缩放
298       dataZoom: {
299         yAxisIndex: false // Y轴不缩放
300       },
301       brush: {
302         type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
303       },
304       saveAsImage: {show: true, name: '物理内存日志图片'} // 保存为图片
305     }
306   },
307   tooltip: {
308     trigger: 'axis',
309     axisPointer: {
310       type: 'cross',
311       label: {
312         backgroundColor: '#6a7985'
313       }
314     },
315     padding: [5, 10]
316   },
317   xAxis: {
318     type: 'category',
319     boundaryGap: false,
320     axisTick: {
321       show: false
322     }
323   },
324   yAxis: {
325     name: "单位(MB)",
326     nameTextStyle: {
327       color: "#aaa",
328       nameLocation: "start",
329     },
330   },
331 }) as EChartsOption
332
333 /** 堆叠面积图配置 */
334 const JVMChartOptions = reactive<EChartsOption>({
335   title: {
336     text: 'JVM内存折线图'
337   },
338   dataset: {
339     dimensions: ['createTime', 'runtimeMax', 'runtimeTotal', 'runtimeUsed', 'runtimeFree'],
340     source: []
341   },
342   grid: {
343     left: 20,
344     right: 20,
345     bottom: 0,
346     top: 70,
347     containLabel: true
348   },
349   legend: {
350     top: 0
351   },
352   series: [
353     {
354       name: 'JVM最大内存', type: 'line',
355       emphasis: {
356         focus: 'series'
357       }, smooth: false
358     },
359     {
360       name: 'JVM占用内存', type: 'line',
361       emphasis: {
362         focus: 'series'
363       }, smooth: false
364     },
365     {
366       name: 'JVM可用内存', type: 'line', areaStyle: {},
367       emphasis: {
368         focus: 'series'
369       }, smooth: false
370     },
371     {
372       name: 'JVM空闲内存', type: 'line', areaStyle: {},
373       emphasis: {
374         focus: 'series'
375       }, smooth: false
376     }
377   ],
378   toolbox: {
379     feature: {
380       // 数据区域缩放
381       dataZoom: {
382         yAxisIndex: false // Y轴不缩放
383       },
384       brush: {
385         type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
386       },
387       saveAsImage: {show: true, name: 'JVM内存日志图片'} // 保存为图片
388     }
389   },
390   tooltip: {
391     trigger: 'axis',
392     axisPointer: {
393       type: 'cross',
394       label: {
395         backgroundColor: '#6a7985'
396       }
397     },
398     padding: [5, 10]
399   },
400   xAxis: {
401     type: 'category',
402     boundaryGap: false,
403     axisTick: {
404       show: false
405     }
406   },
407   yAxis: {
408     name: "单位(MB)",
409     nameTextStyle: {
410       color: "#aaa",
411       nameLocation: "start",
412     },
413   },
414 }) as EChartsOption
415
416 /** 查询统计数据列表 */
417 const getMonitorMemDataList = async () => {
418   const list = await MonitorMemApi.getMonitorMemList(queryParams)
419   for (let item of list) {
420     item.createTime = formatTime(item.createTime, 'yyyy-MM-dd HH:mm:ss')
421   }
422   // 更新 Echarts 数据
423   if (physicalChartOptions.dataset && physicalChartOptions.dataset['source']) {
424     physicalChartOptions.dataset['source'] = list
425   }
426   if (JVMChartOptions.dataset && JVMChartOptions.dataset['source']) {
427     JVMChartOptions.dataset['source'] = list
428   }
429   echartsLoading.value = false
430 }
431
432 /** 切换展示方式 */
433 const switchShow = (type: String) => {
434   showType.value = type
435   if(showType.value == 'data') {
436     getList()
437   } else {
438     getMonitorMemDataList()
439   }
440 }
441
442 /** 导出按钮操作 */
443 const handleExport = async () => {
444   try {
445     // 导出的二次确认
446     await message.exportConfirm()
447     // 发起导出
448     exportLoading.value = true
449     const data = await MonitorMemApi.exportMonitorMem(queryParams)
450     download.excel(data, '内存监控日志.xls')
451   } catch {
452   } finally {
453     exportLoading.value = false
454   }
455 }
456
457 let intervalId;
458 /** 初始化 **/
459 onMounted(() => {
460   showType.value = 'data';
461   const currentDate = new Date();
462   const previousDate = new Date(currentDate);
463   previousDate.setDate(currentDate.getDate() - 1);
464   queryParams.createTime[0] = formatDate(previousDate, 'YYYY-MM-DD HH:mm:ss');
465   queryParams.createTime[1] = formatDate(currentDate, 'YYYY-MM-DD HH:mm:ss');
466   intervalId = setInterval(() => {
467     if(showType.value == 'data') {
468       getList()
469     } else {
470       getMonitorMemDataList()
471     }
472   }, 60000);
473 })
474
475 onUnmounted(() => {
476   clearInterval(intervalId);
477 });
478 </script>