1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024 |
- <template>
- <div class="area-management-system">
- <!-- 查询条件区域 -->
- <el-card class="filter-card">
- <el-form :model="queryParams" ref="searchForm" :inline="true" class="search-form">
- <!-- 区域层级条件 -->
- <el-form-item label="区域名称">
- <el-input v-model="queryParams.areaName" placeholder="请输入区域名称" clearable />
- </el-form-item>
- <el-form-item label="层级">
- <el-select v-model="queryParams.level" placeholder="请选择层级" clearable style="width: 180px;">
- <el-option label="全部" value="" />
- <el-option label="一级区域" value="1" />
- <el-option label="二级区域" value="2" />
- <el-option label="三级区域" value="3" />
- </el-select>
- </el-form-item>
- <!-- 设备统计条件 -->
- <el-form-item label="设备类型">
- <el-select v-model="queryParams.deviceType" placeholder="请选择设备类型" clearable style="width: 180px;">
- <el-option label="全部" value="" />
- <el-option label="控制器" value="CONTROLLER" />
- <el-option label="传感器" value="SENSOR" />
- <el-option label="执行器" value="ACTUATOR" />
- <el-option label="广播设备" value="BROADCASTER" />
- <el-option label="监控设备" value="MONITOR" />
- </el-select>
- </el-form-item>
- <!-- 状态过滤条件 -->
- <el-form-item label="设备状态">
- <el-select v-model="queryParams.status" placeholder="请选择设备状态" clearable style="width: 180px;">
- <el-option label="全部" value="" />
- <el-option label="在线" value="在线" />
- <el-option label="离线" value="离线" />
- <el-option label="故障" value="故障" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
- <el-button :icon="Refresh" @click="resetSearch">重置</el-button>
- <el-button type="success" :icon="Download" @click="handleExport">导出</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- <!-- 统计卡片 -->
- <el-row :gutter="20" class="stats-row">
- <el-col :span="6">
- <el-card class="stats-card">
- <el-statistic title="区域总数" :value="stats.totalAreas">
- <template #prefix>
- <el-icon><Location /></el-icon>
- </template>
- </el-statistic>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stats-card">
- <el-statistic title="设备总数" :value="stats.totalDevices">
- <template #prefix>
- <el-icon><Monitor /></el-icon>
- </template>
- </el-statistic>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stats-card online-card">
- <el-statistic title="在线设备" :value="stats.onlineDevices">
- <template #prefix>
- <el-icon><CircleCheck /></el-icon>
- </template>
- <template #suffix>
- <span class="rate-text">({{ stats.onlineRate }}%)</span>
- </template>
- </el-statistic>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stats-card health-card">
- <el-statistic title="平均健康指数" :value="stats.avgHealthIndex" suffix="分">
- <template #prefix>
- <el-icon><TrendCharts /></el-icon>
- </template>
- </el-statistic>
- </el-card>
- </el-col>
- </el-row>
- <!-- 数据表格区域 -->
- <el-card class="data-table">
- <el-table
- :data="areaList"
- border
- v-loading="isLoading"
- row-key="areaId"
- @row-click="handleRowClick"
- :row-class-name="tableRowClassName"
- >
- <el-table-column label="区域ID" prop="areaId" align="center" width="120" fixed="left" />
- <el-table-column label="区域名称" prop="areaName" align="center" min-width="150">
- <template #default="scope">
- <div class="area-name-cell">
- <span class="level-indicator" :class="`level-${scope.row.level}`"></span>
- {{ scope.row.areaName }}
- </div>
- </template>
- </el-table-column>
- <el-table-column label="层级" prop="level" align="center" width="100">
- <template #default="scope">
- <el-tag :type="getLevelType(scope.row.level)" size="small">
- {{ scope.row.level === 1 ? '一级' : scope.row.level === 2 ? '二级' : '三级' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="设备统计" align="center" width="300">
- <template #default="scope">
- <div class="device-stats">
- <el-tooltip
- v-for="stat in scope.row.deviceStats"
- :key="stat.deviceType"
- :content="`${mapDeviceType(stat.deviceType)}: ${stat.count}台 (在线率: ${stat.onlineRate}%)`"
- placement="top"
- >
- <div class="device-stat-item">
- <el-icon :style="{color: getDeviceTypeColor(stat.deviceType)}">
- <component :is="getDeviceIcon(stat.deviceType)" />
- </el-icon>
- <span>{{ stat.count }}</span>
- </div>
- </el-tooltip>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="设备总数" align="center" width="100" sortable>
- <template #default="scope">
- <el-badge :value="getTotalDeviceCount(scope.row.deviceStats)" class="device-count-badge">
- <el-icon><Monitor /></el-icon>
- </el-badge>
- </template>
- </el-table-column>
- <el-table-column label="平均在线率" align="center" width="120" sortable>
- <template #default="scope">
- <div class="online-rate">
- <el-progress
- :percentage="getAverageOnlineRate(scope.row.deviceStats)"
- :width="60"
- type="circle"
- :color="getOnlineRateColor(getAverageOnlineRate(scope.row.deviceStats))"
- >
- <template #default="{percentage}">
- <span class="percentage-text">{{ percentage.toFixed(0) }}%</span>
- </template>
- </el-progress>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="健康指数" align="center" width="160" sortable>
- <template #default="scope">
- <div class="health-index">
- <span class="health-score">{{ parseKeyMetrics(scope.row.keyMetrics).healthIndex }}</span>
- <el-progress
- :percentage="Number(parseKeyMetrics(scope.row.keyMetrics).healthIndex)"
- :stroke-width="6"
- :color="getHealthColor(Number(parseKeyMetrics(scope.row.keyMetrics).healthIndex))"
- />
- </div>
- </template>
- </el-table-column>
- <el-table-column label="操作" align="center" width="150" fixed="right">
- <template #default="scope">
- <el-button type="primary" size="small" link @click.stop="handleViewDetail(scope.row)">
- 详情
- </el-button>
- <el-button type="warning" size="small" link @click.stop="handleViewChart(scope.row)">
- 图表
- </el-button>
- <el-button type="success" size="small" link @click.stop="handleViewMap(scope.row)">
- 地图
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- <!-- 分页组件 -->
- <el-pagination
- v-show="total > 0"
- v-model:current-page="queryParams.pageNum"
- v-model:page-size="queryParams.pageSize"
- :page-sizes="[10, 20, 50, 100]"
- :total="total"
- layout="total, sizes, prev, pager, next, jumper"
- @size-change="handleSizeChange"
- @current-change="handlePageChange"
- class="pagination"
- />
- </el-card>
- <!-- 详情弹窗 -->
- <el-dialog v-model="detailDialogOpen" title="区域详情" width="1200px" top="5vh">
- <el-descriptions :column="3" border>
- <el-descriptions-item label="区域ID">{{ detailArea.areaId }}</el-descriptions-item>
- <el-descriptions-item label="区域名称">{{ detailArea.areaName }}</el-descriptions-item>
- <el-descriptions-item label="父区域">
- <el-tag v-if="detailArea.parentAreaId" type="info">{{ detailArea.parentAreaId }}</el-tag>
- <span v-else>无</span>
- </el-descriptions-item>
- <el-descriptions-item label="层级">
- <el-tag :type="getLevelType(detailArea.level)">
- {{ detailArea.level === 1 ? '一级区域' : detailArea.level === 2 ? '二级区域' : '三级区域' }}
- </el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="平均能耗" :span="1">
- <el-statistic :value="parseKeyMetrics(detailArea.keyMetrics).averageEnergy" suffix="kWh">
- <template #prefix>
- <el-icon><Lightning /></el-icon>
- </template>
- </el-statistic>
- </el-descriptions-item>
- <el-descriptions-item label="最大负载" :span="1">
- <el-statistic :value="parseKeyMetrics(detailArea.keyMetrics).maxLoad" suffix="W">
- <template #prefix>
- <el-icon><Connection /></el-icon>
- </template>
- </el-statistic>
- </el-descriptions-item>
- </el-descriptions>
- <!-- 设备统计图表 -->
- <el-row :gutter="20" class="mt-4">
- <el-col :span="12">
- <el-card>
- <template #header>
- <span>设备类型分布</span>
- </template>
- <div ref="pieChartRef" style="height: 300px;"></div>
- </el-card>
- </el-col>
- <el-col :span="12">
- <el-card>
- <template #header>
- <span>设备在线率趋势</span>
- </template>
- <div ref="lineChartRef" style="height: 300px;"></div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 空间分布热力图 -->
- <el-card class="mt-4">
- <template #header>
- <span>设备位置热力图</span>
- </template>
- <div class="heatmap-container" ref="heatmapContainer">
- <div
- v-for="point in heatmapPoints"
- :key="point.id"
- class="heatmap-point"
- :style="{
- left: point.x + 'px',
- top: point.y + 'px',
- width: point.size + 'px',
- height: point.size + 'px',
- background: `radial-gradient(circle, rgba(255, 87, 34, ${point.opacity}) 0%, transparent 70%)`
- }"
- @click="handlePointClick(point)"
- >
- <el-tooltip :content="`设备密度: ${point.density}`" placement="top">
- <div class="point-inner"></div>
- </el-tooltip>
- </div>
- </div>
- </el-card>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="detailDialogOpen = false">关闭</el-button>
- <el-button type="primary" @click="handlePrintDetail">打印</el-button>
- </span>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { ref, reactive, computed, onMounted, nextTick } from 'vue';
- import { ElMessage, ElMessageBox } from 'element-plus';
- import {
- Search,
- Refresh,
- Download,
- Location,
- Monitor,
- CircleCheck,
- TrendCharts,
- Lightning,
- Connection,
- Cpu,
- Camera,
- Microphone,
- VideoCamera,
- DataAnalysis
- } from '@element-plus/icons-vue';
- import * as echarts from 'echarts';
- import { getBuildingEquipmentMonitoringAreaList, getAreaStats } from '@/api/buildingEquipmentMonitoring/buildingEquipmentMonitoring';
- // 查询参数
- const queryParams = reactive({
- areaName: '',
- level: '',
- deviceType: '',
- status: '',
- pageNum: 1,
- pageSize: 10,
- });
- // 数据状态
- const areaList = ref([]);
- const total = ref(0);
- const isLoading = ref(false);
- const detailDialogOpen = ref(false);
- const detailArea = ref({});
- // 统计数据
- const stats = reactive({
- totalAreas: 0,
- totalDevices: 0,
- onlineDevices: 0,
- onlineRate: 0,
- avgHealthIndex: 0
- });
- // 图表实例
- const pieChartRef = ref(null);
- const lineChartRef = ref(null);
- let pieChart = null;
- let lineChart = null;
- // 热力图数据
- const heatmapPoints = ref([]);
- const heatmapContainer = ref(null);
- // 获取层级标签类型
- const getLevelType = (level) => {
- const types = ['success', 'warning', 'danger'];
- return types[level - 1] || 'info';
- };
- // 获取设备图标
- const getDeviceIcon = (type) => {
- const icons = {
- 'CONTROLLER': Cpu,
- 'SENSOR': DataAnalysis,
- 'ACTUATOR': Connection,
- 'BROADCASTER': Microphone,
- 'MONITOR': Camera
- };
- return icons[type] || Monitor;
- };
- // 设备类型映射
- const mapDeviceType = (type) => {
- const typeMap = {
- 'CONTROLLER': '控制器',
- 'SENSOR': '传感器',
- 'ACTUATOR': '执行器',
- 'BROADCASTER': '广播设备',
- 'MONITOR': '监控设备',
- };
- return typeMap[type] || type;
- };
- // 设备类型颜色
- const getDeviceTypeColor = (type) => {
- const colorMap = {
- 'CONTROLLER': '#409eff',
- 'SENSOR': '#67c23a',
- 'ACTUATOR': '#f56c6c',
- 'BROADCASTER': '#e6a23c',
- 'MONITOR': '#909399',
- };
- return colorMap[type] || '#409eff';
- };
- // 计算设备总数
- const getTotalDeviceCount = (stats) => {
- return stats.reduce((sum, stat) => sum + stat.count, 0);
- };
- // 计算平均在线率
- const getAverageOnlineRate = (stats) => {
- if (stats.length === 0) return 0;
- const totalDevices = stats.reduce((sum, stat) => sum + stat.count, 0);
- const onlineDevices = stats.reduce((sum, stat) => sum + (stat.count * stat.onlineRate / 100), 0);
- return totalDevices > 0 ? (onlineDevices / totalDevices) * 100 : 0;
- };
- // 获取在线率颜色
- const getOnlineRateColor = (rate) => {
- if (rate >= 90) return '#67c23a';
- if (rate >= 70) return '#e6a23c';
- return '#f56c6c';
- };
- // 解析关键指标
- const parseKeyMetrics = (keyMetricsStr) => {
- if (!keyMetricsStr) return { averageEnergy: 0, maxLoad: 0, healthIndex: 0 };
- const [averageEnergy = 0, maxLoad = 0, healthIndex = 0] = keyMetricsStr.split(',').map(Number);
- return {
- averageEnergy: averageEnergy.toFixed(2),
- maxLoad: maxLoad.toFixed(0),
- healthIndex: healthIndex.toFixed(0),
- };
- };
- // 获取健康指数颜色
- const getHealthColor = (healthIndex) => {
- if (healthIndex >= 90) return '#67c23a';
- if (healthIndex >= 70) return '#409eff';
- if (healthIndex >= 50) return '#e6a23c';
- return '#f56c6c';
- };
- // 表格行样式
- const tableRowClassName = ({ row }) => {
- const healthIndex = Number(parseKeyMetrics(row.keyMetrics).healthIndex);
- if (healthIndex < 60) return 'warning-row';
- if (healthIndex < 40) return 'danger-row';
- return '';
- };
- // 获取统计数据
- const getStats = async () => {
- try {
- const response = await getAreaStats();
- Object.assign(stats, response.data);
- } catch (error) {
- console.error('获取统计数据失败:', error);
- }
- };
- // 获取数据
- const getList = async () => {
- isLoading.value = true;
- try {
- const response = await getBuildingEquipmentMonitoringAreaList(queryParams);
- areaList.value = response.data.list || [];
- total.value = response.data.total || 0;
- } catch (error) {
- ElMessage.error('获取区域数据失败');
- console.error(error);
- } finally {
- isLoading.value = false;
- }
- };
- // 搜索
- const handleSearch = () => {
- queryParams.pageNum = 1;
- getList();
- getStats();
- };
- // 重置
- const resetSearch = () => {
- Object.assign(queryParams, {
- areaName: '',
- level: '',
- deviceType: '',
- status: '',
- pageNum: 1,
- pageSize: 10,
- });
- getList();
- getStats();
- };
- // 导出数据
- const handleExport = async () => {
- try {
- isLoading.value = true;
- await exportAreaData(queryParams);
- ElMessage.success('导出成功');
- } catch (error) {
- ElMessage.error('导出失败');
- } finally {
- isLoading.value = false;
- }
- };
- // 分页
- const handlePageChange = (newPage) => {
- queryParams.pageNum = newPage;
- getList();
- };
- const handleSizeChange = (newSize) => {
- queryParams.pageSize = newSize;
- queryParams.pageNum = 1;
- getList();
- };
- // 查看详情
- const handleViewDetail = (row) => {
- detailArea.value = { ...row };
- detailDialogOpen.value = true;
- nextTick(() => {
- initCharts(row);
- parseHeatmapData(row.heatmapData);
- });
- };
- // 行点击事件
- const handleRowClick = (row) => {
- handleViewDetail(row);
- };
- // 查看图表
- const handleViewChart = (row) => {
- // 可以打开单独的图表弹窗
- ElMessage.info('图表功能开发中...');
- };
- // 查看地图
- const handleViewMap = (row) => {
- // 可以打开地图视图
- ElMessage.info('地图功能开发中...');
- };
- // 初始化图表
- const initCharts = (areaData) => {
- // 销毁旧实例
- if (pieChart) pieChart.dispose();
- if (lineChart) lineChart.dispose();
- // 饼图 - 设备类型分布
- if (pieChartRef.value) {
- pieChart = echarts.init(pieChartRef.value);
- const pieData = areaData.deviceStats.map(stat => ({
- name: mapDeviceType(stat.deviceType),
- value: stat.count,
- itemStyle: { color: getDeviceTypeColor(stat.deviceType) }
- }));
- const pieOption = {
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b}: {c} ({d}%)'
- },
- legend: {
- orient: 'vertical',
- left: 'left',
- data: pieData.map(item => item.name)
- },
- series: [
- {
- name: '设备类型',
- type: 'pie',
- radius: ['40%', '70%'],
- avoidLabelOverlap: false,
- itemStyle: {
- borderRadius: 10,
- borderColor: '#fff',
- borderWidth: 2
- },
- label: {
- show: false,
- position: 'center'
- },
- emphasis: {
- label: {
- show: true,
- fontSize: '20',
- fontWeight: 'bold'
- }
- },
- labelLine: {
- show: false
- },
- data: pieData
- }
- ]
- };
- pieChart.setOption(pieOption);
- }
- // 折线图 - 在线率趋势(模拟数据)
- if (lineChartRef.value) {
- lineChart = echarts.init(lineChartRef.value);
- const hours = Array.from({ length: 24 }, (_, i) => `${i}:00`);
- const onlineRates = hours.map(() => 80 + Math.random() * 20);
- const lineOption = {
- tooltip: {
- trigger: 'axis',
- formatter: '{b}<br/>在线率: {c}%'
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- boundaryGap: false,
- data: hours
- },
- yAxis: {
- type: 'value',
- min: 0,
- max: 100,
- axisLabel: {
- formatter: '{value}%'
- }
- },
- series: [
- {
- name: '在线率',
- type: 'line',
- smooth: true,
- symbol: 'none',
- lineStyle: {
- width: 3,
- color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
- { offset: 0, color: '#409eff' },
- { offset: 1, color: '#67c23a' }
- ])
- },
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
- { offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
- ])
- },
- data: onlineRates
- }
- ]
- };
- lineChart.setOption(lineOption);
- }
- };
- // 解析热力图数据
- const parseHeatmapData = (heatmapStr) => {
- try {
- if (!heatmapStr) {
- // 生成模拟数据
- heatmapPoints.value = Array.from({ length: 20 }, (_, i) => ({
- id: `point-${i}`,
- x: Math.random() * 500,
- y: Math.random() * 300,
- size: 20 + Math.random() * 40,
- opacity: 0.3 + Math.random() * 0.7,
- density: Math.floor(Math.random() * 10)
- }));
- return;
- }
- const { heatData } = JSON.parse(heatmapStr);
- heatmapPoints.value = heatData.map(([x, y, opacity], index) => ({
- id: `point-${index}`,
- x: x * 5,
- y: y * 3,
- size: 20 + opacity * 40,
- opacity: opacity,
- density: Math.floor(opacity * 10)
- }));
- } catch (error) {
- console.error('解析热力图数据失败:', error);
- heatmapPoints.value = [];
- }
- };
- // 点击热力图点
- const handlePointClick = (point) => {
- ElMessage.info(`设备密度: ${point.density}`);
- };
- // 打印详情
- const handlePrintDetail = () => {
- window.print();
- };
- // 窗口大小改变时重绘图表
- const handleResize = () => {
- if (pieChart) pieChart.resize();
- if (lineChart) lineChart.resize();
- };
- // 初始化
- onMounted(() => {
- getList();
- getStats();
- window.addEventListener('resize', handleResize);
- });
- // 清理
- onUnmounted(() => {
- window.removeEventListener('resize', handleResize);
- if (pieChart) pieChart.dispose();
- if (lineChart) lineChart.dispose();
- });
- </script>
- <style scoped>
- .area-management-system {
- padding: 20px;
- }
- .filter-card {
- margin-bottom: 20px;
- }
- .search-form {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
- .stats-row {
- margin-bottom: 20px;
- }
- .stats-card {
- text-align: center;
- transition: all 0.3s;
- }
- .stats-card:hover {
- transform: translateY(-5px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- }
- .online-card :deep(.el-statistic__number) {
- color: #67c23a;
- }
- .health-card :deep(.el-statistic__number) {
- color: #409eff;
- }
- .rate-text {
- font-size: 14px;
- color: #909399;
- margin-left: 5px;
- }
- .data-table {
- min-height: 500px;
- }
- .area-name-cell {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .level-indicator {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- flex-shrink: 0;
- }
- .level-1 { background-color: #67c23a; }
- .level-2 { background-color: #e6a23c; }
- .level-3 { background-color: #f56c6c; }
- .device-stats {
- display: flex;
- justify-content: center;
- gap: 15px;
- }
- .device-stat-item {
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 4px 8px;
- background: #f5f7fa;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.3s;
- }
- .device-stat-item:hover {
- background: #e6e8eb;
- transform: scale(1.05);
- }
- .device-count-badge {
- cursor: pointer;
- }
- .online-rate {
- display: flex;
- justify-content: center;
- }
- .percentage-text {
- font-size: 14px;
- font-weight: bold;
- }
- .health-index {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 5px;
- }
- .health-score {
- font-size: 18px;
- font-weight: bold;
- color: #303133;
- }
- :deep(.warning-row) {
- background-color: #fef0f0;
- }
- :deep(.danger-row) {
- background-color: #fde2e2;
- }
- .mt-4 {
- margin-top: 20px;
- }
- .heatmap-container {
- position: relative;
- height: 400px;
- background: linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%);
- border-radius: 8px;
- overflow: hidden;
- }
- .heatmap-point {
- position: absolute;
- border-radius: 50%;
- cursor: pointer;
- transition: all 0.3s;
- animation: pulse 2s infinite;
- }
- .heatmap-point:hover {
- transform: scale(1.2);
- z-index: 10;
- }
- .point-inner {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- }
- @keyframes pulse {
- 0% {
- transform: scale(1);
- }
- 50% {
- transform: scale(1.05);
- }
- 100% {
- transform: scale(1);
- }
- }
- .pagination {
- margin-top: 20px;
- display: flex;
- justify-content: flex-end;
- }
- .dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
- /* 响应式布局 */
- @media (max-width: 1200px) {
- .stats-row .el-col {
- margin-bottom: 10px;
- }
- .device-stats {
- flex-wrap: wrap;
- }
- }
- @media (max-width: 768px) {
- .search-form {
- display: block;
- }
- .search-form .el-form-item {
- margin-bottom: 10px;
- }
- .el-dialog {
- width: 95% !important;
- }
- .el-table {
- font-size: 12px;
- }
- .device-stat-item {
- padding: 2px 4px;
- font-size: 12px;
- }
- }
- /* 打印样式 */
- @media print {
- .filter-card,
- .stats-row,
- .pagination,
- .dialog-footer,
- .el-table__column:last-child {
- display: none !important;
- }
- .data-table {
- box-shadow: none !important;
- }
- .heatmap-point {
- animation: none !important;
- }
- }
- /* 自定义滚动条 */
- .el-table__body-wrapper::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- }
- .el-table__body-wrapper::-webkit-scrollbar-track {
- background: #f1f1f1;
- }
- .el-table__body-wrapper::-webkit-scrollbar-thumb {
- background: #c0c4cc;
- border-radius: 4px;
- }
- .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
- background: #909399;
- }
- /* 加载动画 */
- .el-loading-mask {
- background-color: rgba(255, 255, 255, 0.9);
- }
- /* 修复Badge数字显示不全的问题 */
- :deep(.el-table__row) {
- /* 确保行有足够的高度容纳Badge */
- min-height: 55px;
- }
- :deep(.el-table__cell) {
- /* 允许内容溢出单元格 */
- overflow: visible !important;
- }
- /* 设备总数单元格特殊处理 */
- .total-device-count {
- display: flex;
- justify-content: center;
- align-items: center;
- position: relative;
- /* 给Badge留出足够的空间 */
- padding-top: 10px;
- padding-bottom: 5px;
- }
- /* Badge容器调整 */
- .device-count-badge {
- display: inline-flex;
- position: relative;
- }
- /* 确保Badge内容不被裁剪 */
- .device-count-badge :deep(.el-badge__content) {
- /* 调整Badge位置,确保在单元格内完全显示 */
- top: -5px !important;
- right: 2px !important;
- }
- /* 针对设备总数这一列的特殊处理 */
- :deep(.el-table__body td:nth-child(5)) {
- /* 第5列是设备总数列,根据实际情况调整序号 */
- overflow: visible !important;
- position: relative;
- }
- :deep(.el-table__body td:nth-child(5) .cell) {
- overflow: visible !important;
- /* 增加上内边距,为Badge留出空间 */
- /*padding-top: 2px !important;*/
- }
- /* 如果上面的方案还不够,可以尝试这个更激进的方案 */
- :deep(.el-table) {
- /* 允许表格内容溢出 */
- overflow: visible !important;
- }
- :deep(.el-table__body-wrapper) {
- overflow-x: auto !important;
- overflow-y: visible !important;
- }
- :deep(.el-table__body) {
- overflow: visible !important;
- }
- </style>
|