Files
lc_frontend/src/views/screen/powerMonitoring.vue

947 lines
23 KiB
Vue
Raw Normal View History

2025-12-03 14:55:57 +08:00
<template>
<div class="big-screen-container">
<!-- 标题栏 -->
<div class="screen-header">
<h1 class="screen-title">动环监控大屏</h1>
<div class="screen-datetime">{{ currentDateTime }}</div>
</div>
<!-- 主要内容区域 -->
<div class="screen-content">
<!-- 第一行统计卡片 -->
<div class="stats-row">
<!-- 总设备数卡片 -->
<div class="stat-card">
<div class="card-title">总设备数</div>
<div class="card-value">{{ totalDevices.toLocaleString() }}</div>
<div class="card-trend growth">
2025-12-03 15:25:06 +08:00
<svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor">
<path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z" />
</svg>
2025-12-12 09:09:41 +08:00
<!-- <span>较上月增长 {{ totalDevicesGrowth }}%</span> -->
2025-12-03 14:55:57 +08:00
</div>
</div>
<!-- 在线设备数卡片 -->
<div class="stat-card">
<div class="card-title">在线设备数</div>
<div class="card-value">{{ onlineDevices.toLocaleString() }}</div>
<div class="card-trend online-rate">
<span>在线率 {{ onlineRate }}%</span>
</div>
</div>
</div>
<!-- 第二行图表 -->
<div class="charts-row">
<!-- 告警趋势折线图 -->
<div class="chart-card">
<div class="chart-title">当月告警趋势</div>
<div ref="alarmTrendChartRef" class="chart-container"></div>
</div>
<!-- 设备类型分布饼图 -->
<div class="chart-card">
<div class="chart-title">告警设备类型分布</div>
<div ref="deviceTypePieChartRef" class="chart-container"></div>
</div>
</div>
<!-- 第三行园区信息列表 -->
<div class="table-row">
<div class="table-card">
<div class="table-title">园区信息列表</div>
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
<el-table
:data="parkList"
2025-12-03 15:25:06 +08:00
:style="{ width: '100%' }"
2025-12-03 14:55:57 +08:00
class="park-table"
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
height="calc(100% - 50px)"
:header-cell-style="{
2025-12-03 15:25:06 +08:00
background: '#1a3a52',
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
color: '#ffffff',
borderBottom: '1px solid rgba(78, 155, 248, 0.15)',
fontWeight: '500',
padding: '12px 8px'
}"
2025-12-03 15:25:06 +08:00
:row-style="getRowStyle"
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
:cell-style="{
borderBottom: '1px solid rgba(78, 155, 248, 0.08)',
color: '#ffffff',
padding: '12px 8px'
}"
2025-12-03 14:55:57 +08:00
>
2025-12-03 15:25:06 +08:00
<el-table-column prop="parkName" label="园区名称" min-width="200" align="left" />
<el-table-column prop="totalDevices" label="设备总数" min-width="120" align="center">
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
<template #default="{ row }">
<span class="device-count">{{ row.totalDevices }}</span>
2025-12-03 14:55:57 +08:00
</template>
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
</el-table-column>
2025-12-03 15:25:06 +08:00
<el-table-column prop="upsStatus" label="UPS" min-width="140" align="center">
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
<template #default="{ row }">
<span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']">
{{ row.upsStatus }}
2025-12-03 14:55:57 +08:00
</span>
</template>
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
</el-table-column>
2025-12-03 15:25:06 +08:00
<el-table-column prop="airConditionStatus" label="精密空调" min-width="140" align="center">
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
<template #default="{ row }">
<span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']">
{{ row.airConditionStatus }}
2025-12-03 14:55:57 +08:00
</span>
</template>
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
</el-table-column>
2025-12-03 15:25:06 +08:00
<el-table-column prop="temperatureStatus" label="温湿度" min-width="140" align="center">
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
<template #default="{ row }">
2025-12-03 15:25:06 +08:00
<span :class="['status-text', row.temperatureStatus === '正常' ? 'status-normal' : 'status-alarm']">
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
{{ row.temperatureStatus }}
2025-12-03 14:55:57 +08:00
</span>
</template>
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
</el-table-column>
2025-12-03 15:25:06 +08:00
<el-table-column prop="otherStatus" label="其他" min-width="140" align="center">
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
<template #default="{ row }">
<span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']">
{{ row.otherStatus }}
2025-12-03 14:55:57 +08:00
</span>
</template>
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
</el-table-column>
2025-12-03 15:25:06 +08:00
<el-table-column label="详情" min-width="180" align="center">
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
<template #default="{ row }">
2025-12-03 14:55:57 +08:00
<a
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
:class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']"
@click="handleViewDetail(row)"
2025-12-12 10:09:30 +08:00
:href="'http://10.0.64.20/configcenter/console/device-manage'"
target="_blank"
2025-12-03 14:55:57 +08:00
>
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
<span v-if="row.hasAlarm" class="alarm-badge">异常</span>
2025-12-03 14:55:57 +08:00
<span v-else class="normal-badge">正常</span>
查看详情
</a>
</template>
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
</el-table-column>
</el-table>
2025-12-03 14:55:57 +08:00
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
2025-12-12 09:09:41 +08:00
import {
getPowerEnvDeviceTotalNum,
getPowerEnvDeviceAlarmDistribution,
getPowerEnvAlarmTrend,
getPowerEnvDeviceGroupTypeCampus
} from '@/api/index';
2025-12-03 14:55:57 +08:00
// 定义园区信息类型
interface ParkInfo {
key: string;
parkName: string;
totalDevices: number;
upsStatus: string;
airConditionStatus: string;
temperatureStatus: string | number;
otherStatus: string;
hasAlarm: boolean;
}
// 当前日期时间
const currentDateTime = ref('');
// 统计数据
2025-12-12 09:09:41 +08:00
const totalDevices = ref(0);
const onlineDevices = ref(0);
const onlineRate = ref(100); // 在线率写死100%
2025-12-03 14:55:57 +08:00
// 图表引用
const alarmTrendChartRef = ref<HTMLDivElement>();
const deviceTypePieChartRef = ref<HTMLDivElement>();
// 图表实例
let alarmTrendChart: echarts.ECharts | null = null;
let deviceTypePieChart: echarts.ECharts | null = null;
// 告警趋势数据
const alarmTrendData = ref({
dates: ['5日', '10日', '15日', '20日', '25日', '30日'],
values: [8, 4, 20, 16, 21, 9]
});
// 设备类型分布数据
const deviceTypeData = ref([
{ name: 'UPS', value: 35 },
{ name: '精密空调', value: 25 },
{ name: '温湿度检测', value: 20 },
{ name: '其他', value: 20 }
]);
// 园区信息列表数据
2025-12-12 09:09:41 +08:00
const parkList = ref<ParkInfo[]>([]);
// 分页参数
const parkListPage = ref({
pageNo: 1,
pageSize: 10,
total: 0
});
2025-12-03 14:55:57 +08:00
// 更新日期时间
const updateDateTime = () => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 初始化告警趋势图表
2025-12-12 09:09:41 +08:00
const initAlarmTrendChart = (yAxisMax: number = 25) => {
2025-12-03 14:55:57 +08:00
if (!alarmTrendChartRef.value) return;
2025-12-12 09:09:41 +08:00
if (!alarmTrendChart) {
alarmTrendChart = echarts.init(alarmTrendChartRef.value);
}
// 计算峰值点(找出最大值)
const maxValue = Math.max(...alarmTrendData.value.values, 0);
const maxIndex = alarmTrendData.value.values.indexOf(maxValue);
const markPointData = maxValue > 0 ? [{ name: '峰值', coord: [maxIndex, maxValue] }] : [];
2025-12-03 14:55:57 +08:00
const option: EChartsOption = {
grid: {
top: '15%',
left: '8%',
right: '5%',
bottom: '12%',
containLabel: true
},
xAxis: {
type: 'category',
data: alarmTrendData.value.dates,
axisLine: {
lineStyle: {
color: '#3a5165'
}
},
axisLabel: {
color: '#8b9bb3',
fontSize: 12
},
splitLine: {
show: true,
lineStyle: {
color: '#1e3a52',
type: 'dashed'
}
}
},
yAxis: {
type: 'value',
min: 0,
2025-12-12 09:09:41 +08:00
max: yAxisMax,
interval: yAxisMax / 5,
2025-12-03 14:55:57 +08:00
axisLine: {
show: false
},
axisLabel: {
color: '#8b9bb3',
fontSize: 12
},
splitLine: {
lineStyle: {
color: '#1e3a52',
type: 'dashed'
}
}
},
series: [
{
data: alarmTrendData.value.values,
type: 'line',
smooth: true,
lineStyle: {
color: '#4e9bf8',
width: 2
},
itemStyle: {
color: '#4e9bf8',
borderWidth: 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(78, 155, 248, 0.3)' },
{ offset: 1, color: 'rgba(78, 155, 248, 0.05)' }
]
}
},
markPoint: {
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#ff5555',
borderWidth: 2,
borderColor: '#ff5555'
},
2025-12-12 09:09:41 +08:00
data: markPointData
2025-12-03 14:55:57 +08:00
}
}
]
};
alarmTrendChart.setOption(option);
};
2025-12-12 09:09:41 +08:00
// 更新告警趋势图表
const updateAlarmTrendChart = (yAxisMax: number = 25) => {
if (!alarmTrendChart) {
initAlarmTrendChart(yAxisMax);
return;
}
// 计算峰值点
const maxValue = Math.max(...alarmTrendData.value.values, 0);
const maxIndex = alarmTrendData.value.values.indexOf(maxValue);
const markPointData = maxValue > 0 ? [{ name: '峰值', coord: [maxIndex, maxValue] }] : [];
alarmTrendChart.setOption({
xAxis: {
data: alarmTrendData.value.dates
},
yAxis: {
max: yAxisMax,
interval: yAxisMax / 5
},
series: [
{
data: alarmTrendData.value.values,
markPoint: {
data: markPointData
}
}
]
});
};
2025-12-03 14:55:57 +08:00
// 初始化设备类型分布饼图
const initDeviceTypePieChart = () => {
if (!deviceTypePieChartRef.value) return;
2025-12-12 09:09:41 +08:00
if (!deviceTypePieChart) {
deviceTypePieChart = echarts.init(deviceTypePieChartRef.value);
}
updateDeviceTypePieChart();
};
// 更新设备类型分布饼图
const updateDeviceTypePieChart = () => {
if (!deviceTypePieChart) {
initDeviceTypePieChart();
return;
}
2025-12-03 14:55:57 +08:00
const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0);
const option: EChartsOption = {
legend: {
orient: 'vertical',
right: '10%',
top: 'center',
textStyle: {
color: '#ffffff',
fontSize: 13
},
formatter: (name: string) => {
const item = deviceTypeData.value.find(d => d.name === name);
return `${name} (${item?.value || 0})`;
}
},
series: [
{
type: 'pie',
radius: ['45%', '70%'],
center: ['35%', '50%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'center',
formatter: () => {
return `{value|${total}}\n{label|告警}`;
},
rich: {
value: {
color: '#ffffff',
fontSize: 28,
fontWeight: 'bold',
lineHeight: 40
},
label: {
color: '#8b9bb3',
fontSize: 14,
lineHeight: 20
}
}
},
labelLine: {
show: false
},
data: deviceTypeData.value.map((item, index) => {
const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c'];
return {
name: item.name,
value: item.value,
itemStyle: {
color: colors[index]
}
};
})
}
]
};
deviceTypePieChart.setOption(option);
};
2025-12-03 15:25:06 +08:00
// 获取行样式(条纹效果)
const getRowStyle = ({ rowIndex }: { rowIndex: number }) => {
if (rowIndex % 2 === 0) {
return {
background: 'rgba(78, 155, 248, 0.03)',
color: '#ffffff'
};
}
return {
background: 'transparent',
color: '#ffffff'
};
};
2025-12-03 14:55:57 +08:00
// 查看详情
const handleViewDetail = (record: ParkInfo) => {
console.log('查看园区详情:', record);
// TODO: 实现详情页面跳转或弹窗
};
2025-12-12 09:09:41 +08:00
// 获取总设备数
const fetchTotalDevices = async () => {
try {
const params = {
pageNo: 1,
pageSize: 10
};
const response = await getPowerEnvDeviceTotalNum(params);
2025-12-12 10:09:30 +08:00
debugger
if (response.records?.length > 0) {
debugger
const total = parseInt(response.records[0].total || '0', 10);
2025-12-12 09:09:41 +08:00
totalDevices.value = total;
onlineDevices.value = total; // 在线设备数也用total
}
} catch (error) {
console.error('获取总设备数失败:', error);
}
};
// 获取告警设备类型分布
const fetchAlarmDistribution = async () => {
try {
const params = {
pageNo: 1,
pageSize: 10
};
const response = await getPowerEnvDeviceAlarmDistribution(params);
if (response.code === 0 && response.data?.records?.length > 0) {
const record = response.data.records[0];
deviceTypeData.value = [
{ name: 'UPS', value: parseInt(record.ups_num || '0', 10) },
{ name: '精密空调', value: parseInt(record.jmkt_num || '0', 10) },
{ name: '温湿度检测', value: parseInt(record.wsd_num || '0', 10) },
{ name: '其他', value: parseInt(record.other_num || '0', 10) }
];
// 更新饼图
updateDeviceTypePieChart();
}
} catch (error) {
console.error('获取告警设备类型分布失败:', error);
}
};
// 获取当月告警趋势
const fetchAlarmTrend = async () => {
try {
const params = {
pageNo: 1,
pageSize: 100 // 可能需要获取更多数据
};
const response = await getPowerEnvAlarmTrend(params);
if (response.code === 0 && response.data?.records?.length > 0) {
const records = response.data.records;
// 处理日期格式:从 "2025-12-10" 转换为 "10日"
const dates = records.map((item: any) => {
const date = new Date(item.alarm_date);
return `${date.getDate()}`;
});
const values = records.map((item: any) => parseInt(item.alarm_count || '0', 10));
alarmTrendData.value = {
dates,
values
};
// 计算最大值用于设置y轴
const maxValue = Math.max(...values, 0);
const yAxisMax = maxValue > 0 ? Math.ceil(maxValue / 5) * 5 : 25;
// 更新折线图
updateAlarmTrendChart(yAxisMax);
}
} catch (error) {
console.error('获取当月告警趋势失败:', error);
}
};
// 格式化状态显示:如果数量>0显示"X个告警",否则"正常"
const formatStatus = (count: string | number): string => {
const num = typeof count === 'string' ? parseInt(count || '0', 10) : count;
return num > 0 ? `${num}个告警` : '正常';
};
// 获取园区信息列表
const fetchParkList = async () => {
try {
const params = {
pageNo: parkListPage.value.pageNo,
pageSize: parkListPage.value.pageSize
};
const response = await getPowerEnvDeviceGroupTypeCampus(params);
2025-12-12 10:09:30 +08:00
debugger
if (response.records?.length>0) {
parkListPage.value.total = response.records?.length || 0;
2025-12-12 09:09:41 +08:00
2025-12-12 10:09:30 +08:00
if (response.records && response.records.length > 0) {
parkList.value = response.records.map((item: any) => {
2025-12-12 09:09:41 +08:00
const upsCount = parseInt(item.ups || '0', 10);
const jmktCount = parseInt(item.jmkt || '0', 10);
const wsdCount = parseInt(item.wsd || '0', 10);
const otherCount = parseInt(item.other || '0', 10);
const alarmCount = parseInt(item.alarm || '0', 10);
return {
key: item.campus_id || item.row_id,
parkName: item.campus_name || '',
totalDevices: parseInt(item.total || '0', 10),
upsStatus: formatStatus(upsCount),
airConditionStatus: formatStatus(jmktCount),
temperatureStatus: formatStatus(wsdCount),
otherStatus: formatStatus(otherCount),
hasAlarm: alarmCount > 0
};
});
} else {
parkList.value = [];
}
}
} catch (error) {
console.error('获取园区信息列表失败:', error);
}
};
2025-12-03 14:55:57 +08:00
// 定时器
let dateTimeTimer: number | null = null;
// 组件挂载
2025-12-12 09:09:41 +08:00
onMounted(async () => {
2025-12-03 14:55:57 +08:00
// 更新日期时间
updateDateTime();
dateTimeTimer = window.setInterval(updateDateTime, 1000);
2025-12-12 09:09:41 +08:00
// 获取总设备数
fetchTotalDevices();
// 获取园区信息列表
fetchParkList();
// 初始化图表(先用默认数据)
2025-12-03 14:55:57 +08:00
setTimeout(() => {
initAlarmTrendChart();
initDeviceTypePieChart();
2025-12-12 09:09:41 +08:00
// 图表初始化后再获取数据并更新
fetchAlarmDistribution();
fetchAlarmTrend();
2025-12-03 14:55:57 +08:00
}, 100);
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
});
// 组件卸载
onBeforeUnmount(() => {
if (dateTimeTimer) {
clearInterval(dateTimeTimer);
}
if (alarmTrendChart) {
alarmTrendChart.dispose();
}
if (deviceTypePieChart) {
deviceTypePieChart.dispose();
}
window.removeEventListener('resize', handleResize);
});
// 处理窗口大小变化
const handleResize = () => {
alarmTrendChart?.resize();
deviceTypePieChart?.resize();
};
</script>
<style scoped lang="scss">
.big-screen-container {
width: 100%;
min-height: 100vh;
background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%);
padding: 20px;
box-sizing: border-box;
overflow: auto;
}
// 标题栏
.screen-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 10px;
.screen-title {
font-size: 32px;
font-weight: bold;
color: #ffffff;
margin: 0;
letter-spacing: 2px;
}
.screen-datetime {
font-size: 18px;
color: #8b9bb3;
font-family: 'Courier New', monospace;
}
}
// 主要内容区域
.screen-content {
display: flex;
flex-direction: column;
gap: 20px;
}
// 统计卡片行
.stats-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.stat-card {
background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%);
border-radius: 12px;
padding: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(78, 155, 248, 0.1);
.card-title {
font-size: 16px;
color: #8b9bb3;
margin-bottom: 15px;
}
.card-value {
font-size: 48px;
font-weight: bold;
color: #ffffff;
margin-bottom: 12px;
font-family: 'Arial', sans-serif;
}
.card-trend {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
&.growth {
color: #52c41a;
.trend-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
}
&.online-rate {
color: #13c2c2;
}
}
}
// 图表行
.charts-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.chart-card {
background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%);
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(78, 155, 248, 0.1);
height: 350px;
.chart-title {
font-size: 16px;
color: #ffffff;
margin-bottom: 15px;
font-weight: 500;
}
.chart-container {
width: 100%;
height: calc(100% - 40px);
}
}
// 表格行
.table-row {
width: 100%;
}
.table-card {
background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%);
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(78, 155, 248, 0.1);
height: 450px;
.table-title {
font-size: 16px;
color: #ffffff;
margin-bottom: 15px;
font-weight: 500;
}
:deep(.park-table) {
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
background: transparent !important;
// 表格主体背景
.el-table__inner-wrapper {
background: transparent !important;
&::before {
display: none;
2025-12-03 14:55:57 +08:00
}
}
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
.el-table__body-wrapper {
background: transparent !important;
// 滚动条样式
2025-12-03 14:55:57 +08:00
scrollbar-width: thin;
scrollbar-color: rgba(78, 155, 248, 0.3) transparent;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: rgba(78, 155, 248, 0.3);
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
.el-table__header-wrapper,
.el-table__footer-wrapper {
background: transparent !important;
}
// 表格行悬浮效果
.el-table__row {
2025-12-03 15:25:06 +08:00
&:hover > td {
background: rgba(78, 155, 248, 0.1) !important;
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
}
}
// 空数据样式
.el-table__empty-block {
background: transparent !important;
.el-table__empty-text {
color: #8b9bb3;
}
}
2025-12-03 14:55:57 +08:00
.device-count {
color: #ffffff;
font-weight: 500;
}
.status-text {
padding: 2px 8px;
border-radius: 4px;
font-size: 13px;
&.status-normal {
color: #52c41a;
}
&.status-alarm {
color: #ff4d4f;
}
}
.detail-link {
cursor: pointer;
font-size: 13px;
display: inline-flex;
align-items: center;
2025-12-03 15:25:06 +08:00
gap: 8px;
2025-12-03 14:55:57 +08:00
text-decoration: none;
2025-12-03 15:25:06 +08:00
color: #4e9bf8;
.alarm-badge,
.normal-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
color: #ffffff;
font-weight: 500;
}
2025-12-03 14:55:57 +08:00
2025-12-03 15:25:06 +08:00
.alarm-badge {
background: #ff4d4f;
2025-12-03 14:55:57 +08:00
}
2025-12-03 15:25:06 +08:00
.normal-badge {
background: #52c41a;
2025-12-03 14:55:57 +08:00
}
&:hover {
opacity: 0.8;
}
}
}
}
// 响应式布局
@media screen and (max-width: 1600px) {
.screen-header {
.screen-title {
font-size: 28px;
}
.screen-datetime {
font-size: 16px;
}
}
.stat-card {
padding: 25px;
.card-value {
font-size: 42px;
}
}
.chart-card {
height: 320px;
}
.table-card {
height: 400px;
}
}
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
@media screen and (max-width: 1280px) {
.screen-header {
.screen-title {
font-size: 26px;
}
.screen-datetime {
font-size: 15px;
}
}
.stat-card {
padding: 20px;
.card-title {
font-size: 14px;
}
.card-value {
font-size: 38px;
}
.card-trend {
font-size: 13px;
}
}
.chart-card {
height: 300px;
padding: 15px;
.chart-title {
font-size: 14px;
}
}
.table-card {
height: 380px;
padding: 15px;
.table-title {
font-size: 14px;
}
}
}
// 仅在非常小的屏幕小于1024px才改为单列
@media screen and (max-width: 1024px) {
2025-12-03 14:55:57 +08:00
.stats-row,
.charts-row {
grid-template-columns: 1fr;
}
.chart-card {
<template> <div class="big-screen-container"> <!-- 标题栏 --> <div class="screen-header"> <h1 class="screen-title">动环监控大屏</h1> <div class="screen-datetime">{{ currentDateTime }}</div> </div> <!-- 主要内容区域 --> <div class="screen-content"> <!-- 第一行:统计卡片 --> <div class="stats-row"> <!-- 总设备数卡片 --> <div class="stat-card"> <div class="card-title">总设备数</div> <div class="card-value">{{ totalDevices.toLocaleString() }}</div> <div class="card-trend growth"> <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"> <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path> </svg> <span>较上月增长 {{ totalDevicesGrowth }}%</span> </div> </div> <!-- 在线设备数卡片 --> <div class="stat-card"> <div class="card-title">在线设备数</div> <div class="card-value">{{ onlineDevices.toLocaleString() }}</div> <div class="card-trend online-rate"> <span>在线率 {{ onlineRate }}%</span> </div> </div> </div> <!-- 第二行:图表 --> <div class="charts-row"> <!-- 告警趋势折线图 --> <div class="chart-card"> <div class="chart-title">当月告警趋势</div> <div ref="alarmTrendChartRef" class="chart-container"></div> </div> <!-- 设备类型分布饼图 --> <div class="chart-card"> <div class="chart-title">告警设备类型分布</div> <div ref="deviceTypePieChartRef" class="chart-container"></div> </div> </div> <!-- 第三行:园区信息列表 --> <div class="table-row"> <div class="table-card"> <div class="table-title">园区信息列表</div> <el-table :data="parkList" stripe style="width: 100%" class="park-table" height="calc(100% - 50px)" :header-cell-style="{ background: 'rgba(78, 155, 248, 0.08)', color: '#ffffff', borderBottom: '1px solid rgba(78, 155, 248, 0.15)', fontWeight: '500', padding: '12px 8px' }" :row-style="{ background: 'transparent', color: '#ffffff' }" :cell-style="{ borderBottom: '1px solid rgba(78, 155, 248, 0.08)', color: '#ffffff', padding: '12px 8px' }" > <el-table-column prop="parkName" label="园区名称" width="180" align="left" /> <el-table-column prop="totalDevices" label="设备总数" width="100" align="center"> <template #default="{ row }"> <span class="device-count">{{ row.totalDevices }}</span> </template> </el-table-column> <el-table-column prop="upsStatus" label="UPS" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.upsStatus }} </span> </template> </el-table-column> <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.airConditionStatus }} </span> </template> </el-table-column> <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']"> {{ row.temperatureStatus }} </span> </template> </el-table-column> <el-table-column prop="otherStatus" label="其他" width="120" align="center"> <template #default="{ row }"> <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']"> {{ row.otherStatus }} </span> </template> </el-table-column> <el-table-column label="详情" width="150" align="center"> <template #default="{ row }"> <a :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']" @click="handleViewDetail(row)" > <span v-if="row.hasAlarm" class="alarm-badge">异常</span> <span v-else class="normal-badge">正常</span> 查看详情 </a> </template> </el-table-column> </el-table> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; // 定义园区信息类型 interface ParkInfo { key: string; parkName: string; totalDevices: number; upsStatus: string; airConditionStatus: string; temperatureStatus: string | number; otherStatus: string; hasAlarm: boolean; } // 当前日期时间 const currentDateTime = ref(''); // 统计数据 const totalDevices = ref(1286); const totalDevicesGrowth = ref(5.2); const onlineDevices = ref(1254); const onlineRate = ref(97.5); // 图表引用 const alarmTrendChartRef = ref<HTMLDivElement>(); const deviceTypePieChartRef = ref<HTMLDivElement>(); // 图表实例 let alarmTrendChart: echarts.ECharts | null = null; let deviceTypePieChart: echarts.ECharts | null = null; // 告警趋势数据 const alarmTrendData = ref({ dates: ['5日', '10日', '15日', '20日', '25日', '30日'], values: [8, 4, 20, 16, 21, 9] }); // 设备类型分布数据 const deviceTypeData = ref([ { name: 'UPS', value: 35 }, { name: '精密空调', value: 25 }, { name: '温湿度检测', value: 20 }, { name: '其他', value: 20 } ]); // 园区信息列表数据 const parkList = ref<ParkInfo[]>([ { key: '1', parkName: '雄安总部', totalDevices: 156, upsStatus: '3个告警', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '2', parkName: '恒毅大厦', totalDevices: 98, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '3', parkName: '丰台创新中心', totalDevices: 124, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 2, otherStatus: '正常', hasAlarm: false }, { key: '4', parkName: '重庆产业大厦', totalDevices: 92, upsStatus: '正常', airConditionStatus: '2个告警', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: true }, { key: '5', parkName: '海南园区', totalDevices: 78, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '6', parkName: '雄安地面站', totalDevices: 64, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '7', parkName: '铜川地面站', totalDevices: 58, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '8', parkName: '佳木斯地面站', totalDevices: 72, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '9', parkName: '库尔勒地面站', totalDevices: 68, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: 1, otherStatus: '正常', hasAlarm: false }, { key: '10', parkName: '澄迈地面站', totalDevices: 56, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false }, { key: '11', parkName: '文昌地面站', totalDevices: 60, upsStatus: '正常', airConditionStatus: '正常', temperatureStatus: '正常', otherStatus: '正常', hasAlarm: false } ]); // 更新日期时间 const updateDateTime = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; // 初始化告警趋势图表 const initAlarmTrendChart = () => { if (!alarmTrendChartRef.value) return; alarmTrendChart = echarts.init(alarmTrendChartRef.value); const option: EChartsOption = { grid: { top: '15%', left: '8%', right: '5%', bottom: '12%', containLabel: true }, xAxis: { type: 'category', data: alarmTrendData.value.dates, axisLine: { lineStyle: { color: '#3a5165' } }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#1e3a52', type: 'dashed' } } }, yAxis: { type: 'value', min: 0, max: 25, interval: 5, axisLine: { show: false }, axisLabel: { color: '#8b9bb3', fontSize: 12 }, splitLine: { lineStyle: { color: '#1e3a52', type: 'dashed' } } }, series: [ { data: alarmTrendData.value.values, type: 'line', smooth: true, lineStyle: { color: '#4e9bf8', width: 2 }, itemStyle: { color: '#4e9bf8', borderWidth: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(78, 155, 248, 0.3)' }, { offset: 1, color: 'rgba(78, 155, 248, 0.05)' } ] } }, markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff5555', borderWidth: 2, borderColor: '#ff5555' }, data: [ { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值 { name: '峰值2', coord: [4, 21] } // 25日的告警峰值 ] } } ] }; alarmTrendChart.setOption(option); }; // 初始化设备类型分布饼图 const initDeviceTypePieChart = () => { if (!deviceTypePieChartRef.value) return; deviceTypePieChart = echarts.init(deviceTypePieChartRef.value); const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0); const option: EChartsOption = { legend: { orient: 'vertical', right: '10%', top: 'center', textStyle: { color: '#ffffff', fontSize: 13 }, formatter: (name: string) => { const item = deviceTypeData.value.find(d => d.name === name); return `${name} (${item?.value || 0})`; } }, series: [ { type: 'pie', radius: ['45%', '70%'], center: ['35%', '50%'], avoidLabelOverlap: false, label: { show: true, position: 'center', formatter: () => { return `{value|${total}}\n{label|告警}`; }, rich: { value: { color: '#ffffff', fontSize: 28, fontWeight: 'bold', lineHeight: 40 }, label: { color: '#8b9bb3', fontSize: 14, lineHeight: 20 } } }, labelLine: { show: false }, data: deviceTypeData.value.map((item, index) => { const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c']; return { name: item.name, value: item.value, itemStyle: { color: colors[index] } }; }) } ] }; deviceTypePieChart.setOption(option); }; // 查看详情 const handleViewDetail = (record: ParkInfo) => { console.log('查看园区详情:', record); // TODO: 实现详情页面跳转或弹窗 }; // 定时器 let dateTimeTimer: number | null = null; // 组件挂载 onMounted(() => { // 更新日期时间 updateDateTime(); dateTimeTimer = window.setInterval(updateDateTime, 1000); // 初始化图表 setTimeout(() => { initAlarmTrendChart(); initDeviceTypePieChart(); }, 100); // 监听窗口大小变化 window.addEventListener('resize', handleResize); }); // 组件卸载 onBeforeUnmount(() => { if (dateTimeTimer) { clearInterval(dateTimeTimer); } if (alarmTrendChart) { alarmTrendChart.dispose(); } if (deviceTypePieChart) { deviceTypePieChart.dispose(); } window.removeEventListener('resize', handleResize); }); // 处理窗口大小变化 const handleResize = () => { alarmTrendChart?.resize(); deviceTypePieChart?.resize(); }; </script> <style scoped lang="scss"> .big-screen-container { width: 100%; min-height: 100vh; background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%); padding: 20px; box-sizing: border-box; overflow: auto; } // 标题栏 .screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 0 10px; .screen-title { font-size: 32px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 2px; } .screen-datetime { font-size: 18px; color: #8b9bb3; font-family: 'Courier New', monospace; } } // 主要内容区域 .screen-content { display: flex; flex-direction: column; gap: 20px; } // 统计卡片行 .stats-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .stat-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); .card-title { font-size: 16px; color: #8b9bb3; margin-bottom: 15px; } .card-value { font-size: 48px; font-weight: bold; color: #ffffff; margin-bottom: 12px; font-family: 'Arial', sans-serif; } .card-trend { display: flex; align-items: center; gap: 6px; font-size: 14px; &.growth { color: #52c41a; .trend-icon { width: 16px; height: 16px; flex-shrink: 0; } } &.online-rate { color: #13c2c2; } } } // 图表行 .charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .chart-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 350px; .chart-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } .chart-container { width: 100%; height: calc(100% - 40px); } } // 表格行 .table-row { width: 100%; } .table-card { background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(78, 155, 248, 0.1); height: 450px; .table-title { font-size: 16px; color: #ffffff; margin-bottom: 15px; font-weight: 500; } :deep(.park-table) { background: transparent !important; // 表格主体背景 .el-table__inner-wrapper { background: transparent !important; &::before { display: none; } } .el-table__body-wrapper { background: transparent !important; // 滚动条样式 scrollbar-width: thin; scrollbar-color: rgba(78, 155, 248, 0.3) transparent; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: rgba(78, 155, 248, 0.3); border-radius: 3px; } &::-webkit-scrollbar-track { background: transparent; } } .el-table__header-wrapper, .el-table__footer-wrapper { background: transparent !important; } // 表格行悬浮效果 .el-table__row { &:hover { background: rgba(78, 155, 248, 0.05) !important; } } // 空数据样式 .el-table__empty-block { background: transparent !important; .el-table__empty-text { color: #8b9bb3; } } .device-count { color: #ffffff; font-weight: 500; } .status-text { padding: 2px 8px; border-radius: 4px; font-size: 13px; &.status-normal { color: #52c41a; } &.status-alarm { color: #ff4d4f; } } .detail-link { cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; text-decoration: none; &.detail-normal { color: #52c41a; .normal-badge { color: #52c41a; } } &.detail-alarm { .alarm-badge { color: #ff4d4f; } } &:hover { opacity: 0.8; } } } } // 响应式布局 @media screen and (max-width: 1600px) { .screen-header { .screen-title { font-size: 28px; } .screen-datetime { font-size: 16px; } } .stat-card { padding: 25px; .card-value { font-size: 42px; } } .chart-card { height: 320px; } .table-card { height: 400px; } } @media screen and (max-width: 1280px) { .screen-header { .screen-title { font-size: 26px; } .screen-datetime { font-size: 15px; } } .stat-card { padding: 20px; .card-title { font-size: 14px; } .card-value { font-size: 38px; } .card-trend { font-size: 13px; } } .chart-card { height: 300px; padding: 15px; .chart-title { font-size: 14px; } } .table-card { height: 380px; padding: 15px; .table-title { font-size: 14px; } } } // 仅在非常小的屏幕(小于1024px)才改为单列 @media screen and (max-width: 1024px) { .stats-row, .charts-row { grid-template-columns: 1fr; } .chart-card { height: 280px; } .table-card { height: 360px; } } </style>
2025-12-03 15:12:18 +08:00
height: 280px;
}
.table-card {
height: 360px;
2025-12-03 14:55:57 +08:00
}
}
</style>