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 @@
+
+
+
+ 设备信息
+ 坐标信息
+
+
+
+ --:--
+
+
+
+
+
+
+
+
Windy 天气预测
+
+
+
+
-
测量中
+
@@ -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];
+ }