Files
nonye/platform/src/pages/ReportsPage.vue
2025-07-17 23:13:04 +08:00

1390 lines
39 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import Wendu2 from "../components2/wendu2.vue";
import wendu3 from "../components2/wendu3.vue";
import Turang2 from "../components2/turang2.vue";
import Shidu2 from "../components2/shidu2.vue";
import Shidu3 from "../components2/shidu3.vue";
import Ph from "../components2/ph.vue";
import Ph3 from "../components2/ph3.vue";
import Guang2 from "../components2/guang2.vue";
import finding2 from "../components2/finding2.vue";
import Askai from "../components1/askai.vue";
import { ref, onMounted, nextTick } from 'vue';
import { ElMessageBox, ElNotification, ElDialog, ElMessage } from 'element-plus';
import axios from 'axios';
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
const drawerVisible = ref(false);
const drawerVisibleHumidity = ref(false);
const drawerVisiblePh = ref(false);
const progressValue = ref(85);
const isExporting = ref(false);
// PDF preview related
const pdfModalVisible = ref(false);
const pdfModalContent = ref<HTMLElement | null>(null);
// Report export related
const exportReportDialogVisible = ref(false);
const isReportLoading = ref(false);
const reportErrorMessage = ref('');
const isDownloadReady = ref(false);
// Data related
const currentTemperature = ref('27.5');
const isTemperatureLoading = ref(true);
const currentPh = ref('6.5');
const currentHumidity = ref('34%');
const currentLightIntensity = ref('0');
const maintenanceSuggestions = ref<{
deviceName: string;
warningType: string;
warningValue?: number;
urgent: boolean;
faultDescription?: string;
timestamp?: string;
}[]>([]);
const isMaintenanceLoading = ref(false);
// 添加标志跟踪关键温度警告是否已显示
const hasCriticalTemperatureWarning = ref(false);
// Simulated abnormal status
const isTemperatureAbnormal = ref(false);
const isHumidityAbnormal = ref(false);
const hasCriticalHumidityWarning = ref(false);
// Enhanced maintenance suggestions with faster response
const fetchMaintenanceSuggestions = () => {
// 检查是否已经显示关键温度警告,如果是则跳过更新
if (hasCriticalTemperatureWarning.value) {
return;
}
isMaintenanceLoading.value = true;
// Simulate faster response (500ms instead of 800ms)
setTimeout(() => {
const suggestions = [];
// Default normal status
if (!isTemperatureAbnormal.value && !isHumidityAbnormal.value) {
suggestions.push({
deviceName: '所有设备',
warningType: 'normal',
urgent: false,
faultDescription: '设备运行正常',
timestamp: new Date().toLocaleString()
});
}
// Temperature alerts - 保持不变
if (isTemperatureAbnormal.value) {
}
//
maintenanceSuggestions.value = suggestions;
isMaintenanceLoading.value = false;
}, 500); // Reduced from 800ms to 500ms for faster response
};
const handleWarning = (value) => {
if(maintenanceSuggestions.value.length >= 2) return
if(value === 1) {
maintenanceSuggestions.value = []
maintenanceSuggestions.value.push({
deviceName: '温度传感器',
warningType: 'temperature_high',
warningValue: 32,
urgent: true,
faultDescription: '6月8日检测到温度异常高(38°C)',
timestamp: new Date().toLocaleString()
});
}
if(value === 2) {
maintenanceSuggestions.value.push({
deviceName: '湿度传感器',
warningType: 'temperature_high',
warningValue: 38,
urgent: true,
faultDescription: '6月11日检测到湿度异常高(95°C)',
timestamp: '2025-06-08 14:30'
});
}
hasCriticalTemperatureWarning.value = true;
}
// 计算温州地区当前时间的光照强度(使用高斯曲线模拟)
const calculateWenzhouLightIntensity = () => {
const now = new Date();
const hour = now.getHours() + now.getMinutes() / 60; // 计算小时数(包含分钟)
let intensity = 0;
// 模拟温州晴天的真实光照强度单位lux
if (hour >= 5 && hour < 20) {
// 日出到日落之间有光照
const normalizedHour = (hour - 5) / 15; // 映射到 [0, 1]
const peakTime = 0.5; // 正午12:30左右达到顶峰
const curve = Math.exp(-Math.pow((normalizedHour - peakTime) * 3, 2)); // 高斯曲线
intensity = 100000 * curve;
}
// 添加±5%随机波动
return Math.max(0, Math.min(100000, intensity * (1 + 0.05 * (Math.random() * 2 - 1))));
};
// Simulate getting temperature data
const fetchDeviceATemperature = async () => {
try {
isTemperatureLoading.value = true;
currentTemperature.value = isTemperatureAbnormal.value ? '32.5°C' : '27.5°C';
} catch (error) {
console.error('获取温度数据失败:', error);
currentTemperature.value = '获取失败';
} finally {
isTemperatureLoading.value = false;
}
};
// Simulate getting humidity data - 修改为66%-75%随机
const fetchHumidityData = async () => {
try {
// 生成66到75之间的随机湿度值
const randomHumidity = Math.floor(66 + Math.random() * 9); // 66 + (0-8.999...) => 66-74.999... => 66-74
currentHumidity.value = `${randomHumidity}%`;
} catch (error) {
console.error('获取湿度数据失败:', error);
currentHumidity.value = '获取失败';
}
};
// Simulate getting pH data - 修改为6.6-7.3随机
const fetchPhData = async () => {
try {
// 生成6.6到7.3之间的随机PH值
const randomPh = (6.6 + Math.random() * 0.7).toFixed(1); // 6.6 + (0-0.699...) => 6.6-7.299... => 6.6-7.3
currentPh.value = randomPh;
} catch (error) {
console.error('获取pH数据失败', error);
currentPh.value = '获取失败';
}
};
const handleClose = (done: () => void) => {
ElMessageBox.confirm('确定要关闭此分析页面吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
})
.then(() => done())
.catch(() => {});
};
const exportToPDF = async () => {
try {
isExporting.value = true;
ElNotification({
title: '正在准备报表',
message: '正在生成报表预览...',
type: 'info',
duration: 2000
});
await Promise.all([
fetchDeviceATemperature(),
fetchPhData(),
fetchHumidityData()
]);
pdfModalVisible.value = true;
await nextTick();
} catch (error) {
console.error('生成预览失败:', error);
ElNotification({
title: '错误',
message: '生成预览时出错: ' + (error as Error).message,
type: 'error',
duration: 5000
});
} finally {
isExporting.value = false;
}
};
const downloadPDF = async () => {
try {
isReportLoading.value = true;
isDownloadReady.value = false;
const element = pdfModalContent.value;
if (!element) throw new Error('找不到要导出的内容');
ElMessage({
message: '正在生成PDF...',
type: 'loading',
duration: 0,
center: true
});
const canvas = await html2canvas(element, {
scale: 3,
useCORS: true,
allowTaint: true,
backgroundColor: '#ffffff',
logging: false
});
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm'
});
const imgData = canvas.toDataURL('image/jpeg', 0.95);
const imgWidth = pdf.internal.pageSize.getWidth() - 20;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
pdf.addImage(imgData, 'JPEG', 10, 10, imgWidth, imgHeight);
const fileName = `农业环境分析报告_${new Date().toLocaleDateString().replace(/\//g, '-')}.pdf`;
pdf.save(fileName);
isReportLoading.value = false;
isDownloadReady.value = true;
ElMessage.closeAll();
ElNotification({
title: '导出成功',
message: `报表已成功导出为 ${fileName}`,
type: 'success',
duration: 5000
});
pdfModalVisible.value = false;
} catch (error) {
isReportLoading.value = false;
isDownloadReady.value = false;
ElMessage.closeAll();
console.error('导出PDF失败:', error);
ElNotification({
title: '导出失败',
message: '生成PDF时出错: ' + (error as Error).message,
type: 'error',
duration: 5000
});
}
};
const suggestionText = (suggestion: {
deviceName: string;
warningType: string;
urgent: boolean;
faultDescription?: string;
}) => {
if (suggestion.faultDescription) {
const timeInfo = suggestion.timestamp ? `` : '';
return `${suggestion.deviceName}检测到${suggestion.faultDescription}`;
}
switch (suggestion.warningType) {
case 'temperature_high':
return `${suggestion.deviceName}温度持续异常,请立即检查散热系统`;
case 'humidity_high':
return `${suggestion.deviceName}湿度持续异常,请检查灌溉系统`;
case 'ph_abnormal':
return `${suggestion.deviceName}pH值持续异常请检查液体成分`;
default:
return `${suggestion.deviceName}运行正常`;
}
};
const handleExportReport = async () => {
try {
isReportLoading.value = true;
isDownloadReady.value = false;
reportErrorMessage.value = '';
await exportToPDF();
} catch (error: any) {
console.error('导出报表失败:', error);
reportErrorMessage.value = error.message || '生成报表时出错';
ElNotification({
title: '导出失败',
message: reportErrorMessage.value,
type: 'error',
duration: 5000
});
}
};
const handleDownloadReport = () => {
exportToPDF();
};
const closeExportDialog = () => {
exportReportDialogVisible.value = false;
};
const handlePdfClose = (done: () => void) => {
ElMessageBox.confirm('确定要关闭报表预览吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
done();
})
.catch(() => {});
};
// Simulate environment changes
const simulateEnvironmentChange = () => {
isTemperatureAbnormal.value = Math.random() > 0.7;
isHumidityAbnormal.value = Math.random() > 0.7;
// 计算温州地区当前光照强度,取整数
currentLightIntensity.value = calculateWenzhouLightIntensity().toFixed(0);
fetchDeviceATemperature();
fetchHumidityData();
fetchPhData();
fetchMaintenanceSuggestions();
};
onMounted(() => {
fetchDeviceATemperature();
fetchPhData();
fetchHumidityData();
// 初始化光照强度,取整数
currentLightIntensity.value = calculateWenzhouLightIntensity().toFixed(0);
// 每5秒更新一次环境数据
setInterval(simulateEnvironmentChange, 5000);
});
</script>
<template>
<!-- 模板部分保持不变 -->
<div id="all">
<!-- Fixed top data cards -->
<div class="top-container">
<div class="box">
<div class="data-container">
<div class="number">{{ currentTemperature }}</div>
<svg t="1747892350883" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6895" width="200" height="200"><path d="M752.65536 220.16c-59.38176 0-107.52 48.13824-107.52 107.52s48.13824 107.52 107.52 107.52 107.52-48.13824 107.52-107.52-48.13824-107.52-107.52-107.52z m0 61.44a46.08 46.08 0 1 1 0 92.16 46.08 46.08 0 0 1 0-92.16zM396.83584 51.22048C313.17504 52.57728 245.77536 120.81664 245.77536 204.8v365.85472l-1.65888 1.50016c-67.5072 61.5168-92.68736 157.81888-62.59712 245.22752 32.01024 92.98944 119.51104 155.40736 217.856 155.40736 98.33984 0 185.84064-62.41792 217.856-155.40736l0.93696-2.7904c28.60544-86.66112 3.2512-181.57568-63.5392-242.43712l-1.65376-1.49504V204.8c0-84.83328-68.77184-153.6-153.6-153.6l-2.53952 0.02048zM399.37536 112.64c50.8928 0 92.16 41.26208 92.16 92.16v380.2624l0.01536 1.15712c0.36864 10.73664 5.77024 19.14368 13.312 24.2176l0.1536 0.09216 1.97632 1.60768A168.96 168.96 0 1 1 239.616 797.38368l-0.7424-2.2016a168.96 168.96 0 0 1 52.85888-183.02976l1.9968-1.62304 0.1536-0.09728c7.81312-5.25312 13.33248-14.08 13.33248-25.37472V204.8c0-50.89792 41.26208-92.16 92.16-92.16z" fill="#2ECC71" p-id="6896"></path><path d="M399.37536 634.88c-59.38176 0-107.52 48.13824-107.52 107.52s48.13824 107.52 107.52 107.52 107.52-48.13824 107.52-107.52-48.13824-107.52-107.52-107.52z m0 61.44a46.08 46.08 0 1 1 0 92.16 46.08 46.08 0 0 1 0-92.16z" fill="#2ECC71" p-id="6897"></path><path d="M399.37536 327.68a30.72 30.72 0 0 1 30.69952 29.568l0.02048 1.152v307.2a30.72 30.72 0 0 1-61.41952 1.152l-0.02048-1.152V358.4a30.72 30.72 0 0 1 30.72-30.72z" fill="#2ECC71" p-id="6898"></path></svg>
<div class="label1">平均温度</div>
</div>
</div>
<div class="box">
<div class="data-container">
<div class="number">{{ currentHumidity }}</div>
<svg t="1747892908314" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="37543" width="200" height="200"><path d="M424.3456 0h-0.068267a43.690667 43.690667 0 0 0-33.109333 15.223467C354.645333 58.026667 34.133333 439.159467 34.133333 638.293333 34.133333 851.012267 209.851733 1024 425.847467 1024c215.995733 0 391.7824-172.987733 391.7824-385.6384 0-199.338667-323.3792-580.471467-360.2432-623.274667A43.690667 43.690667 0 0 0 424.3456 0z m1.501867 938.666667c-168.209067 0-305.015467-134.690133-305.015467-300.305067 0-127.931733 195.857067-395.6736 303.650133-528.452267 108.7488 132.7104 306.449067 400.452267 306.449067 528.452267 0 165.614933-136.874667 300.305067-305.083733 300.305067zM873.198933 0s-115.575467 135.441067-115.575466 198.587733c0 63.146667 51.950933 114.2784 116.053333 114.2784 64.170667 0 116.189867-51.2 116.189867-114.2784C989.866667 135.441067 873.198933 0 873.198933 0z" fill="#f7c244" p-id="37544"></path><path d="M243.370667 434.312533C205.141333 494.045867 170.666667 520.055467 170.666667 600.405333c0 158.5152 127.044267 283.101867 259.003733 283.101867S684.714667 783.701333 684.714667 625.117867c0-25.463467 0-37.341867-12.0832-71.953067-1.2288-3.549867-17.681067-35.976533-18.8416-39.3216-6.826667-20.2752-52.155733-40.072533-68.130134-29.627733-16.315733 10.6496-35.498667 21.162667-57.344 25.531733-75.434667 15.086933-97.211733 2.048-150.869333-30.242133-50.312533-30.242133-109.704533-83.217067-134.075733-45.192534z" fill="#f7d37e" p-id="37545"></path></svg>
<div class="label1">平均湿度</div>
</div>
</div>
<div class="box">
<div class="data-container">
<div class="number">{{ currentPh }}</div>
<svg t="1747893022949" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="40565" width="200" height="200"><path d="M597.930667 364.987733l-0.802134-2.542933-0.853333-3.6352a34.133333 34.133333 0 0 1-0.392533-3.003733l-0.136534-3.0208V154.1632a34.133333 34.133333 0 0 1 68.181334-2.56l0.085333 2.56v188.893867l277.8624 425.130666 1.501867 2.525867c35.9936 66.986667-10.632533 148.4288-85.572267 151.074133l-3.754667 0.068267H169.949867c-75.861333 0-124.330667-80.3328-91.0848-147.780267l1.723733-3.362133 1.501867-2.5088L359.9872 343.04V153.0368a34.133333 34.133333 0 0 1 31.573333-34.048l2.56-0.085333a34.133333 34.133333 0 0 1 34.048 31.573333l0.085334 2.56v199.8336a34.133333 34.133333 0 0 1-0.6656 6.690133l-0.7168 3.003734a34.133333 34.133333 0 0 1-2.6624 6.690133l-1.655467 2.816L140.288 803.959467l-0.648533 1.365333c-9.728 21.6064 4.898133 46.4896 27.8528 48.1792l2.491733 0.085333h684.066133c24.3712 0 40.3968-25.873067 30.344534-48.247466l-0.682667-1.3824-282.2144-431.8208a34.133333 34.133333 0 0 1-3.549867-7.150934z" fill="#444444" p-id="40566"></path><path d="M716.8 102.4a34.133333 34.133333 0 0 1 2.56 68.164267L716.8 170.666667H307.2a34.133333 34.133333 0 0 1-2.56-68.181334L307.2 102.4h409.6z" fill="#444444" p-id="40567"></path><path d="M699.733333 699.733333v34.133334a34.133333 34.133333 0 0 1-34.133333 34.133333H358.4a34.133333 34.133333 0 0 1-34.133333-34.133333v-34.133334h375.466666z" fill="#00B386" p-id="40568"></path></svg>
<div class="label">平均PH值</div>
</div>
</div>
<div class="box">
<div class="data-container">
<div class="number">{{ currentLightIntensity }}</div>
<svg t="1747893107823" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="41680" width="200" height="200"><path d="M512 256c141.385 0 256 114.615 256 256S653.385 768 512 768 256 653.385 256 512s114.615-256 256-256z m0 64c-106.039 0-192 85.961-192 192s85.961 192 192 192 192-85.961 192-192-85.961-192-192-192z" fill="#d81e06" p-id="41681"></path><path d="M480 64m32 0l0 0q32 0 32 32l0 64q0 32-32 32l0 0q-32 0-32-32l0-64q0-32 32-32Z" fill="#d81e06" p-id="41682"></path><path d="M883.979626 260.287187m16 27.712813l0 0q16 27.712813-11.712813 43.712813l-55.425626 32q-27.712813 16-43.712813-11.712813l0 0q-16-27.712813 11.712813-43.712813l55.425626-32q27.712813-16 43.712813 11.712813Z" fill="#d81e06" p-id="41683"></path><path d="M915.979626 708.287187m-16 27.712813l0 0q-16 27.712813-43.712813 11.712813l-55.425626-32q-27.712813-16-11.712813-43.712813l0 0q16-27.712813 43.712813-11.712813l55.425626 32q27.712813 16 11.712813 43.712813Z" fill="#d81e06" p-id="41684"></path><path d="M544 960m-32 0l0 0q-32 0-32-32l0-64q0-32 32-32l0 0q32 0 32 32l0 64q0 32-32 32Z" fill="#d81e06" p-id="41685"></path><path d="M140.020374 763.712813m-16-27.712813l0 0q-16-27.712813 11.712813-43.712813l55.425626-32q27.712813-16 43.712813 11.712813l0 0q16 27.712813-11.712813 43.712813l-55.425626 32q-27.712813 16-43.712813-11.712813Z" fill="#d81e06" p-id="41686"></path><path d="M108.020374 315.712813m16-27.712813l0 0q16-27.712813 43.712813-11.712813l55.425626 32q27.712813 16 11.712813 43.712813l0 0q-16 27.712813-43.712813 11.712813l-55.425626-32q-27.712813-16-11.712813-43.712813Z" fill="#d81e06" p-id="41687"></path></svg>
<div class="label">光照强度</div>
</div>
</div>
</div>
<!-- Main content -->
<div class="body">
<div class="body-top-box">
<!-- Temperature content -->
<div class="body-top-temp">
<h3 class="clickable-area" @click="drawerVisible = true">设备温度日志表柱形图</h3>
<wendu2 @handleWarning="handleWarning(1)"/>
</div>
<!-- Humidity content -->
<div class="body-top-shidu">
<h3 class="clickable-area01" @click="drawerVisibleHumidity = true" >设备湿度日志图</h3>
<shidu2 @handleWarning="handleWarning(2)" class="uu"/>
</div>
</div>
<div class="body-middle-box">
<!-- pH content -->
<div class="body-mid-ph">
<h3 class="clickable-area" @click="drawerVisiblePh = true">PH值的散点分布图</h3>
<ph/>
</div>
<div class="body-mid-guang">
<guang2 class="gg1"/>
</div>
</div>
<div class="under-box">
<div class="body-under">
<turang2/>
</div>
<div class="body-under-right">
<finding2/>
</div>
</div>
<!-- Smart analysis section -->
<div class="smart-analysis-container">
<div class="analysis-header">
<h2 class="analysis-title">智能分析</h2>
<button class="baobiao-button" @click="handleExportReport">
导出报表
</button>
</div>
<div class="confidence-section">
<h3 class="section-title">故障预测置信度</h3>
<div class="custom-progress-bar">
<div class="progress-background"></div>
<div class="progress-fill" :style="{ width: progressValue + '%' }"></div>
</div>
<div class="confidence-value">85%</div>
</div>
<div class="maintenance-section">
<h3 class="section-title">异常检测</h3>
<ul class="maintenance-list" v-if="!isMaintenanceLoading && maintenanceSuggestions.length > 0">
<li
v-for="(suggestion, index) in maintenanceSuggestions"
:key="index"
:class="['maintenance-item', { 'urgent': suggestion.urgent }]"
>
<span class="item-icon" :class="suggestion.urgent ? 'urgent-icon' : 'normal-icon'">
{{ suggestion.urgent ? '!' : '✓' }}
</span>
<span class="item-text">{{ suggestionText(suggestion) }}</span>
</li>
</ul>
<div v-else-if="isMaintenanceLoading" class="loading-container">
<span class="item-icon"></span>
<span class="item-text">正在加载维护建议...</span>
</div>
</div>
<div>
<askai />
</div>
</div>
</div>
<!-- Drawer components -->
<el-drawer
v-model="drawerVisible"
direction="rtl"
size="750px"
:before-close="handleClose"
:with-header="false"
>
<div class="drawer-content">
<wendu3/>
</div>
</el-drawer>
<el-drawer
v-model="drawerVisibleHumidity"
direction="rtl"
size="750px"
:before-close="handleClose"
:with-header="false"
>
<div class="drawer-content">
<shidu3/>
</div>
</el-drawer>
<el-drawer
v-model="drawerVisiblePh"
size="750px"
direction="rtl"
:before-close="handleClose"
:with-header="false"
>
<ph3/>
</el-drawer>
<!-- PDF preview modal -->
<el-dialog
v-model="pdfModalVisible"
width="85%"
center
:before-close="(done) => { pdfModalVisible.value = false; done(); }"
>
<div ref="pdfModalContent" class="pdf-preview-content">
<!-- PDF preview content will be generated by html2canvas -->
<div class="preview-header">
<h3 style="padding-top: 10px; letter-spacing: 6px;">数据报表</h3>
<p>生成时间: {{ new Date().toLocaleString() }}</p>
</div>
<div class="preview-body">
<div class="preview-section">
<div class="data-cards">
<div class="data-card">
<div class="card-title">平均温度</div>
<div class="card-value">{{ currentTemperature }}</div>
</div>
<div class="data-card">
<div class="card-title">平均湿度</div>
<div class="card-value">{{ currentHumidity }}</div>
</div>
<div class="data-card">
<div class="card-title">平均PH值</div>
<div class="card-value">{{ currentPh }}</div>
</div>
<div class="data-card">
<div class="card-title">光照强度</div>
<div class="card-value">{{ currentLightIntensity }}</div>
</div>
</div>
<div class="shuju">
<div class="header">
<div class="sj-top1">
<div class="wendu">
<h1>设备温度日志表柱形图</h1>
<wendu2></wendu2>
</div>
</div>
<div class="sj-top2">
<div class="shidu">
<div>
<h1>设备湿度日志图</h1>
</div>
<shidu2 style="width: 650px"></shidu2>
</div>
</div>
</div>
<div class="foot">
<div class="sj-foot1">
<div class="ph">
<h1>PH散点分布图</h1>
<ph style="width: 600px;.device-select{position: relative;
top: 10px}"></ph>
</div>
</div>
<div class="sj-foot2">
<div class="guang">
<guang2></guang2>
</div>
</div>
</div>
<div class="under">
<div class="sj-un1">
<div class="turang">
<turang2></turang2>
</div>
</div>
<div class="sj-un2">
<div class="finding">
<finding2></finding2>
</div>
</div>
</div>
</div>
</div>
<!-- PDF预览中的智能分析区域 -->
<div class="smart-analysis-container1">
<div class="analysis-header">
<h2 class="analysis-title">智能分析</h2>
</div>
<div class="confidence-section">
<h3 class="section-title" style="padding-top: 10px;">故障预测置信度</h3>
<div class="custom-progress-bar">
<div class="progress-background"></div>
<div class="progress-fill" :style="{ width: progressValue + '%' }"></div>
</div>
<div class="confidence-value">85%</div>
</div>
<div class="maintenance-section" style="position: relative;top: -10px">
<h3 class="section-title">建议维护措施</h3>
<ul class="maintenance-list" v-if="!isMaintenanceLoading && maintenanceSuggestions.length > 0">
<li
v-for="(suggestion, index) in maintenanceSuggestions"
:key="index"
:class="['maintenance-item', { 'urgent': suggestion.urgent }]"
>
<span class="item-icon" :class="suggestion.urgent ? 'urgent-icon' : 'normal-icon'">
{{ suggestion.urgent ? '!' : '✓' }}
</span>
<span class="item-text">{{ suggestionText(suggestion) }}</span>
</li>
</ul>
<ul class="maintenance-list" v-else-if="!isMaintenanceLoading">
<li class="maintenance-item">
<span class="item-icon normal-icon">✓</span>
<span class="item-text">当前没有需要维护的设备</span>
</li>
</ul>
<div v-else class="loading-container">
<span class="item-icon">⌛</span>
<span class="item-text">正在加载维护建议...</span>
</div>
<div v-if="maintenanceError" class="error-container">
<span class="item-icon error-icon">❌</span>
<span class="item-text">{{ maintenanceError }}</span>
</div>
</div>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="pdfModalVisible = false">取消</el-button>
<el-button type="primary" @click="downloadPDF">下载报表</el-button>
</div>
</template>
</el-dialog>
<!-- Export report dialog -->
<el-dialog
v-model="exportReportDialogVisible"
width="300px"
:modal="false"
:close-on-click-modal="false"
:show-close="false"
custom-class="export-report-dialog"
>
<div class="blank-dialog-content">
<div v-if="isReportLoading" class="loading-spinner">
<i class="el-icon-loading"></i>
<span>报表生成中...</span>
</div>
<div v-else-if="!isDownloadReady" class="error-content" v-if="reportErrorMessage">
<i class="el-icon-error error-icon"></i>
<span>{{ reportErrorMessage }}</span>
</div>
<div v-else class="success-content">
<div class="success-icon">
<i class="el-icon-success"></i>
</div>
<p>报表已生成</p>
<button @click="handleDownloadReport" class="download-btn">下载报表</button>
<button @click="closeExportDialog" class="close-btn">取消</button>
</div>
</div>
</el-dialog>
</div>
</template>
<style scoped>
/* 样式部分保持不变 */
.top-container {
display: flex;
padding-top: 10px;
justify-content: center;
gap: 40px;
background: #F5F7FA;
width: calc(100% - 80px);
z-index: 1000;
transition: all 0.3s ease;
margin: 10px 0 -10px 40px;
}
/* 主内容区域调整 */
.body {
padding: 20px 5px 20px 30px;
overflow-y: auto;
background: #f5f5f5;
margin-top: 0;
width: calc(100% - 0px);
box-sizing: border-box;
}
.box {
flex: 1;
max-width: 310px;
background-color: #FFFFFF;
padding: 15px;
text-align: center;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1);
border-radius: 15px;
height: 120px;
}
.data-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.gg1{
}
.icon{
position: relative;
left: 118px;
width: 50px;
height: 200px;
}
.number {
font-size: 30px;
font-weight: bold;
text-align: center;
}
.label {
font-size: 14px;
font-weight: 500;
letter-spacing: 0.5px;
color: #626363;
text-align: center;
line-height: 25px;
}
.label1 {
font-size: 14px;
font-weight: 500;
letter-spacing: 0.5px;
color: #626363;
line-height: 25px;
position: relative;
right:5px;
}
/* 图表区域调整 */
.body-top-box{
display: flex;
width: calc(100% - 30px); /* 减去右边间距 */
height: 420px;
gap: 26px;
margin-top: 20px;
}
.body-top-temp, .body-top-shidu,
.body-mid-ph, .body-mid-guang,
.body-under, .body-under-right {
padding: 20px;
border-radius: 8px;
border: none;
background: #FFFFFF;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
box-sizing: border-box; /* 确保padding不影响宽度 */
}
.body-top-temp, .body-top-shidu {
flex: 1;
}
.body-top-temp h3{
position: relative;
top: -13px;
right: 180px;
}
.body-top-shidu h3{
position: relative;
top: -13px;
right: 190px;
}
.body-middle-box{
display: flex;
width: calc(100% - 30px); /* 减去右边间距 */
height: 420px;
gap: 26px;
margin-top: 30px; /* 增加顶部间距使布局更规整 */
}
.body-mid-ph, .body-mid-guang {
flex: 1;
}
.body-mid-ph h3 {
margin-top: -13.2px;
position: relative;
z-index: 10;
top: 20px;
right: 202px;
}
.under-box {
display: flex;
width: calc(100% - 30px); /* 减去右边间距 */
gap: 26px;
margin-top: 30px; /* 增加顶部间距使布局更规整 */
}
/* 土壤营养分布图与关键发现的宽是42 */
.body-under {
flex: 2; /* 4/(4+2) = 2/3 */
height: 370px;
}
.body-under-right {
flex: 1; /* 2/(4+2) = 1/3 */
height: 370px;
}
/* Smart analysis */
.smart-analysis-container {
font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
width: calc(100% - 30px); /* 减去右边间距 */
padding: 20px;
background-color: #FFFFFF;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-top: 30px; /* 增加顶部间距使布局更规整 */
margin-bottom: 30px;
box-sizing: border-box;
}
.analysis-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eaeaea;
width: 100%;
margin-bottom: 20px;
padding-bottom: 10px;
}
.analysis-title {
color: #333;
font-size: 25px;
}
.uu{
margin-top: 20px;
}
.section-title {
color: #555;
font-size: 1.1rem;
margin-bottom: 12px;
}
.custom-progress-bar {
flex-grow: 1;
height: 6px;
background-color: #f0f0f0;
border-radius: 3px;
overflow: hidden;
margin-right: 20px;
}
.progress-fill {
width: 85%;
height: 100%;
background-color: #4285f4;
transition: width 0.3s ease;
}
.confidence-section {
margin-top: -6px;
margin-bottom: 10px;
}
.confidence-value {
color: #5e99fa;
font-size: 18px;
font-weight: 800;
min-width: 40px;
text-align: right;
position: relative;
top: -35px;
right: 15px;
}
/* 建议维护措施的样式保持不变 */
.maintenance-list {
list-style: none;
padding: 0;
margin: 0;
margin-right: 16px;
}
.maintenance-item {
display: flex;
align-items: center;
padding: 12px 15px;
margin-bottom: 10px;
background-color: #fff;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.maintenance-item.urgent {
border-left: 4px solid #ff6b6b;
}
.item-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 12px;
font-weight: bold;
}
.normal-icon {
background-color: #4CAF50;
color: white;
}
.urgent-icon {
background-color: #ff6b6b;
color: white;
}
.item-text {
flex: 1;
color: #333;
}
.loading-container {
display: flex;
align-items: center;
padding: 12px 15px;
margin-bottom: 10px;
background-color: #fff;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.error-container {
display: flex;
align-items: center;
padding: 12px 15px;
margin-bottom: 10px;
background-color: #fff;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border-left: 4px solid #f56c6c;
}
.clickable-area {
border-radius: 4px;
cursor: pointer;
margin: 20px;
margin-left: -40px;
text-align: center;
transition: background-color 0.3s;
z-index: 100;
}
.clickable-area:hover {
color: #838282;
}
.clickable-area01 {
border-radius: 4px;
cursor: pointer;
text-align: left;
margin-top: 20px;
transition: background-color 0.3s;
position: relative;
left: 17px;
}
.clickable-area01:hover {
color: #838282;
}
.drawer-content {
padding: 20px;
position: relative;
top: -20px;
}
.baobiao-button {
background-color: #3498db;
border-color: #3498db;
padding: 8px 16px;
border-radius: 4px;
font-weight: bold;
transition: all 0.3s ease;
position: relative;
top: -7px;
border: none;
color: white;
letter-spacing: 2px;
}
.baobiao-button:hover {
background-color: #2980b9;
border-color: #2980b9;
transform: translateY(-2px);
box-shadow: 0 4px 2px rgba(0, 0, 0, 0.1);
}
/* PDF preview styles */
.pdf-preview-content {
width: 100%;
padding: 20px;
box-sizing: border-box;
}
.preview-header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
margin-top: 30px;
}
.preview-header h3 {
font-size: 40px;
margin-bottom: -20px;
color: #333;
position: relative;
top: -30px;
}
.preview-header p {
font-size: 15px;
color: #666;
}
.preview-section {
margin-bottom: 30px;
background-color: #FFFFFF;
border-radius: 6px;
}
.preview-section h4 {
font-size: 18px;
color: #555;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.data-cards {
display: flex; /* Enable flex layout */
justify-content: space-evenly; /* Evenly distribute cards */
align-items: stretch; /* Stretch card heights */
margin-bottom: 20px;
gap: 15px; /* Card spacing */
overflow-x: auto; /* Show scrollbar when needed */
padding-bottom: 5px; /* Space for scrollbar */
}
.data-card {
flex: 1; /* Equal space distribution */
min-width: 0; /* Allow shrinking */
background-color: #fff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 15px;
border-radius: 6px;
text-align: center;
}
/* Small screen optimization */
@media (max-width: 1200px) {
.data-cards {
gap: 10px;
}
}
.card-title {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.card-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.preview-suggestions {
list-style: none;
padding: 0;
}
.preview-suggestion {
padding: 10px 0;
border-bottom: 1px dashed #eee;
}
.preview-suggestion:last-child {
border-bottom: none;
}
.urgent-tag, .normal-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
margin-right: 10px;
font-weight: bold;
}
.urgent-tag {
background-color: #ff6b6b;
color: white;
}
.normal-tag {
background-color: #4CAF50;
color: white;
}
.suggestion-text {
font-size: 14px;
color: #333;
}
.no-suggestions {
padding: 15px 0;
color: #666;
font-size: 14px;
}
/* Export report dialog styles */
.export-report-dialog .blank-dialog-content {
padding: 30px 20px;
text-align: center;
}
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
}
.loading-spinner i {
font-size: 24px;
color: #409EFF;
margin-bottom: 10px;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(360deg); }
to { transform: rotate(360deg); }
}
.loading-spinner span {
color: #666;
font-size: 14px;
}
.error-content {
display: flex;
flex-direction: column;
align-items: center;
}
.error-icon {
font-size: 24px;
color: #f56c6c;
margin-bottom: 10px;
}
.success-content {
padding: 20px 0;
}
.success-icon {
font-size: 48px;
color: #67c23a;
margin-bottom: 20px;
}
.success-content p {
color: #666;
margin-bottom: 20px;
font-size: 14px;
}
.download-btn, .close-btn {
display: block;
width: 100%;
margin-bottom: 10px;
padding: 10px;
border-radius: 4px;
border: none;
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s;
}
.download-btn {
background-color: #409EFF;
color: white;
}
.download-btn:hover {
background-color: #3a8ee6;
}
.close-btn {
background-color: #909399;
color: white;
}
.close-btn:hover {
background-color: #606266;
}
/* Dialog footer styles */
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.header {
display: flex; /* Enable flex layout */
gap: 20px; /* Element spacing */
width: 100%; /* Full parent width */
height: 400px;
}
.sj-top1, .sj-top2 {
flex: 1; /* Equal parent width */
background-color: #fff; /* Child background */
padding: 15px; /* Padding */
border-radius: 6px; /* Rounded corners */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
min-width: 0; /* Allow shrinking */
box-sizing: border-box; /* Include padding in width */
}
.sj-top1 h1 {
position: relative;
top: -20px;
left: 10px;
font-size: 19px;
}
.sj-top2 h1{
position: relative;
top: -10px;
left: 10px;
font-size: 19px;
}
/* Remove positioning that might affect layout */
.wendu, .shidu {
position: static; /* Remove absolute positioning */
top: 0; /* Remove top offset */
right: 0; /* Remove right offset */
}
/* Ensure chart containers fit parent */
.wendu > div, .shidu > div {
width: 100%; /* Full parent width */
height: auto; /* Maintain proper height */
}
.wendu{
position: relative;
top: 20px;
}
.shidu{
position: relative;
top: 10px;
}
.foot {
display: flex; /* Enable flex layout */
justify-content: space-between; /* Space between elements */
gap: 20px; /* Element spacing */
margin-top: 20px;
height: 420px;
}
.shuju{
}
.sj-foot1, .sj-foot2 {
flex: 1; /* Equal parent width */
background-color: #fff; /* Child background */
padding: 15px; /* Padding */
border-radius: 6px; /* Rounded corners */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.sj-foot1 h1{
position: relative;
top: 10px;
left: 10px;
font-size: 19px;
}
.ph{
position: relative;
top: -15px;
}
.guang{
position: relative;
top: 10px;
}
.under {
display: flex; /* Enable flex layout */
gap: 20px; /* Element spacing */
margin-top: 20px;
height: 320px;
margin-bottom: 20px;
}
.sj-un1 {
width: 925px;
background-color: #fff; /* Child background */
padding: 15px; /* Padding */
border-radius: 6px; /* Rounded corners */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
height: 345px;
}
.sj-un2 {
width:500px;
background-color: #fff; /* Child background */
padding: 15px; /* Padding */
border-radius: 6px; /* Rounded corners */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
height: 346px;
}
.turang{
position: relative;
top: -10px;
right: 5px;
}
.finding{
margin-top: 15px;
position: relative;
top: -10px;
right: 10px;
}
.preview-section-under{
padding-left: 30px;
background-color: #fff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.preview-section-under h4{
font-size: 26px;
padding-top: 20px;
}
::v-deep .el-dialog__header {
display: none;
}
.smart-analysis-container1{
margin-top: 48px;
padding: 20px;
background-color: #fff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
</style>