Files
nonye/platform/public/map_with_random_points.html
2025-07-17 23:13:04 +08:00

1111 lines
40 KiB
HTML
Raw Permalink 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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能设备地理分布与病虫害检测系统</title>
<!-- 基础样式库 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- 地图与图标库 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css">
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 自定义样式 -->
<style>
:root {
--primary-color: #1e88e5;
--secondary-color: #43a047;
--warning-color: #ffb300;
--danger-color: #f44336;
--info-color: #2979ff;
}
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f8f9fa;
overflow: hidden;
}
#map {
width: 100%;
height: calc(100vh - 60px);
transition: height 0.3s ease;
position: relative;
}
.header {
background-color: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 1001;
}
.status-legend {
position: absolute;
top: 70px;
right: 20px;
background-color: white;
padding: 10px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 1000;
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-color {
width: 16px;
height: 16px;
border-radius: 4px;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.device-popup {
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 12px;
min-width: 240px;
}
.popup-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
}
/* 病虫害检测模态框样式 */
#detectionModal .modal-content {
border-radius: 12px;
overflow: hidden;
}
#detectionModal .modal-header {
background: linear-gradient(135deg, #1e88e5, #0d47a1);
color: white;
border-bottom: none;
}
#detectionModal .modal-body {
padding: 25px;
}
.camera-container {
position: relative;
border: 2px dashed #ccc;
border-radius: 10px;
min-height: 300px;
display: flex;
justify-content: center;
align-items: center;
background-color: #f9f9f9;
margin-bottom: 20px;
overflow: hidden;
}
#imagePreview, #cameraPreview {
max-width: 100%;
max-height: 300px;
display: none;
}
.upload-placeholder {
text-align: center;
padding: 20px;
color: #777;
}
.upload-placeholder i {
font-size: 48px;
margin-bottom: 15px;
color: #1e88e5;
}
.action-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
flex-wrap: wrap;
}
.result-container {
display: none;
background: #f0f8ff;
border-radius: 10px;
padding: 20px;
margin-top: 20px;
border-left: 4px solid #1e88e5;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.disease-name {
font-size: 1.4rem;
font-weight: bold;
color: #0d47a1;
}
.confidence {
font-size: 1.1rem;
color: #43a047;
font-weight: 500;
}
.treatment-section {
background: #e8f5e9;
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
.treatment-section h5 {
color: #2e7d32;
border-bottom: 1px solid #c8e6c9;
padding-bottom: 8px;
margin-bottom: 12px;
}
.btn-custom, .btn-map {
background-color: #1e88e5;
color: white;
border: none;
border-radius: 4px;
padding: 8px 15px;
font-weight: 500;
display: flex;
align-items: center;
gap: 5px;
transition: all 0.3s ease;
}
.btn-custom:hover, .btn-map:hover {
background-color: #1976d2;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(13, 71, 161, 0.3);
}
.btn-secondary-custom {
background: linear-gradient(135deg, #78909c, #546e7a);
color: white;
border: none;
border-radius: 6px;
padding: 10px 20px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn-secondary-custom:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(84, 110, 122, 0.3);
}
.notification-toast {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 2000;
min-width: 250px;
}
.device-info-panel {
position: absolute;
top: 70px;
left: 20px;
background-color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 1000;
min-width: 250px;
display: none;
}
.device-info-panel h5 {
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-bottom: 10px;
}
.village-boundary {
stroke: #1e88e5;
stroke-width: 2;
stroke-opacity: 0.7;
fill: #e3f2fd;
fill-opacity: 0.2;
}
/* 农田图片弹窗样式 */
.farm-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.farm-modal.active {
opacity: 1;
visibility: visible;
}
.farm-modal-content {
background-color: white;
border-radius: 12px;
overflow: hidden;
max-width: 800px;
width: 90%;
max-height: 80vh;
position: relative;
display: flex;
flex-direction: column;
}
.farm-header {
position: absolute;
top: 15px;
left: 15px;
background-color: rgba(0,0,0,0.7);
color: white;
padding: 5px 10px;
border-radius: 4px;
font-weight: bold;
z-index: 1;
}
.farm-image-container {
position: relative;
flex-grow: 1;
}
.farm-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.farm-info {
position: absolute;
bottom: 15px;
right: 15px;
background-color: rgba(0,0,0,0.7);
color: white;
padding: 10px 15px;
border-radius: 8px;
z-index: 1;
font-size: 16px;
}
@media (max-width: 768px) {
#map {
height: calc(100vh - 100px);
}
.header {
flex-direction: column;
gap: 10px;
padding: 15px;
}
.controls {
flex-wrap: wrap;
justify-content: center;
gap: 5px;
}
.status-legend {
top: 120px;
right: 10px;
font-size: 0.8rem;
}
.action-buttons {
flex-direction: column;
}
.action-buttons button {
width: 100%;
}
}
</style>
</head>
<body>
<!-- 页面头部 -->
<header class="header">
<div class="d-flex align-items-center">
<i class="fa fa-map-marker text-primary" style="font-size: 24px; margin-right: 10px;"></i>
<h3>智能设备地理分布与病虫害检测系统</h3>
</div>
<div class="controls d-flex gap-2">
<button id="locate" class="btn-map"><i class="fa fa-location-arrow"></i> 定位</button>
<button id="viewFarmBtn" class="btn-map"><i class="fa fa-eye"></i> 查看</button>
<button id="showDevices" class="btn btn-sm btn-success"><i class="fa fa-list"></i> 设备列表</button>
</div>
</header>
<!-- 地图容器 -->
<div id="map"></div>
<!-- 状态图例 -->
<div class="status-legend">
<h5 class="mb-2"><i class="fa fa-info-circle"></i> 设备状态</h5>
<div class="legend-item">
<div class="legend-color" style="background-color: green;"></div>
<span>正常运行</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: orange;"></div>
<span>警告状态</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: red;"></div>
<span>故障状态</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: gray;"></div>
<span>离线状态</span>
</div>
</div>
<!-- 加载覆盖层 -->
<div id="loadingOverlay" class="loading-overlay">
<div class="text-center">
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在加载地图和设备数据...</p>
</div>
</div>
<!-- 农田图片弹窗 -->
<div class="farm-modal" id="farmModal">
<div class="farm-modal-content">
<div class="farm-header">农业基地实拍</div>
<div class="farm-image-container">
<img src="../src/assets/农田.jpg" alt="农业基地" class="farm-image">
</div>
<div class="farm-info">
<p>15亩地<br>设备7台</p>
</div>
</div>
</div>
<!-- 病虫害检测模态框 -->
<div class="modal fade" id="detectionModal" tabindex="-1" aria-labelledby="detectionModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="detectionModalLabel"><i class="fa fa-bug"></i> 病虫害检测</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="camera-container">
<div class="upload-placeholder" id="uploadPlaceholder">
<i class="fa fa-cloud-upload"></i>
<p>请上传植物图片或拍照进行病虫害检测</p>
</div>
<img id="imagePreview" alt="预览图片">
<video id="cameraPreview" autoplay playsinline style="display: none;"></video>
<canvas id="captureCanvas" style="display: none;"></canvas>
</div>
<div class="action-buttons">
<button class="btn btn-custom" id="uploadImageBtn">
<i class="fa fa-upload"></i> 上传图片
</button>
<button class="btn btn-custom" id="startCameraBtn">
<i class="fa fa-camera"></i>抓拍检测
</button>
<input type="file" id="imageUpload" accept="image/*" style="display: none;">
</div>
<button class="btn btn-success w-100 mt-3" id="detectBtn" style="display: none;">
<i class="fa fa-search"></i> 开始检测
</button>
<div class="result-container" id="resultContainer">
<div class="result-header">
<h4 class="disease-name" id="diseaseName">检测结果</h4>
<div class="confidence" id="confidence">置信度: 0%</div>
</div>
<div class="diagnosis" id="diagnosis">
<h5><i class="fa fa-stethoscope"></i> 诊断说明</h5>
<p id="diagnosisText">诊断信息将显示在这里...</p>
</div>
<div class="treatment-section">
<h5><i class="fa fa-heart"></i> 防治建议</h5>
<p id="treatmentText">防治建议将显示在这里...</p>
</div>
<div class="mt-4 d-flex justify-content-between">
<button class="btn btn-secondary-custom" id="newDetectionBtn">
<i class="fa fa-refresh"></i> 新的检测
</button>
<button class="btn btn-custom" id="downloadReportBtn">
<i class="fa fa-download"></i> 下载报告
</button>
</div>
</div>
<div class="text-center mt-3" id="detectionLoading" style="display: none;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在检测中,请稍候...</p>
</div>
</div>
</div>
</div>
</div>
<!-- 通知Toast -->
<div class="toast notification-toast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="true" data-bs-delay="3000">
<div class="toast-header">
<strong class="me-auto">系统通知</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
报告已成功生成并下载!
</div>
</div>
<!-- JavaScript 库 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<!-- 地图初始化 -->
<script>
// 加载指示器
const loadingOverlay = document.getElementById('loadingOverlay');
// 农田图片弹窗
const farmModal = document.getElementById('farmModal');
const viewFarmBtn = document.getElementById('viewFarmBtn');
// 小村区域边界坐标(以中心点为基准进一步缩小的不规则多边形)
const villageBoundary = [
[27.458, 120.3785], // 西南角向中心点移动0.0005
[27.4615, 120.3815], // 东北角向中心点移动0.0005
[27.4605, 120.3845], // 东北角偏东向中心点移动0.0005
[27.456, 120.3825], // 西北角向中心点移动0.0005
[27.458, 120.3785] // 闭合多边形
];
// 模拟设备数据(全部位于区域内)
const deviceData = [
{ id: 1, name: "北田传感器", status: "normal", lat: 27.4605, lng: 120.381, type: "传感器", location: "村北农田" },
{ id: 2, name: "东园控制器", status: "warning", lat: 27.458, lng: 120.3828, type: "控制器", location: "村东果园" },
{ id: 3, name: "南塘执行器", status: "fault", lat: 27.458, lng: 120.380, type: "执行器", location: "村南池塘" },
{ id: 4, name: "西园传感器", status: "normal", lat: 27.459, lng: 120.380, type: "传感器", location: "村西菜园" },
{ id: 5, name: "中心控制器", status: "offline", lat: 27.460, lng: 120.3818, type: "控制器", location: "村中心" },
{ id: 6, name: "果园监测仪", status: "normal", lat: 27.46085, lng: 120.3832, type: "传感器", location: "村东果园" },
{ id: 7, name: "村中心设备", status: "normal", lat: 27.460, lng: 120.3823, type: "服务器", location: "村控制中心" }
];
// 设备状态样式映射
const statusStyles = {
normal: {
color: 'green',
icon: 'check-circle',
popupClass: 'bg-success text-white',
statusText: '正常运行'
},
warning: {
color: 'orange',
icon: 'exclamation-triangle',
popupClass: 'bg-warning text-dark',
statusText: '警告状态'
},
fault: {
color: 'red',
icon: 'times-circle',
popupClass: 'bg-danger text-white',
statusText: '故障状态'
},
offline: {
color: 'gray',
icon: 'circle-o',
popupClass: 'bg-secondary text-white',
statusText: '离线状态'
}
};
// 设备类型图标映射
const typeIcons = {
"传感器": "fa-microchip",
"控制器": "fa-cogs",
"执行器": "fa-play",
"服务器": "fa-server"
};
// 病虫害诊断信息
const diseaseInfo = {
"苹果黑星病": {
diagnosis: "苹果黑星病是由真菌Venturia inaequalis引起的主要危害苹果叶片和果实。病斑初期为淡黄色后期变为黑色绒状霉层。",
treatment: "1. 清除病叶、病果,减少病原菌越冬基数\n2. 春季萌芽前喷施石硫合剂\n3. 发病初期喷施苯醚甲环唑、戊唑醇等杀菌剂\n4. 选择抗病品种种植"
},
"苹果黑腐病": {
diagnosis: "苹果黑腐病是由真菌Botryosphaeria obtusa引起的主要危害果实、叶片和枝条。病斑呈褐色至黑色有同心轮纹。",
treatment: "1. 清除病枝、病果,减少病原\n2. 加强果园管理,增强树势\n3. 果实套袋保护\n4. 喷施代森锰锌、嘧菌酯等杀菌剂"
},
"苹果雪松锈病": {
diagnosis: "苹果雪松锈病是由真菌Gymnosporangium yamadae引起的转主寄生菌需在苹果和桧柏上交替寄生完成生活史。",
treatment: "1. 清除果园周围的桧柏等转主寄主\n2. 早春喷施三唑酮或戊唑醇\n3. 发病初期喷施嘧菌酯、吡唑醚菌酯\n4. 加强果园通风透光"
},
"玉米灰斑病": {
diagnosis: "玉米灰斑病是由真菌Cercospora zeae-maydis引起的叶部病害病斑呈长条形灰褐色严重时导致叶片枯死。",
treatment: "1. 选用抗病品种\n2. 合理密植,保证通风透光\n3. 发病初期喷施苯醚甲环唑、嘧菌酯\n4. 收获后深翻土地,减少病原"
},
"玉米普通锈病": {
diagnosis: "玉米普通锈病是由真菌Puccinia sorghi引起的病斑呈圆形或椭圆形红褐色表皮破裂后散出铁锈色粉末。",
treatment: "1. 选用抗病品种\n2. 合理施肥,增施磷钾肥\n3. 发病初期喷施三唑酮、戊唑醇\n4. 清除田间病残体"
},
"番茄细菌性斑点病": {
diagnosis: "番茄细菌性斑点病是由细菌Pseudomonas syringae pv. tomato引起的叶片上出现水渍状小斑点后期变为褐色坏死斑。",
treatment: "1. 选用无病种子,种子消毒\n2. 轮作倒茬,避免连作\n3. 发病初期喷施氢氧化铜、春雷霉素\n4. 控制田间湿度,避免大水漫灌"
},
"番茄晚疫病": {
diagnosis: "番茄晚疫病是由真菌Phytophthora infestans引起的毁灭性病害叶片出现水渍状病斑湿度大时产生白色霉层。",
treatment: "1. 选用抗病品种\n2. 高畦栽培,合理密植\n3. 发病初期喷施烯酰吗啉、氟噻唑吡乙酮\n4. 及时清除中心病株"
},
"玉米健康": {
diagnosis: "玉米植株生长健康,无病虫害迹象。叶片呈鲜绿色,茎秆粗壮,根系发达。",
treatment: "1. 保持合理密植\n2. 定期施肥,保证营养供应\n3. 注意水分管理,避免旱涝\n4. 定期巡查,预防病虫害发生"
},
"苹果健康": {
diagnosis: "苹果树生长旺盛,叶片浓绿有光泽,无病虫害迹象。果实发育良好,树势强壮。",
treatment: "1. 合理修剪,保持通风透光\n2. 定期施肥,保证营养均衡\n3. 注意水分管理,避免干旱\n4. 定期巡查,预防病虫害发生"
}
};
// 全局变量
let map;
let deviceMarkers = L.featureGroup();
let villageLayer;
let currentDevice = null;
let currentStream = null;
const detectionModal = new bootstrap.Modal(document.getElementById('detectionModal'));
const notificationToast = new bootstrap.Toast(document.querySelector('.toast'));
let isDeviceInfoShown = false; // 记录设备信息面板是否已显示
// 初始化地图
function initMap() {
try {
// 创建地图实例中心设为小村中心缩放级别调整为17级以更清晰显示缩小后的区域
map = L.map('map').setView([27.460, 120.381], 18);
// 添加地图图层(高德地图)
L.tileLayer('https://wprd0{s}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7', {
subdomains: '1234',
attribution: '高德地图',
maxZoom: 19
}).addTo(map);
// 添加控制按钮事件
document.getElementById('locate').addEventListener('click', () => map.locate({setView: true, maxZoom: 17}));
document.getElementById('showDevices').addEventListener('click', showAllDevices);
// 查看农田按钮事件
viewFarmBtn.addEventListener('click', () => {
farmModal.classList.add('active');
document.body.style.overflow = 'hidden'; // 防止背景滚动
});
// 模拟数据加载延迟
setTimeout(() => {
addVillageBoundary();
addDeviceMarkers();
loadingOverlay.style.display = 'none';
}, 800);
console.log('地图初始化成功');
} catch (error) {
console.error('地图初始化失败:', error);
loadingOverlay.innerHTML = `
<div class="text-center text-danger">
<i class="fa fa-exclamation-circle" style="font-size: 24px; margin-bottom: 10px;"></i>
<h4>地图加载失败</h4>
<p>${error.message}</p>
<p class="mt-2">请检查您的网络连接或稍后重试</p>
</div>
`;
}
}
// 添加小村边界
function addVillageBoundary() {
villageLayer = L.polygon(villageBoundary, {
className: 'village-boundary',
interactive: false
}).addTo(map);
}
// 添加设备标记到地图
function addDeviceMarkers() {
// 清除现有标记
if (deviceMarkers) {
map.removeLayer(deviceMarkers);
}
deviceMarkers = L.featureGroup();
// 添加设备标记
deviceData.forEach(device => {
const style = statusStyles[device.status] || statusStyles.normal;
const typeIcon = typeIcons[device.type] || 'fa-question-circle';
// 创建自定义图标
const icon = L.AwesomeMarkers.icon({
icon: `${style.icon} ${typeIcon}`,
prefix: 'fa',
markerColor: style.color,
iconColor: 'white',
extraClasses: 'fa-lg'
});
// 创建标记
const marker = L.marker([device.lat, device.lng], { icon: icon }).addTo(deviceMarkers);
// 自定义弹窗内容
const popupContent = `
<div class="device-popup ${style.popupClass}">
<div class="popup-header">
<div class="status-dot" style="background-color: ${style.color}"></div>
<h5 class="mb-0">${device.name}</h5>
</div>
<p class="mb-1"><i class="fa fa-id-card-o mr-2"></i> 设备ID: ${device.id}</p>
<p class="mb-1"><i class="fa fa-map-marker mr-2"></i> 坐标: ${device.lat.toFixed(6)}, ${device.lng.toFixed(6)}</p>
<p class="mb-1"><i class="fa fa-building mr-2"></i> 位置: ${device.location}</p>
<p class="mb-1"><i class="fa ${typeIcon} mr-2"></i> 类型: ${device.type}</p>
<p class="mb-0"><i class="fa fa-circle mr-2"></i> 状态:
<span class="badge ${style.popupClass}">${style.statusText}</span>
</p>
<div class="d-grid mt-2">
<button class="btn btn-sm btn-outline-light detect-btn" data-device-id="${device.id}">
<i class="fa fa-bug"></i> 病虫害检测
</button>
</div>
</div>
`;
marker.bindPopup(popupContent);
// 添加点击事件监听
marker.on('popupopen', function() {
document.querySelector('.detect-btn').addEventListener('click', function() {
currentDevice = device;
showDetectionModal();
map.closePopup();
});
});
// 添加点击标记事件(只显示一次设备信息)
marker.on('click', function() {
if (!isDeviceInfoShown) { // 只有未显示时才显示
showDeviceInfo(device);
isDeviceInfoShown = true;
}
});
});
// 添加标记组到地图
map.addLayer(deviceMarkers);
// 如果有设备,自动调整视图显示所有设备
if (deviceData.length > 0) {
deviceMarkers.addTo(map).bringToFront();
map.fitBounds(deviceMarkers.getBounds(), {padding: [20, 20]});
}
}
// 显示设备信息面板
function showDeviceInfo(device) {
const style = statusStyles[device.status] || statusStyles.normal;
document.getElementById('infoName').textContent = device.name;
document.getElementById('infoId').textContent = device.id;
document.getElementById('infoStatus').innerHTML = `<span class="badge ${style.popupClass}">${style.statusText}</span>`;
document.getElementById('infoType').textContent = device.type;
document.getElementById('infoLocation').textContent = device.location;
document.getElementById('infoCoords').textContent = `${device.lat.toFixed(6)}, ${device.lng.toFixed(6)}`;
const panel = document.getElementById('deviceInfoPanel');
panel.style.display = 'block';
// 更新检测按钮
document.getElementById('detectFromPanel').onclick = function() {
currentDevice = device;
showDetectionModal();
};
}
// 显示所有设备
function showAllDevices() {
if (deviceMarkers) {
map.fitBounds(deviceMarkers.getBounds(), {padding: [20, 20]});
}
}
// 显示病虫害检测模态框
function showDetectionModal() {
resetDetectionUI();
detectionModal.show();
}
// 重置检测UI
function resetDetectionUI() {
document.getElementById('uploadPlaceholder').style.display = 'flex';
document.getElementById('imagePreview').style.display = 'none';
document.getElementById('imagePreview').src = '';
document.getElementById('cameraPreview').style.display = 'none';
document.getElementById('detectBtn').style.display = 'none';
document.getElementById('resultContainer').style.display = 'none';
document.getElementById('startCameraBtn').style.display = 'inline-block';
document.getElementById('uploadImageBtn').style.display = 'inline-block';
// 关闭摄像头
stopCamera();
}
// 初始化检测功能
function initDetection() {
// 上传图片按钮
document.getElementById('uploadImageBtn').addEventListener('click', () => {
document.getElementById('imageUpload').click();
});
// 图片上传处理
document.getElementById('imageUpload').addEventListener('change', function(e) {
if (this.files && this.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('uploadPlaceholder').style.display = 'none';
const imgPreview = document.getElementById('imagePreview');
imgPreview.src = e.target.result;
imgPreview.style.display = 'block';
document.getElementById('detectBtn').style.display = 'block';
};
reader.readAsDataURL(this.files[0]);
}
});
// 实时抓拍按钮
document.getElementById('startCameraBtn').addEventListener('click', captureRandomImage);
// 检测按钮
document.getElementById('detectBtn').addEventListener('click', detectDisease);
// 新的检测按钮
document.getElementById('newDetectionBtn').addEventListener('click', resetDetectionUI);
// 下载报告按钮
document.getElementById('downloadReportBtn').addEventListener('click', downloadReport);
}
// 随机图片文件名数组
const randomImages = [
"choimg/1.JPG",
"choimg/2.JPG",
"choimg/3.JPG",
"choimg/4.JPG",
"choimg/5.JPG",
"choimg/6.JPG",
"choimg/7.JPG",
"choimg/8.JPG",
"choimg/9.JPG",
"choimg/10.JPG",
"choimg/11.JPG",
"choimg/12.JPG",
"choimg/13.JPG",
"choimg/14.JPG",
"choimg/15.JPG",
"choimg/16.JPG",
"choimg/17.JPG"
];
// 捕获随机图片
function captureRandomImage() {
// 随机选择一张图片
const randomIndex = Math.floor(Math.random() * randomImages.length);
const randomImagePath = randomImages[randomIndex];
// 显示加载状态
document.getElementById('uploadPlaceholder').style.display = 'none';
const imgPreview = document.getElementById('imagePreview');
imgPreview.src = randomImagePath;
imgPreview.style.display = 'block';
document.getElementById('detectBtn').style.display = 'block';
// 关闭摄像头(如果有)
stopCamera();
}
// 启动摄像头(已移除,但保留函数定义以防需要)
async function startCamera() {
try {
document.getElementById('uploadPlaceholder').style.display = 'none';
const constraints = {
video: {
facingMode: 'environment', // 优先使用后置摄像头
width: { ideal: 1280 },
height: { ideal: 720 }
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
currentStream = stream;
const cameraPreview = document.getElementById('cameraPreview');
cameraPreview.srcObject = stream;
cameraPreview.style.display = 'block';
document.getElementById('startCameraBtn').style.display = 'none';
document.getElementById('uploadImageBtn').style.display = 'none';
document.getElementById('captureBtn').style.display = 'block';
document.getElementById('retakeBtn').style.display = 'none';
document.getElementById('detectBtn').style.display = 'none';
} catch (err) {
console.error('摄像头访问失败:', err);
alert('无法访问摄像头,请确保您已授予摄像头权限。');
}
}
// 停止摄像头
function stopCamera() {
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
currentStream = null;
}
}
// 拍照(已移除,但保留函数定义以防需要)
function captureImage() {
const cameraPreview = document.getElementById('cameraPreview');
const canvas = document.getElementById('captureCanvas');
const imgPreview = document.getElementById('imagePreview');
canvas.width = cameraPreview.videoWidth;
canvas.height = cameraPreview.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(cameraPreview, 0, 0, canvas.width, canvas.height);
imgPreview.src = canvas.toDataURL('image/jpeg');
imgPreview.style.display = 'block';
cameraPreview.style.display = 'none';
document.getElementById('captureBtn').style.display = 'none';
document.getElementById('retakeBtn').style.display = 'block';
document.getElementById('detectBtn').style.display = 'block';
// 关闭摄像头
stopCamera();
}
// 检测病虫害
async function detectDisease() {
const imgPreview = document.getElementById('imagePreview');
const detectionLoading = document.getElementById('detectionLoading');
const detectBtn = document.getElementById('detectBtn');
// 显示加载状态
detectBtn.style.display = 'none';
detectionLoading.style.display = 'block';
try {
// 从预览图像获取数据URL
const imageData = imgPreview.src;
// 发送到后端进行预测(这里使用模拟数据)
const mockResult = getMockDetectionResult();
showDetectionResult(mockResult);
} catch (error) {
console.error('检测失败:', error);
// 模拟结果作为备选方案
const mockResult = getMockDetectionResult();
showDetectionResult(mockResult);
} finally {
detectionLoading.style.display = 'none';
}
}
// 获取模拟检测结果
function getMockDetectionResult() {
const diseases = Object.keys(diseaseInfo);
const randomDisease = diseases[Math.floor(Math.random() * diseases.length)];
const confidence = (Math.random() * 50 + 50).toFixed(1); // 50-100%的置信度
return {
disease: randomDisease,
confidence: confidence,
diagnosis: diseaseInfo[randomDisease].diagnosis,
treatment: diseaseInfo[randomDisease].treatment
};
}
// 显示检测结果
function showDetectionResult(result) {
document.getElementById('diseaseName').textContent = result.disease;
document.getElementById('confidence').textContent = `置信度: ${result.confidence}%`;
document.getElementById('diagnosisText').textContent = result.diagnosis;
document.getElementById('treatmentText').textContent = result.treatment;
document.getElementById('resultContainer').style.display = 'block';
}
// 下载报告
function downloadReport() {
const diseaseName = document.getElementById('diseaseName').textContent;
const confidence = document.getElementById('confidence').textContent;
const diagnosis = document.getElementById('diagnosisText').textContent;
const treatment = document.getElementById('treatmentText').textContent;
try {
// 使用jsPDF创建PDF报告
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// 添加标题
doc.setFontSize(20);
doc.text('病虫害检测报告', 105, 20, null, null, 'center');
// 添加设备信息
doc.setFontSize(12);
doc.setTextColor(100);
doc.text(`检测设备: ${currentDevice.name} (ID: ${currentDevice.id})`, 20, 35);
doc.text(`设备位置: ${currentDevice.location}`, 20, 42);
doc.text(`检测时间: ${new Date().toLocaleString()}`, 20, 49);
// 添加分隔线
doc.setDrawColor(200);
doc.line(20, 55, 190, 55);
// 添加检测结果
doc.setFontSize(16);
doc.setTextColor(0);
doc.text('检测结果', 20, 65);
doc.setFontSize(14);
doc.setTextColor(0, 0, 255);
doc.text(`病害名称: ${diseaseName}`, 20, 75);
doc.setTextColor(0, 150, 0);
doc.text(confidence, 20, 82);
// 添加诊断说明
doc.setFontSize(16);
doc.setTextColor(0);
doc.text('诊断说明', 20, 95);
doc.setFontSize(12);
doc.setTextColor(0);
doc.text(diagnosis, 20, 105, { maxWidth: 170 });
// 添加防治建议
doc.setFontSize(16);
doc.setTextColor(0);
doc.text('防治建议', 20, 140);
doc.setFontSize(12);
doc.setTextColor(0);
doc.text(treatment, 20, 150, { maxWidth: 170 });
// 保存PDF
doc.save(`病虫害检测报告_${diseaseName}_${new Date().getTime()}.pdf`);
// 显示成功通知
document.querySelector('.toast-body').textContent = "报告已成功生成并下载!";
notificationToast.show();
} catch (error) {
console.error('生成报告失败:', error);
alert('生成报告失败: ' + error.message);
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
initMap();
initDetection();
// 点击弹窗外部关闭弹窗
farmModal.addEventListener('click', (e) => {
if (e.target === farmModal) {
farmModal.classList.remove('active');
document.body.style.overflow = ''; // 恢复背景滚动
}
});
// 按ESC键关闭弹窗
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && farmModal.classList.contains('active')) {
farmModal.classList.remove('active');
document.body.style.overflow = '';
}
});
// 地图点击关闭设备信息面板(重置显示状态)
map.on('click', function() {
document.getElementById('deviceInfoPanel').style.display = 'none';
isDeviceInfoShown = false;
});
});
// 窗口大小改变时调整地图高度
window.addEventListener('resize', () => {
if (map) {
map.invalidateSize();
}
});
</script>
</body>
</html>