Files
nonye/platform/src/pages/ReportsPage.vue

1390 lines
39 KiB
Vue
Raw Normal View History

2025-07-17 23:13:04 +08:00
<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>