feat: 优化页面效果

This commit is contained in:
yarnom 2025-07-04 16:39:48 +08:00
parent 1c4633881d
commit c79c868c41

View File

@ -536,6 +536,244 @@
height: 24px; height: 24px;
min-width: 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;
}
}
</style> </style>
<script src="../js/ol.js"></script> <script src="../js/ol.js"></script>
@ -592,6 +830,32 @@
<div class="map-card"> <div class="map-card">
<div id="map-container"> <div id="map-container">
<div id="weatherForecastCard" class="weather-forecast-card" style="display: none;">
<div class="weather-card-header">
<h3 class="weather-card-title">Windy 天气预测 :) </h3>
<button class="weather-close-btn" onclick="closeWeatherCard()">×</button>
</div>
<div id="weatherDeviceInfo" class="weather-device-info">
<span id="weatherDeviceId">设备信息</span>
<span id="weatherDeviceCoords">坐标信息</span>
</div>
<div class="weather-forecast-navigation">
<button id="prevForecast" class="weather-nav-btn" onclick="showPrevForecast()" disabled>
<i class="layui-icon layui-icon-left"></i>
</button>
<span id="forecastTimeDisplay" class="forecast-time-display">--:--</span>
<button id="nextForecast" class="weather-nav-btn" onclick="showNextForecast()">
<i class="layui-icon layui-icon-right"></i>
</button>
</div>
<div id="weatherForecastContent" class="weather-card-content">
<div class="weather-loading">
<i class="layui-icon layui-icon-loading layui-icon-anim-rotate"></i>
<p>正在获取天气预测数据...</p>
</div>
</div>
</div>
<div class="map-toolbar"> <div class="map-toolbar">
<div class="toolbar-item"> <div class="toolbar-item">
<select id="mapTypeSelectNew" class="toolbar-select" lay-filter="mapTypeNew" onchange="onMapTypeChange()"> <select id="mapTypeSelectNew" class="toolbar-select" lay-filter="mapTypeNew" onchange="onMapTypeChange()">
@ -659,11 +923,21 @@
清除测距 清除测距
</div> </div>
</div> </div>
<div class="dropdown-divider"></div>
<div class="dropdown-group">
<div class="dropdown-group-title">Windy 天气预测</div>
<div class="dropdown-item">
<label class="switch-label">
<span>启用天气预测(需网络被代理)</span>
<input type="checkbox" id="enableWeatherSwitch" onchange="toggleWeatherForecast()">
<span class="switch-slider"></span>
</label>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 测量状态指示器 -->
<div id="measureStatus" class="toolbar-item" style="display: none;"> <div id="measureStatus" class="toolbar-item" style="display: none;">
<span style="color: #48bb78; font-weight: bold; font-size: 12px;">测量中</span> <span style="color: #48bb78; font-weight: bold; font-size: 12px;">测量中</span>
<button class="toolbar-btn" onclick="finishMeasuring()" style="background: #f56565; margin-left: 6px;"> <button class="toolbar-btn" onclick="finishMeasuring()" style="background: #f56565; margin-left: 6px;">
@ -673,6 +947,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -703,19 +978,25 @@
// 测距 // 测距
var measureSource; var measureSource;
var measureLayer; var measureLayer;
// 天气预测
var currentWeatherDevice = null;
var weatherApiKey = 'Uxh4IdMuAvhSiBnsf4UUDVGF4e3YAp2B';
var weatherEnabled = false;
var weatherData = null;
var currentForecastIndex = 0;
// 天地图 API 密钥fengyarnom@gmail.com // 天地图 API 密钥fengyarnom@gmail.com
var TIANDITU_KEY = '0c260b8a094a4e0bc507808812cefdac'; var TIANDITU_KEY = '0c260b8a094a4e0bc507808812cefdac';
var deviceList = [ var deviceList = [
[# th:each="device : ${deviceList}"] /*[# th:each="device : ${deviceList}"]*/
{ {
deviceid: [[${device.deviceid}]], deviceid: /*[[${device.deviceid}]]*/ '',
latitude: [[${device.latitude}]], latitude: /*[[${device.latitude}]]*/ 0,
longitude: [[${device.longitude}]], longitude: /*[[${device.longitude}]]*/ 0,
warning: [[${device.warning}]] warning: /*[[${device.warning}]]*/ 0
}, }/*[# th:if="${!deviceStat.last}"]*/,/*[/]*/
[/] /*[/]*/
]; ];
var pi = 3.14159265358979324; var pi = 3.14159265358979324;
@ -1092,11 +1373,24 @@
if (feature) { if (feature) {
var features = feature.get('features'); var features = feature.get('features');
if (features && features.length > 1) { if (features && features.length > 1) {
// 集群点击,扩展视图
var extent = vectorSource.getExtent(); var extent = vectorSource.getExtent();
map.getView().fit(extent, { map.getView().fit(extent, {
padding: [50, 50, 50, 50], padding: [50, 50, 50, 50],
duration: 1000 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() { function onMapTypeChange() {
var mapType = document.getElementById('mapTypeSelectNew').value; var mapType = document.getElementById('mapTypeSelectNew').value;
if(mapType.startsWith('google_') && [[${role}]] != "SUPER_ADMIN") { if(mapType.startsWith('google_') && /*[[${role}]]*/ 'USER' != "SUPER_ADMIN") {
layer.msg('拒绝使用'); layer.msg('拒绝使用');
document.getElementById('mapTypeSelectNew').value = 'tianditu_terrain_hybrid'; document.getElementById('mapTypeSelectNew').value = 'tianditu_terrain_hybrid';
mapType = 'tianditu_terrain_hybrid'; mapType = 'tianditu_terrain_hybrid';
@ -1508,6 +1802,7 @@
if (found && targetFeature) { if (found && targetFeature) {
var geometry = targetFeature.getGeometry(); var geometry = targetFeature.getGeometry();
var deviceCoord = geometry.getCoordinates(); var deviceCoord = geometry.getCoordinates();
var deviceInfo = targetFeature.get('deviceInfo');
map.getView().animate({ map.getView().animate({
center: deviceCoord, center: deviceCoord,
@ -1516,6 +1811,11 @@
}); });
layer.msg('已定位到设备: ' + deviceId); layer.msg('已定位到设备: ' + deviceId);
// 只有在天气功能开启时才显示天气预测
if (weatherEnabled) {
showWeatherForecast(deviceInfo);
}
} else { } else {
layer.msg('未找到设备: ' + deviceId); layer.msg('未找到设备: ' + deviceId);
} }
@ -1564,7 +1864,6 @@
// 首先过滤地图上的设备显示 // 首先过滤地图上的设备显示
filterDevicesByStatus(status_type); filterDevicesByStatus(status_type);
// 然后打开设备列表弹窗
var index = layer.open({ var index = layer.open({
title: '设备列表', title: '设备列表',
type: 2, type: 2,
@ -1578,7 +1877,6 @@
}); });
} }
// 新增:根据状态过滤地图上的设备
function filterDevicesByStatus(status_type) { function filterDevicesByStatus(status_type) {
var savedMyLocationFeature = myLocationFeature; var savedMyLocationFeature = myLocationFeature;
vectorSource.clear(); vectorSource.clear();
@ -1587,7 +1885,6 @@
vectorSource.addFeature(savedMyLocationFeature); vectorSource.addFeature(savedMyLocationFeature);
} }
// 根据状态类型显示对应的设备
switch(status_type) { switch(status_type) {
case 'warning1': case 'warning1':
for (var i = 0; i < orangeFeatures.length; i++) { for (var i = 0; i < orangeFeatures.length; i++) {
@ -1666,14 +1963,12 @@
layer.msg(showDeviceId ? '已显示设备信息' : '已隐藏设备信息'); layer.msg(showDeviceId ? '已显示设备信息' : '已隐藏设备信息');
} }
// 集群显示开关
function toggleCluster() { function toggleCluster() {
showCluster = document.getElementById('showClusterSwitch').checked; showCluster = document.getElementById('showClusterSwitch').checked;
updateLayerVisibility(); updateLayerVisibility();
layer.msg(showCluster ? '已启用集群显示' : '已禁用集群显示'); layer.msg(showCluster ? '已启用集群显示' : '已禁用集群显示');
} }
// 更新图层可见性
function updateLayerVisibility() { function updateLayerVisibility() {
var zoom = map.getView().getZoom(); var zoom = map.getView().getZoom();
if (showCluster) { if (showCluster) {
@ -1900,7 +2195,210 @@
layer.msg('测距标记已清除'); layer.msg('测距标记已清除');
document.getElementById('mapFunctionsMenu').classList.remove('show'); 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 =
'<div class="weather-loading">' +
'<i class="layui-icon layui-icon-loading layui-icon-anim-rotate"></i>' +
'<p>请确保网络可访问 Windy API 服务</p>' +
'<p>正在获取天气预测数据...</p>' +
'</div>';
// 重置翻页按钮
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 = '<div class="weather-forecast-item"><div class="weather-param-grid">';
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 += '</div></div>';
document.getElementById('weatherForecastContent').innerHTML = forecastHtml;
}
function displayWeatherError(message) {
document.getElementById('weatherForecastContent').innerHTML =
'<div class="weather-error">' +
'<i class="layui-icon layui-icon-close"></i>' +
'<p>' + message + '</p>' +
'</div>';
}
function createWeatherParam(icon, label, value) {
return '<div class="weather-param">' +
'<span class="weather-param-icon">' + icon + '</span>' +
'<span class="weather-param-label">' + label + '</span>' +
'<span class="weather-param-value">' + value + '</span>' +
'</div>';
}
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];
}
</script> </script>
</body> </body>
</html>
</html> </html>