| | |
| | | :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 |
| | |
| | | |
| | | <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"> |
| | |
| | | </el-skeleton> |
| | | </div> |
| | | </div> |
| | | <!-- <div v-for="(host, hostIndex) in hostList" :key="hostIndex">--> |
| | | <!-- <div style="margin-top: 10px">--> |
| | | <!-- <el-skeleton :loading="echartsLoading" animated>--> |
| | | <!-- {{ hostIndex }} 主机名: {{ host.name }} --> |
| | | <!-- 服务器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> |
| | | |
| | | <!-- 列表 --> |
| | |
| | | |
| | | 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, |
| | |
| | | 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 () => { |
| | |
| | | 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 = () => { |
| | |
| | | } |
| | | } |
| | | |
| | | /** 堆叠面积图配置 */ |
| | | 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() |
| | | } |
| | | } |
| | | |
| | | /** 切换展示方式 */ |
| | |
| | | /** 初始化 **/ |
| | | 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(() => { |
| | |
| | | font-size: 0.9em; |
| | | color: #666; |
| | | } |
| | | |
| | | </style> |