新增前端vue
This commit is contained in:
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -6,664 +6,134 @@
|
|||||||
:showOkBtn="false"
|
:showOkBtn="false"
|
||||||
:showCancelBtn="false"
|
:showCancelBtn="false"
|
||||||
defaultFullscreen="true"
|
defaultFullscreen="true"
|
||||||
width="70%"
|
width="100%"
|
||||||
|
class="server-detail-modal"
|
||||||
>
|
>
|
||||||
<div class="server-detail-container">
|
<div class="server-detail-container">
|
||||||
<!-- 基础信息+磁盘信息 -->
|
|
||||||
<div class="server-info-top">
|
<div class="server-info-top">
|
||||||
<div class="server-info-left">
|
<div class="server-info-left card-container">
|
||||||
<div class="info-card base-info-card">
|
<LeftOut :formParams="FormValues" />
|
||||||
<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>
|
</div>
|
||||||
<div class="server-info-right">
|
<div class="server-info-right card-container">
|
||||||
<div class="info-card disk-info-card">
|
<RightOut :formParams="FormValues" />
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 监控信息 -->
|
<!-- 分隔线 -->
|
||||||
<div class="server-info-bottom">
|
<div class="divider"></div>
|
||||||
<div class="info-card monitor-card">
|
<!-- 下半部分:监控面板 -->
|
||||||
<h3 class="card-title">监控信息</h3>
|
<div class="server-info-bottom card-container">
|
||||||
<div ref="chartDom" class="monitor-content"></div>
|
<Monitor :formParams="FormValues" />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { BasicModal, useModalInner } from '@jeesite/core/components/Modal';
|
||||||
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
|
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 LeftOut from './Echart/LeftOut.vue';
|
||||||
import type { ECharts, EChartsOption, TooltipFormatterCallbackParams } from 'echarts';
|
import RightOut from './Echart/RightOut.vue';
|
||||||
|
import Monitor from './Echart/Monitor.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { BasicModal },
|
components: {
|
||||||
|
BasicModal,
|
||||||
|
LeftOut,
|
||||||
|
RightOut,
|
||||||
|
Monitor
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
// 基础状态
|
const FormValues = ref<Record<string, any>>({
|
||||||
const serverInfo = ref<any>({});
|
hostId: ''
|
||||||
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 [register, { closeModal }] = useModalInner(async (data: any) => {
|
const [register, { closeModal }] = useModalInner(async (data: any) => {
|
||||||
if (!data || !data.hostId) return;
|
if (!data || !data.hostId) return;
|
||||||
serverInfo.value = { ...data };
|
FormValues.value.hostId = data.hostId;
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
register,
|
register,
|
||||||
closeModal,
|
closeModal,
|
||||||
serverInfo,
|
FormValues
|
||||||
diskList,
|
|
||||||
hoveredRow,
|
|
||||||
chartDom,
|
|
||||||
monitorData,
|
|
||||||
isLoadingMonitor,
|
|
||||||
getProgressColor
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 整体容器样式 - 上下布局 */
|
/* 模态框容器 - 强制100vh高度 */
|
||||||
|
.server-detail-modal {
|
||||||
|
height: 100vh !important;
|
||||||
|
max-height: 100vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 整体容器 - 100vh高度,上下布局 */
|
||||||
.server-detail-container {
|
.server-detail-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column; /* 核心:改为垂直方向布局 */
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 8px;
|
||||||
padding: 20px;
|
padding: 12px;
|
||||||
background-color: #f0f8ff;
|
background-color: #f5f7fa;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden; /* 防止整体溢出 */
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 上半部分:左右分栏容器 */
|
/* 上半部分 - 精确50%高度,左右分栏 */
|
||||||
.server-info-top {
|
.server-info-top {
|
||||||
display: flex; /* 保持左右布局 */
|
display: flex;
|
||||||
gap: 20px;
|
gap: 12px;
|
||||||
flex: 0 0 auto; /* 高度自适应内容,不拉伸 */
|
height: calc(50% - 4px); /* 减去gap的一半,保证总高度精准50% */
|
||||||
min-height: 300px; /* 保证基础高度 */
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 下半部分:Monitor组件容器 */
|
/* 下半部分 - 精确50%高度 */
|
||||||
.server-info-bottom {
|
.server-info-bottom {
|
||||||
flex: 1; /* 占满剩余高度 */
|
height: calc(50% - 4px); /* 减去gap的一半,保证总高度精准50% */
|
||||||
min-height: 200px; /* 保证最小高度 */
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 左侧基础信息容器 (30%) */
|
/* 左侧容器 - 30%宽度,满高 */
|
||||||
.server-info-left {
|
.server-info-left {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 100%; /* 撑满上半部分高度 */
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 右侧磁盘信息容器 (70%) */
|
/* 右侧容器 - 70%宽度,满高 */
|
||||||
.server-info-right {
|
.server-info-right {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
height: 100%; /* 撑满上半部分高度 */
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 卡片通用样式 */
|
/* 卡片容器样式 - 美化UI */
|
||||||
.info-card {
|
.card-container {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 8px;
|
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;
|
padding: 12px;
|
||||||
background: #f8f9fa;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
border-radius: 6px;
|
transition: box-shadow 0.2s ease;
|
||||||
transition: all 0.3s ease;
|
overflow: hidden; /* 关键修改:禁用滚动,超出内容直接隐藏 */
|
||||||
/* 强制不换行 */
|
|
||||||
white-space: nowrap;
|
|
||||||
/* 防止卡片本身溢出 */
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-label {
|
.card-container:hover {
|
||||||
flex: 0 0 80px;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
/* 标签固定宽度,防止挤压 */
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 核心修复:值区域强制省略 */
|
/* 分隔线样式 */
|
||||||
.metric-value {
|
.divider {
|
||||||
flex: 1;
|
height: 1px;
|
||||||
font-size: 14px;
|
background-color: #e5e6eb;
|
||||||
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 {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 6px;
|
margin: 0;
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #fff;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-header {
|
/* 移除原有的滚动条美化样式(已无必要) */
|
||||||
display: flex;
|
</style>
|
||||||
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>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user