diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/coordinate-utils.js b/sec-beidou/src/main/resources/static/js/device-overview-module/coordinate-utils.js
new file mode 100644
index 00000000..bbb4d1a8
--- /dev/null
+++ b/sec-beidou/src/main/resources/static/js/device-overview-module/coordinate-utils.js
@@ -0,0 +1,94 @@
+/**
+ * 坐标转换工具模块
+ * 提供WGS84和GCJ-02坐标系之间的转换功能
+ */
+var CoordinateUtils = (function() {
+ 'use strict';
+
+ var pi = 3.14159265358979324;
+ var a = 6378245.0;
+ var ee = 0.00669342162296594323;
+
+ /**
+ * 判断是否在国内,不在国内则不做偏移
+ */
+ function outOfChina(lon, lat) {
+ if ((lon < 72.004 || lon > 137.8347) && (lat < 0.8293 || lat > 55.8271)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function transformLat(x, y) {
+ var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
+ ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
+ ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
+ ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
+ return ret;
+ }
+
+ function transformLon(x, y) {
+ var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
+ ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
+ ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
+ ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
+ return ret;
+ }
+
+ /**
+ * WGS84转GCJ-02坐标系
+ * @param {number} wgLat - WGS84纬度
+ * @param {number} wgLon - WGS84经度
+ * @returns {object} 转换后的坐标 {lat, lon}
+ */
+ function transform(wgLat, wgLon) {
+ var mars_point = {lon: 0, lat: 0};
+ if (outOfChina(wgLon, wgLat)) {
+ mars_point.lat = wgLat;
+ mars_point.lon = wgLon;
+ return mars_point;
+ }
+ var dLat = transformLat(wgLon - 105.0, wgLat - 35.0);
+ var dLon = transformLon(wgLon - 105.0, wgLat - 35.0);
+ var radLat = wgLat / 180.0 * pi;
+ var magic = Math.sin(radLat);
+ magic = 1 - ee * magic * magic;
+ var sqrtMagic = Math.sqrt(magic);
+ dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
+ dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
+ mars_point.lat = wgLat + dLat;
+ mars_point.lon = wgLon + dLon;
+ return mars_point;
+ }
+
+ /**
+ * 根据地图类型转换坐标
+ * @param {number} lat - 纬度
+ * @param {number} lon - 经度
+ * @param {string} mapType - 地图类型
+ * @returns {Array} OpenLayers坐标格式
+ */
+ function getMapCoordinates(lat, lon, mapType) {
+ var coordinates;
+ if (mapType === 'amap' || mapType === 'amap_satellite') {
+ // 高德地图 WGS84 转换为 GCJ-02
+ var gcjCoord = transform(lat, lon);
+ coordinates = ol.proj.fromLonLat([gcjCoord.lon, gcjCoord.lat]);
+ } else if (mapType.startsWith('google_')) {
+ // Google地图使用WGS84坐标系,直接使用
+ coordinates = ol.proj.fromLonLat([lon, lat]);
+ } else {
+ // 天地图 CGCS2000,2000国家大地坐标系,与WGS84实质一样
+ coordinates = ol.proj.fromLonLat([lon, lat]);
+ }
+ return coordinates;
+ }
+
+ // 公开API
+ return {
+ transform: transform,
+ getMapCoordinates: getMapCoordinates,
+ outOfChina: outOfChina
+ };
+})();
\ No newline at end of file
diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/device-markers.js b/sec-beidou/src/main/resources/static/js/device-overview-module/device-markers.js
new file mode 100644
index 00000000..b5aa41f0
--- /dev/null
+++ b/sec-beidou/src/main/resources/static/js/device-overview-module/device-markers.js
@@ -0,0 +1,413 @@
+/**
+ * 设备标记管理模块
+ * 负责设备标记的创建、分类和显示管理
+ */
+var DeviceMarkers = (function() {
+ 'use strict';
+
+ // 私有变量
+ var greenFeatures = [];
+ var orangeFeatures = [];
+ var redFeatures = [];
+ var allFeatures = [];
+ var myLocationFeature = null;
+ var myLocationInterval = null;
+ var showDeviceId = true;
+ var minZoomForLabels = 4;
+
+ // 外部依赖的引用
+ var map = null;
+ var vectorSource = null;
+ var vectorLayer = null;
+
+ /**
+ * 初始化设备标记管理器
+ * @param {ol.Map} mapInstance - 地图实例
+ * @param {ol.source.Vector} vectorSourceInstance - 矢量数据源
+ * @param {ol.layer.Vector} vectorLayerInstance - 矢量图层
+ */
+ function init(mapInstance, vectorSourceInstance, vectorLayerInstance) {
+ map = mapInstance;
+ vectorSource = vectorSourceInstance;
+ vectorLayer = vectorLayerInstance;
+ }
+
+ /**
+ * 创建设备标记样式
+ * @param {ol.Feature} feature - 要素对象
+ * @returns {ol.style.Style} 样式对象
+ */
+ function createDeviceStyle(feature) {
+ if (feature.get('isMyLocation')) {
+ return new ol.style.Style({
+ image: new ol.style.Icon({
+ anchor: [0.5, 1],
+ src: '../images/loc_blue.png',
+ scale: 0.7
+ })
+ });
+ }
+
+ var deviceInfo = feature.get('deviceInfo');
+ if (!deviceInfo) return null;
+
+ var iconSrc;
+ var color = '#000';
+ var isHovered = feature.get('hovered') === true;
+ var scale = isHovered ? 0.85 : 0.7;
+
+ // 根据告警级别选择图标
+ if (deviceInfo.warning == 2) {
+ iconSrc = '../images/loc1_red.png';
+ } else if (deviceInfo.warning == 1) {
+ iconSrc = '../images/loc1_orange.png';
+ } else {
+ iconSrc = '../images/loc1_green.png';
+ }
+
+ var style = new ol.style.Style({
+ image: new ol.style.Icon({
+ anchor: [0.5, 1],
+ src: iconSrc,
+ scale: scale
+ })
+ });
+
+ // 根据缩放级别和设置决定是否显示设备ID
+ if (showDeviceId && map && map.getView().getZoom() >= minZoomForLabels) {
+ style.setText(new ol.style.Text({
+ text: deviceInfo.deviceid,
+ offsetY: -30,
+ fill: new ol.style.Fill({
+ color: isHovered ? '#1aa094' : color
+ }),
+ stroke: new ol.style.Stroke({
+ color: '#fff',
+ width: isHovered ? 3 : 2
+ }),
+ font: isHovered ? 'bold 12px Arial' : '12px Arial'
+ }));
+ }
+
+ return style;
+ }
+
+ /**
+ * 添加设备标记到地图
+ * @param {Array} deviceList - 设备列表
+ */
+ function addDeviceMarkers(deviceList) {
+ if (!vectorSource || !deviceList) return;
+
+ // 保存当前位置标记
+ var savedMyLocationFeature = myLocationFeature;
+
+ // 清空现有标记
+ vectorSource.clear();
+ greenFeatures = [];
+ orangeFeatures = [];
+ redFeatures = [];
+ allFeatures = [];
+
+ // 添加设备标记
+ for (var i = 0; i < deviceList.length; i++) {
+ var device = deviceList[i];
+ var currentMapType = getCurrentMapType();
+ var mapCoordinates = CoordinateUtils.getMapCoordinates(
+ device.latitude,
+ device.longitude,
+ currentMapType
+ );
+
+ var feature = new ol.Feature({
+ geometry: new ol.geom.Point(mapCoordinates),
+ deviceInfo: device
+ });
+
+ // 按告警级别分类
+ if (device.warning == 2) {
+ redFeatures.push(feature);
+ } else if (device.warning == 1) {
+ orangeFeatures.push(feature);
+ } else {
+ greenFeatures.push(feature);
+ }
+
+ allFeatures.push(feature);
+ vectorSource.addFeature(feature);
+ }
+
+ // 恢复位置标记
+ if (savedMyLocationFeature) {
+ vectorSource.addFeature(savedMyLocationFeature);
+ }
+
+ // 强制更新样式
+ if (vectorLayer) {
+ vectorLayer.changed();
+ }
+ }
+
+ /**
+ * 获取当前地图类型
+ * @returns {string} 地图类型
+ */
+ function getCurrentMapType() {
+ var mapTypeSelect = document.getElementById('mapTypeSelectNew');
+ return mapTypeSelect ? mapTypeSelect.value : 'tianditu_satellite';
+ }
+
+ /**
+ * 显示所有设备
+ */
+ function showAllDevices() {
+ if (!vectorSource) return;
+
+ var savedMyLocationFeature = myLocationFeature;
+ vectorSource.clear();
+
+ if (savedMyLocationFeature) {
+ vectorSource.addFeature(savedMyLocationFeature);
+ }
+
+ for (var i = 0; i < allFeatures.length; i++) {
+ vectorSource.addFeature(allFeatures[i]);
+ }
+ }
+
+ /**
+ * 只显示一般告警设备
+ */
+ function showWarning1Devices() {
+ if (!vectorSource) return;
+
+ var savedMyLocationFeature = myLocationFeature;
+ vectorSource.clear();
+
+ if (savedMyLocationFeature) {
+ vectorSource.addFeature(savedMyLocationFeature);
+ }
+
+ for (var i = 0; i < allFeatures.length; i++) {
+ vectorSource.addFeature(allFeatures[i]);
+ }
+
+ hideGreenFeatures();
+ hideRedFeatures();
+ }
+
+ /**
+ * 只显示严重告警设备
+ */
+ function showWarning2Devices() {
+ if (!vectorSource) return;
+
+ var savedMyLocationFeature = myLocationFeature;
+ vectorSource.clear();
+
+ if (savedMyLocationFeature) {
+ vectorSource.addFeature(savedMyLocationFeature);
+ }
+
+ for (var i = 0; i < allFeatures.length; i++) {
+ vectorSource.addFeature(allFeatures[i]);
+ }
+
+ hideGreenFeatures();
+ hideOrangeFeatures();
+ }
+
+ /**
+ * 隐藏绿色(正常)设备
+ */
+ function hideGreenFeatures() {
+ for (var i = 0; i < greenFeatures.length; i++) {
+ vectorSource.removeFeature(greenFeatures[i]);
+ }
+ }
+
+ /**
+ * 隐藏橙色(一般告警)设备
+ */
+ function hideOrangeFeatures() {
+ for (var i = 0; i < orangeFeatures.length; i++) {
+ vectorSource.removeFeature(orangeFeatures[i]);
+ }
+ }
+
+ /**
+ * 隐藏红色(严重告警)设备
+ */
+ function hideRedFeatures() {
+ for (var i = 0; i < redFeatures.length; i++) {
+ vectorSource.removeFeature(redFeatures[i]);
+ }
+ }
+
+ /**
+ * 根据设备ID查找设备
+ * @param {string} deviceId - 设备ID
+ * @returns {ol.Feature|null} 找到的设备要素
+ */
+ function findDeviceById(deviceId) {
+ for (var i = 0; i < allFeatures.length; i++) {
+ var feature = allFeatures[i];
+ var deviceInfo = feature.get('deviceInfo');
+ if (deviceInfo && deviceInfo.deviceid.includes(deviceId)) {
+ return feature;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 定位到指定设备
+ * @param {string} deviceId - 设备ID
+ * @returns {boolean} 是否成功定位
+ */
+ function locateDevice(deviceId) {
+ var targetFeature = findDeviceById(deviceId);
+
+ if (targetFeature) {
+ var geometry = targetFeature.getGeometry();
+ var deviceCoord = geometry.getCoordinates();
+
+ if (map) {
+ map.getView().animate({
+ center: deviceCoord,
+ zoom: 16,
+ duration: 800
+ });
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取我的位置
+ */
+ function getMyLocation() {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(function(position) {
+ var lon = position.coords.longitude;
+ var lat = position.coords.latitude;
+ var currentMapType = getCurrentMapType();
+ var coordinates = CoordinateUtils.getMapCoordinates(lat, lon, currentMapType);
+
+ // 如果已经存在位置标记,则先移除
+ if (myLocationFeature) {
+ vectorSource.removeFeature(myLocationFeature);
+ }
+
+ myLocationFeature = new ol.Feature({
+ geometry: new ol.geom.Point(coordinates),
+ isMyLocation: true,
+ originalCoords: {lat: lat, lon: lon}
+ });
+
+ myLocationFeature.setStyle(createDeviceStyle(myLocationFeature));
+ vectorSource.addFeature(myLocationFeature);
+
+ if (map) {
+ map.getView().setCenter(coordinates);
+ }
+ });
+ }
+ }
+
+ /**
+ * 开始定期更新位置
+ */
+ function startLocationUpdates() {
+ if (myLocationInterval) {
+ clearInterval(myLocationInterval);
+ }
+
+ myLocationInterval = setInterval(function() {
+ getMyLocation();
+ }, 10 * 60 * 1000); // 10分钟更新一次
+ }
+
+ /**
+ * 停止位置更新
+ */
+ function stopLocationUpdates() {
+ if (myLocationInterval) {
+ clearInterval(myLocationInterval);
+ myLocationInterval = null;
+ }
+ }
+
+ /**
+ * 根据地图类型更新我的位置标记
+ * @param {string} mapType - 地图类型
+ */
+ function updateMyLocationForMapType(mapType) {
+ if (myLocationFeature) {
+ var originalCoords = myLocationFeature.get('originalCoords');
+ if (originalCoords) {
+ var coordinates = CoordinateUtils.getMapCoordinates(
+ originalCoords.lat,
+ originalCoords.lon,
+ mapType
+ );
+ myLocationFeature.getGeometry().setCoordinates(coordinates);
+ }
+ }
+ }
+
+ /**
+ * 设置是否显示设备ID
+ * @param {boolean} show - 是否显示
+ */
+ function setShowDeviceId(show) {
+ showDeviceId = show;
+ if (vectorLayer) {
+ vectorLayer.changed();
+ }
+ }
+
+ /**
+ * 获取各类设备数量统计
+ * @returns {Object} 统计信息
+ */
+ function getDeviceStats() {
+ return {
+ total: allFeatures.length,
+ green: greenFeatures.length,
+ orange: orangeFeatures.length,
+ red: redFeatures.length
+ };
+ }
+
+ // 公开API
+ return {
+ init: init,
+ createDeviceStyle: createDeviceStyle,
+ addDeviceMarkers: addDeviceMarkers,
+ showAllDevices: showAllDevices,
+ showWarning1Devices: showWarning1Devices,
+ showWarning2Devices: showWarning2Devices,
+ hideGreenFeatures: hideGreenFeatures,
+ hideOrangeFeatures: hideOrangeFeatures,
+ hideRedFeatures: hideRedFeatures,
+ findDeviceById: findDeviceById,
+ locateDevice: locateDevice,
+ getMyLocation: getMyLocation,
+ startLocationUpdates: startLocationUpdates,
+ stopLocationUpdates: stopLocationUpdates,
+ updateMyLocationForMapType: updateMyLocationForMapType,
+ setShowDeviceId: setShowDeviceId,
+ getDeviceStats: getDeviceStats,
+
+ // 获取器方法
+ getAllFeatures: function() { return allFeatures; },
+ getGreenFeatures: function() { return greenFeatures; },
+ getOrangeFeatures: function() { return orangeFeatures; },
+ getRedFeatures: function() { return redFeatures; },
+ getMyLocationFeature: function() { return myLocationFeature; }
+ };
+})();
\ No newline at end of file
diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/device-overview-main.js b/sec-beidou/src/main/resources/static/js/device-overview-module/device-overview-main.js
new file mode 100644
index 00000000..b20a2b18
--- /dev/null
+++ b/sec-beidou/src/main/resources/static/js/device-overview-module/device-overview-main.js
@@ -0,0 +1,218 @@
+/**
+ * 设备总览主入口文件
+ * 负责整个设备总览模块的初始化和全局函数暴露
+ */
+var DeviceOverview = (function() {
+ 'use strict';
+
+ // 全局变量(从原HTML中提取)
+ var deviceList = [];
+
+ /**
+ * 初始化设备总览模块
+ * @param {Array} devices - 设备列表数据
+ * @param {Object} options - 配置选项
+ */
+ function init(devices, options) {
+ deviceList = devices || [];
+
+ // 设置全局变量供其他模块使用
+ window.deviceList = deviceList;
+ window.userRole = options && options.role ? options.role : 'USER';
+
+ // 等待layui加载完成后初始化
+ layui.use(['form'], function(){
+ var form = layui.form;
+
+ // 绑定表单事件
+ form.on('select(mapTypeNew)', function(data){
+ MapCore.switchMapType(data.value);
+ });
+
+ // 初始化地图核心
+ MapCore.initialize(deviceList);
+
+ // 默认显示所有设备
+ document.getElementById('warningFilter').value = 'all';
+ SearchFilter.showAllDevices();
+ });
+ }
+
+ // 暴露给HTML使用的全局函数
+
+ /**
+ * 地图类型变化处理
+ */
+ function onMapTypeChange() {
+ return MapCore.onMapTypeChange();
+ }
+
+ /**
+ * 搜索设备
+ */
+ function searchDeviceNew() {
+ return MapCore.searchDeviceNew();
+ }
+
+ /**
+ * 告警过滤变化处理
+ */
+ function onWarningFilterChange() {
+ return MapCore.onWarningFilterChange();
+ }
+
+ /**
+ * 切换地图功能菜单
+ */
+ function toggleMapFunctionsMenu() {
+ return MapCore.toggleMapFunctionsMenu();
+ }
+
+ /**
+ * 切换设备ID显示
+ */
+ function toggleDeviceId() {
+ return MapCore.toggleDeviceId();
+ }
+
+ /**
+ * 切换集群显示
+ */
+ function toggleCluster() {
+ return MapCore.toggleCluster();
+ }
+
+ /**
+ * 切换测距功能
+ */
+ function toggleMeasureDistance() {
+ return MeasureTools.toggleMeasureDistance();
+ }
+
+ /**
+ * 完成测量
+ */
+ function finishMeasuring() {
+ return MeasureTools.finishMeasuring();
+ }
+
+ /**
+ * 清除测距
+ */
+ function clearMeasure() {
+ return MeasureTools.clearMeasure();
+ }
+
+ /**
+ * 切换天气预报功能
+ */
+ function toggleWeatherForecast() {
+ return WeatherForecast.toggleWeatherForecast();
+ }
+
+ /**
+ * 关闭天气卡片
+ */
+ function closeWeatherCard() {
+ return WeatherForecast.closeWeatherCard();
+ }
+
+ /**
+ * 显示上一个天气预报
+ */
+ function showPrevForecast() {
+ return WeatherForecast.showPrevForecast();
+ }
+
+ /**
+ * 显示下一个天气预报
+ */
+ function showNextForecast() {
+ return WeatherForecast.showNextForecast();
+ }
+
+ /**
+ * 查询设备
+ * @param {string} statusType - 状态类型
+ */
+ function queryDevices(statusType) {
+ return SearchFilter.queryDevices(statusType);
+ }
+
+ /**
+ * 定位设备到地图
+ * @param {string} deviceId - 设备ID
+ * @param {number} latitude - 纬度
+ * @param {number} longitude - 经度
+ */
+ function locateDeviceOnMap(deviceId, latitude, longitude) {
+ return SearchFilter.locateDeviceOnMap(deviceId, latitude, longitude);
+ }
+
+ /**
+ * 直接定位设备
+ * @param {string} deviceId - 设备ID
+ */
+ function locateDeviceDirectly(deviceId) {
+ return SearchFilter.locateDeviceDirectly(deviceId);
+ }
+
+ /**
+ * 切换地图类型
+ * @param {string} mapType - 地图类型
+ */
+ function switchMapType(mapType) {
+ return MapCore.switchMapType(mapType);
+ }
+
+ // 公开API
+ return {
+ init: init,
+
+ // 地图相关
+ onMapTypeChange: onMapTypeChange,
+ switchMapType: switchMapType,
+
+ // 搜索和过滤
+ searchDeviceNew: searchDeviceNew,
+ onWarningFilterChange: onWarningFilterChange,
+ queryDevices: queryDevices,
+ locateDeviceOnMap: locateDeviceOnMap,
+ locateDeviceDirectly: locateDeviceDirectly,
+
+ // 地图功能
+ toggleMapFunctionsMenu: toggleMapFunctionsMenu,
+ toggleDeviceId: toggleDeviceId,
+ toggleCluster: toggleCluster,
+
+ // 测距工具
+ toggleMeasureDistance: toggleMeasureDistance,
+ finishMeasuring: finishMeasuring,
+ clearMeasure: clearMeasure,
+
+ // 天气预报
+ toggleWeatherForecast: toggleWeatherForecast,
+ closeWeatherCard: closeWeatherCard,
+ showPrevForecast: showPrevForecast,
+ showNextForecast: showNextForecast
+ };
+})();
+
+// 将主要函数暴露到全局作用域,供HTML中的onclick等使用
+window.DeviceOverview = DeviceOverview;
+window.onMapTypeChange = DeviceOverview.onMapTypeChange;
+window.searchDeviceNew = DeviceOverview.searchDeviceNew;
+window.onWarningFilterChange = DeviceOverview.onWarningFilterChange;
+window.toggleMapFunctionsMenu = DeviceOverview.toggleMapFunctionsMenu;
+window.toggleDeviceId = DeviceOverview.toggleDeviceId;
+window.toggleCluster = DeviceOverview.toggleCluster;
+window.toggleMeasureDistance = DeviceOverview.toggleMeasureDistance;
+window.finishMeasuring = DeviceOverview.finishMeasuring;
+window.clearMeasure = DeviceOverview.clearMeasure;
+window.toggleWeatherForecast = DeviceOverview.toggleWeatherForecast;
+window.closeWeatherCard = DeviceOverview.closeWeatherCard;
+window.showPrevForecast = DeviceOverview.showPrevForecast;
+window.showNextForecast = DeviceOverview.showNextForecast;
+window.queryDevices = DeviceOverview.queryDevices;
+window.locateDeviceOnMap = DeviceOverview.locateDeviceOnMap;
+window.locateDeviceDirectly = DeviceOverview.locateDeviceDirectly;
\ No newline at end of file
diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/map-core.js b/sec-beidou/src/main/resources/static/js/device-overview-module/map-core.js
new file mode 100644
index 00000000..6df0aa57
--- /dev/null
+++ b/sec-beidou/src/main/resources/static/js/device-overview-module/map-core.js
@@ -0,0 +1,421 @@
+/**
+ * 地图核心模块
+ * 负责地图初始化、事件处理和核心功能管理
+ */
+var MapCore = (function() {
+ 'use strict';
+
+ // 私有变量
+ var map = null;
+ var vectorSource = null;
+ var vectorLayer = null;
+ var clusterSource = null;
+ var clusterLayer = null;
+ var currentBaseLayer = null;
+ var showDeviceId = true;
+ var showCluster = true;
+ var minZoomForLabels = 4;
+ var maxZoomForClustering = 8;
+ var hoveredFeature = null;
+
+ /**
+ * 初始化地图
+ * @param {Array} deviceList - 设备列表
+ */
+ function initialize(deviceList) {
+ // 创建矢量数据源和图层
+ vectorSource = new ol.source.Vector();
+ vectorLayer = new ol.layer.Vector({
+ source: vectorSource,
+ style: function(feature) {
+ return DeviceMarkers.createDeviceStyle(feature);
+ }
+ });
+
+ // 创建集群数据源和图层
+ clusterSource = new ol.source.Cluster({
+ distance: 40,
+ source: vectorSource
+ });
+
+ clusterLayer = new ol.layer.Vector({
+ source: clusterSource,
+ style: function(feature) {
+ var size = feature.get('features').length;
+ var style = new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 15,
+ fill: new ol.style.Fill({
+ color: '#3399CC'
+ })
+ }),
+ text: new ol.style.Text({
+ text: size.toString(),
+ fill: new ol.style.Fill({
+ color: '#fff'
+ })
+ })
+ });
+ return style;
+ }
+ });
+
+ // 获取初始地图类型
+ var initialMapType = document.getElementById('mapTypeSelectNew').value || 'tianditu_satellite';
+ currentBaseLayer = MapLayers.getLayer(initialMapType);
+
+ // 创建地图
+ map = new ol.Map({
+ target: 'map-container',
+ layers: [
+ currentBaseLayer,
+ clusterLayer,
+ vectorLayer
+ ],
+ view: new ol.View({
+ center: ol.proj.fromLonLat([116.404, 39.915]),
+ zoom: 7
+ })
+ });
+
+ // 初始化各个模块
+ DeviceMarkers.init(map, vectorSource, vectorLayer);
+ MeasureTools.init(map);
+ WeatherForecast.init();
+
+ // 添加比例尺控件
+ var scaleLineControl = new ol.control.ScaleLine();
+ map.addControl(scaleLineControl);
+
+ // 设置初始图层可见性
+ var initialZoom = map.getView().getZoom();
+ updateLayerVisibility(initialZoom);
+
+ // 绑定缩放变化事件
+ map.getView().on('change:resolution', function() {
+ var zoom = map.getView().getZoom();
+ updateLayerVisibility(zoom);
+ vectorLayer.changed();
+ });
+
+ // 绑定鼠标事件
+ bindMouseEvents();
+
+ // 设置地图中心点
+ if (deviceList && deviceList.length > 0) {
+ setCenterFromDevices(deviceList);
+ }
+
+ // 添加设备标记
+ DeviceMarkers.addDeviceMarkers(deviceList);
+
+ // 获取我的位置并开始位置更新
+ DeviceMarkers.getMyLocation();
+ DeviceMarkers.startLocationUpdates();
+
+ // 设置初始开关状态
+ document.getElementById('showDeviceIdSwitch').checked = showDeviceId;
+ document.getElementById('showClusterSwitch').checked = showCluster;
+ }
+
+ /**
+ * 更新图层可见性
+ * @param {number} zoom - 缩放级别
+ */
+ function updateLayerVisibility(zoom) {
+ if (showCluster) {
+ if (zoom >= maxZoomForClustering) {
+ clusterLayer.setVisible(false);
+ vectorLayer.setVisible(true);
+ } else {
+ clusterLayer.setVisible(true);
+ vectorLayer.setVisible(false);
+ }
+ } else {
+ clusterLayer.setVisible(false);
+ vectorLayer.setVisible(true);
+ }
+ }
+
+ /**
+ * 绑定鼠标事件
+ */
+ function bindMouseEvents() {
+ // 鼠标移动事件
+ map.on('pointermove', function(evt) {
+ if (evt.dragging) {
+ return;
+ }
+
+ var pixel = map.getEventPixel(evt.originalEvent);
+ var hit = map.hasFeatureAtPixel(pixel);
+
+ map.getTargetElement().style.cursor = hit ? 'pointer' : '';
+
+ // 处理悬停效果
+ var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
+ return feature;
+ });
+
+ if (hoveredFeature && hoveredFeature !== feature) {
+ hoveredFeature.set('hovered', false);
+ }
+
+ if (feature) {
+ // 处理集群
+ var features = feature.get('features');
+ if (features && features.length === 1) {
+ features[0].set('hovered', true);
+ hoveredFeature = features[0];
+ } else if (!features && feature.get('deviceInfo')) {
+ feature.set('hovered', true);
+ hoveredFeature = feature;
+ }
+ }
+
+ vectorLayer.changed();
+ });
+
+ // 点击事件
+ map.on('click', function(evt) {
+ var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) {
+ return feature;
+ });
+
+ 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) {
+ showDeviceInfo(deviceInfo);
+ // 如果天气预测开启,显示天气卡片
+ if (WeatherForecast.isEnabled()) {
+ WeatherForecast.showWeatherForecast(deviceInfo);
+ }
+ }
+ } else if (feature.get('deviceInfo')) {
+ // 直接点击设备标记
+ var deviceInfo = feature.get('deviceInfo');
+ showDeviceInfo(deviceInfo);
+ // 如果天气预测开启,显示天气卡片
+ if (WeatherForecast.isEnabled()) {
+ WeatherForecast.showWeatherForecast(deviceInfo);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * 显示设备信息
+ * @param {Object} deviceInfo - 设备信息
+ */
+ function showDeviceInfo(deviceInfo) {
+ var infoMsg = '
' +
+ '
设备编号: ' + deviceInfo.deviceid + '
' +
+ '
坐标: ' + deviceInfo.latitude.toFixed(6) + ', ' + deviceInfo.longitude.toFixed(6) + '
';
+
+ if (deviceInfo.warning === 2) {
+ infoMsg += '
状态: 严重告警
';
+ } else if (deviceInfo.warning === 1) {
+ infoMsg += '
状态: 一般告警
';
+ } else {
+ infoMsg += '
状态: 正常
';
+ }
+
+ infoMsg += '
';
+
+ if (window.layer && typeof window.layer.open === 'function') {
+ window.layer.open({
+ type: 1,
+ title: '设备信息',
+ area: ['300px', 'auto'],
+ shade: 0,
+ offset: 'auto',
+ content: infoMsg,
+ time: 3000,
+ anim: 2
+ });
+ }
+ }
+
+ /**
+ * 根据设备列表设置地图中心点
+ * @param {Array} deviceList - 设备列表
+ */
+ function setCenterFromDevices(deviceList) {
+ if (!deviceList || deviceList.length === 0) return;
+
+ var centerLat = 0;
+ var centerLon = 0;
+
+ for (var i = 0; i < deviceList.length; i++) {
+ centerLat += deviceList[i].latitude;
+ centerLon += deviceList[i].longitude;
+ }
+
+ centerLat = centerLat / deviceList.length;
+ centerLon = centerLon / deviceList.length;
+
+ map.getView().setCenter(ol.proj.fromLonLat([centerLon, centerLat]));
+ }
+
+ /**
+ * 切换地图类型
+ * @param {string} mapType - 地图类型
+ */
+ function switchMapType(mapType) {
+ if (!MapLayers.hasLayer(mapType)) {
+ console.error('未知的地图类型:', mapType);
+ return;
+ }
+
+ map.removeLayer(currentBaseLayer);
+ currentBaseLayer = MapLayers.getLayer(mapType);
+ map.getLayers().insertAt(0, currentBaseLayer);
+
+ DeviceMarkers.updateMyLocationForMapType(mapType);
+
+ // 重新获取设备列表并添加标记
+ var deviceList = window.deviceList || [];
+ DeviceMarkers.addDeviceMarkers(deviceList);
+ }
+
+ /**
+ * 切换设备ID显示
+ */
+ function toggleDeviceId() {
+ showDeviceId = document.getElementById('showDeviceIdSwitch').checked;
+ DeviceMarkers.setShowDeviceId(showDeviceId);
+
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg(showDeviceId ? '已显示设备信息' : '已隐藏设备信息');
+ }
+ }
+
+ /**
+ * 切换集群显示
+ */
+ function toggleCluster() {
+ showCluster = document.getElementById('showClusterSwitch').checked;
+ var zoom = map.getView().getZoom();
+ updateLayerVisibility(zoom);
+
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg(showCluster ? '已启用集群显示' : '已禁用集群显示');
+ }
+ }
+
+ /**
+ * 切换地图功能菜单
+ */
+ function toggleMapFunctionsMenu() {
+ var menu = document.getElementById('mapFunctionsMenu');
+ if (menu) {
+ menu.classList.toggle('show');
+ if (menu.classList.contains('show')) {
+ document.addEventListener('click', closeMapFunctionsMenu);
+ }
+ }
+ }
+
+ /**
+ * 关闭地图功能菜单
+ * @param {Event} event - 点击事件
+ */
+ function closeMapFunctionsMenu(event) {
+ var dropdown = document.getElementById('mapFunctionsMenu');
+ var toggleBtn = document.querySelector('.dropdown-toggle');
+
+ if (dropdown && !dropdown.contains(event.target) && !toggleBtn.contains(event.target)) {
+ dropdown.classList.remove('show');
+ document.removeEventListener('click', closeMapFunctionsMenu);
+ }
+ }
+
+ /**
+ * 地图类型变化处理
+ */
+ function onMapTypeChange() {
+ var mapType = document.getElementById('mapTypeSelectNew').value;
+ switchMapType(mapType);
+ }
+
+ /**
+ * 告警过滤变化处理
+ */
+ function onWarningFilterChange() {
+ var filterValue = document.getElementById('warningFilter').value;
+
+ if (filterValue === 'all') {
+ SearchFilter.showAllDevices();
+ } else if (filterValue === 'warning1') {
+ SearchFilter.showWarning1Devices();
+ } else if (filterValue === 'warning2') {
+ SearchFilter.showWarning2Devices();
+ }
+ }
+
+ /**
+ * 搜索设备
+ */
+ function searchDeviceNew() {
+ var deviceId = document.getElementById('deviceSearchNew').value.trim();
+
+ if (!deviceId) {
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('请输入设备编号');
+ }
+ return;
+ }
+
+ SearchFilter.searchDevice(deviceId);
+ }
+
+ /**
+ * 获取地图实例
+ * @returns {ol.Map} 地图实例
+ */
+ function getMap() {
+ return map;
+ }
+
+ /**
+ * 获取矢量数据源
+ * @returns {ol.source.Vector} 矢量数据源
+ */
+ function getVectorSource() {
+ return vectorSource;
+ }
+
+ /**
+ * 获取矢量图层
+ * @returns {ol.layer.Vector} 矢量图层
+ */
+ function getVectorLayer() {
+ return vectorLayer;
+ }
+
+ // 公开API
+ return {
+ initialize: initialize,
+ switchMapType: switchMapType,
+ toggleDeviceId: toggleDeviceId,
+ toggleCluster: toggleCluster,
+ toggleMapFunctionsMenu: toggleMapFunctionsMenu,
+ onMapTypeChange: onMapTypeChange,
+ onWarningFilterChange: onWarningFilterChange,
+ searchDeviceNew: searchDeviceNew,
+ getMap: getMap,
+ getVectorSource: getVectorSource,
+ getVectorLayer: getVectorLayer
+ };
+})();
\ No newline at end of file
diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/map-layers.js b/sec-beidou/src/main/resources/static/js/device-overview-module/map-layers.js
new file mode 100644
index 00000000..e97161af
--- /dev/null
+++ b/sec-beidou/src/main/resources/static/js/device-overview-module/map-layers.js
@@ -0,0 +1,195 @@
+/**
+ * 地图图层管理模块
+ * 管理不同类型的地图图层(天地图、高德、谷歌等)
+ */
+var MapLayers = (function() {
+ 'use strict';
+
+ // 天地图 API 密钥
+ var TIANDITU_KEY = '0c260b8a094a4e0bc507808812cefdac';
+
+ /**
+ * 创建天地图瓦片加载函数,包含错误处理
+ */
+ function createTiandituTileLoadFunction() {
+ return function(imageTile, src) {
+ imageTile.getImage().src = src;
+ imageTile.getImage().onerror = function() {
+ // 天地图加载失败时切换到高德地图
+ var mapTypeSelect = document.getElementById('mapTypeSelectNew');
+ if(mapTypeSelect && mapTypeSelect.value.startsWith('tianditu_')) {
+ mapTypeSelect.value = 'amap';
+ if (window.DeviceOverview && typeof window.DeviceOverview.switchMapType === 'function') {
+ window.DeviceOverview.switchMapType('amap');
+ }
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('天地图加载失败,已自动切换到高德地图');
+ }
+ // 重新渲染layui表单
+ if (window.layui && window.layui.form) {
+ window.layui.form.render('select');
+ }
+ }
+ };
+ };
+ }
+
+ /**
+ * 地图图层配置
+ */
+ var mapLayers = {
+ // 高德地图
+ amap: new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
+ })
+ }),
+
+ // 高德卫星图
+ amap_satellite: new ol.layer.Group({
+ layers: [
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
+ })
+ }),
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}'
+ })
+ })
+ ]
+ }),
+
+ // 谷歌卫星图
+ google_satellite: new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://mt{0-3}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
+ crossOrigin: 'anonymous'
+ })
+ }),
+
+ // 谷歌地形图
+ google_terrain: new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://mt{0-3}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}',
+ crossOrigin: 'anonymous'
+ })
+ }),
+
+ // 谷歌道路图
+ google_roadmap: new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://mt{0-3}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
+ crossOrigin: 'anonymous'
+ })
+ }),
+
+ // 谷歌混合图
+ google_hybrid: new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://mt{0-3}.google.com/vt/lyrs=y&x={x}&y={y}&z={z}',
+ crossOrigin: 'anonymous'
+ })
+ }),
+
+ // 天地图卫星影像
+ tianditu_satellite: new ol.layer.Group({
+ layers: [
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://t{0-7}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
+ tileLoadFunction: createTiandituTileLoadFunction()
+ })
+ }),
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://t{0-7}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
+ tileLoadFunction: createTiandituTileLoadFunction()
+ })
+ })
+ ]
+ }),
+
+ // 天地图矢量图
+ tianditu_normal: new ol.layer.Group({
+ layers: [
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://t{0-7}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
+ tileLoadFunction: createTiandituTileLoadFunction()
+ })
+ }),
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://t{0-7}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
+ tileLoadFunction: createTiandituTileLoadFunction()
+ })
+ })
+ ]
+ }),
+
+ // 天地图地形图
+ tianditu_terrain: new ol.layer.Group({
+ layers: [
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
+ tileLoadFunction: createTiandituTileLoadFunction()
+ })
+ })
+ ]
+ }),
+
+ // 天地图地形混合图
+ tianditu_terrain_hybrid: new ol.layer.Group({
+ layers: [
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
+ tileLoadFunction: createTiandituTileLoadFunction()
+ })
+ }),
+ new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: 'https://t{0-7}.tianditu.gov.cn/cta_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
+ tileLoadFunction: createTiandituTileLoadFunction()
+ })
+ })
+ ]
+ })
+ };
+
+ /**
+ * 获取指定类型的地图图层
+ * @param {string} mapType - 地图类型
+ * @returns {ol.layer.Base} 地图图层
+ */
+ function getLayer(mapType) {
+ return mapLayers[mapType];
+ }
+
+ /**
+ * 获取所有可用的地图图层
+ * @returns {Object} 所有地图图层对象
+ */
+ function getAllLayers() {
+ return mapLayers;
+ }
+
+ /**
+ * 检查地图类型是否存在
+ * @param {string} mapType - 地图类型
+ * @returns {boolean} 是否存在
+ */
+ function hasLayer(mapType) {
+ return mapLayers.hasOwnProperty(mapType);
+ }
+
+ // 公开API
+ return {
+ getLayer: getLayer,
+ getAllLayers: getAllLayers,
+ hasLayer: hasLayer
+ };
+})();
\ No newline at end of file
diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/measure-tools.js b/sec-beidou/src/main/resources/static/js/device-overview-module/measure-tools.js
new file mode 100644
index 00000000..48d224d5
--- /dev/null
+++ b/sec-beidou/src/main/resources/static/js/device-overview-module/measure-tools.js
@@ -0,0 +1,363 @@
+/**
+ * 测距工具模块
+ * 提供地图测距功能
+ */
+var MeasureTools = (function() {
+ 'use strict';
+
+ // 私有变量
+ var measureActive = false;
+ var measureDraw = null;
+ var measureSource = null;
+ var measureLayer = null;
+ var measureTooltips = [];
+ var measureFeatures = [];
+ var currentMeasureTooltips = [];
+ var currentSketch = null;
+ var currentListener = null;
+ var segmentTooltips = [];
+ var map = null;
+
+ /**
+ * 初始化测距工具
+ * @param {ol.Map} mapInstance - 地图实例
+ */
+ function init(mapInstance) {
+ map = mapInstance;
+
+ // 创建测距专用图层
+ measureSource = new ol.source.Vector();
+ measureLayer = new ol.layer.Vector({
+ source: measureSource,
+ style: new ol.style.Style({
+ fill: new ol.style.Fill({ color: 'rgba(255,255,255,0.2)' }),
+ stroke: new ol.style.Stroke({ color: '#ffcc33', width: 2 }),
+ image: new ol.style.RegularShape({
+ points: 4,
+ radius: 8,
+ radius2: 0,
+ angle: Math.PI / 4,
+ stroke: new ol.style.Stroke({ color: '#ed8936', width: 2 })
+ })
+ })
+ });
+
+ if (map) {
+ map.addLayer(measureLayer);
+ }
+ }
+
+ /**
+ * 切换测距功能
+ */
+ function toggleMeasureDistance() {
+ if (measureActive) {
+ deactivateMeasure();
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('测距功能已关闭');
+ }
+ } else {
+ activateMeasure();
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('点击左键添加距离节点,点击右键结束测量 :)');
+ }
+ }
+
+ // 关闭地图功能菜单
+ var menu = document.getElementById('mapFunctionsMenu');
+ if (menu) {
+ menu.classList.remove('show');
+ }
+ }
+
+ /**
+ * 完成当前测量
+ */
+ function finishMeasuring() {
+ if (measureActive && currentSketch && measureDraw) {
+ measureDraw.finishDrawing();
+ }
+ }
+
+ /**
+ * 激活测距功能
+ */
+ function activateMeasure() {
+ if (!map || !measureSource) return;
+
+ measureActive = true;
+
+ // 显示测量状态指示器
+ var measureStatus = document.getElementById('measureStatus');
+ if (measureStatus) {
+ measureStatus.style.display = 'flex';
+ }
+
+ // 创建绘制交互
+ measureDraw = new ol.interaction.Draw({
+ source: measureSource,
+ type: 'LineString',
+ style: new ol.style.Style({
+ fill: new ol.style.Fill({ color: 'rgba(255,255,255,0.2)' }),
+ stroke: new ol.style.Stroke({ color: '#ffcc33', width: 2 }),
+ image: new ol.style.RegularShape({
+ points: 4,
+ radius: 8,
+ radius2: 0,
+ angle: Math.PI / 4,
+ stroke: new ol.style.Stroke({ color: '#ed8936', width: 2 })
+ })
+ })
+ });
+
+ map.addInteraction(measureDraw);
+
+ // 添加右键菜单事件监听
+ map.getViewport().addEventListener('contextmenu', function(e) {
+ if (measureActive && currentSketch) {
+ e.preventDefault();
+ measureDraw.finishDrawing();
+ }
+ });
+
+ // 绘制开始事件
+ measureDraw.on('drawstart', function(evt) {
+ currentSketch = evt.feature;
+ currentMeasureTooltips = [];
+ segmentTooltips = [];
+
+ // 监听几何变化
+ currentListener = currentSketch.getGeometry().on('change', function(e) {
+ var geom = e.target;
+ var coords = geom.getCoordinates();
+
+ clearTemporaryTooltips();
+ clearSegmentTooltips();
+
+ // 计算并显示每个节点的距离
+ var total = 0;
+ for (var i = 0; i < coords.length; i++) {
+ if (i === 0) {
+ // 起点
+ var startTooltip = createMeasureTooltip(coords[0], '起点');
+ currentMeasureTooltips.push(startTooltip);
+ } else {
+ // 计算段距离
+ var seg = new ol.geom.LineString([coords[i-1], coords[i]]);
+ var segmentLength = ol.sphere.getLength(seg);
+ total += segmentLength;
+
+ // 显示每个节点的累计距离
+ var output = formatLength(total);
+ var tooltip = createMeasureTooltip(coords[i], output);
+ segmentTooltips.push(tooltip);
+
+ if (i === coords.length - 1) {
+ currentMeasureTooltips.push(tooltip);
+ }
+ }
+ }
+ });
+ });
+
+ // 绘制结束事件
+ measureDraw.on('drawend', function(evt) {
+ var coords = evt.feature.getGeometry().getCoordinates();
+
+ clearTemporaryTooltips();
+ clearSegmentTooltips();
+
+ // 创建永久性的测量标记
+ var total = 0;
+ for (var i = 0; i < coords.length; i++) {
+ if (i === 0) {
+ var startTooltip = createMeasureTooltip(coords[0], '起点', true);
+ measureTooltips.push(startTooltip);
+ } else {
+ var seg = new ol.geom.LineString([coords[i-1], coords[i]]);
+ total += ol.sphere.getLength(seg);
+
+ var output = formatLength(total);
+ var tooltip = createMeasureTooltip(coords[i], output, true);
+ measureTooltips.push(tooltip);
+ }
+ }
+
+ measureFeatures.push(evt.feature);
+
+ // 清理监听器
+ if (currentListener) {
+ ol.Observable.unByKey(currentListener);
+ }
+ currentSketch = null;
+ currentListener = null;
+
+ // 隐藏测量状态指示器
+ var measureStatus = document.getElementById('measureStatus');
+ if (measureStatus) {
+ measureStatus.style.display = 'none';
+ }
+
+ // 移除绘制交互
+ map.removeInteraction(measureDraw);
+ measureActive = false;
+
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('测量完成');
+ }
+ });
+ }
+
+ /**
+ * 停用测距功能
+ */
+ function deactivateMeasure() {
+ measureActive = false;
+
+ // 隐藏测量状态指示器
+ var measureStatus = document.getElementById('measureStatus');
+ if (measureStatus) {
+ measureStatus.style.display = 'none';
+ }
+
+ if (measureDraw && map) {
+ map.removeInteraction(measureDraw);
+ measureDraw = null;
+ }
+
+ if (currentListener) {
+ ol.Observable.unByKey(currentListener);
+ currentListener = null;
+ }
+
+ currentSketch = null;
+ clearTemporaryTooltips();
+ clearSegmentTooltips();
+ }
+
+ /**
+ * 创建测量提示框
+ * @param {Array} coord - 坐标
+ * @param {string} text - 显示文本
+ * @param {boolean} isStatic - 是否为静态提示框
+ * @returns {ol.Overlay} 覆盖层对象
+ */
+ function createMeasureTooltip(coord, text, isStatic) {
+ var elem = document.createElement('div');
+ elem.className = isStatic ? 'ol-tooltip ol-tooltip-static' : 'ol-tooltip ol-tooltip-measure';
+ elem.innerHTML = text;
+
+ var overlay = new ol.Overlay({
+ element: elem,
+ offset: [0, -15],
+ positioning: 'bottom-center'
+ });
+
+ overlay.setPosition(coord);
+
+ if (map) {
+ map.addOverlay(overlay);
+ }
+
+ return overlay;
+ }
+
+ /**
+ * 格式化长度显示
+ * @param {number} length - 长度(米)
+ * @returns {string} 格式化后的长度字符串
+ */
+ function formatLength(length) {
+ if (length > 1000) {
+ return (Math.round(length / 100) / 10) + ' km';
+ } else {
+ return (Math.round(length * 10) / 10) + ' m';
+ }
+ }
+
+ /**
+ * 清除所有测量提示框
+ */
+ function clearAllMeasureTooltips() {
+ if (!map) return;
+
+ for (var i = 0; i < measureTooltips.length; i++) {
+ map.removeOverlay(measureTooltips[i]);
+ }
+ measureTooltips = [];
+
+ clearTemporaryTooltips();
+ }
+
+ /**
+ * 清除临时提示框
+ */
+ function clearTemporaryTooltips() {
+ if (!map) return;
+
+ for (var i = 0; i < currentMeasureTooltips.length; i++) {
+ map.removeOverlay(currentMeasureTooltips[i]);
+ }
+ currentMeasureTooltips = [];
+ }
+
+ /**
+ * 清除段落提示框
+ */
+ function clearSegmentTooltips() {
+ if (!map) return;
+
+ for (var i = 0; i < segmentTooltips.length; i++) {
+ map.removeOverlay(segmentTooltips[i]);
+ }
+ segmentTooltips = [];
+ }
+
+ /**
+ * 清除所有测量标记
+ */
+ function clearMeasure() {
+ if (measureSource) {
+ measureSource.clear();
+ }
+
+ clearAllMeasureTooltips();
+ measureFeatures = [];
+
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('测距标记已清除');
+ }
+
+ // 关闭地图功能菜单
+ var menu = document.getElementById('mapFunctionsMenu');
+ if (menu) {
+ menu.classList.remove('show');
+ }
+ }
+
+ /**
+ * 检查测距功能是否激活
+ * @returns {boolean} 是否激活
+ */
+ function isActive() {
+ return measureActive;
+ }
+
+ /**
+ * 获取测量结果数量
+ * @returns {number} 测量结果数量
+ */
+ function getMeasureCount() {
+ return measureFeatures.length;
+ }
+
+ // 公开API
+ return {
+ init: init,
+ toggleMeasureDistance: toggleMeasureDistance,
+ finishMeasuring: finishMeasuring,
+ clearMeasure: clearMeasure,
+ isActive: isActive,
+ getMeasureCount: getMeasureCount
+ };
+})();
\ No newline at end of file
diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/search-filter.js b/sec-beidou/src/main/resources/static/js/device-overview-module/search-filter.js
new file mode 100644
index 00000000..a0cd12fe
--- /dev/null
+++ b/sec-beidou/src/main/resources/static/js/device-overview-module/search-filter.js
@@ -0,0 +1,249 @@
+/**
+ * 搜索和过滤功能模块
+ * 负责设备搜索、过滤和查询功能
+ */
+var SearchFilter = (function() {
+ 'use strict';
+
+ // 私有变量
+ var currentSearchedDevice = null;
+ var markerState = 3; // 1:all; 2:orange; 3:red
+
+ /**
+ * 搜索设备
+ * @param {string} deviceId - 设备ID
+ * @returns {boolean} 是否找到设备
+ */
+ function searchDevice(deviceId) {
+ if (!deviceId || !deviceId.trim()) {
+ clearSearch();
+ return false;
+ }
+
+ deviceId = deviceId.trim();
+ currentSearchedDevice = deviceId;
+
+ var success = DeviceMarkers.locateDevice(deviceId);
+
+ if (success) {
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('已定位到设备: ' + deviceId);
+ }
+
+ // 获取设备信息并显示天气预测(如果启用)
+ var targetFeature = DeviceMarkers.findDeviceById(deviceId);
+ if (targetFeature && window.WeatherForecast && window.WeatherForecast.isEnabled()) {
+ var deviceInfo = targetFeature.get('deviceInfo');
+ if (deviceInfo) {
+ window.WeatherForecast.showWeatherForecast(deviceInfo);
+ }
+ }
+ } else {
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('未找到设备: ' + deviceId);
+ }
+ }
+
+ return success;
+ }
+
+ /**
+ * 清除搜索
+ */
+ function clearSearch() {
+ currentSearchedDevice = null;
+ var searchInput = document.getElementById('deviceSearchNew');
+ if (searchInput) {
+ searchInput.value = '';
+ }
+
+ // 恢复到当前的过滤状态
+ applyCurrentFilter();
+ }
+
+ /**
+ * 应用当前过滤状态
+ */
+ function applyCurrentFilter() {
+ switch(markerState) {
+ case 1:
+ showAllDevices();
+ break;
+ case 2:
+ showWarning1Devices();
+ break;
+ case 3:
+ showWarning2Devices();
+ break;
+ }
+ }
+
+ /**
+ * 显示所有设备
+ */
+ function showAllDevices() {
+ currentSearchedDevice = null;
+ clearSearchInput();
+ DeviceMarkers.showAllDevices();
+ markerState = 1;
+ updateFilterSelect('all');
+ }
+
+ /**
+ * 显示一般告警设备
+ */
+ function showWarning1Devices() {
+ currentSearchedDevice = null;
+ clearSearchInput();
+ DeviceMarkers.showWarning1Devices();
+ markerState = 2;
+ updateFilterSelect('warning1');
+ }
+
+ /**
+ * 显示严重告警设备
+ */
+ function showWarning2Devices() {
+ currentSearchedDevice = null;
+ clearSearchInput();
+ DeviceMarkers.showWarning2Devices();
+ markerState = 3;
+ updateFilterSelect('warning2');
+ }
+
+ /**
+ * 清除搜索输入框
+ */
+ function clearSearchInput() {
+ var searchInput = document.getElementById('deviceSearchNew');
+ if (searchInput) {
+ searchInput.value = '';
+ }
+ }
+
+ /**
+ * 更新过滤选择器
+ * @param {string} filterValue - 过滤值
+ */
+ function updateFilterSelect(filterValue) {
+ var filterSelect = document.getElementById('warningFilter');
+ if (filterSelect) {
+ filterSelect.value = filterValue;
+ }
+ }
+
+ /**
+ * 根据状态过滤设备
+ * @param {string} statusType - 状态类型
+ */
+ function filterDevicesByStatus(statusType) {
+ switch(statusType) {
+ case 'warning1':
+ showWarning1Devices();
+ break;
+ case 'warning2':
+ showWarning2Devices();
+ break;
+ case 'offline':
+ case 'no_fwd':
+ case 'nofixed':
+ case 'nogga':
+ // 对于这些状态,显示所有设备,让用户在弹窗中选择
+ showAllDevices();
+ break;
+ default:
+ showAllDevices();
+ break;
+ }
+ }
+
+ /**
+ * 查询设备并打开列表页面
+ * @param {string} statusType - 状态类型
+ */
+ function queryDevices(statusType) {
+ // 首先过滤地图上的设备显示
+ filterDevicesByStatus(statusType);
+
+ // 打开设备列表弹窗
+ if (window.layer && typeof window.layer.open === 'function') {
+ var index = window.layer.open({
+ title: '设备列表',
+ type: 2,
+ shade: 0.2,
+ maxmin: true,
+ shadeClose: true,
+ anim: 2,
+ offset: 'rb',
+ area: ['100%', '50%'],
+ content: '../page/gnss_q_status?query=' + statusType,
+ });
+ }
+ }
+
+ /**
+ * 直接定位到设备(用于外部调用)
+ * @param {string} deviceId - 设备ID
+ * @param {number} latitude - 纬度(可选)
+ * @param {number} longitude - 经度(可选)
+ */
+ function locateDeviceOnMap(deviceId, latitude, longitude) {
+ currentSearchedDevice = deviceId;
+ var success = DeviceMarkers.locateDevice(deviceId);
+
+ if (success && window.layer && typeof window.layer.closeAll === 'function') {
+ window.layer.closeAll();
+ }
+
+ return success;
+ }
+
+ /**
+ * 直接定位设备(简化版)
+ * @param {string} deviceId - 设备ID
+ */
+ function locateDeviceDirectly(deviceId) {
+ currentSearchedDevice = deviceId;
+ return DeviceMarkers.locateDevice(deviceId);
+ }
+
+ /**
+ * 获取当前搜索的设备ID
+ * @returns {string|null} 当前搜索的设备ID
+ */
+ function getCurrentSearchedDevice() {
+ return currentSearchedDevice;
+ }
+
+ /**
+ * 获取当前标记状态
+ * @returns {number} 标记状态
+ */
+ function getMarkerState() {
+ return markerState;
+ }
+
+ /**
+ * 设置标记状态
+ * @param {number} state - 标记状态
+ */
+ function setMarkerState(state) {
+ markerState = state;
+ }
+
+ // 公开API
+ return {
+ searchDevice: searchDevice,
+ clearSearch: clearSearch,
+ showAllDevices: showAllDevices,
+ showWarning1Devices: showWarning1Devices,
+ showWarning2Devices: showWarning2Devices,
+ filterDevicesByStatus: filterDevicesByStatus,
+ queryDevices: queryDevices,
+ locateDeviceOnMap: locateDeviceOnMap,
+ locateDeviceDirectly: locateDeviceDirectly,
+ getCurrentSearchedDevice: getCurrentSearchedDevice,
+ getMarkerState: getMarkerState,
+ setMarkerState: setMarkerState
+ };
+})();
\ No newline at end of file
diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/weather-forecast.js b/sec-beidou/src/main/resources/static/js/device-overview-module/weather-forecast.js
new file mode 100644
index 00000000..57fde0d6
--- /dev/null
+++ b/sec-beidou/src/main/resources/static/js/device-overview-module/weather-forecast.js
@@ -0,0 +1,513 @@
+/**
+ * 天气预报模块
+ * 提供Windy天气API调用和天气卡片显示功能
+ */
+var WeatherForecast = (function() {
+ 'use strict';
+
+ // 私有变量
+ var weatherApiKey = 'Uxh4IdMuAvhSiBnsf4UUDVGF4e3YAp2B';
+ var weatherEnabled = false;
+ var weatherData = null;
+ var currentForecastIndex = 0;
+ var currentWeatherDevice = null;
+ var isDragging = false;
+
+ /**
+ * 初始化天气预报模块
+ */
+ function init() {
+ initWeatherCardDrag();
+ }
+
+ /**
+ * 切换天气预报功能
+ */
+ function toggleWeatherForecast() {
+ // 权限检查
+ var role = window.userRole || 'USER'; // 从全局变量获取角色
+ if (role !== 'SUPER_ADMIN') {
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('您没有权限使用此功能');
+ }
+ return;
+ }
+
+ var enableSwitch = document.getElementById('enableWeatherSwitch');
+ weatherEnabled = enableSwitch ? enableSwitch.checked : false;
+
+ if (weatherEnabled) {
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg(
+ '搜索设备或点击地图设备图标即可自动查询天气预测',
+ {time: 3000, area: ['300px', '80px']}
+ );
+ }
+ } else {
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('天气预测功能已关闭');
+ }
+ closeWeatherCard();
+ }
+
+ // 关闭地图功能菜单
+ var menu = document.getElementById('mapFunctionsMenu');
+ if (menu) {
+ menu.classList.remove('show');
+ }
+ }
+
+ /**
+ * 显示天气预报卡片
+ * @param {Object} deviceInfo - 设备信息
+ */
+ function showWeatherForecast(deviceInfo) {
+ currentWeatherDevice = deviceInfo;
+ weatherData = null;
+
+ // 权限检查
+ var role = window.userRole || 'USER';
+ if (role !== 'SUPER_ADMIN') {
+ return;
+ }
+
+ if (!weatherEnabled) {
+ if (window.layer && typeof window.layer.msg === 'function') {
+ window.layer.msg('天气预测功能未启用', {time: 2000});
+ }
+ return;
+ }
+
+ // 更新设备信息显示
+ var deviceIdElement = document.getElementById('weatherDeviceId');
+ var deviceCoordsElement = document.getElementById('weatherDeviceCoords');
+
+ if (deviceIdElement) {
+ deviceIdElement.textContent = '设备: ' + deviceInfo.deviceid;
+ }
+
+ if (deviceCoordsElement) {
+ deviceCoordsElement.textContent =
+ '坐标: ' + deviceInfo.latitude.toFixed(4) + ', ' + deviceInfo.longitude.toFixed(4);
+ }
+
+ // 显示天气卡片
+ var weatherCard = document.getElementById('weatherForecastCard');
+ if (weatherCard) {
+ weatherCard.style.display = 'block';
+ }
+
+ initWeatherCardDrag();
+
+ // 显示加载状态
+ var contentElement = document.getElementById('weatherForecastContent');
+ if (contentElement) {
+ contentElement.innerHTML =
+ '' +
+ '
' +
+ '
请确保网络可访问 Windy API 服务
' +
+ '
正在获取天气预测数据...
' +
+ '
';
+ }
+
+ // 重置翻页按钮
+ var prevBtn = document.getElementById('prevForecast');
+ var nextBtn = document.getElementById('nextForecast');
+ var timeDisplay = document.getElementById('forecastTimeDisplay');
+
+ if (prevBtn) prevBtn.disabled = true;
+ if (nextBtn) nextBtn.disabled = true;
+ if (timeDisplay) timeDisplay.textContent = '--:--';
+
+ // 调用天气API
+ fetchWeatherData(deviceInfo.latitude, deviceInfo.longitude);
+ }
+
+ /**
+ * 关闭天气卡片
+ */
+ function closeWeatherCard() {
+ var weatherCard = document.getElementById('weatherForecastCard');
+ if (weatherCard) {
+ weatherCard.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');
+
+ if (prevBtn) prevBtn.disabled = (currentForecastIndex <= 0);
+ if (nextBtn) nextBtn.disabled = (currentForecastIndex >= weatherData.ts.length - 1);
+
+ if (timeDisplay) {
+ var timestamp = weatherData.ts[currentForecastIndex];
+ var date = new Date(timestamp);
+ timeDisplay.textContent = formatDateTime(date);
+ }
+ }
+
+ /**
+ * 获取天气数据
+ * @param {number} lat - 纬度
+ * @param {number} lon - 经度
+ */
+ 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,
+ "hours": 72 // 请求未来72小时的预报数据
+ };
+
+ 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;
+
+ // 找到最接近当前时间的预报索引,优先选择未来时间点
+ var currentTime = new Date().getTime();
+ var closestIndex = 0;
+ var futureIndex = -1;
+
+ if (weatherData.ts && weatherData.ts.length > 0) {
+ // 首先尝试找到第一个未来时间点
+ for (var i = 0; i < weatherData.ts.length; i++) {
+ if (weatherData.ts[i] > currentTime) {
+ futureIndex = i;
+ break;
+ }
+ }
+
+ // 如果找到了未来时间点,直接使用
+ if (futureIndex >= 0) {
+ closestIndex = futureIndex;
+ } else {
+ // 否则找最接近的时间点
+ var smallestDiff = Number.MAX_VALUE;
+ for (var i = 0; i < weatherData.ts.length; i++) {
+ var diff = Math.abs(weatherData.ts[i] - currentTime);
+ if (diff < smallestDiff) {
+ smallestDiff = diff;
+ closestIndex = i;
+ }
+ }
+ }
+ }
+
+ currentForecastIndex = closestIndex;
+ 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 += '
';
+
+ var contentElement = document.getElementById('weatherForecastContent');
+ if (contentElement) {
+ contentElement.innerHTML = forecastHtml;
+ }
+ }
+
+ /**
+ * 显示天气错误信息
+ * @param {string} message - 错误消息
+ */
+ function displayWeatherError(message) {
+ var contentElement = document.getElementById('weatherForecastContent');
+ if (contentElement) {
+ contentElement.innerHTML =
+ '' +
+ '
' +
+ '
' + message + '
' +
+ '
';
+ }
+ }
+
+ /**
+ * 创建天气参数HTML
+ * @param {string} label - 参数标签
+ * @param {string} value - 参数值
+ * @returns {string} HTML字符串
+ */
+ function createWeatherParam(label, value) {
+ return '' +
+ '' + label + '' +
+ '' + value + '' +
+ '
';
+ }
+
+ /**
+ * 格式化日期时间
+ * @param {Date} date - 日期对象
+ * @returns {string} 格式化后的日期时间字符串
+ */
+ 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;
+ }
+
+ /**
+ * 获取风向
+ * @param {number} u - U分量
+ * @param {number} v - V分量
+ * @returns {string} 风向字符串
+ */
+ 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];
+ }
+
+ /**
+ * 初始化天气卡片拖拽功能
+ */
+ function initWeatherCardDrag() {
+ var weatherCard = document.getElementById('weatherForecastCard');
+ if (!weatherCard) return;
+
+ var startX, startY;
+ var startLeft, startTop;
+
+ var originalStyles = {
+ top: '55%',
+ right: '20px',
+ left: 'auto',
+ transform: 'translateY(-50%)'
+ };
+
+ var cardHeader = weatherCard.querySelector('.weather-card-header');
+ if (!cardHeader) return;
+
+ // 双击重置位置
+ cardHeader.addEventListener('dblclick', function() {
+ weatherCard.style.top = originalStyles.top;
+ weatherCard.style.right = originalStyles.right;
+ weatherCard.style.left = originalStyles.left;
+ weatherCard.style.transform = originalStyles.transform;
+ });
+
+ // 鼠标拖拽
+ cardHeader.addEventListener('mousedown', function(e) {
+ if (e.target.tagName === 'BUTTON' ||
+ e.target.classList.contains('weather-nav-btn') ||
+ e.target.classList.contains('weather-close-btn')) {
+ return;
+ }
+
+ e.preventDefault();
+ startDrag(e.clientX, e.clientY);
+ });
+
+ // 触摸拖拽
+ cardHeader.addEventListener('touchstart', function(e) {
+ if (e.target.tagName === 'BUTTON' ||
+ e.target.classList.contains('weather-nav-btn') ||
+ e.target.classList.contains('weather-close-btn')) {
+ return;
+ }
+
+ e.preventDefault();
+ var touch = e.touches[0];
+ startDrag(touch.clientX, touch.clientY);
+ });
+
+ function startDrag(clientX, clientY) {
+ startX = clientX;
+ startY = clientY;
+
+ var rect = weatherCard.getBoundingClientRect();
+ startLeft = rect.left;
+ startTop = rect.top;
+
+ isDragging = true;
+
+ // 设置为绝对定位
+ if (getComputedStyle(weatherCard).position !== 'absolute') {
+ weatherCard.style.position = 'absolute';
+ weatherCard.style.top = rect.top + 'px';
+ weatherCard.style.left = rect.left + 'px';
+ weatherCard.style.right = 'auto';
+ weatherCard.style.transform = 'none';
+ }
+
+ weatherCard.style.opacity = '0.9';
+ weatherCard.style.boxShadow = '0 12px 48px rgba(0, 0, 0, 0.25)';
+ weatherCard.style.zIndex = '1200';
+ document.body.style.userSelect = 'none';
+ }
+
+ // 鼠标移动事件
+ document.addEventListener('mousemove', function(e) {
+ if (!isDragging) return;
+ moveDrag(e.clientX, e.clientY);
+ });
+
+ // 触摸移动事件
+ document.addEventListener('touchmove', function(e) {
+ if (!isDragging) return;
+ e.preventDefault();
+ var touch = e.touches[0];
+ moveDrag(touch.clientX, touch.clientY);
+ });
+
+ function moveDrag(clientX, clientY) {
+ var dx = clientX - startX;
+ var dy = clientY - startY;
+
+ var newLeft = startLeft + dx;
+ var newTop = startTop + dy;
+
+ // 限制在视口内
+ var maxX = window.innerWidth - weatherCard.offsetWidth;
+ var maxY = window.innerHeight - weatherCard.offsetHeight;
+
+ newLeft = Math.max(0, Math.min(newLeft, maxX));
+ newTop = Math.max(0, Math.min(newTop, maxY));
+
+ weatherCard.style.left = newLeft + 'px';
+ weatherCard.style.top = newTop + 'px';
+ }
+
+ // 结束拖拽
+ document.addEventListener('mouseup', endDrag);
+ document.addEventListener('touchend', endDrag);
+
+ function endDrag() {
+ if (!isDragging) return;
+
+ isDragging = false;
+ weatherCard.style.opacity = '1';
+ weatherCard.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.15)';
+ document.body.style.userSelect = '';
+ }
+ }
+
+ /**
+ * 检查天气功能是否启用
+ * @returns {boolean} 是否启用
+ */
+ function isEnabled() {
+ return weatherEnabled;
+ }
+
+ // 公开API
+ return {
+ init: init,
+ toggleWeatherForecast: toggleWeatherForecast,
+ showWeatherForecast: showWeatherForecast,
+ closeWeatherCard: closeWeatherCard,
+ showPrevForecast: showPrevForecast,
+ showNextForecast: showNextForecast,
+ isEnabled: isEnabled
+ };
+})();
\ No newline at end of file
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 cb43ec11..5fa39e60 100644
--- a/sec-beidou/src/main/resources/templates/page/device_overview.html
+++ b/sec-beidou/src/main/resources/templates/page/device_overview.html
@@ -795,218 +795,200 @@
-
-
-
-
-
-
- 设备数量
- 1101
-
-
-
- 装机量
- 1123
-
-
-
- 掉线数
- 22
-
-
-
- 未推送数
- 11
-
-
-
- 长期无效解
- 10
-
-
-
- 严重告警
- 40
-
-
-
- 一般告警
- 79
-
-
-
-
无GGA告警
-
4
+
+
+
+
+
+ 设备数量
+ 1101
+
+
+
+ 装机量
+ 1123
+
+
+
+ 掉线数
+ 22
+
+
+
+ 未推送数
+ 11
+
+
+
+ 长期无效解
+ 10
+
+
+
+ 严重告警
+ 40
+
+
+
+ 一般告警
+ 79
+
+
+
+ 无GGA告警
+ 4
+
-
-
-
-
-
-
-
-
-
- 设备信息
- 坐标信息
-
-
-
- --:--
-
-
-
-
-
-
正在获取天气预测数据...
+
+
+
+
+
+
+
+
+ 设备信息
+ 坐标信息
+
+
+
+ --:--
+
+
+
-
-
-