From c79c868c41ee56c838b6ed125cedd41f21030e4a Mon Sep 17 00:00:00 2001 From: yarnom Date: Fri, 4 Jul 2025 16:39:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/page/device_overview.html | 526 +++++++++++++++++- 1 file changed, 512 insertions(+), 14 deletions(-) diff --git a/sec-beidou/src/main/resources/templates/page/device_overview.html b/sec-beidou/src/main/resources/templates/page/device_overview.html index 35c0cf38..1138efa8 100644 --- a/sec-beidou/src/main/resources/templates/page/device_overview.html +++ b/sec-beidou/src/main/resources/templates/page/device_overview.html @@ -536,6 +536,244 @@ height: 24px; min-width: 24px; } + + .weather-forecast-card { + position: absolute; + top: 55%; + right: 20px; + transform: translateY(-50%); + width: 280px; + background: rgba(255, 255, 255, 0.96); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); + border: 1px solid rgba(255, 255, 255, 0.3); + z-index: 1100; + animation: slideInRight 0.3s ease-out; + padding: 16px; + } + + @keyframes slideInRight { + from { + transform: translate(100%, -50%); + opacity: 0; + } + to { + transform: translate(0, -50%); + opacity: 1; + } + } + + .weather-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + } + + .weather-card-title { + font-size: 16px; + font-weight: 600; + color: #2d3748; + margin: 0; + } + + .weather-close-btn { + background: none; + border: none; + color: #718096; + cursor: pointer; + font-size: 18px; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; + line-height: 1; + } + + .weather-close-btn:hover { + background: rgba(116, 129, 141, 0.1); + color: #4a5568; + } + + .weather-device-info { + background: rgba(26, 160, 148, 0.1); + border-radius: 8px; + padding: 10px; + margin-bottom: 12px; + display: flex; + flex-direction: column; + gap: 6px; + } + + .weather-device-info span { + font-size: 12px; + color: #4a5568; + } + + .weather-forecast-navigation { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + background: #f7fafc; + border-radius: 8px; + padding: 6px 10px; + } + + .weather-nav-btn { + background: rgba(26, 160, 148, 0.1); + border: none; + color: #1aa094; + border-radius: 4px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + } + + .weather-nav-btn:hover:not(:disabled) { + background: rgba(26, 160, 148, 0.2); + } + + .weather-nav-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .forecast-time-display { + font-size: 13px; + font-weight: 600; + color: #2d3748; + } + + .weather-card-content { + padding: 12px; + background: #f7fafc; + border-radius: 8px; + } + + .weather-loading { + text-align: center; + padding: 20px; + color: #718096; + } + + .weather-error { + text-align: center; + padding: 20px; + color: #e53e3e; + background: rgba(229, 62, 62, 0.1); + border-radius: 8px; + margin: 10px 0; + } + + .weather-forecast-item { + border-radius: 6px; + background: rgba(247, 250, 252, 0.5); + } + + .weather-param-grid { + display: grid; + grid-template-columns: 1fr; + gap: 4px; + } + + .weather-param { + display: flex; + align-items: center; + gap: 4px; + } + + .weather-param-icon { + font-size: 10px; + color: #1aa094; + width: 14px; + text-align: center; + } + + .weather-param-label { + font-size: 10px; + color: #718096; + font-weight: 500; + flex: 1; + } + + .weather-param-value { + font-size: 10px; + color: #2d3748; + font-weight: 600; + } + + .weather-wind-direction { + display: inline-block; + transform-origin: center; + transition: transform 0.3s ease; + } + + /* 手机端适配 */ + @media screen and (max-width: 768px) { + .weather-forecast-card { + right: 10px; + width: 240px; + padding: 12px; + max-height: 80vh; + overflow-y: auto; + } + + .weather-card-header { + margin-bottom: 8px; + } + + .weather-card-title { + font-size: 14px; + } + + .weather-device-info { + padding: 8px; + margin-bottom: 8px; + gap: 4px; + } + + .weather-device-info span { + font-size: 10px; + } + + .weather-forecast-navigation { + padding: 4px 8px; + margin-bottom: 8px; + } + + .weather-nav-btn { + width: 24px; + height: 24px; + } + + .forecast-time-display { + font-size: 11px; + } + + .weather-card-content { + padding: 8px; + } + + .weather-param { + gap: 2px; + } + + .weather-param-icon { + font-size: 8px; + width: 12px; + } + + .weather-param-label, + .weather-param-value { + font-size: 8px; + } + } @@ -592,6 +830,32 @@
+ +
+ + +
+
- + @@ -703,19 +978,25 @@ // 测距 var measureSource; var measureLayer; + // 天气预测 + var currentWeatherDevice = null; + var weatherApiKey = 'Uxh4IdMuAvhSiBnsf4UUDVGF4e3YAp2B'; + var weatherEnabled = false; + var weatherData = null; + var currentForecastIndex = 0; // 天地图 API 密钥(fengyarnom@gmail.com) var TIANDITU_KEY = '0c260b8a094a4e0bc507808812cefdac'; var deviceList = [ - [# th:each="device : ${deviceList}"] + /*[# th:each="device : ${deviceList}"]*/ { - deviceid: [[${device.deviceid}]], - latitude: [[${device.latitude}]], - longitude: [[${device.longitude}]], - warning: [[${device.warning}]] - }, - [/] + deviceid: /*[[${device.deviceid}]]*/ '', + latitude: /*[[${device.latitude}]]*/ 0, + longitude: /*[[${device.longitude}]]*/ 0, + warning: /*[[${device.warning}]]*/ 0 + }/*[# th:if="${!deviceStat.last}"]*/,/*[/]*/ + /*[/]*/ ]; var pi = 3.14159265358979324; @@ -1092,11 +1373,24 @@ if (feature) { var features = feature.get('features'); if (features && features.length > 1) { + // 集群点击,扩展视图 var extent = vectorSource.getExtent(); map.getView().fit(extent, { padding: [50, 50, 50, 50], duration: 1000 }); + } else if (features && features.length === 1) { + // 单个设备点击 + var deviceInfo = features[0].get('deviceInfo'); + if (deviceInfo && weatherEnabled) { + showWeatherForecast(deviceInfo); + } + } else if (feature.get('deviceInfo')) { + // 直接点击设备标记 + var deviceInfo = feature.get('deviceInfo'); + if (weatherEnabled) { + showWeatherForecast(deviceInfo); + } } } }); @@ -1229,7 +1523,7 @@ function onMapTypeChange() { var mapType = document.getElementById('mapTypeSelectNew').value; - if(mapType.startsWith('google_') && [[${role}]] != "SUPER_ADMIN") { + if(mapType.startsWith('google_') && /*[[${role}]]*/ 'USER' != "SUPER_ADMIN") { layer.msg('拒绝使用'); document.getElementById('mapTypeSelectNew').value = 'tianditu_terrain_hybrid'; mapType = 'tianditu_terrain_hybrid'; @@ -1508,6 +1802,7 @@ if (found && targetFeature) { var geometry = targetFeature.getGeometry(); var deviceCoord = geometry.getCoordinates(); + var deviceInfo = targetFeature.get('deviceInfo'); map.getView().animate({ center: deviceCoord, @@ -1516,6 +1811,11 @@ }); layer.msg('已定位到设备: ' + deviceId); + + // 只有在天气功能开启时才显示天气预测 + if (weatherEnabled) { + showWeatherForecast(deviceInfo); + } } else { layer.msg('未找到设备: ' + deviceId); } @@ -1564,7 +1864,6 @@ // 首先过滤地图上的设备显示 filterDevicesByStatus(status_type); - // 然后打开设备列表弹窗 var index = layer.open({ title: '设备列表', type: 2, @@ -1578,7 +1877,6 @@ }); } - // 新增:根据状态过滤地图上的设备 function filterDevicesByStatus(status_type) { var savedMyLocationFeature = myLocationFeature; vectorSource.clear(); @@ -1587,7 +1885,6 @@ vectorSource.addFeature(savedMyLocationFeature); } - // 根据状态类型显示对应的设备 switch(status_type) { case 'warning1': for (var i = 0; i < orangeFeatures.length; i++) { @@ -1666,14 +1963,12 @@ layer.msg(showDeviceId ? '已显示设备信息' : '已隐藏设备信息'); } - // 集群显示开关 function toggleCluster() { showCluster = document.getElementById('showClusterSwitch').checked; updateLayerVisibility(); layer.msg(showCluster ? '已启用集群显示' : '已禁用集群显示'); } - // 更新图层可见性 function updateLayerVisibility() { var zoom = map.getView().getZoom(); if (showCluster) { @@ -1900,7 +2195,210 @@ layer.msg('测距标记已清除'); document.getElementById('mapFunctionsMenu').classList.remove('show'); } + function toggleWeatherForecast() { + weatherEnabled = document.getElementById('enableWeatherSwitch').checked; + if (weatherEnabled) { + layer.msg( + '搜索设备或点击地图设备图标即可自动查询天气预测', + {time: 3000, area: ['300px', '80px']} + ); + } else { + layer.msg('天气预测功能已关闭'); + closeWeatherCard(); + } + document.getElementById('mapFunctionsMenu').classList.remove('show'); + } + + // 显示天气预测卡片 + function showWeatherForecast(deviceInfo) { + if (!weatherEnabled) { + return; + } + + currentWeatherDevice = deviceInfo; + currentForecastIndex = 0; + weatherData = null; + + document.getElementById('weatherDeviceId').textContent = '设备: ' + deviceInfo.deviceid; + document.getElementById('weatherDeviceCoords').textContent = + '坐标: ' + deviceInfo.latitude.toFixed(4) + ', ' + deviceInfo.longitude.toFixed(4); + + document.getElementById('weatherForecastCard').style.display = 'block'; + + document.getElementById('weatherForecastContent').innerHTML = + '
' + + '' + + '

请确保网络可访问 Windy API 服务

' + + '

正在获取天气预测数据...

' + + '
'; + + // 重置翻页按钮 + document.getElementById('prevForecast').disabled = true; + document.getElementById('nextForecast').disabled = true; + document.getElementById('forecastTimeDisplay').textContent = '--:--'; + + // 调用天气API + fetchWeatherData(deviceInfo.latitude, deviceInfo.longitude); + } + + function closeWeatherCard() { + document.getElementById('weatherForecastCard').style.display = 'none'; + currentWeatherDevice = null; + weatherData = null; + } + function showPrevForecast() { + if (!weatherData || currentForecastIndex <= 0) return; + + currentForecastIndex--; + displayCurrentForecast(); + updateForecastNavigation(); + } + + function showNextForecast() { + if (!weatherData || !weatherData.ts || currentForecastIndex >= weatherData.ts.length - 1) return; + + currentForecastIndex++; + displayCurrentForecast(); + updateForecastNavigation(); + } + + function updateForecastNavigation() { + if (!weatherData || !weatherData.ts) return; + + var prevBtn = document.getElementById('prevForecast'); + var nextBtn = document.getElementById('nextForecast'); + var timeDisplay = document.getElementById('forecastTimeDisplay'); + + prevBtn.disabled = (currentForecastIndex <= 0); + nextBtn.disabled = (currentForecastIndex >= weatherData.ts.length - 1); + + var timestamp = weatherData.ts[currentForecastIndex]; + var date = new Date(timestamp); + timeDisplay.textContent = formatDateTime(date); + } + + // 获取天气数据 + function fetchWeatherData(lat, lon) { + var requestBody = { + "lat": parseFloat(lat.toFixed(2)), + "lon": parseFloat(lon.toFixed(2)), + "model": "gfs", + "parameters": ["temp", "wind", "precip", "pressure", "rh", "windGust"], + "levels": ["surface"], + "key": weatherApiKey + }; + + fetch('https://api.windy.com/api/point-forecast/v2', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(requestBody) + }) + .then(function(response) { + if (!response.ok) { + throw new Error('网络响应状态: ' + response.status); + } + return response.json(); + }) + .then(function(data) { + weatherData = data; + currentForecastIndex = 0; + displayCurrentForecast(); + updateForecastNavigation(); + }) + .catch(function(error) { + console.error('天气数据获取失败:', error); + displayWeatherError('获取天气数据失败: ' + error.message); + }); + } + + function displayCurrentForecast() { + if (!weatherData || !weatherData.ts || weatherData.ts.length === 0) { + displayWeatherError('无可用的天气预测数据'); + return; + } + + var i = currentForecastIndex; + var forecastHtml = '
'; + + if (weatherData['temp-surface'] && weatherData['temp-surface'][i] !== null) { + var temp = (weatherData['temp-surface'][i] - 273.15).toFixed(1); // 转换为摄氏度 + forecastHtml += createWeatherParam('🌡️', '温度', temp + '°C'); + } + + if (weatherData['wind_u-surface'] && weatherData['wind_v-surface'] && + weatherData['wind_u-surface'][i] !== null && weatherData['wind_v-surface'][i] !== null) { + var windU = weatherData['wind_u-surface'][i]; + var windV = weatherData['wind_v-surface'][i]; + var windSpeed = Math.sqrt(windU * windU + windV * windV).toFixed(1); + var windDir = getWindDirection(windU, windV); + forecastHtml += createWeatherParam('💨', '风速', windSpeed + ' m/s'); + forecastHtml += createWeatherParam('🧭', '风向', windDir); + } + + if (weatherData['past3hprecip-surface'] && weatherData['past3hprecip-surface'][i] !== null) { + var precip = weatherData['past3hprecip-surface'][i].toFixed(1); + forecastHtml += createWeatherParam('🌧️', '降水', precip + ' mm'); + } + + if (weatherData['rh-surface'] && weatherData['rh-surface'][i] !== null) { + var humidity = weatherData['rh-surface'][i].toFixed(0); + forecastHtml += createWeatherParam('💧', '湿度', humidity + '%'); + } + + // 气压 + if (weatherData['pressure-surface'] && weatherData['pressure-surface'][i] !== null) { + var pressure = (weatherData['pressure-surface'][i] / 100).toFixed(0); // 转换为百帕 + forecastHtml += createWeatherParam('📊', '气压', pressure + ' hPa'); + } + + // 阵风 + if (weatherData['gust-surface'] && weatherData['gust-surface'][i] !== null) { + var gust = weatherData['gust-surface'][i].toFixed(1); + forecastHtml += createWeatherParam('💨', '阵风', gust + ' m/s'); + } + + forecastHtml += '
'; + document.getElementById('weatherForecastContent').innerHTML = forecastHtml; + } + + function displayWeatherError(message) { + document.getElementById('weatherForecastContent').innerHTML = + '
' + + '' + + '

' + message + '

' + + '
'; + } + + function createWeatherParam(icon, label, value) { + return '
' + + '' + icon + '' + + '' + label + '' + + '' + value + '' + + '
'; + } + + function formatDateTime(date) { + var month = (date.getMonth() + 1).toString().padStart(2, '0'); + var day = date.getDate().toString().padStart(2, '0'); + var hours = date.getHours().toString().padStart(2, '0'); + var minutes = date.getMinutes().toString().padStart(2, '0'); + + return month + '-' + day + ' ' + hours + ':' + minutes; + } + + function getWindDirection(u, v) { + var angle = Math.atan2(-u, -v) * 180 / Math.PI; + angle = (angle + 360) % 360; + + var directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北']; + var index = Math.round(angle / 45) % 8; + return directions[index]; + } + \ No newline at end of file