新增前端vue

This commit is contained in:
2026-02-09 13:03:18 +08:00
parent c34a879a48
commit e6e005618f
4 changed files with 722 additions and 598 deletions

View File

@@ -0,0 +1,143 @@
<template>
<Card title="基础信息">
<div class="card-grid base-info-content">
<div class="metric-card">
<div class="metric-label">主机名称:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.sysHostname || '--'">
{{ serverInfo?.sysHostname || '--' }}
</div>
<div class="metric-icon">🖥</div>
</div>
<div class="metric-card">
<div class="metric-label">IP 地址:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.ipAddress || '--'">
{{ serverInfo?.ipAddress || '--' }}
</div>
<div class="metric-icon">🌐</div>
</div>
<div class="metric-card">
<div class="metric-label">CPU 型号:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.cpuModel || '--'">
{{ serverInfo?.cpuModel || '--' }}
</div>
<div class="metric-icon"></div>
</div>
<div class="metric-card">
<div class="metric-label">内存大小:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.memoryTotal || '--'">
{{ serverInfo?.memoryTotal || '--' }}
</div>
<div class="metric-icon">🧠</div>
</div>
<div class="metric-card">
<div class="metric-label">系统版本:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.kernelVersion || '--'">
{{ serverInfo?.kernelVersion || '--' }}
</div>
<div class="metric-icon">🖨</div>
</div>
</div>
</Card>
</template>
<script lang="ts" setup name="LeftOut">
import { ref, onMounted } from 'vue';
import { Card } from 'ant-design-vue';
import { useRouter } from 'vue-router';
const props = defineProps<{
formParams: Record<string, any>; // 接收参数
}>();
const serverInfo = ref<any>({});
</script>
<!-- 模板和脚本部分保持不变仅替换style部分 -->
<style scoped>
/* 基础信息卡片容器:核心滚动逻辑 */
.card-grid.base-info-content {
width: 100%;
height: 240px;
display: grid;
box-sizing: border-box;
}
/* 单个指标卡片样式(美化,提升体验) */
.metric-card {
display: flex;
align-items: center;
padding: 4px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
transition: all 0.2s ease;
min-height: 40px;
box-sizing: border-box;
}
/* 卡片hover效果 */
.metric-card:hover {
background-color: #f5f7fa;
border-color: #dee2e6;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
/* 标签样式 */
.metric-label {
font-size: 14px;
color: #666;
font-weight: 500;
min-width: 80px;
margin-right: 8px;
}
/* 值的样式(省略逻辑) */
.metric-value {
flex: 1;
font-size: 14px;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 图标样式 */
.metric-icon {
font-size: 20px;
margin-left: 12px;
color: #409eff;
}
/* 文字省略通用样式 */
.ellipsis-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 滚动条美化(可选,提升视觉体验) */
.card-grid.base-info-content::-webkit-scrollbar {
width: 6px; /* 滚动条宽度 */
}
.card-grid.base-info-content::-webkit-scrollbar-track {
background: #f1f1f1; /* 滚动条轨道颜色 */
border-radius: 3px;
}
.card-grid.base-info-content::-webkit-scrollbar-thumb {
background: #c1c1c1; /* 滚动条滑块颜色 */
border-radius: 3px;
}
.card-grid.base-info-content::-webkit-scrollbar-thumb:hover {
background: #a8a8a8; /* 滑块hover颜色 */
}
/* 兼容Firefox浏览器滚动条 */
.card-grid.base-info-content {
scrollbar-width: thin; /* 细滚动条 */
scrollbar-color: #c1c1c1 #f1f1f1; /* 滑块颜色 轨道颜色 */
}
</style>

View File

@@ -0,0 +1,233 @@
<template>
<Card title="监控信息">
<div ref="chartDom" class="monitor-content" style="width: 100%; height: 200px;"></div>
</Card>
</template>
<script lang="ts" setup name="Monitor">
import { ref, onMounted, watch, onUnmounted } from 'vue';
import { Card } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { BizResourceMonitor, bizResourceMonitorListAll } from '@jeesite/biz/api/biz/resourceMonitor';
import * as echarts from 'echarts';
// 定义接收的参数类型
const props = defineProps<{
formParams: Record<string, any>; // 接收参数
}>();
// 监控数据列表
const monitorList = ref<BizResourceMonitor[]>([]);
// 改用普通ref管理echarts实例移除shallowRef
const chartInstance = ref<echarts.ECharts | null>(null);
// 图表DOM引用
const chartDom = ref<HTMLDivElement | null>(null);
// 存储resize回调函数方便卸载时移除
const resizeHandler = () => {
chartInstance.value?.resize();
};
// 获取监控数据
const fetchList = async (params: Record<string, any>) => {
try {
const result = await bizResourceMonitorListAll(params);
monitorList.value = result || [];
// 数据更新后重新渲染图表
renderChart();
} catch (error) {
console.error('获取数据列表失败:', error);
monitorList.value = [];
// 数据获取失败也清空图表
renderChart();
}
};
// 初始化ECharts实例
const initChart = () => {
if (!chartDom.value) return;
// 避免重复创建实例
if (chartInstance.value) {
chartInstance.value.dispose();
chartInstance.value = null;
}
// 创建ECharts实例
chartInstance.value = echarts.init(chartDom.value);
// 监听窗口大小变化,自适应调整图表
window.addEventListener('resize', resizeHandler);
};
// 渲染图表
const renderChart = () => {
if (!chartInstance.value || monitorList.value.length === 0) {
// 清空图表
chartInstance.value?.setOption({
series: [{ data: [] }, { data: [] }],
xAxis: { data: [] }
});
return;
}
// 处理数据提取X轴和Y轴数据
const xAxisData = monitorList.value.map(item => item.hourTime || '');
const cpuData = monitorList.value.map(item => {
// 处理空值/异常值确保数据合法性0-100
const value = item.cpuUsage ?? 0;
return Math.max(0, Math.min(100, value));
});
const memoryData = monitorList.value.map(item => {
// 处理空值/异常值确保数据合法性0-100
const value = item.memoryUsage ?? 0;
return Math.max(0, Math.min(100, value));
});
// 计算Y轴自适应的最大值和最小值
// 合并所有数据,找到极值
const allData = [...cpuData, ...memoryData];
const minValue = Math.min(...allData);
const maxValue = Math.max(...allData);
// 给Y轴极值增加一点余量让图表显示更美观比如±5%
// 确保最小值不小于0最大值不大于100
const yMin = Math.max(0, minValue - 5);
const yMax = Math.min(100, maxValue + 5);
// 自动计算刻度间隔按5/10/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);
// 配置ECharts选项
const option = {
// 图表标题
title: {
left: 'center'
},
// 提示框组件
tooltip: {
trigger: 'axis', // 坐标轴触发
formatter: '{b} <br/>{a}: {c}%', // 自定义提示框格式
textStyle: {
fontSize: 12
}
},
// 图例组件
legend: {
data: ['CPU使用率', '内存使用率'],
top: 30
},
// 网格配置
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true // 包含坐标轴标签
},
// X轴配置
xAxis: {
type: 'category',
data: xAxisData,
axisLabel: {
rotate: 30, // 标签旋转30度避免重叠
fontSize: 11
}
},
// Y轴配置核心修改自适应极值和刻度
yAxis: {
type: 'value',
name: '使用率 (%)',
min: yMin, // 自适应最小值
max: yMax, // 自适应最大值
interval: yInterval, // 自适应刻度间隔
axisLabel: {
formatter: '{value} %'
}
},
// 系列数据(两条曲线)
series: [
{
name: 'CPU使用率',
type: 'line', // 折线图
data: cpuData,
smooth: true, // 平滑曲线
lineStyle: {
width: 2
},
itemStyle: {
color: '#f56c6c' // CPU曲线颜色
},
areaStyle: {
// 面积填充,增加视觉效果
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(245, 108, 108, 0.3)' },
{ offset: 1, color: 'rgba(245, 108, 108, 0.05)' }
])
}
},
{
name: '内存使用率',
type: 'line', // 折线图
data: memoryData,
smooth: true, // 平滑曲线
lineStyle: {
width: 2
},
itemStyle: {
color: '#409eff' // 内存曲线颜色
},
areaStyle: {
// 面积填充
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.05)' }
])
}
}
]
};
// 设置图表配置项
chartInstance.value.setOption(option);
};
// 生命周期:挂载时初始化图表并获取数据
onMounted(() => {
initChart();
fetchList(props.formParams);
});
// 监听参数变化,重新获取数据
watch(
() => props.formParams,
async (newParams) => {
await fetchList(newParams);
},
{ deep: true, immediate: false }
);
// 组件卸载时清理资源(新增/完善onUnmounted
onUnmounted(() => {
// 1. 移除窗口resize事件监听
window.removeEventListener('resize', resizeHandler);
// 2. 销毁ECharts实例释放内存
if (chartInstance.value) {
chartInstance.value.dispose();
chartInstance.value = null;
}
});
</script>
<style scoped>
.monitor-content {
width: 100%;
height: 200px; /* 固定图表高度,确保渲染正常 */
}
</style>

View File

@@ -0,0 +1,278 @@
<template>
<Card title="磁盘信息">
<div class="disk-table-container disk-info-content">
<div class="custom-table-wrapper">
<!-- 固定表头 -->
<div class="table-header">
<div class="table-th" style="width: 200px">
<div class="ellipsis-text">挂载路径</div>
</div>
<div class="table-th" style="width: 180px">
<div class="ellipsis-text">设备名称</div>
</div>
<div class="table-th" style="width: 100px">
<div class="ellipsis-text">总容量</div>
</div>
<div class="table-th" style="width: 100px">
<div class="ellipsis-text">已用容量</div>
</div>
<div class="table-th flex-1">
<div class="ellipsis-text">磁盘使用率</div>
</div>
</div>
<!-- 可滚动的内容区域 -->
<div class="table-body-wrapper">
<div class="table-body">
<div v-if="diskList.length === 0" class="empty-tip">
<div class="empty-icon">📁</div>
<div class="empty-text">暂无磁盘信息</div>
</div>
<div
v-for="(disk, index) in diskList"
:key="index"
class="table-tr"
:class="{ 'table-tr-stripe': index % 2 === 1 }"
@mouseenter="hoveredRow = index"
@mouseleave="hoveredRow = -1"
>
<div class="table-td" style="width: 200px">
<span class="path-icon">📂</span>
<div class="ellipsis-text" :title="disk.mountPoint || '--'">
{{ disk.mountPoint || '--' }}
</div>
</div>
<div class="table-td" style="width: 180px">
<div class="ellipsis-text" :title="disk.device || '--'">
{{ disk.device || '--' }}
</div>
</div>
<div class="table-td" style="width: 100px">
<div class="ellipsis-text" :title="`${disk.totalSize || '--'}`">
{{ disk.totalSize || '--' }}
</div>
</div>
<div class="table-td" style="width: 100px">
<div class="ellipsis-text" :title="`${disk.usedSize || '--'}`">
{{ disk.usedSize || '--' }}
</div>
</div>
<div class="table-td flex-1">
<div class="custom-progress-container">
<div
class="custom-progress-bar"
:style="{
width: `${disk.usageRate || 0}%`,
backgroundColor: getProgressColor(disk.usageRate)
}"
:class="{ 'progress-hover': hoveredRow === index }"
></div>
<span class="custom-progress-text">{{ disk.usageRate || 0 }}%</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Card>
</template>
<script lang="ts" setup name="RigthOut">
import { ref, onMounted, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { BizDeviceInfo, bizDeviceInfoListAll } from '@jeesite/biz/api/biz/deviceInfo';
const props = defineProps<{
formParams: Record<string, any>; // 接收参数
}>();
const diskList = ref<BizDeviceInfo[]>([]);
const hoveredRow = ref(-1); // 初始化 hoveredRow
const fetchList = async (params: Record<string, any>) => {
try {
const result = await bizDeviceInfoListAll(params);
diskList.value = result || [];
} catch (error) {
console.error('获取数据列表失败:', error);
diskList.value = [];
}
};
// 根据使用率获取进度条颜色
const getProgressColor = (rate?: number) => {
const usageRate = typeof rate === 'number' ? rate : 0;
if (usageRate >= 80) return '#f56c6c'; // 红色
if (usageRate >= 60) return '#e6a23c'; // 黄色
return '#409eff'; // 蓝色(默认)
};
// 生命周期
onMounted(() => {
fetchList(props.formParams);
});
watch(
() => props.formParams,
async (newParams) => {
await fetchList(newParams);
},
{ deep: true, immediate: false }
);
</script>
<style scoped>
/* 核心修复:给外层容器设置明确高度约束 */
.disk-table-container {
width: 100%;
/* 关键:设置固定高度,也可根据需求改为 max-height */
height: 240px;
/* 或者用百分比height: 80vh;(相对于视口高度) */
box-sizing: border-box;
}
.custom-table-wrapper {
width: 100%;
height: 100%; /* 继承外层容器高度 */
display: flex;
flex-direction: column;
}
/* 固定表头样式 */
.table-header {
display: flex;
align-items: center;
height: 48px;
background-color: #f8f9fa;
border-bottom: 1px solid #e9ecef;
font-weight: 600;
padding: 0 16px;
box-sizing: border-box;
/* 固定表头sticky 结合父容器定位 */
position: relative;
z-index: 10;
}
/* 核心修复:内容区域强制高度 + 滚动 */
.table-body-wrapper {
/* 表头高度48px剩余高度全部给内容区 */
height: calc(100% - 48px);
/* 强制显示滚动条也可保留auto仅溢出时显示 */
overflow-y: scroll;
overflow-x: hidden;
box-sizing: border-box;
}
/* 表格行/列基础样式 */
.table-th, .table-td {
display: flex;
align-items: center;
padding: 0 8px;
box-sizing: border-box;
}
.table-tr {
display: flex;
align-items: center;
height: 48px;
border-bottom: 1px solid #e9ecef;
padding: 0 16px;
box-sizing: border-box;
transition: background-color 0.2s;
}
/* 斑马纹样式 */
.table-tr-stripe {
background-color: #fafafa;
}
/* 行hover效果 */
.table-tr:hover {
background-color: #f5f7fa;
}
/* 空数据提示 */
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
color: #909399;
}
.empty-icon {
font-size: 48px;
margin-bottom: 8px;
}
/* 进度条样式 */
.custom-progress-container {
width: 100%;
height: 8px;
background-color: #e5e6eb;
border-radius: 8px;
position: relative;
overflow: hidden;
}
.custom-progress-bar {
height: 100%;
border-radius: 8px;
transition: width 0.3s, background-color 0.3s;
}
.progress-hover {
opacity: 0.8;
}
.custom-progress-text {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
font-size: 10px;
color: #666;
}
/* 文字省略样式 */
.ellipsis-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
/* 挂载路径图标 */
.path-icon {
margin-right: 4px;
}
/* 强制显示滚动条(兼容不同浏览器) */
.table-body-wrapper {
/* Firefox */
scrollbar-width: thin;
scrollbar-color: #c1c1c1 #f1f1f1;
}
/* Webkit 浏览器滚动条样式 */
.table-body-wrapper::-webkit-scrollbar {
width: 8px; /* 加宽滚动条,更容易看到 */
}
.table-body-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.table-body-wrapper::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.table-body-wrapper::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>

View File

@@ -6,664 +6,134 @@
:showOkBtn="false"
:showCancelBtn="false"
defaultFullscreen="true"
width="70%"
width="100%"
class="server-detail-modal"
>
<div class="server-detail-container">
<!-- 基础信息+磁盘信息 -->
<div class="server-info-top">
<div class="server-info-left">
<div class="info-card base-info-card">
<h3 class="card-title">基础信息</h3>
<div class="card-grid base-info-content">
<div class="metric-card">
<div class="metric-label">主机名称:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.sysHostname || '--'">
{{ serverInfo?.sysHostname || '--' }}
</div>
<div class="metric-icon">🖥</div>
</div>
<div class="metric-card">
<div class="metric-label">IP 地址:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.ipAddress || '--'">
{{ serverInfo?.ipAddress || '--' }}
</div>
<div class="metric-icon">🌐</div>
</div>
<div class="metric-card">
<div class="metric-label">CPU 型号:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.cpuModel || '--'">
{{ serverInfo?.cpuModel || '--' }}
</div>
<div class="metric-icon"></div>
</div>
<div class="metric-card">
<div class="metric-label">内存大小:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.memoryTotal || '--'">
{{ serverInfo?.memoryTotal || '--' }}
</div>
<div class="metric-icon">🧠</div>
</div>
<div class="metric-card">
<div class="metric-label">系统版本:</div>
<div class="metric-value ellipsis-text" :title="serverInfo?.kernelVersion || '--'">
{{ serverInfo?.kernelVersion || '--' }}
</div>
<div class="metric-icon">🖨</div>
</div>
</div>
</div>
<div class="server-info-left card-container">
<LeftOut :formParams="FormValues" />
</div>
<div class="server-info-right">
<div class="info-card disk-info-card">
<h3 class="card-title">磁盘信息</h3>
<div class="disk-table-container disk-info-content">
<div class="custom-table-wrapper">
<div class="table-header">
<div class="table-th" style="width: 200px"><div class="ellipsis-text">挂载路径</div></div>
<div class="table-th" style="width: 180px"><div class="ellipsis-text">设备名称</div></div>
<div class="table-th" style="width: 100px"><div class="ellipsis-text">总容量</div></div>
<div class="table-th" style="width: 100px"><div class="ellipsis-text">已用容量</div></div>
<div class="table-th flex-1"><div class="ellipsis-text">磁盘使用率</div></div>
</div>
<div class="table-body-wrapper">
<div class="table-body">
<div v-if="diskList.length === 0" class="empty-tip">
<div class="empty-icon">📁</div>
<div class="empty-text">暂无磁盘信息</div>
</div>
<div
v-for="(disk, index) in diskList"
:key="index"
class="table-tr"
:class="{ 'table-tr-stripe': index % 2 === 1 }"
@mouseenter="hoveredRow = index"
@mouseleave="hoveredRow = -1"
>
<div class="table-td" style="width: 200px">
<span class="path-icon">📂</span>
<div class="ellipsis-text" :title="disk.mountPoint || '--'">
{{ disk.mountPoint || '--' }}
</div>
</div>
<div class="table-td" style="width: 180px">
<div class="ellipsis-text" :title="disk.device || '--'">
{{ disk.device || '--' }}
</div>
</div>
<div class="table-td" style="width: 100px">
<div class="ellipsis-text" :title="`${disk.totalSize || '--'}`">
{{ disk.totalSize || '--' }}
</div>
</div>
<div class="table-td" style="width: 100px">
<div class="ellipsis-text" :title="`${disk.usedSize || '--'}`">
{{ disk.usedSize || '--' }}
</div>
</div>
<div class="table-td flex-1">
<div class="custom-progress-container">
<div
class="custom-progress-bar"
:style="{
width: `${disk.usageRate || 0}%`,
backgroundColor: getProgressColor(disk.usageRate)
}"
:class="{ 'progress-hover': hoveredRow === index }"
></div>
<span class="custom-progress-text">{{ disk.usageRate || 0 }}%</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="server-info-right card-container">
<RightOut :formParams="FormValues" />
</div>
</div>
<!-- 监控信息 -->
<div class="server-info-bottom">
<div class="info-card monitor-card">
<h3 class="card-title">监控信息</h3>
<div ref="chartDom" class="monitor-content"></div>
</div>
<!-- 分隔线 -->
<div class="divider"></div>
<!-- 下半部分监控面板 -->
<div class="server-info-bottom card-container">
<Monitor :formParams="FormValues" />
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
import { defineComponent, ref } from 'vue';
import { BasicModal, useModalInner } from '@jeesite/core/components/Modal';
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
import { BizDeviceInfo, bizDeviceInfoListAll } from '@jeesite/biz/api/biz/deviceInfo';
import { BizResourceMonitor, bizResourceMonitorListAll } from '@jeesite/biz/api/biz/resourceMonitor';
import * as echarts from 'echarts';
import type { ECharts, EChartsOption, TooltipFormatterCallbackParams } from 'echarts';
import LeftOut from './Echart/LeftOut.vue';
import RightOut from './Echart/RightOut.vue';
import Monitor from './Echart/Monitor.vue';
export default defineComponent({
components: { BasicModal },
components: {
BasicModal,
LeftOut,
RightOut,
Monitor
},
setup() {
// 基础状态
const serverInfo = ref<any>({});
const diskList = ref<BizDeviceInfo[]>([]);
const hoveredRow = ref(-1);
const hostId = ref<string | number>('');
const isLoadingMonitor = ref(false);
// 图表核心状态
const chartDom = ref<HTMLDivElement | null>(null);
const chartInstance = ref<ECharts | null>(null);
const monitorData = ref<BizResourceMonitor[]>([]);
// 初始化图表
const initChart = () => {
if (!chartDom.value) return;
// 销毁已有实例,避免重复创建
if (chartInstance.value) {
chartInstance.value.dispose();
}
// 创建新实例
chartInstance.value = echarts.init(chartDom.value);
// 设置默认配置
const defaultOption: EChartsOption = {
tooltip: {
trigger: 'axis',
triggerOn: 'mousemove|click', // 鼠标移动或点击时触发
textStyle: { fontSize: 12 },
backgroundColor: 'rgba(255,255,255,0.95)', // 提高背景透明度,更清晰
borderColor: '#e6e6e6',
borderWidth: 1,
padding: 12,
borderRadius: 6, // 圆角更美观
},
legend: {
data: ['CPU使用率(%)', '内存使用率(%)'],
textStyle: { fontSize: 12 },
top: 0
},
grid: {
left: '2%',
right: '2%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: [],
axisLabel: { fontSize: 11 },
axisLine: { lineStyle: { color: '#e6e6e6' } }
},
yAxis: {
type: 'value',
min: 0,
max: 100,
name: '使用率(%)',
nameTextStyle: { fontSize: 12 },
axisLabel: {
fontSize: 11,
formatter: '{value}%'
},
axisLine: { lineStyle: { color: '#e6e6e6' } },
splitLine: { lineStyle: { color: '#f5f5f5' } }
},
series: [
{
name: 'CPU使用率(%)',
type: 'line',
data: [],
smooth: true,
lineStyle: { width: 2 },
itemStyle: { color: '#409eff' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.05)' }
])
},
symbol: 'circle',
symbolSize: 6,
},
{
name: '内存使用率(%)',
type: 'line',
data: [],
smooth: true,
lineStyle: { width: 2 },
itemStyle: { color: '#e6a23c' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(230, 162, 60, 0.3)' },
{ offset: 1, color: 'rgba(230, 162, 60, 0.05)' }
])
},
symbol: 'circle',
symbolSize: 6,
}
]
};
chartInstance.value.setOption(defaultOption);
// 监听窗口大小变化,自适应图表
window.addEventListener('resize', resizeChart);
};
// 调整图表大小
const resizeChart = () => {
if (chartInstance.value) {
chartInstance.value.resize();
}
};
// 更新图表数据
const updateChart = () => {
if (!chartInstance.value || monitorData.value.length === 0) return;
const xAxisData = monitorData.value.map(item => item.hourTime || '');
const cpuData = monitorData.value.map(item => item.cpuUsage || 0);
const memoryData = monitorData.value.map(item => item.memoryUsage || 0);
// 更新图表配置
const option: EChartsOption = {
xAxis: { data: xAxisData },
series: [
{ data: cpuData },
{ data: memoryData }
]
};
chartInstance.value.setOption(option);
};
// 模态框初始化
const FormValues = ref<Record<string, any>>({
hostId: ''
});
const [register, { closeModal }] = useModalInner(async (data: any) => {
if (!data || !data.hostId) return;
serverInfo.value = { ...data };
diskList.value = [];
monitorData.value = [];
hostId.value = data.hostId;
isLoadingMonitor.value = true;
try {
diskList.value = await bizDeviceInfoListAll({ hostId: data.hostId });
monitorData.value = await bizResourceMonitorListAll({ hostId: data.hostId });
initChart();
} catch (error) {
console.error('主机数据加载失败:', error);
monitorData.value = [];
} finally {
isLoadingMonitor.value = false;
}
});
// 根据使用率获取进度条颜色
const getProgressColor = (rate?: number) => {
const usageRate = typeof rate === 'number' ? rate : 0;
if (usageRate >= 80) return '#f56c6c'; // 红色
if (usageRate >= 60) return '#e6a23c'; // 黄色
return '#409eff'; // 蓝色(默认)
};
// 监听监控数据变化,更新图表
watch(monitorData, () => {
if (!isLoadingMonitor.value) {
updateChart();
}
FormValues.value.hostId = data.hostId;
});
return {
register,
closeModal,
serverInfo,
diskList,
hoveredRow,
chartDom,
monitorData,
isLoadingMonitor,
getProgressColor
FormValues
};
},
});
</script>
<style scoped>
/* 整体容器样式 - 上下布局 */
/* 模态框容器 - 强制100vh高度 */
.server-detail-modal {
height: 100vh !important;
max-height: 100vh !important;
}
/* 整体容器 - 100vh高度上下布局 */
.server-detail-container {
display: flex;
flex-direction: column; /* 核心:改为垂直方向布局 */
gap: 20px;
padding: 20px;
background-color: #f0f8ff;
border-radius: 8px;
flex-direction: column;
gap: 8px;
padding: 12px;
background-color: #f5f7fa;
border-radius: 12px;
height: 100%;
box-sizing: border-box;
overflow: hidden; /* 防止整体溢出 */
overflow: hidden;
}
/* 上半部分左右分栏容器 */
/* 上半部分 - 精确50%高度,左右分栏 */
.server-info-top {
display: flex; /* 保持左右布局 */
gap: 20px;
flex: 0 0 auto; /* 高度自适应内容,不拉伸 */
min-height: 300px; /* 保证基础高度 */
display: flex;
gap: 12px;
height: calc(50% - 4px); /* 减去gap的一半保证总高度精准50% */
min-height: 0;
}
/* 下半部分Monitor组件容器 */
/* 下半部分 - 精确50%高度 */
.server-info-bottom {
flex: 1; /* 占满剩余高度 */
min-height: 200px; /* 保证最小高度 */
height: calc(50% - 4px); /* 减去gap的一半保证总高度精准50% */
min-height: 0;
}
/* 左侧基础信息容器 (30%) */
/* 左侧容器 - 30%宽度,满高 */
.server-info-left {
width: 30%;
flex-shrink: 0;
height: 100%; /* 撑满上半部分高度 */
height: 100%;
min-height: 0;
}
/* 右侧磁盘信息容器 (70%) */
/* 右侧容器 - 70%宽度,满高 */
.server-info-right {
width: 70%;
flex-grow: 1;
height: 100%; /* 撑满上半部分高度 */
height: 100%;
min-height: 0;
}
/* 卡片通用样式 */
.info-card {
/* 卡片容器样式 - 美化UI */
.card-container {
background: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
padding: 16px;
height: 100%; /* 撑满父容器高度 */
box-sizing: border-box;
display: flex;
flex-direction: column; /* 卡片内部垂直布局,标题+内容 */
}
.card-title {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 600;
color: #303133;
border-bottom: 1px solid #e6e6e6;
padding-bottom: 8px;
flex: 0 0 auto; /* 标题不拉伸 */
}
/* 基础信息网格布局 */
.card-grid {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
flex: 1; /* 撑满卡片剩余高度 */
overflow-y: auto; /* 内容过多时滚动 */
}
/* 核心修复:基础信息卡片布局约束 */
.metric-card {
display: flex;
align-items: center;
padding: 12px;
background: #f8f9fa;
border-radius: 6px;
transition: all 0.3s ease;
/* 强制不换行 */
white-space: nowrap;
/* 防止卡片本身溢出 */
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: box-shadow 0.2s ease;
overflow: hidden; /* 关键修改:禁用滚动,超出内容直接隐藏 */
}
.metric-label {
flex: 0 0 80px;
font-size: 14px;
color: #606266;
/* 标签固定宽度,防止挤压 */
flex-shrink: 0;
.card-container:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* 核心修复:值区域强制省略 */
.metric-value {
flex: 1;
font-size: 14px;
color: #303133;
font-weight: 500;
/* 关键:强制文本省略的完整属性 */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* 限制最大宽度,避免挤压图标 */
max-width: calc(100% - 104px);
}
.metric-icon {
flex: 0 0 24px;
font-size: 18px;
text-align: right;
/* 图标固定宽度,不参与弹性布局 */
flex-shrink: 0;
}
/* 表格容器样式 */
.custom-table-wrapper {
/* 分隔线样式 */
.divider {
height: 1px;
background-color: #e5e6eb;
width: 100%;
border-radius: 6px;
border: 1px solid #e6e6e6;
overflow: hidden;
background: #fff;
height: 100%;
display: flex;
flex-direction: column;
margin: 0;
}
.table-header {
display: flex;
background: #f8f9fa;
border-bottom: 1px solid #e6e6e6;
font-weight: 600;
flex: 0 0 auto; /* 表头不拉伸 */
}
.table-th {
padding: 12px 8px;
font-size: 14px;
color: #303133;
text-align: left;
box-sizing: border-box;
}
.table-body-wrapper {
flex: 1; /* 表格内容占满剩余高度 */
overflow-y: auto;
}
.table-body {
width: 100%;
}
.table-tr {
display: flex;
border-bottom: 1px solid #e6e6e6;
transition: background-color 0.2s ease;
}
.table-tr:last-child {
border-bottom: none;
}
.table-tr-stripe {
background-color: #fafafa;
}
.table-tr:hover {
background-color: #f5f7fa;
}
.table-td {
padding: 12px 8px;
font-size: 14px;
color: #606266;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 6px;
}
/* 省略文本样式 */
.ellipsis-text {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.flex-1 {
flex: 1;
min-width: 0; /* 解决flex子元素省略失效问题 */
}
/* 空数据提示 */
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
color: #909399;
height: 100%;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
}
.empty-text {
font-size: 14px;
}
/* 自定义进度条 - 调整为更细的样式 */
.custom-progress-container {
width: 100%;
height: 10px;
background-color: #f5f5f5;
border-radius: 4px;
position: relative;
overflow: hidden;
}
.custom-progress-bar {
height: 100%;
border-radius: 4px;
transition: width 0.3s ease, background-color 0.3s ease;
}
.progress-hover {
filter: brightness(1.1);
}
.custom-progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 11px;
color: #303133;
line-height: 1;
font-weight: 500;
}
/* 图标样式 */
.disk-icon, .path-icon {
font-size: 16px;
flex-shrink: 0;
}
/* Monitor组件容器样式 */
.monitor-card {
height: 100%;
}
.monitor-content {
flex: 1; /* 撑满卡片剩余高度 */
height: 100%;
overflow: hidden; /* 防止Monitor组件溢出 */
padding: 8px 0;
}
/* 图表加载状态 */
.chart-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #909399;
}
.loading-icon {
font-size: 48px;
margin-bottom: 16px;
animation: rotate 1.5s linear infinite;
}
.loading-text {
font-size: 14px;
}
/* 图表空数据状态 */
.chart-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #909399;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 响应式适配 */
@media (max-width: 1024px) {
.server-info-top {
flex-direction: column; /* 小屏上半部分改为垂直布局 */
min-height: auto;
}
.server-info-left, .server-info-right {
width: 100%;
height: auto;
}
.server-info-left {
margin-bottom: 20px;
}
/* 小屏下列宽进一步优化 */
.table-th, .table-td {
width: auto !important;
flex: 1;
min-width: 80px;
}
.table-th:nth-child(1), .table-td:nth-child(1) {
flex: 2;
}
.table-th:nth-child(5), .table-td:nth-child(5) {
flex: 1.5;
}
}
/* 适配移动端 */
@media (max-width: 768px) {
:deep(.server-detail-modal) {
width: 95% !important;
}
.custom-progress-text {
font-size: 10px;
}
}
</style>
/* 移除原有的滚动条美化样式(已无必要) */
</style>