2025-07-04 22:00:01 +08:00

1030 lines
33 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>设备总览</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="../lib/font-awesome-4.7.0/css/font-awesome.min.css" media="all">
<link rel="stylesheet" href="../css/public.css" media="all">
<link rel="stylesheet" href="../css/ol.css">
<!-- 谷歌地图的API接口这个访问没有天地图的次数多所以这边限制只有管理员能使用 -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBEBxB5n9vikPHbfNMzo_dzneXJi77YqjE" async defer></script>
<style>
.top-panel {
border: 1px solid #eceff9;
border-radius: 5px;
text-align: center;
}
.stats-panel {
height: 100%;
display: flex;
flex-direction: column;
}
.stats-panel > .layui-card-body {
height: auto;
min-height: 60px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.top-panel-number {
font-size: 30px;
border-right: 1px solid #eceff9;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.top-panel-tips{
line-height:30px;
font-size: 12px
}
.flex-row {
display: flex;
align-items: stretch;
flex-wrap: wrap;
}
.flex-row > .layui-col-xs12,
.flex-row > .layui-col-md6 {
display: flex;
flex-direction: column;
}
.stats-panel .layui-col-xs3 a,
.stats-panel .layui-col-md4 a,
.stats-panel .layui-col-xs3 div a,
.stats-panel .layui-col-md4 div a {
display: block;
margin-bottom: 2px;
}
ul li {
list-style: none;
}
.layuimini-main {
height: 96vh;
display: flex;
flex-direction: column;
}
.map-wrapper {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
}
.map-card {
flex: 1;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 1px solid #e6e8eb;
display: flex;
flex-direction: column;
overflow: hidden;
}
#map-container {
flex: 1;
width: 100%;
padding: 0;
margin: 0;
position: relative;
}
.device-overview-bar {
background: white;
border: 1px solid #e6e8eb;
border-radius: 6px;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
padding: 10px 15px;
margin-bottom: 10px;
color: #333;
}
.map-toolbar {
position: absolute;
top: 15px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
background: rgba(255, 255, 255, 0.96);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 8px 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
gap: 12px;
width:auto;
max-width: 95vw;
}
.toolbar-item {
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.toolbar-label {
font-size: 12px;
color: #666;
white-space: nowrap;
font-weight: 500;
}
.toolbar-select {
border: 1px solid rgba(230, 230, 230, 0.8);
border-radius: 6px;
padding: 4px 8px;
font-size: 12px;
background: rgba(255, 255, 255, 0.9);
min-width: 120px;
height: 28px;
}
.toolbar-input {
border: 1px solid rgba(230, 230, 230, 0.8);
border-radius: 6px;
padding: 4px 8px;
font-size: 12px;
background: rgba(255, 255, 255, 0.9);
width: 140px;
height: 28px;
}
.toolbar-btn {
background: rgba(26, 160, 148, 0.9);
color: white;
border: none;
border-radius: 6px;
padding: 4px 12px;
font-size: 12px;
cursor: pointer;
height: 28px;
transition: all 0.2s ease;
}
.toolbar-btn:hover {
background: rgba(26, 160, 148, 1);
transform: translateY(-1px);
}
.toolbar-divider {
width: 1px;
height: 20px;
background: rgba(230, 230, 230, 0.6);
}
.overview-stats {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.stat-item {
display: flex;
align-items: center;
/* background: #f8f9fa;
border: 1px solid #e9ecef; */
border-radius: 4px;
padding: 5px 10px;
min-width: 85px;
transition: background-color 0.2s ease;
cursor: pointer;
height: 28px;
}
.stat-item:hover {
background: #e9ecef;
}
.stat-item.non-clickable {
cursor: default;
}
.stat-item.non-clickable:hover {
background: #f8f9fa;
}
.stat-dot {
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 6px;
flex-shrink: 0;
}
.dot-green { background-color: #48bb78; }
.dot-blue { background-color: #4299e1; }
.dot-gray { background-color: #a0aec0; }
.dot-orange { background-color: #ed8936; }
.dot-red { background-color: #f56565; }
.dot-yellow { background-color: #ecc94b; }
.stat-item span:nth-child(2) {
font-size: 12px;
color: #666;
margin-right: 4px;
white-space: nowrap;
}
.stat-number {
font-size: 13px;
font-weight: bold;
color: #333;
}
/* 由于不使用layui的元素所以这边还得适配手机端 */
@media screen and (max-width: 768px) {
.layuimini-main {
height: 120vh;
overflow: hidden;
}
.map-wrapper {
flex: 1;
overflow: hidden;
}
.map-card {
margin: 8px;
height: calc(100% - 16px);
overflow: hidden;
}
#map-container {
height: 100%;
overflow: hidden;
}
.device-overview-bar {
padding: 8px 12px;
margin-bottom: 8px;
flex-shrink: 0;
}
.overview-stats {
gap: 6px;
flex-wrap: wrap;
justify-content: flex-start;
}
.stat-item {
flex: 0 0 auto;
min-width: 70px;
width: auto;
max-width: calc(50% - 4px);
padding: 4px 6px;
font-size: 10px;
height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
gap: 2px;
}
.stat-item span:not(.stat-dot):not(.stat-number) {
font-size: 9px;
max-width: 40px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.stat-dot {
width: 6px;
height: 6px;
margin-right: 0;
flex-shrink: 0;
}
.stat-number {
font-size: 12px;
font-weight: bold;
color: #333;
}
.map-toolbar {
position: absolute;
transform: none;
left: auto;
margin: 8px;
margin-bottom: 8px;
min-width: auto;
max-width: none;
flex-wrap: wrap;
gap: 6px;
padding: 6px 10px;
top:0px;
}
.toolbar-item {
gap: 4px;
flex: 0 0 auto;
width:100%
}
.toolbar-label {
font-size: 10px;
display: none;
}
.toolbar-select,
.toolbar-input {
font-size: 11px;
height: 24px;
width: 100%;
}
.toolbar-btn {
font-size: 11px;
height: 24px;
padding: 2px 8px;
}
.toolbar-divider {
display: none;
}
#toolbar-device-locate span:not(.toolbar-label) {
font-size: 10px;
padding: 2px 4px;
}
.dropdown-menu {
min-width: 180px;
max-width: 95vw;
}
.dropdown-item {
padding: 8px 12px;
font-size: 12px;
}
}
.ol-tooltip {
position: absolute;
background: rgba(255,255,255,0.8);
border-radius: 4px;
padding: 4px 8px;
border: 1px solid #d9d9d9;
color: #333;
font-size: 12px;
white-space: nowrap;
pointer-events: none;
z-index: 1001;
}
.ol-tooltip-static {
background: #ffcc33;
color: #222;
border: 1px solid #ffcc33;
}
.dropdown-container {
position: relative;
display: inline-block;
width: 100%;
}
.dropdown-toggle {
cursor: pointer;
display: flex;
align-items: center;
width: 100%;
justify-content: center;
}
.dropdown-toggle::after {
content: '';
display: inline-block;
margin-left: 6px;
border-top: 4px solid;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
vertical-align: middle;
}
.dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
z-index: 1100;
min-width: 200px;
padding: 8px 0;
margin-top: 2px;
background: white;
border-radius: 6px;
box-shadow: 0 3px 12px rgba(0,0,0,0.15);
border: 1px solid rgba(0,0,0,0.1);
}
.dropdown-menu.show {
display: block;
}
.dropdown-group {
padding: 0 8px;
}
.dropdown-group-title {
font-size: 12px;
color: #666;
padding: 6px 8px;
font-weight: 500;
}
.dropdown-item {
padding: 6px 12px;
cursor: pointer;
font-size: 13px;
color: #333;
border-radius: 4px;
margin: 2px 0;
}
.dropdown-item:hover {
background: rgba(26, 160, 148, 0.1);
}
.dropdown-divider {
height: 1px;
background: #e9ecef;
margin: 8px 0;
}
.switch-label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
cursor: pointer;
}
.switch-label input {
opacity: 0;
width: 0;
height: 0;
position: absolute;
}
.switch-slider {
position: relative;
display: inline-block;
width: 36px;
height: 18px;
background-color: #ccc;
border-radius: 18px;
transition: .4s;
flex-shrink: 0;
}
.switch-slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 2px;
bottom: 2px;
background-color: white;
border-radius: 50%;
transition: .4s;
}
input:checked + .switch-slider {
background-color: #1aa094;
}
input:checked + .switch-slider:before {
transform: translateX(18px);
}
/* 测量状态指示器样式 */
#measureStatus {
background: rgba(255, 255, 255, 0.98);
border: 1px solid rgba(72, 187, 120, 0.3);
border-radius: 8px;
padding: 6px 12px;
box-shadow: 0 2px 8px rgba(72, 187, 120, 0.2);
align-items: center;
gap: 8px;
}
#measureStatus .toolbar-btn {
padding: 2px 6px;
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;
padding: 10px 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
border-radius: 8px 8px 0 0;
transition: background-color 0.2s ease;
margin: -16px -16px 12px -16px;
}
.weather-card-header:hover {
background-color: rgba(26, 160, 148, 0.05);
}
.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;
}
#measureStatus{
display: flex;
justify-content: center;
padding: 6px 0px;
}
}
</style>
<script src="../js/ol.js"></script>
</head>
<body>
<div class="layuimini-main">
<div class="device-overview-bar">
<div class="overview-stats">
<div class="stat-item non-clickable">
<span class="stat-dot dot-green"></span>
<span>设备数量</span>
<span class="stat-number" th:text="${deviceOnlineNum}">1101</span>
</div>
<div class="stat-item non-clickable">
<span class="stat-dot dot-blue"></span>
<span>装机量</span>
<span class="stat-number" th:text="${deviceDeployedNum}">1123</span>
</div>
<div class="stat-item" onclick="queryDevices('offline')" th:if="${deviceOfflineNum > 0}">
<span class="stat-dot dot-gray"></span>
<span>掉线数</span>
<span class="stat-number" th:text="${deviceOfflineNum}">22</span>
</div>
<div class="stat-item" onclick="queryDevices('no_fwd')" th:if="${deviceNoFwdNum > 0}">
<span class="stat-dot dot-orange"></span>
<span>未推送数</span>
<span class="stat-number" th:text="${deviceNoFwdNum}">11</span>
</div>
<div class="stat-item" onclick="queryDevices('nofixed')" th:if="${noFix > 0}">
<span class="stat-dot dot-red"></span>
<span>长期无效解</span>
<span class="stat-number" th:text="${noFix}">10</span>
</div>
<div class="stat-item" onclick="queryDevices('warning2')" th:if="${warning2Num > 0}">
<span class="stat-dot dot-red"></span>
<span>严重告警</span>
<span class="stat-number" th:text="${warning2Num}">40</span>
</div>
<div class="stat-item" onclick="queryDevices('warning1')" th:if="${warning1Num > 0}">
<span class="stat-dot dot-orange"></span>
<span>一般告警</span>
<span class="stat-number" th:text="${warning1Num}">79</span>
</div>
<div class="stat-item" onclick="queryDevices('nogga')" th:if="${noGGA > 0}">
<span class="stat-dot dot-gray"></span>
<span>无GGA告警</span>
<span class="stat-number" th:text="${noGGA}">4</span>
</div>
</div>
</div>
<div class="map-wrapper">
<div class="map-card">
<div id="map-container">
<div id="weatherForecastCard" class="weather-forecast-card" style="display: none;" th:if="${role=='SUPER_ADMIN'}">
<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="toolbar-item">
<select id="mapTypeSelectNew" class="toolbar-select" lay-filter="mapTypeNew" onchange="onMapTypeChange()">
<option value="tianditu_satellite" selected>天地图-卫星影像</option>
<option value="tianditu_terrain_hybrid" >天地图-地形混合</option>
<option value="tianditu_normal">天地图-矢量</option>
<option value="tianditu_terrain">天地图-地形</option>
<option value="amap_satellite">高德-卫星影像</option>
<option value="amap">高德-矢量</option>
<!-- 只能超级管理员使用 -->
<option value="google_satellite" th:if="${role=='SUPER_ADMIN'}">谷歌-卫星影像</option>
<option value="google_terrain" th:if="${role=='SUPER_ADMIN'}">谷歌-地形图</option>
<option value="google_roadmap" th:if="${role=='SUPER_ADMIN'}">谷歌-道路图</option>
<option value="google_hybrid" th:if="${role=='SUPER_ADMIN'}">谷歌-混合</option>
</select>
</div>
<div class="toolbar-divider"></div>
<div class="toolbar-item">
<input type="text" id="deviceSearchNew" class="toolbar-input" placeholder="搜索设备编号">
<button class="toolbar-btn" onclick="searchDeviceNew()">搜索</button>
</div>
<div class="toolbar-divider"></div>
<div class="toolbar-item">
<select id="warningFilter" class="toolbar-select" onchange="onWarningFilterChange()">
<option value="all">全部设备</option>
<option value="warning1">一般告警</option>
<option value="warning2" selected>严重告警</option>
</select>
</div>
<div class="toolbar-divider"></div>
<div class="toolbar-item">
<div class="dropdown-container">
<button class="toolbar-btn dropdown-toggle" onclick="toggleMapFunctionsMenu()">地图功能</button>
<div id="mapFunctionsMenu" class="dropdown-menu">
<div class="dropdown-group">
<div class="dropdown-group-title">信息显示</div>
<div class="dropdown-item">
<label class="switch-label">
<span>显示设备信息</span>
<input type="checkbox" id="showDeviceIdSwitch" checked onchange="toggleDeviceId()">
<span class="switch-slider"></span>
</label>
</div>
<div class="dropdown-item">
<label class="switch-label">
<span>显示集群</span>
<input type="checkbox" id="showClusterSwitch" checked onchange="toggleCluster()">
<span class="switch-slider"></span>
</label>
</div>
</div>
<div class="dropdown-divider"></div>
<div class="dropdown-group">
<div class="dropdown-group-title">测量工具</div>
<div class="dropdown-item" onclick="toggleMeasureDistance()">
测距
</div>
<div class="dropdown-item" onclick="clearMeasure()">
清除测距
</div>
</div>
<div class="dropdown-divider"></div>
<div class="dropdown-group" th:if="${role=='SUPER_ADMIN'}">
<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 id="measureStatus" class="toolbar-item" style="display: none;">
<span style="color: #48bb78; font-weight: bold; font-size: 12px;">测量中</span>
<button class="toolbar-btn" onclick="finishMeasuring()" style="background: #f56565; margin-left: 6px;">
<i class="layui-icon layui-icon-close" style="font-size: 12px;"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- JavaScript 依赖 -->
<script src="../js/ol.js"></script>
<script src="../lib/jquery-3.4.1/jquery-3.4.1.min.js" charset="utf-8"></script>
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<!-- 设备总览模块化JavaScript文件 -->
<script src="../js/device-overview-module/coordinate-utils.js"></script>
<script src="../js/device-overview-module/map-layers.js"></script>
<script src="../js/device-overview-module/device-markers.js"></script>
<script src="../js/device-overview-module/search-filter.js"></script>
<script src="../js/device-overview-module/measure-tools.js"></script>
<script src="../js/device-overview-module/weather-forecast.js"></script>
<script src="../js/device-overview-module/map-core.js"></script>
<script src="../js/device-overview-module/device-overview-main.js"></script>
<script th:inline="javascript">
// 从服务器获取设备列表数据
var deviceList = [
/*[# th:each="device : ${deviceList}"]*/
{
deviceid: /*[[${device.deviceid}]]*/ '',
latitude: /*[[${device.latitude}]]*/ 0,
longitude: /*[[${device.longitude}]]*/ 0,
warning: /*[[${device.warning}]]*/ 0
}/*[# th:if="${!deviceStat.last}"]*/,/*[/]*/
/*[/]*/
];
// 获取用户角色
var userRole = /*[[${role}]]*/ 'USER';
// 初始化设备总览模块
DeviceOverview.init(deviceList, {
role: userRole
});
// 暴露全局函数供HTML调用
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;
window.switchMapType = DeviceOverview.switchMapType;
</script>
</body>
</html>