|
|
// 智慧河道管理平台 - JavaScript 主文件
|
|
|
|
|
|
// 全局变量
|
|
|
let map;
|
|
|
let currentTab = 'monitor';
|
|
|
let workorderData = [];
|
|
|
let deviceData = [];
|
|
|
let warningData = [];
|
|
|
|
|
|
let dashboardData = {
|
|
|
realTimeStatus: {
|
|
|
online: 12,
|
|
|
warning: 3,
|
|
|
processing: 2
|
|
|
},
|
|
|
waterQuality: {
|
|
|
excellent: 8,
|
|
|
good: 4,
|
|
|
warning: 1
|
|
|
},
|
|
|
efficiency: {
|
|
|
daily: 85,
|
|
|
weekly: 78,
|
|
|
monthly: 82
|
|
|
},
|
|
|
alerts: [
|
|
|
{id: 1, type: 'high', title: '水质异常', desc: '湘江中路段检测到污染物超标', time: '2分钟前'},
|
|
|
{id: 2, type: 'medium', title: '设备故障', desc: '橘子洲头摄像头离线', time: '15分钟前'},
|
|
|
{id: 3, type: 'low', title: '清理完成', desc: '杜甫江阁段垃圾清理完毕', time: '1小时前'}
|
|
|
],
|
|
|
envData: {
|
|
|
temperature: 24,
|
|
|
windSpeed: 3.2,
|
|
|
visibility: 8.5
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 初始化应用
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
// The initializeApp function will be called by onTdtMapApiLoaded
|
|
|
});
|
|
|
|
|
|
function initializeApp() {
|
|
|
initializeNavigation();
|
|
|
initializeDashboard();
|
|
|
initializeTime();
|
|
|
initializeMap();
|
|
|
initializeCharts();
|
|
|
startRealTimeUpdates();
|
|
|
|
|
|
// 初始化其他模块
|
|
|
initializeWorkorder();
|
|
|
initializeDevice();
|
|
|
initializeWarning();
|
|
|
initializeStatistics();
|
|
|
|
|
|
// 启用大屏模式
|
|
|
enableFullscreenMode();
|
|
|
}
|
|
|
|
|
|
function onTdtMapApiLoaded() {
|
|
|
window.T = T; // Expose T to the global scope
|
|
|
initializeApp();
|
|
|
}
|
|
|
|
|
|
// 初始化大屏仪表板
|
|
|
function initializeDashboard() {
|
|
|
updateRealTimeStatus();
|
|
|
updateWaterQuality();
|
|
|
updateEfficiencyStats();
|
|
|
updateAlerts();
|
|
|
updateEnvironmentData();
|
|
|
updateWeatherInfo();
|
|
|
}
|
|
|
|
|
|
// 导航系统
|
|
|
function initializeNavigation() {
|
|
|
const navButtons = document.querySelectorAll('.nav-btn');
|
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
|
|
navButtons.forEach(button => {
|
|
|
button.addEventListener('click', function() {
|
|
|
const targetTab = this.getAttribute('data-tab');
|
|
|
switchTab(targetTab);
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function switchTab(tabName) {
|
|
|
// 如果已经是当前标签页,则不执行任何操作
|
|
|
if (currentTab === tabName) return;
|
|
|
|
|
|
// 更新导航按钮状态
|
|
|
document.querySelectorAll('.nav-btn').forEach(btn => {
|
|
|
btn.classList.remove('active');
|
|
|
});
|
|
|
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
|
|
|
|
|
|
// 更新内容区域
|
|
|
document.querySelectorAll('.tab-content').forEach(content => {
|
|
|
content.classList.remove('active');
|
|
|
});
|
|
|
document.getElementById(tabName).classList.add('active');
|
|
|
|
|
|
currentTab = tabName;
|
|
|
|
|
|
// 大屏模式控制
|
|
|
if (tabName === 'monitor') {
|
|
|
enableFullscreenMode();
|
|
|
} else {
|
|
|
disableFullscreenMode();
|
|
|
}
|
|
|
|
|
|
// 根据不同标签页执行特定初始化
|
|
|
switch(tabName) {
|
|
|
case 'monitor':
|
|
|
if (map) {
|
|
|
setTimeout(() => map.invalidateSize(), 100);
|
|
|
}
|
|
|
// 刷新监控管理一张图的图表
|
|
|
refreshMonitorCharts();
|
|
|
break;
|
|
|
case 'workorder':
|
|
|
refreshWorkorderTable();
|
|
|
break;
|
|
|
case 'device':
|
|
|
refreshDeviceGrid();
|
|
|
break;
|
|
|
case 'warning':
|
|
|
refreshWarningData();
|
|
|
break;
|
|
|
case 'statistics':
|
|
|
refreshCharts();
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 启用大屏模式
|
|
|
function enableFullscreenMode() {
|
|
|
document.body.classList.add('fullscreen-mode');
|
|
|
// 添加ESC键退出大屏模式的监听
|
|
|
document.addEventListener('keydown', handleEscapeKey);
|
|
|
}
|
|
|
|
|
|
// 禁用大屏模式
|
|
|
function disableFullscreenMode() {
|
|
|
document.body.classList.remove('fullscreen-mode');
|
|
|
document.removeEventListener('keydown', handleEscapeKey);
|
|
|
}
|
|
|
|
|
|
// 处理ESC键退出大屏模式
|
|
|
function handleEscapeKey(event) {
|
|
|
if (event.key === 'Escape' && document.body.classList.contains('fullscreen-mode')) {
|
|
|
// 切换到统计分析页面
|
|
|
switchTab('statistics');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 添加大屏模式切换按钮功能
|
|
|
function toggleFullscreenMode() {
|
|
|
if (document.body.classList.contains('fullscreen-mode')) {
|
|
|
disableFullscreenMode();
|
|
|
} else {
|
|
|
enableFullscreenMode();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 时间显示
|
|
|
function initializeTime() {
|
|
|
updateTime();
|
|
|
setInterval(updateTime, 1000);
|
|
|
}
|
|
|
|
|
|
function updateTime() {
|
|
|
const now = new Date();
|
|
|
const timeString = now.toLocaleString('zh-CN', {
|
|
|
year: 'numeric',
|
|
|
month: '2-digit',
|
|
|
day: '2-digit',
|
|
|
hour: '2-digit',
|
|
|
minute: '2-digit',
|
|
|
second: '2-digit'
|
|
|
});
|
|
|
const datetimeElement = document.getElementById('datetime');
|
|
|
if (datetimeElement) {
|
|
|
datetimeElement.textContent = timeString;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 更新实时状态
|
|
|
function updateRealTimeStatus() {
|
|
|
const statusGrid = document.querySelector('.status-grid');
|
|
|
if (!statusGrid) return;
|
|
|
|
|
|
const statusData = dashboardData.realTimeStatus;
|
|
|
const statusCards = [
|
|
|
{key: 'online', label: '在线设备', value: statusData.online, class: 'online'},
|
|
|
{key: 'warning', label: '告警设备', value: statusData.warning, class: 'warning'},
|
|
|
{key: 'processing', label: '处理中', value: statusData.processing, class: 'processing'}
|
|
|
];
|
|
|
|
|
|
statusGrid.innerHTML = statusCards.map(card => `
|
|
|
<div class="status-card ${card.class}">
|
|
|
<div class="status-icon">
|
|
|
<i class="fas fa-${card.key === 'online' ? 'check-circle' : card.key === 'warning' ? 'exclamation-triangle' : 'cog'}"></i>
|
|
|
</div>
|
|
|
<div class="status-info">
|
|
|
<div class="status-value">${card.value}</div>
|
|
|
<div class="status-label">${card.label}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
// 更新水质监测
|
|
|
function updateWaterQuality() {
|
|
|
const qualityData = dashboardData.waterQuality;
|
|
|
const qualityItems = document.querySelectorAll('.quality-item');
|
|
|
|
|
|
const items = [
|
|
|
{element: qualityItems[0], key: 'excellent', label: '优秀', class: 'excellent'},
|
|
|
{element: qualityItems[1], key: 'good', label: '良好', class: 'good'},
|
|
|
{element: qualityItems[2], key: 'warning', label: '预警', class: 'warning'}
|
|
|
];
|
|
|
|
|
|
items.forEach(item => {
|
|
|
if (item.element) {
|
|
|
item.element.className = `quality-item ${item.class}`;
|
|
|
item.element.innerHTML = `
|
|
|
<div class="quality-value">${qualityData[item.key]}</div>
|
|
|
<div class="quality-text">${item.label}</div>
|
|
|
<div class="quality-status">监测点</div>
|
|
|
`;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 更新清理效率统计
|
|
|
function updateEfficiencyStats() {
|
|
|
const efficiencyData = dashboardData.efficiency;
|
|
|
const statRows = document.querySelectorAll('.stat-row');
|
|
|
|
|
|
const stats = [
|
|
|
{element: statRows[0], label: '日清理率', value: efficiencyData.daily},
|
|
|
{element: statRows[1], label: '周清理率', value: efficiencyData.weekly},
|
|
|
{element: statRows[2], label: '月清理率', value: efficiencyData.monthly}
|
|
|
];
|
|
|
|
|
|
stats.forEach(stat => {
|
|
|
if (stat.element) {
|
|
|
stat.element.innerHTML = `
|
|
|
<span class="stat-label">${stat.label}</span>
|
|
|
<span class="stat-value">${stat.value}%</span>
|
|
|
`;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 更新告警信息
|
|
|
function updateAlerts() {
|
|
|
const alertList = document.querySelector('.alert-list');
|
|
|
if (!alertList) return;
|
|
|
|
|
|
const alerts = dashboardData.alerts;
|
|
|
alertList.innerHTML = alerts.map(alert => `
|
|
|
<div class="alert-item ${alert.type}">
|
|
|
<div class="alert-icon">
|
|
|
<i class="fas fa-${alert.type === 'high' ? 'exclamation-circle' : alert.type === 'medium' ? 'exclamation-triangle' : 'info-circle'}"></i>
|
|
|
</div>
|
|
|
<div class="alert-content">
|
|
|
<div class="alert-title">${alert.title}</div>
|
|
|
<div class="alert-desc">${alert.desc}</div>
|
|
|
<div class="alert-time">${alert.time}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
// 更新环境数据
|
|
|
function updateEnvironmentData() {
|
|
|
const envData = dashboardData.envData;
|
|
|
const envItems = document.querySelectorAll('.env-item');
|
|
|
|
|
|
const items = [
|
|
|
{element: envItems[0], icon: 'thermometer-half', label: '水温', value: envData.temperature, unit: '°C'},
|
|
|
{element: envItems[1], icon: 'wind', label: '风速', value: envData.windSpeed, unit: 'm/s'},
|
|
|
{element: envItems[2], icon: 'eye', label: '能见度', value: envData.visibility, unit: 'km'}
|
|
|
];
|
|
|
|
|
|
items.forEach(item => {
|
|
|
if (item.element) {
|
|
|
item.element.innerHTML = `
|
|
|
<div class="env-icon">
|
|
|
<i class="fas fa-${item.icon}"></i>
|
|
|
</div>
|
|
|
<div class="env-info">
|
|
|
<div class="env-label">${item.label}</div>
|
|
|
<div class="env-value">${item.value}${item.unit}</div>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 更新天气信息
|
|
|
function updateWeatherInfo() {
|
|
|
const weatherElement = document.getElementById('weather');
|
|
|
if (weatherElement) {
|
|
|
weatherElement.innerHTML = `
|
|
|
<i class="fas fa-sun"></i>
|
|
|
<span>晴 24°C</span>
|
|
|
`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 地图系统
|
|
|
function initializeMap() {
|
|
|
// 初始化天地图
|
|
|
const mapElement = document.getElementById('map');
|
|
|
if (!mapElement) return;
|
|
|
|
|
|
map = new T.Map('map');
|
|
|
const point = new T.LngLat(112.9388, 28.2282); // 长沙坐标
|
|
|
map.centerAndZoom(point, 12);
|
|
|
|
|
|
// 启用地图功能
|
|
|
map.enableScrollWheelZoom();
|
|
|
map.addControl(new T.Control.Zoom());
|
|
|
map.addControl(new T.Control.Scale());
|
|
|
map.addControl(new T.Control.MapType());
|
|
|
|
|
|
// 添加湘江河道轮廓
|
|
|
addRiverOverlay();
|
|
|
|
|
|
// 添加设备标记
|
|
|
addDeviceMarkers();
|
|
|
|
|
|
// 添加垃圾污染点
|
|
|
addGarbageMarkers();
|
|
|
|
|
|
// 地图控制事件
|
|
|
const toggleLayerBtn = document.getElementById('toggleLayer');
|
|
|
const fullscreenBtn = document.getElementById('fullscreen');
|
|
|
|
|
|
if (toggleLayerBtn) {
|
|
|
toggleLayerBtn.addEventListener('click', toggleMapLayers);
|
|
|
}
|
|
|
if (fullscreenBtn) {
|
|
|
fullscreenBtn.addEventListener('click', toggleFullscreen);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 初始化图表
|
|
|
function initializeCharts() {
|
|
|
initializeTrendChart();
|
|
|
initializeMonitorCharts();
|
|
|
}
|
|
|
|
|
|
// 初始化趋势图表
|
|
|
function initializeTrendChart() {
|
|
|
const canvas = document.getElementById('trendChart');
|
|
|
if (!canvas) return;
|
|
|
|
|
|
// 销毁已存在的图表实例
|
|
|
destroyChart('trendChart');
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
// 创建渐变
|
|
|
const gradient1 = ctx.createLinearGradient(0, 0, 0, 200);
|
|
|
gradient1.addColorStop(0, 'rgba(255, 68, 68, 0.3)');
|
|
|
gradient1.addColorStop(1, 'rgba(255, 68, 68, 0)');
|
|
|
|
|
|
const gradient2 = ctx.createLinearGradient(0, 0, 0, 200);
|
|
|
gradient2.addColorStop(0, 'rgba(0, 212, 255, 0.3)');
|
|
|
gradient2.addColorStop(1, 'rgba(0, 212, 255, 0)');
|
|
|
|
|
|
const gradient3 = ctx.createLinearGradient(0, 0, 0, 200);
|
|
|
gradient3.addColorStop(0, 'rgba(0, 255, 136, 0.3)');
|
|
|
gradient3.addColorStop(1, 'rgba(0, 255, 136, 0)');
|
|
|
|
|
|
window.chartInstances['trendChart'] = new Chart(ctx, {
|
|
|
type: 'line',
|
|
|
data: {
|
|
|
labels: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'],
|
|
|
datasets: [{
|
|
|
label: '告警数量',
|
|
|
data: [12, 8, 15, 6, 10, 4],
|
|
|
borderColor: '#ff4444',
|
|
|
backgroundColor: gradient1,
|
|
|
borderWidth: 2,
|
|
|
fill: true,
|
|
|
tension: 0.4
|
|
|
}, {
|
|
|
label: '处理数量',
|
|
|
data: [8, 12, 10, 18, 14, 16],
|
|
|
borderColor: '#00d4ff',
|
|
|
backgroundColor: gradient2,
|
|
|
borderWidth: 2,
|
|
|
fill: true,
|
|
|
tension: 0.4
|
|
|
}, {
|
|
|
label: '完成数量',
|
|
|
data: [6, 10, 8, 15, 12, 14],
|
|
|
borderColor: '#00ff88',
|
|
|
backgroundColor: gradient3,
|
|
|
borderWidth: 2,
|
|
|
fill: true,
|
|
|
tension: 0.4
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
display: false
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
grid: {
|
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|
|
},
|
|
|
ticks: {
|
|
|
color: '#b0c4de',
|
|
|
font: {
|
|
|
size: 10
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
y: {
|
|
|
grid: {
|
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|
|
},
|
|
|
ticks: {
|
|
|
color: '#b0c4de',
|
|
|
font: {
|
|
|
size: 10
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
elements: {
|
|
|
point: {
|
|
|
radius: 3,
|
|
|
hoverRadius: 6
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 开始实时更新
|
|
|
function startRealTimeUpdates() {
|
|
|
// 每30秒更新一次数据
|
|
|
setInterval(() => {
|
|
|
updateDashboardData();
|
|
|
updateChartData();
|
|
|
}, 30000);
|
|
|
}
|
|
|
|
|
|
// 更新仪表板数据
|
|
|
function updateDashboardData() {
|
|
|
// 模拟数据变化
|
|
|
dashboardData.realTimeStatus.online += Math.floor(Math.random() * 3) - 1;
|
|
|
dashboardData.realTimeStatus.warning += Math.floor(Math.random() * 2) - 1;
|
|
|
dashboardData.realTimeStatus.processing += Math.floor(Math.random() * 2) - 1;
|
|
|
|
|
|
// 确保数据在合理范围内
|
|
|
dashboardData.realTimeStatus.online = Math.max(8, Math.min(15, dashboardData.realTimeStatus.online));
|
|
|
dashboardData.realTimeStatus.warning = Math.max(0, Math.min(5, dashboardData.realTimeStatus.warning));
|
|
|
dashboardData.realTimeStatus.processing = Math.max(0, Math.min(5, dashboardData.realTimeStatus.processing));
|
|
|
|
|
|
// 更新环境数据
|
|
|
dashboardData.envData.temperature += (Math.random() - 0.5) * 2;
|
|
|
dashboardData.envData.windSpeed += (Math.random() - 0.5) * 1;
|
|
|
dashboardData.envData.visibility += (Math.random() - 0.5) * 2;
|
|
|
|
|
|
// 确保环境数据在合理范围内
|
|
|
dashboardData.envData.temperature = Math.max(15, Math.min(35, dashboardData.envData.temperature));
|
|
|
dashboardData.envData.windSpeed = Math.max(0, Math.min(10, dashboardData.envData.windSpeed));
|
|
|
dashboardData.envData.visibility = Math.max(3, Math.min(15, dashboardData.envData.visibility));
|
|
|
|
|
|
// 更新界面
|
|
|
updateRealTimeStatus();
|
|
|
updateEnvironmentData();
|
|
|
}
|
|
|
|
|
|
// 更新图表数据
|
|
|
function updateChartData() {
|
|
|
if (window.chartInstances.trendChart) {
|
|
|
const datasets = window.chartInstances.trendChart.data.datasets;
|
|
|
datasets.forEach(dataset => {
|
|
|
// 移除第一个数据点,添加新的数据点
|
|
|
dataset.data.shift();
|
|
|
dataset.data.push(Math.floor(Math.random() * 20) + 5);
|
|
|
});
|
|
|
window.chartInstances.trendChart.update('none');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function addRiverOverlay() {
|
|
|
// 湘江长沙段轮廓(简化坐标)
|
|
|
const riverPoints = [
|
|
|
new T.LngLat(112.9200, 28.3000),
|
|
|
new T.LngLat(112.9300, 28.2800),
|
|
|
new T.LngLat(112.9400, 28.2600),
|
|
|
new T.LngLat(112.9500, 28.2400),
|
|
|
new T.LngLat(112.9600, 28.2200),
|
|
|
new T.LngLat(112.9700, 28.2000),
|
|
|
new T.LngLat(112.9800, 28.1800)
|
|
|
];
|
|
|
|
|
|
const polyline = new T.Polyline(riverPoints, {
|
|
|
color: '#3498db',
|
|
|
weight: 8,
|
|
|
opacity: 0.7
|
|
|
});
|
|
|
map.addOverLay(polyline);
|
|
|
|
|
|
// 添加信息窗口
|
|
|
const infoWindow = new T.InfoWindow();
|
|
|
infoWindow.setContent('湘江长沙段');
|
|
|
polyline.addEventListener('click', function() {
|
|
|
infoWindow.openInfoWindow(map, riverPoints[Math.floor(riverPoints.length/2)]);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function addDeviceMarkers() {
|
|
|
// 摄像头标记
|
|
|
const cameras = [
|
|
|
{lat: 28.2282, lng: 112.9388, name: '摄像头01-橘子洲头', status: 'online'},
|
|
|
{lat: 28.2150, lng: 112.9450, name: '摄像头02-杜甫江阁', status: 'online'},
|
|
|
{lat: 28.2400, lng: 112.9300, name: '摄像头03-湘江中路', status: 'warning'},
|
|
|
{lat: 28.2600, lng: 112.9200, name: '摄像头04-猴子石大桥', status: 'online'}
|
|
|
];
|
|
|
|
|
|
cameras.forEach(camera => {
|
|
|
const point = new T.LngLat(camera.lng, camera.lat);
|
|
|
const marker = new T.Marker(point);
|
|
|
|
|
|
// 创建自定义图标
|
|
|
const icon = new T.Icon({
|
|
|
iconUrl: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(`
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">
|
|
|
<circle cx="15" cy="15" r="12" fill="white" stroke="#ddd" stroke-width="2"/>
|
|
|
<text x="15" y="20" text-anchor="middle" font-family="FontAwesome" font-size="14" fill="${getStatusColor(camera.status)}"></text>
|
|
|
</svg>
|
|
|
`)}`,
|
|
|
iconSize: new T.Point(30, 30)
|
|
|
});
|
|
|
marker.setIcon(icon);
|
|
|
|
|
|
map.addOverLay(marker);
|
|
|
|
|
|
const infoWindow = new T.InfoWindow();
|
|
|
infoWindow.setContent(`${camera.name}<br>状态: ${getStatusText(camera.status)}`);
|
|
|
marker.addEventListener('click', function() {
|
|
|
infoWindow.openInfoWindow(map, point);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 无人船标记
|
|
|
const ships = [
|
|
|
{lat: 28.2200, lng: 112.9400, name: '无人船01', status: 'working'},
|
|
|
{lat: 28.2500, lng: 112.9350, name: '无人船02', status: 'working'},
|
|
|
{lat: 28.2350, lng: 112.9420, name: '无人船03', status: 'idle'}
|
|
|
];
|
|
|
|
|
|
ships.forEach(ship => {
|
|
|
const point = new T.LngLat(ship.lng, ship.lat);
|
|
|
const marker = new T.Marker(point);
|
|
|
|
|
|
const icon = new T.Icon({
|
|
|
iconUrl: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(`
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">
|
|
|
<circle cx="15" cy="15" r="12" fill="white" stroke="#ddd" stroke-width="2"/>
|
|
|
<text x="15" y="20" text-anchor="middle" font-family="FontAwesome" font-size="14" fill="${getStatusColor(ship.status)}"></text>
|
|
|
</svg>
|
|
|
`)}`,
|
|
|
iconSize: new T.Point(30, 30)
|
|
|
});
|
|
|
marker.setIcon(icon);
|
|
|
|
|
|
map.addOverLay(marker);
|
|
|
|
|
|
const infoWindow = new T.InfoWindow();
|
|
|
infoWindow.setContent(`${ship.name}<br>状态: ${getStatusText(ship.status)}`);
|
|
|
marker.addEventListener('click', function() {
|
|
|
infoWindow.openInfoWindow(map, point);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 无人机标记
|
|
|
const drones = [
|
|
|
{lat: 28.2300, lng: 112.9380, name: '无人机01', status: 'flying'},
|
|
|
{lat: 28.2450, lng: 112.9320, name: '无人机02', status: 'charging'}
|
|
|
];
|
|
|
|
|
|
drones.forEach(drone => {
|
|
|
const point = new T.LngLat(drone.lng, drone.lat);
|
|
|
const marker = new T.Marker(point);
|
|
|
|
|
|
const icon = new T.Icon({
|
|
|
iconUrl: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(`
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">
|
|
|
<circle cx="15" cy="15" r="12" fill="white" stroke="#ddd" stroke-width="2"/>
|
|
|
<text x="15" y="20" text-anchor="middle" font-family="FontAwesome" font-size="14" fill="${getStatusColor(drone.status)}"></text>
|
|
|
</svg>
|
|
|
`)}`,
|
|
|
iconSize: new T.Point(30, 30)
|
|
|
});
|
|
|
marker.setIcon(icon);
|
|
|
|
|
|
map.addOverLay(marker);
|
|
|
|
|
|
const infoWindow = new T.InfoWindow();
|
|
|
infoWindow.setContent(`${drone.name}<br>状态: ${getStatusText(drone.status)}`);
|
|
|
marker.addEventListener('click', function() {
|
|
|
infoWindow.openInfoWindow(map, point);
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function addGarbageMarkers() {
|
|
|
const garbagePoints = [
|
|
|
{lat: 28.2250, lng: 112.9410, type: 'water', severity: 'high', description: '大量漂浮垃圾'},
|
|
|
{lat: 28.2180, lng: 112.9460, type: 'beach', severity: 'medium', description: '滩涂堆积物'},
|
|
|
{lat: 28.2420, lng: 112.9330, type: 'water', severity: 'low', description: '少量枯枝'},
|
|
|
{lat: 28.2380, lng: 112.9390, type: 'illegal', severity: 'high', description: '违法倾倒点'}
|
|
|
];
|
|
|
|
|
|
garbagePoints.forEach(point => {
|
|
|
const mapPoint = new T.LngLat(point.lng, point.lat);
|
|
|
const marker = new T.Marker(mapPoint);
|
|
|
|
|
|
const icon = new T.Icon({
|
|
|
iconUrl: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(`
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25">
|
|
|
<circle cx="12.5" cy="12.5" r="10" fill="white" stroke="#ddd" stroke-width="2"/>
|
|
|
<text x="12.5" y="17" text-anchor="middle" font-family="FontAwesome" font-size="12" fill="${getSeverityColor(point.severity)}"></text>
|
|
|
</svg>
|
|
|
`)}`,
|
|
|
iconSize: new T.Point(25, 25)
|
|
|
});
|
|
|
marker.setIcon(icon);
|
|
|
|
|
|
map.addOverLay(marker);
|
|
|
|
|
|
const infoWindow = new T.InfoWindow();
|
|
|
infoWindow.setContent(`${point.description}<br>类型: ${getTypeText(point.type)}<br>严重程度: ${point.severity}`);
|
|
|
marker.addEventListener('click', function() {
|
|
|
infoWindow.openInfoWindow(map, mapPoint);
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function getStatusColor(status) {
|
|
|
const colors = {
|
|
|
'online': '#2ecc71',
|
|
|
'offline': '#e74c3c',
|
|
|
'warning': '#f39c12',
|
|
|
'working': '#3498db',
|
|
|
'idle': '#95a5a6',
|
|
|
'flying': '#9b59b6',
|
|
|
'charging': '#f1c40f'
|
|
|
};
|
|
|
return colors[status] || '#95a5a6';
|
|
|
}
|
|
|
|
|
|
function getStatusText(status) {
|
|
|
const texts = {
|
|
|
'online': '在线',
|
|
|
'offline': '离线',
|
|
|
'warning': '警告',
|
|
|
'working': '作业中',
|
|
|
'idle': '待机',
|
|
|
'flying': '飞行中',
|
|
|
'charging': '充电中'
|
|
|
};
|
|
|
return texts[status] || '未知';
|
|
|
}
|
|
|
|
|
|
function getSeverityColor(severity) {
|
|
|
const colors = {
|
|
|
'high': '#e74c3c',
|
|
|
'medium': '#f39c12',
|
|
|
'low': '#f1c40f'
|
|
|
};
|
|
|
return colors[severity] || '#95a5a6';
|
|
|
}
|
|
|
|
|
|
function getTypeText(type) {
|
|
|
const texts = {
|
|
|
'water': '水面垃圾',
|
|
|
'beach': '滩涂垃圾',
|
|
|
'illegal': '违法倾倒'
|
|
|
};
|
|
|
return texts[type] || '未知';
|
|
|
}
|
|
|
|
|
|
function toggleMapLayers() {
|
|
|
// 图层切换逻辑
|
|
|
console.log('切换地图图层');
|
|
|
}
|
|
|
|
|
|
function toggleFullscreen() {
|
|
|
const mapContainer = document.querySelector('.map-container');
|
|
|
if (!document.fullscreenElement) {
|
|
|
mapContainer.requestFullscreen().then(() => {
|
|
|
setTimeout(() => map.invalidateSize(), 100);
|
|
|
});
|
|
|
} else {
|
|
|
document.exitFullscreen();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 工单系统
|
|
|
function initializeWorkorderSystem() {
|
|
|
// 创建工单按钮
|
|
|
document.getElementById('createWorkorder').addEventListener('click', showCreateWorkorderModal);
|
|
|
|
|
|
// 导出数据按钮
|
|
|
document.getElementById('exportWorkorder').addEventListener('click', exportWorkorderData);
|
|
|
|
|
|
// 筛选器事件
|
|
|
document.querySelectorAll('.filter-select').forEach(select => {
|
|
|
select.addEventListener('change', filterWorkorders);
|
|
|
});
|
|
|
|
|
|
// 搜索框事件
|
|
|
document.querySelector('.search-input').addEventListener('input', searchWorkorders);
|
|
|
|
|
|
// 初始化工单表格
|
|
|
refreshWorkorderTable();
|
|
|
}
|
|
|
|
|
|
function showCreateWorkorderModal() {
|
|
|
const modalBody = document.getElementById('modalBody');
|
|
|
modalBody.innerHTML = `
|
|
|
<div class="modal-header">
|
|
|
<h2>创建新工单</h2>
|
|
|
<button class="close" onclick="closeModal()">×</button>
|
|
|
</div>
|
|
|
<div class="modal-body">
|
|
|
<form id="createWorkorderForm">
|
|
|
<div class="form-group">
|
|
|
<label>垃圾类型:</label>
|
|
|
<select name="type" required>
|
|
|
<option value="water">水面垃圾</option>
|
|
|
<option value="beach">滩涂垃圾</option>
|
|
|
<option value="illegal">违法倾倒</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>位置描述:</label>
|
|
|
<input type="text" name="location" required placeholder="请输入具体位置">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>严重程度:</label>
|
|
|
<select name="severity" required>
|
|
|
<option value="low">低</option>
|
|
|
<option value="medium">中</option>
|
|
|
<option value="high">高</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>描述:</label>
|
|
|
<textarea name="description" rows="3" placeholder="请描述垃圾情况"></textarea>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button type="submit" class="btn btn-primary">创建工单</button>
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">取消</button>
|
|
|
</div>
|
|
|
</form>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
document.getElementById('modal').style.display = 'block';
|
|
|
|
|
|
// 表单提交事件
|
|
|
document.getElementById('createWorkorderForm').addEventListener('submit', function(e) {
|
|
|
e.preventDefault();
|
|
|
createWorkorder(new FormData(this));
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function createWorkorder(formData) {
|
|
|
const workorder = {
|
|
|
id: 'WO' + Date.now(),
|
|
|
type: formData.get('type'),
|
|
|
location: formData.get('location'),
|
|
|
severity: formData.get('severity'),
|
|
|
description: formData.get('description'),
|
|
|
status: 'pending',
|
|
|
assignee: '待分配',
|
|
|
createTime: new Date().toLocaleString('zh-CN'),
|
|
|
discoveryMethod: '手动创建'
|
|
|
};
|
|
|
|
|
|
workorderData.unshift(workorder);
|
|
|
refreshWorkorderTable();
|
|
|
closeModal();
|
|
|
showNotification('工单创建成功', 'success');
|
|
|
}
|
|
|
|
|
|
function refreshWorkorderTable() {
|
|
|
const tbody = document.getElementById('workorderTableBody');
|
|
|
if (!tbody) return;
|
|
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
|
|
workorderData.forEach(workorder => {
|
|
|
const row = document.createElement('tr');
|
|
|
row.innerHTML = `
|
|
|
<td>${workorder.id}</td>
|
|
|
<td>${getTypeText(workorder.type)}</td>
|
|
|
<td>${workorder.location}</td>
|
|
|
<td>${workorder.discoveryMethod}</td>
|
|
|
<td><span class="badge ${getStatusBadgeClass(workorder.status)}">${getStatusText(workorder.status)}</span></td>
|
|
|
<td>${workorder.assignee}</td>
|
|
|
<td>${workorder.createTime}</td>
|
|
|
<td>
|
|
|
<button class="btn btn-sm" onclick="viewWorkorder('${workorder.id}')">查看</button>
|
|
|
<button class="btn btn-sm" onclick="editWorkorder('${workorder.id}')">编辑</button>
|
|
|
</td>
|
|
|
`;
|
|
|
tbody.appendChild(row);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function getStatusBadgeClass(status) {
|
|
|
const classes = {
|
|
|
'pending': 'warning',
|
|
|
'processing': 'info',
|
|
|
'completed': 'success',
|
|
|
'cancelled': 'danger'
|
|
|
};
|
|
|
return classes[status] || 'secondary';
|
|
|
}
|
|
|
|
|
|
function filterWorkorders() {
|
|
|
// 筛选逻辑
|
|
|
refreshWorkorderTable();
|
|
|
}
|
|
|
|
|
|
function searchWorkorders() {
|
|
|
// 搜索逻辑
|
|
|
refreshWorkorderTable();
|
|
|
}
|
|
|
|
|
|
function exportWorkorderData() {
|
|
|
// 导出数据逻辑
|
|
|
console.log('导出工单数据');
|
|
|
showNotification('数据导出成功', 'success');
|
|
|
}
|
|
|
|
|
|
function viewWorkorder(id) {
|
|
|
const workorder = workorderData.find(w => w.id === id);
|
|
|
if (!workorder) return;
|
|
|
|
|
|
const modalBody = document.getElementById('modalBody');
|
|
|
modalBody.innerHTML = `
|
|
|
<h3>工单详情 - ${workorder.id}</h3>
|
|
|
<div class="workorder-details">
|
|
|
<div class="detail-item">
|
|
|
<label>类型:</label>
|
|
|
<span>${getTypeText(workorder.type)}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>位置:</label>
|
|
|
<span>${workorder.location}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>状态:</label>
|
|
|
<span class="badge ${getStatusBadgeClass(workorder.status)}">${getStatusText(workorder.status)}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>负责人:</label>
|
|
|
<span>${workorder.assignee}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>创建时间:</label>
|
|
|
<span>${workorder.createTime}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>描述:</label>
|
|
|
<span>${workorder.description || '无'}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">关闭</button>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
document.getElementById('modal').style.display = 'block';
|
|
|
}
|
|
|
|
|
|
function editWorkorder(id) {
|
|
|
// 编辑工单逻辑
|
|
|
console.log('编辑工单:', id);
|
|
|
}
|
|
|
|
|
|
// 设备管理
|
|
|
function initializeDeviceManagement() {
|
|
|
// 添加设备按钮
|
|
|
document.getElementById('addDevice').addEventListener('click', showAddDeviceModal);
|
|
|
|
|
|
// 维护计划按钮
|
|
|
document.getElementById('deviceMaintenance').addEventListener('click', showMaintenanceModal);
|
|
|
|
|
|
// 设备分类切换
|
|
|
document.querySelectorAll('.category-card').forEach(card => {
|
|
|
card.addEventListener('click', function() {
|
|
|
document.querySelectorAll('.category-card').forEach(c => c.classList.remove('active'));
|
|
|
this.classList.add('active');
|
|
|
filterDevicesByCategory(this.getAttribute('data-category'));
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 初始化设备网格
|
|
|
refreshDeviceGrid();
|
|
|
}
|
|
|
|
|
|
function showAddDeviceModal() {
|
|
|
const modalBody = document.getElementById('modalBody');
|
|
|
modalBody.innerHTML = `
|
|
|
<div class="modal-header">
|
|
|
<h2>添加新设备</h2>
|
|
|
<button class="close" onclick="closeModal()">×</button>
|
|
|
</div>
|
|
|
<div class="modal-body">
|
|
|
<form id="addDeviceForm">
|
|
|
<div class="form-group">
|
|
|
<label>设备类型:</label>
|
|
|
<select name="category" required>
|
|
|
<option value="camera">智能摄像头</option>
|
|
|
<option value="ship">无人船</option>
|
|
|
<option value="drone">无人机</option>
|
|
|
<option value="sensor">传感器</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>设备名称:</label>
|
|
|
<input type="text" name="name" required placeholder="请输入设备名称">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>设备型号:</label>
|
|
|
<input type="text" name="model" required placeholder="请输入设备型号">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>安装位置:</label>
|
|
|
<input type="text" name="location" required placeholder="请输入安装位置">
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button type="submit" class="btn btn-primary">添加设备</button>
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">取消</button>
|
|
|
</div>
|
|
|
</form>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
document.getElementById('modal').style.display = 'block';
|
|
|
|
|
|
document.getElementById('addDeviceForm').addEventListener('submit', function(e) {
|
|
|
e.preventDefault();
|
|
|
addDevice(new FormData(this));
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function addDevice(formData) {
|
|
|
const device = {
|
|
|
id: 'DEV' + Date.now(),
|
|
|
category: formData.get('category'),
|
|
|
name: formData.get('name'),
|
|
|
model: formData.get('model'),
|
|
|
location: formData.get('location'),
|
|
|
status: 'online',
|
|
|
battery: Math.floor(Math.random() * 100),
|
|
|
lastMaintenance: new Date().toLocaleDateString('zh-CN'),
|
|
|
installDate: new Date().toLocaleDateString('zh-CN')
|
|
|
};
|
|
|
|
|
|
deviceData.push(device);
|
|
|
refreshDeviceGrid();
|
|
|
closeModal();
|
|
|
showNotification('设备添加成功', 'success');
|
|
|
}
|
|
|
|
|
|
function refreshDeviceGrid() {
|
|
|
const grid = document.getElementById('deviceGrid');
|
|
|
if (!grid) return;
|
|
|
|
|
|
grid.innerHTML = '';
|
|
|
|
|
|
deviceData.forEach(device => {
|
|
|
const card = document.createElement('div');
|
|
|
card.className = 'device-card';
|
|
|
card.innerHTML = `
|
|
|
<div class="device-header">
|
|
|
<div class="device-icon">
|
|
|
<i class="fas ${getDeviceIcon(device.category)}"></i>
|
|
|
</div>
|
|
|
<div class="device-info">
|
|
|
<h4>${device.name}</h4>
|
|
|
<span class="device-model">${device.model}</span>
|
|
|
</div>
|
|
|
<div class="device-status">
|
|
|
<span class="status-indicator ${device.status}"></span>
|
|
|
<span class="status-text">${getStatusText(device.status)}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="device-body">
|
|
|
<div class="device-detail">
|
|
|
<span class="detail-label">位置:</span>
|
|
|
<span class="detail-value">${device.location}</span>
|
|
|
</div>
|
|
|
<div class="device-detail">
|
|
|
<span class="detail-label">电量:</span>
|
|
|
<span class="detail-value">${device.battery}%</span>
|
|
|
<div class="progress">
|
|
|
<div class="progress-bar" style="width: ${device.battery}%"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="device-detail">
|
|
|
<span class="detail-label">上次维护:</span>
|
|
|
<span class="detail-value">${device.lastMaintenance}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="device-actions">
|
|
|
<button class="btn btn-sm" onclick="viewDevice('${device.id}')">详情</button>
|
|
|
<button class="btn btn-sm" onclick="controlDevice('${device.id}')">控制</button>
|
|
|
<button class="btn btn-sm" onclick="maintainDevice('${device.id}')">维护</button>
|
|
|
</div>
|
|
|
`;
|
|
|
grid.appendChild(card);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function getDeviceIcon(category) {
|
|
|
const icons = {
|
|
|
'camera': 'fa-video',
|
|
|
'ship': 'fa-ship',
|
|
|
'drone': 'fa-helicopter',
|
|
|
'sensor': 'fa-satellite-dish'
|
|
|
};
|
|
|
return icons[category] || 'fa-cog';
|
|
|
}
|
|
|
|
|
|
function filterDevicesByCategory(category) {
|
|
|
// 设备分类筛选逻辑
|
|
|
refreshDeviceGrid();
|
|
|
}
|
|
|
|
|
|
function viewDevice(id) {
|
|
|
const device = deviceData.find(d => d.id === id);
|
|
|
if (!device) return;
|
|
|
|
|
|
const modalBody = document.getElementById('modalBody');
|
|
|
modalBody.innerHTML = `
|
|
|
<h3>设备详情 - ${device.name}</h3>
|
|
|
<div class="device-details">
|
|
|
<div class="detail-item">
|
|
|
<label>设备ID:</label>
|
|
|
<span>${device.id}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>设备类型:</label>
|
|
|
<span>${getCategoryText(device.category)}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>设备型号:</label>
|
|
|
<span>${device.model}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>安装位置:</label>
|
|
|
<span>${device.location}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>设备状态:</label>
|
|
|
<span class="badge ${getStatusBadgeClass(device.status)}">${getStatusText(device.status)}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>电池电量:</label>
|
|
|
<span>${device.battery}%</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>安装日期:</label>
|
|
|
<span>${device.installDate}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<label>上次维护:</label>
|
|
|
<span>${device.lastMaintenance}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">关闭</button>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
document.getElementById('modal').style.display = 'block';
|
|
|
}
|
|
|
|
|
|
function getCategoryText(category) {
|
|
|
const texts = {
|
|
|
'camera': '智能摄像头',
|
|
|
'ship': '无人船',
|
|
|
'drone': '无人机',
|
|
|
'sensor': '传感器'
|
|
|
};
|
|
|
return texts[category] || '未知';
|
|
|
}
|
|
|
|
|
|
function controlDevice(id) {
|
|
|
console.log('控制设备:', id);
|
|
|
showNotification('设备控制指令已发送', 'info');
|
|
|
}
|
|
|
|
|
|
function maintainDevice(id) {
|
|
|
console.log('维护设备:', id);
|
|
|
showNotification('维护任务已创建', 'success');
|
|
|
}
|
|
|
|
|
|
function showMaintenanceModal() {
|
|
|
const modalBody = document.getElementById('modalBody');
|
|
|
modalBody.innerHTML = `
|
|
|
<div class="modal-header">
|
|
|
<h2>设备维护计划</h2>
|
|
|
<button class="close" onclick="closeModal()">×</button>
|
|
|
</div>
|
|
|
<div class="modal-body">
|
|
|
<div class="maintenance-schedule">
|
|
|
<div class="schedule-item">
|
|
|
<div class="schedule-date">2024-01-15</div>
|
|
|
<div class="schedule-device">无人船-001</div>
|
|
|
<div class="schedule-type">定期保养</div>
|
|
|
<div class="schedule-status pending">待执行</div>
|
|
|
</div>
|
|
|
<div class="schedule-item">
|
|
|
<div class="schedule-date">2024-01-18</div>
|
|
|
<div class="schedule-device">摄像头-025</div>
|
|
|
<div class="schedule-type">故障维修</div>
|
|
|
<div class="schedule-status processing">进行中</div>
|
|
|
</div>
|
|
|
<div class="schedule-item">
|
|
|
<div class="schedule-date">2024-01-20</div>
|
|
|
<div class="schedule-device">无人机-003</div>
|
|
|
<div class="schedule-type">电池更换</div>
|
|
|
<div class="schedule-status completed">已完成</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button type="button" class="btn btn-primary">添加维护计划</button>
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">关闭</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
document.getElementById('modal').style.display = 'block';
|
|
|
}
|
|
|
|
|
|
// 预警系统
|
|
|
function initializeWarningSystem() {
|
|
|
// 预警设置按钮
|
|
|
document.getElementById('warningSettings').addEventListener('click', showWarningSettingsModal);
|
|
|
|
|
|
// 历史记录按钮
|
|
|
document.getElementById('warningHistory').addEventListener('click', showWarningHistoryModal);
|
|
|
|
|
|
// 初始化预警数据
|
|
|
refreshWarningData();
|
|
|
}
|
|
|
|
|
|
function refreshWarningData() {
|
|
|
// 更新天气信息
|
|
|
updateWeatherInfo();
|
|
|
|
|
|
// 更新垃圾堆积预警
|
|
|
updateGarbageAlerts();
|
|
|
|
|
|
// 更新设备故障预警
|
|
|
updateDeviceAlerts();
|
|
|
}
|
|
|
|
|
|
function updateWeatherInfo() {
|
|
|
// 模拟天气数据更新
|
|
|
const weatherItems = document.querySelectorAll('.weather-item .weather-value');
|
|
|
if (weatherItems.length >= 3) {
|
|
|
weatherItems[0].textContent = '多云 ' + (20 + Math.floor(Math.random() * 10)) + '°C';
|
|
|
weatherItems[1].textContent = Math.floor(Math.random() * 5 + 1) + '级';
|
|
|
weatherItems[2].textContent = Math.floor(Math.random() * 100) + '%';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function updateGarbageAlerts() {
|
|
|
// 更新垃圾堆积预警数据
|
|
|
console.log('更新垃圾堆积预警');
|
|
|
}
|
|
|
|
|
|
function updateDeviceAlerts() {
|
|
|
// 更新设备故障预警数据
|
|
|
console.log('更新设备故障预警');
|
|
|
}
|
|
|
|
|
|
function showWarningSettingsModal() {
|
|
|
const modalBody = document.getElementById('modalBody');
|
|
|
modalBody.innerHTML = `
|
|
|
<div class="modal-header">
|
|
|
<h2>预警设置</h2>
|
|
|
<button class="close" onclick="closeModal()">×</button>
|
|
|
</div>
|
|
|
<div class="modal-body">
|
|
|
<form id="warningSettingsForm">
|
|
|
<div class="settings-section">
|
|
|
<h4>天气预警</h4>
|
|
|
<div class="form-group">
|
|
|
<label>风力预警阈值:</label>
|
|
|
<select name="windThreshold">
|
|
|
<option value="3">3级</option>
|
|
|
<option value="4" selected>4级</option>
|
|
|
<option value="5">5级</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>降雨预警阈值:</label>
|
|
|
<select name="rainThreshold">
|
|
|
<option value="50">50%</option>
|
|
|
<option value="70" selected>70%</option>
|
|
|
<option value="80">80%</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="settings-section">
|
|
|
<h4>设备预警</h4>
|
|
|
<div class="form-group">
|
|
|
<label>电量预警阈值:</label>
|
|
|
<select name="batteryThreshold">
|
|
|
<option value="10">10%</option>
|
|
|
<option value="20" selected>20%</option>
|
|
|
<option value="30">30%</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>离线预警时间:</label>
|
|
|
<select name="offlineThreshold">
|
|
|
<option value="5">5分钟</option>
|
|
|
<option value="10" selected>10分钟</option>
|
|
|
<option value="15">15分钟</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button type="submit" class="btn btn-primary">保存设置</button>
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">取消</button>
|
|
|
</div>
|
|
|
</form>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
document.getElementById('modal').style.display = 'block';
|
|
|
|
|
|
document.getElementById('warningSettingsForm').addEventListener('submit', function(e) {
|
|
|
e.preventDefault();
|
|
|
saveWarningSettings(new FormData(this));
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function saveWarningSettings(formData) {
|
|
|
// 保存预警设置
|
|
|
console.log('保存预警设置');
|
|
|
closeModal();
|
|
|
showNotification('预警设置已保存', 'success');
|
|
|
}
|
|
|
|
|
|
function showWarningHistoryModal() {
|
|
|
const modalBody = document.getElementById('modalBody');
|
|
|
modalBody.innerHTML = `
|
|
|
<div class="modal-header">
|
|
|
<h2>预警历史记录</h2>
|
|
|
<button class="close" onclick="closeModal()">×</button>
|
|
|
</div>
|
|
|
<div class="modal-body">
|
|
|
<div class="warning-history">
|
|
|
<div class="history-item critical">
|
|
|
<div class="history-time">2024-01-10 14:30</div>
|
|
|
<div class="history-type">设备故障</div>
|
|
|
<div class="history-message">无人船-001电池电量严重不足</div>
|
|
|
<div class="history-status resolved">已处理</div>
|
|
|
</div>
|
|
|
<div class="history-item high">
|
|
|
<div class="history-time">2024-01-09 09:15</div>
|
|
|
<div class="history-type">天气预警</div>
|
|
|
<div class="history-message">预计下午有大风,建议暂停水面作业</div>
|
|
|
<div class="history-status resolved">已处理</div>
|
|
|
</div>
|
|
|
<div class="history-item medium">
|
|
|
<div class="history-time">2024-01-08 16:45</div>
|
|
|
<div class="history-type">垃圾堆积</div>
|
|
|
<div class="history-message">橘子洲头弯道垃圾堆积超过阈值</div>
|
|
|
<div class="history-status pending">处理中</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">关闭</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
document.getElementById('modal').style.display = 'block';
|
|
|
}
|
|
|
|
|
|
// 统计分析事件绑定
|
|
|
function bindStatisticsEvents() {
|
|
|
// 时间范围选择
|
|
|
const timeRangeSelect = document.querySelector('.time-range');
|
|
|
if (timeRangeSelect) {
|
|
|
timeRangeSelect.addEventListener('change', function() {
|
|
|
refreshCharts();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 导出报告按钮
|
|
|
const exportBtn = document.getElementById('exportReport');
|
|
|
if (exportBtn) {
|
|
|
exportBtn.addEventListener('click', exportReport);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 重复的chart初始化代码已被完全移除
|
|
|
// 现在统计分析页面的charts由setupStatisticsCharts()函数统一管理
|
|
|
|
|
|
// 初始化监控管理一张图的图表
|
|
|
function initializeMonitorCharts() {
|
|
|
// 垃圾清理统计柱状图
|
|
|
const cleanupChartCtx = document.getElementById('cleanupChart');
|
|
|
if (cleanupChartCtx) {
|
|
|
// 销毁已存在的图表实例
|
|
|
destroyChart('cleanupStats');
|
|
|
window.chartInstances.cleanupStats = new Chart(cleanupChartCtx, {
|
|
|
type: 'bar',
|
|
|
data: {
|
|
|
labels: ['今日', '昨日', '本周', '上周', '本月'],
|
|
|
datasets: [{
|
|
|
label: '清理量(kg)',
|
|
|
data: [156, 142, 1200, 980, 4500],
|
|
|
backgroundColor: [
|
|
|
'rgba(52, 152, 219, 0.8)',
|
|
|
'rgba(46, 204, 113, 0.8)',
|
|
|
'rgba(155, 89, 182, 0.8)',
|
|
|
'rgba(241, 196, 15, 0.8)',
|
|
|
'rgba(231, 76, 60, 0.8)'
|
|
|
],
|
|
|
borderColor: [
|
|
|
'#3498db',
|
|
|
'#2ecc71',
|
|
|
'#9b59b6',
|
|
|
'#f1c40f',
|
|
|
'#e74c3c'
|
|
|
],
|
|
|
borderWidth: 2
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
display: false
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
ticks: {
|
|
|
color: '#b0c4de',
|
|
|
font: {
|
|
|
size: 10
|
|
|
}
|
|
|
},
|
|
|
grid: {
|
|
|
color: 'rgba(176, 196, 222, 0.1)'
|
|
|
}
|
|
|
},
|
|
|
y: {
|
|
|
beginAtZero: true,
|
|
|
ticks: {
|
|
|
color: '#b0c4de',
|
|
|
font: {
|
|
|
size: 10
|
|
|
}
|
|
|
},
|
|
|
grid: {
|
|
|
color: 'rgba(176, 196, 222, 0.1)'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 实时数据趋势线图
|
|
|
const trendChartCtx = document.getElementById('trendChart');
|
|
|
if (trendChartCtx) {
|
|
|
// 销毁已存在的图表实例
|
|
|
destroyChart('trendChart');
|
|
|
window.chartInstances['trendChart'] = new Chart(trendChartCtx, {
|
|
|
type: 'line',
|
|
|
data: {
|
|
|
labels: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
|
|
|
datasets: [
|
|
|
{
|
|
|
label: '垃圾检测数量',
|
|
|
data: [5, 3, 8, 12, 15, 9, 6],
|
|
|
borderColor: '#e74c3c',
|
|
|
backgroundColor: 'rgba(231, 76, 60, 0.1)',
|
|
|
tension: 0.4,
|
|
|
fill: false
|
|
|
},
|
|
|
{
|
|
|
label: '清理完成数量',
|
|
|
data: [3, 2, 6, 10, 13, 8, 5],
|
|
|
borderColor: '#3498db',
|
|
|
backgroundColor: 'rgba(52, 152, 219, 0.1)',
|
|
|
tension: 0.4,
|
|
|
fill: false
|
|
|
},
|
|
|
{
|
|
|
label: '设备在线率(%)',
|
|
|
data: [95, 92, 98, 96, 94, 97, 95],
|
|
|
borderColor: '#2ecc71',
|
|
|
backgroundColor: 'rgba(46, 204, 113, 0.1)',
|
|
|
tension: 0.4,
|
|
|
fill: false,
|
|
|
yAxisID: 'y1'
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
display: false
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
ticks: {
|
|
|
color: '#b0c4de',
|
|
|
font: {
|
|
|
size: 9
|
|
|
}
|
|
|
},
|
|
|
grid: {
|
|
|
color: 'rgba(176, 196, 222, 0.1)'
|
|
|
}
|
|
|
},
|
|
|
y: {
|
|
|
type: 'linear',
|
|
|
display: true,
|
|
|
position: 'left',
|
|
|
beginAtZero: true,
|
|
|
ticks: {
|
|
|
color: '#b0c4de',
|
|
|
font: {
|
|
|
size: 9
|
|
|
}
|
|
|
},
|
|
|
grid: {
|
|
|
color: 'rgba(176, 196, 222, 0.1)'
|
|
|
}
|
|
|
},
|
|
|
y1: {
|
|
|
type: 'linear',
|
|
|
display: true,
|
|
|
position: 'right',
|
|
|
min: 0,
|
|
|
max: 100,
|
|
|
ticks: {
|
|
|
color: '#b0c4de',
|
|
|
font: {
|
|
|
size: 9
|
|
|
}
|
|
|
},
|
|
|
grid: {
|
|
|
drawOnChartArea: false
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function refreshCharts() {
|
|
|
// 刷新所有图表数据
|
|
|
Object.values(window.chartInstances).forEach(chart => {
|
|
|
if (chart && chart.update) {
|
|
|
chart.update();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 刷新监控管理一张图的图表
|
|
|
function refreshMonitorCharts() {
|
|
|
// 更新垃圾清理统计数据
|
|
|
if (window.chartInstances.cleanupStats) {
|
|
|
const newData = {
|
|
|
labels: ['今日', '昨日', '本周', '上周', '本月'],
|
|
|
datasets: [{
|
|
|
label: '清理量(kg)',
|
|
|
data: [
|
|
|
Math.floor(Math.random() * 50) + 130, // 今日: 130-180
|
|
|
Math.floor(Math.random() * 40) + 120, // 昨日: 120-160
|
|
|
Math.floor(Math.random() * 400) + 1000, // 本周: 1000-1400
|
|
|
Math.floor(Math.random() * 300) + 800, // 上周: 800-1100
|
|
|
Math.floor(Math.random() * 1000) + 4000 // 本月: 4000-5000
|
|
|
],
|
|
|
backgroundColor: [
|
|
|
'rgba(52, 152, 219, 0.8)',
|
|
|
'rgba(46, 204, 113, 0.8)',
|
|
|
'rgba(155, 89, 182, 0.8)',
|
|
|
'rgba(241, 196, 15, 0.8)',
|
|
|
'rgba(231, 76, 60, 0.8)'
|
|
|
],
|
|
|
borderColor: [
|
|
|
'#3498db',
|
|
|
'#2ecc71',
|
|
|
'#9b59b6',
|
|
|
'#f1c40f',
|
|
|
'#e74c3c'
|
|
|
],
|
|
|
borderWidth: 2
|
|
|
}]
|
|
|
};
|
|
|
window.chartInstances.cleanupStats.data = newData;
|
|
|
window.chartInstances.cleanupStats.update();
|
|
|
}
|
|
|
|
|
|
// 更新实时数据趋势
|
|
|
if (window.chartInstances.trendStats) {
|
|
|
const currentHour = new Date().getHours();
|
|
|
const timeLabels = [];
|
|
|
const detectionData = [];
|
|
|
const cleanupData = [];
|
|
|
const onlineRateData = [];
|
|
|
|
|
|
// 生成过去24小时的数据
|
|
|
for (let i = 6; i >= 0; i--) {
|
|
|
const hour = (currentHour - i * 4 + 24) % 24;
|
|
|
timeLabels.push(String(hour).padStart(2, '0') + ':00');
|
|
|
detectionData.push(Math.floor(Math.random() * 10) + 3);
|
|
|
cleanupData.push(Math.floor(Math.random() * 8) + 2);
|
|
|
onlineRateData.push(Math.floor(Math.random() * 8) + 92);
|
|
|
}
|
|
|
|
|
|
window.chartInstances.trendStats.data.labels = timeLabels;
|
|
|
window.chartInstances.trendStats.data.datasets[0].data = detectionData;
|
|
|
window.chartInstances.trendStats.data.datasets[1].data = cleanupData;
|
|
|
window.chartInstances.trendStats.data.datasets[2].data = onlineRateData;
|
|
|
window.chartInstances.trendStats.update();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function exportReport() {
|
|
|
console.log('导出统计报告');
|
|
|
showNotification('报告导出成功', 'success');
|
|
|
}
|
|
|
|
|
|
// 模态框系统
|
|
|
function initializeModal() {
|
|
|
const modal = document.getElementById('modal');
|
|
|
const closeBtn = document.querySelector('.close');
|
|
|
|
|
|
// 点击关闭按钮
|
|
|
closeBtn.addEventListener('click', closeModal);
|
|
|
|
|
|
// 点击模态框外部关闭
|
|
|
window.addEventListener('click', function(event) {
|
|
|
if (event.target === modal) {
|
|
|
closeModal();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// ESC键关闭
|
|
|
document.addEventListener('keydown', function(event) {
|
|
|
if (event.key === 'Escape') {
|
|
|
closeModal();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function closeModal() {
|
|
|
document.getElementById('modal').style.display = 'none';
|
|
|
}
|
|
|
|
|
|
// 通知系统
|
|
|
function showNotification(message, type = 'info') {
|
|
|
const notification = document.createElement('div');
|
|
|
notification.className = `notification ${type}`;
|
|
|
notification.innerHTML = `
|
|
|
<div class="notification-content">
|
|
|
<i class="fas ${getNotificationIcon(type)}"></i>
|
|
|
<span>${message}</span>
|
|
|
</div>
|
|
|
<button class="notification-close" onclick="this.parentElement.remove()">
|
|
|
<i class="fas fa-times"></i>
|
|
|
</button>
|
|
|
`;
|
|
|
|
|
|
// 添加样式
|
|
|
notification.style.cssText = `
|
|
|
position: fixed;
|
|
|
top: 100px;
|
|
|
right: 20px;
|
|
|
background: white;
|
|
|
border-radius: 8px;
|
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
|
padding: 1rem;
|
|
|
z-index: 2001;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
min-width: 300px;
|
|
|
border-left: 4px solid ${getNotificationColor(type)};
|
|
|
animation: slideInRight 0.3s ease;
|
|
|
`;
|
|
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
|
|
// 自动移除
|
|
|
setTimeout(() => {
|
|
|
if (notification.parentElement) {
|
|
|
notification.remove();
|
|
|
}
|
|
|
}, 5000);
|
|
|
}
|
|
|
|
|
|
function getNotificationIcon(type) {
|
|
|
const icons = {
|
|
|
'success': 'fa-check-circle',
|
|
|
'error': 'fa-exclamation-circle',
|
|
|
'warning': 'fa-exclamation-triangle',
|
|
|
'info': 'fa-info-circle'
|
|
|
};
|
|
|
return icons[type] || 'fa-info-circle';
|
|
|
}
|
|
|
|
|
|
function getNotificationColor(type) {
|
|
|
const colors = {
|
|
|
'success': '#2ecc71',
|
|
|
'error': '#e74c3c',
|
|
|
'warning': '#f39c12',
|
|
|
'info': '#3498db'
|
|
|
};
|
|
|
return colors[type] || '#3498db';
|
|
|
}
|
|
|
|
|
|
// 数据加载
|
|
|
function loadMockData() {
|
|
|
// 加载模拟工单数据
|
|
|
workorderData = [
|
|
|
{
|
|
|
id: 'WO202401001',
|
|
|
type: 'water',
|
|
|
location: '橘子洲头弯道',
|
|
|
discoveryMethod: 'AI自动识别',
|
|
|
status: 'processing',
|
|
|
assignee: '张三',
|
|
|
createTime: '2024-01-10 09:30:00',
|
|
|
description: '发现大量漂浮垃圾'
|
|
|
},
|
|
|
{
|
|
|
id: 'WO202401002',
|
|
|
type: 'beach',
|
|
|
location: '湘江中路段滩涂',
|
|
|
discoveryMethod: '巡查员上报',
|
|
|
status: 'completed',
|
|
|
assignee: '李四',
|
|
|
createTime: '2024-01-09 14:15:00',
|
|
|
description: '滩涂堆积物清理'
|
|
|
},
|
|
|
{
|
|
|
id: 'WO202401003',
|
|
|
type: 'illegal',
|
|
|
location: '猴子石大桥下',
|
|
|
discoveryMethod: '公众反馈',
|
|
|
status: 'pending',
|
|
|
assignee: '待分配',
|
|
|
createTime: '2024-01-11 16:45:00',
|
|
|
description: '疑似违法倾倒垃圾'
|
|
|
}
|
|
|
];
|
|
|
|
|
|
// 加载模拟设备数据
|
|
|
deviceData = [
|
|
|
{
|
|
|
id: 'CAM001',
|
|
|
category: 'camera',
|
|
|
name: '摄像头01',
|
|
|
model: 'HC-AI-8000',
|
|
|
location: '橘子洲头',
|
|
|
status: 'online',
|
|
|
battery: 95,
|
|
|
lastMaintenance: '2024-01-01',
|
|
|
installDate: '2023-06-15'
|
|
|
},
|
|
|
{
|
|
|
id: 'SHIP001',
|
|
|
category: 'ship',
|
|
|
name: '无人船01',
|
|
|
model: 'AS-3500',
|
|
|
location: '湘江中段',
|
|
|
status: 'working',
|
|
|
battery: 78,
|
|
|
lastMaintenance: '2024-01-05',
|
|
|
installDate: '2023-08-20'
|
|
|
},
|
|
|
{
|
|
|
id: 'DRONE001',
|
|
|
category: 'drone',
|
|
|
name: '无人机01',
|
|
|
model: 'DJI-M300',
|
|
|
location: '监控中心',
|
|
|
status: 'charging',
|
|
|
battery: 45,
|
|
|
lastMaintenance: '2024-01-08',
|
|
|
installDate: '2023-09-10'
|
|
|
}
|
|
|
];
|
|
|
}
|
|
|
|
|
|
// 实时数据更新
|
|
|
function startRealTimeUpdates() {
|
|
|
// 每30秒更新一次实时数据
|
|
|
setInterval(() => {
|
|
|
updateRealTimeData();
|
|
|
}, 30000);
|
|
|
}
|
|
|
|
|
|
function updateRealTimeData() {
|
|
|
// 更新设备状态
|
|
|
deviceData.forEach(device => {
|
|
|
// 随机更新电池电量
|
|
|
if (device.status === 'working' || device.status === 'flying') {
|
|
|
device.battery = Math.max(0, device.battery - Math.floor(Math.random() * 3));
|
|
|
} else if (device.status === 'charging') {
|
|
|
device.battery = Math.min(100, device.battery + Math.floor(Math.random() * 5));
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 如果当前在设备管理页面,刷新显示
|
|
|
if (currentTab === 'device') {
|
|
|
refreshDeviceGrid();
|
|
|
}
|
|
|
|
|
|
// 更新地图标记
|
|
|
if (map && currentTab === 'monitor') {
|
|
|
// 这里可以添加地图标记更新逻辑
|
|
|
}
|
|
|
|
|
|
// 更新预警数据
|
|
|
if (currentTab === 'warning') {
|
|
|
refreshWarningData();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 工具函数
|
|
|
function formatDate(date) {
|
|
|
return new Date(date).toLocaleDateString('zh-CN');
|
|
|
}
|
|
|
|
|
|
function formatDateTime(date) {
|
|
|
return new Date(date).toLocaleString('zh-CN');
|
|
|
}
|
|
|
|
|
|
function generateId(prefix = 'ID') {
|
|
|
return prefix + Date.now() + Math.floor(Math.random() * 1000);
|
|
|
}
|
|
|
|
|
|
function debounce(func, wait) {
|
|
|
let timeout;
|
|
|
return function executedFunction(...args) {
|
|
|
const later = () => {
|
|
|
clearTimeout(timeout);
|
|
|
func(...args);
|
|
|
};
|
|
|
clearTimeout(timeout);
|
|
|
timeout = setTimeout(later, wait);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function throttle(func, limit) {
|
|
|
let inThrottle;
|
|
|
return function() {
|
|
|
const args = arguments;
|
|
|
const context = this;
|
|
|
if (!inThrottle) {
|
|
|
func.apply(context, args);
|
|
|
inThrottle = true;
|
|
|
setTimeout(() => inThrottle = false, limit);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 错误处理
|
|
|
window.addEventListener('error', function(event) {
|
|
|
console.error('JavaScript错误:', event.error);
|
|
|
showNotification('系统发生错误,请刷新页面重试', 'error');
|
|
|
});
|
|
|
|
|
|
// 网络状态监测
|
|
|
window.addEventListener('online', function() {
|
|
|
showNotification('网络连接已恢复', 'success');
|
|
|
});
|
|
|
|
|
|
window.addEventListener('offline', function() {
|
|
|
showNotification('网络连接已断开', 'warning');
|
|
|
});
|
|
|
|
|
|
// 页面可见性变化处理
|
|
|
document.addEventListener('visibilitychange', function() {
|
|
|
if (document.hidden) {
|
|
|
// 页面隐藏时暂停某些更新
|
|
|
console.log('页面隐藏,暂停实时更新');
|
|
|
} else {
|
|
|
// 页面显示时恢复更新
|
|
|
console.log('页面显示,恢复实时更新');
|
|
|
updateRealTimeData();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 工单系统初始化
|
|
|
function initializeWorkorder() {
|
|
|
loadWorkorderData();
|
|
|
setupWorkorderEvents();
|
|
|
}
|
|
|
|
|
|
// 设备管理初始化
|
|
|
function initializeDevice() {
|
|
|
loadDeviceData();
|
|
|
setupDeviceEvents();
|
|
|
}
|
|
|
|
|
|
// 告警系统初始化
|
|
|
function initializeWarning() {
|
|
|
loadWarningData();
|
|
|
setupWarningEvents();
|
|
|
}
|
|
|
|
|
|
// 统计分析初始化
|
|
|
function initializeStatistics() {
|
|
|
loadStatisticsData();
|
|
|
bindStatisticsEvents();
|
|
|
setupStatisticsCharts();
|
|
|
}
|
|
|
|
|
|
// 加载工单数据
|
|
|
function loadWorkorderData() {
|
|
|
const workorderTableBody = document.getElementById('workorderTableBody');
|
|
|
if (!workorderTableBody) return;
|
|
|
|
|
|
const workorders = [
|
|
|
{id: 'WO001', type: '垃圾清理', location: '黄浦江段A区', method: '无人机发现', status: 'created', assignee: '张三', createTime: '2024-01-15 09:30', priority: '高', responseTime: '2024-01-15 11:30'},
|
|
|
{id: 'WO002', type: '水质异常', location: '苏州河段B区', method: '传感器监测', status: 'assigned', assignee: '李四', createTime: '2024-01-15 10:15', priority: '中', responseTime: '2024-01-15 14:15'},
|
|
|
{id: 'WO003', type: '设备维护', location: '黄浦江段C区', method: '定期巡检', status: 'completed', assignee: '王五', createTime: '2024-01-14 14:20', priority: '低', responseTime: '2024-01-15 02:20'},
|
|
|
{id: 'WO004', type: '垃圾清理', location: '苏州河段D区', method: '市民举报', status: 'dispatched', assignee: '赵六', createTime: '2024-01-15 11:45', priority: '高', responseTime: '2024-01-15 13:45'},
|
|
|
{id: 'WO005', type: '水质监测', location: '黄浦江段E区', method: '无人船巡检', status: 'arrived', assignee: '钱七', createTime: '2024-01-15 13:10', priority: '中', responseTime: '2024-01-15 17:10'},
|
|
|
{id: 'WO006', type: '设备故障', location: '苏州河段F区', method: '传感器报警', status: 'departed', assignee: '孙八', createTime: '2024-01-15 14:20', priority: '高', responseTime: '2024-01-15 16:20'},
|
|
|
{id: 'WO007', type: '垃圾清理', location: '黄浦江段G区', method: '巡检发现', status: 'processed', assignee: '周九', createTime: '2024-01-15 15:30', priority: '中', responseTime: '2024-01-15 19:30'},
|
|
|
{id: 'WO008', type: '水质异常', location: '苏州河段H区', method: '市民举报', status: 'transported', assignee: '吴十', createTime: '2024-01-15 16:45', priority: '低', responseTime: '2024-01-16 04:45'}
|
|
|
];
|
|
|
|
|
|
const statusMap = {
|
|
|
'created': {text: '新建', class: 'status-created'},
|
|
|
'assigned': {text: '待分配', class: 'status-assigned'},
|
|
|
'dispatched': {text: '待出发', class: 'status-dispatched'},
|
|
|
'departed': {text: '已出发', class: 'status-departed'},
|
|
|
'arrived': {text: '已抵达', class: 'status-arrived'},
|
|
|
'processed': {text: '已处理', class: 'status-processed'},
|
|
|
'transported': {text: '已转运', class: 'status-transported'},
|
|
|
'completed': {text: '已完成', class: 'status-completed'}
|
|
|
};
|
|
|
|
|
|
workorderTableBody.innerHTML = workorders.map(wo => `
|
|
|
<tr>
|
|
|
<td><input type="checkbox" value="${wo.id}"></td>
|
|
|
<td>${wo.id}</td>
|
|
|
<td>${wo.method}</td>
|
|
|
<td>${wo.type}</td>
|
|
|
<td>${wo.location}</td>
|
|
|
<td><span class="priority-badge priority-${wo.priority || 'medium'}">${wo.priority || '中'}</span></td>
|
|
|
<td><span class="status-badge ${statusMap[wo.status].class}">${statusMap[wo.status].text}</span></td>
|
|
|
<td>${wo.assignee}</td>
|
|
|
<td>${wo.createTime}</td>
|
|
|
<td>${wo.responseTime || '待设定'}</td>
|
|
|
<td>
|
|
|
<button class="btn btn-sm btn-primary" onclick="viewWorkorder('${wo.id}')">查看</button>
|
|
|
<button class="btn btn-sm btn-secondary" onclick="editWorkorder('${wo.id}')">编辑</button>
|
|
|
</td>
|
|
|
</tr>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
// 加载设备数据
|
|
|
function loadDeviceData() {
|
|
|
const deviceGrid = document.getElementById('deviceGrid');
|
|
|
if (!deviceGrid) return;
|
|
|
|
|
|
const devices = [
|
|
|
{id: 'CAM001', name: '监控摄像头-01', type: 'camera', status: 'online', location: '黄浦江段A区', battery: 85},
|
|
|
{id: 'DRONE001', name: '清理无人机-01', type: 'drone', status: 'online', location: '苏州河段B区', battery: 92},
|
|
|
{id: 'SHIP001', name: '巡检无人船-01', type: 'ship', status: 'offline', location: '黄浦江段C区', battery: 23},
|
|
|
{id: 'SENSOR001', name: '水质传感器-01', type: 'sensor', status: 'online', location: '苏州河段D区', battery: 78},
|
|
|
{id: 'CAM002', name: '监控摄像头-02', type: 'camera', status: 'warning', location: '黄浦江段E区', battery: 45},
|
|
|
{id: 'DRONE002', name: '清理无人机-02', type: 'drone', status: 'online', location: '苏州河段F区', battery: 88}
|
|
|
];
|
|
|
|
|
|
const statusMap = {
|
|
|
'online': {text: '在线', class: 'device-online'},
|
|
|
'offline': {text: '离线', class: 'device-offline'},
|
|
|
'warning': {text: '告警', class: 'device-warning'}
|
|
|
};
|
|
|
|
|
|
const typeIcons = {
|
|
|
'camera': 'fas fa-video',
|
|
|
'drone': 'fas fa-helicopter',
|
|
|
'ship': 'fas fa-ship',
|
|
|
'sensor': 'fas fa-thermometer-half'
|
|
|
};
|
|
|
|
|
|
deviceGrid.innerHTML = devices.map(device => `
|
|
|
<div class="device-card ${statusMap[device.status].class}">
|
|
|
<div class="device-header">
|
|
|
<div class="device-icon">
|
|
|
<i class="${typeIcons[device.type]}"></i>
|
|
|
</div>
|
|
|
<div class="device-status">
|
|
|
<span class="status-dot"></span>
|
|
|
${statusMap[device.status].text}
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="device-info">
|
|
|
<h4>${device.name}</h4>
|
|
|
<p class="device-id">${device.id}</p>
|
|
|
<p class="device-location">${device.location}</p>
|
|
|
<div class="device-battery">
|
|
|
<span>电量: ${device.battery}%</span>
|
|
|
<div class="battery-bar">
|
|
|
<div class="battery-fill" style="width: ${device.battery}%"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="device-actions">
|
|
|
<button class="btn btn-sm btn-primary" onclick="controlDevice('${device.id}')">控制</button>
|
|
|
<button class="btn btn-sm btn-secondary" onclick="deviceDetail('${device.id}')">详情</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
// 加载告警数据
|
|
|
function loadWarningData() {
|
|
|
loadWaterQualityWarnings();
|
|
|
loadDeviceWarnings();
|
|
|
}
|
|
|
|
|
|
function loadWaterQualityWarnings() {
|
|
|
const container = document.getElementById('waterQualityWarnings');
|
|
|
if (!container) return;
|
|
|
|
|
|
const warnings = [
|
|
|
{id: 'WQ001', location: '黄浦江段A区', parameter: 'pH值', value: '8.9', threshold: '6.5-8.5', level: 'medium', time: '10分钟前'},
|
|
|
{id: 'WQ002', location: '苏州河段B区', parameter: '溶解氧', value: '3.2mg/L', threshold: '>5mg/L', level: 'high', time: '25分钟前'},
|
|
|
{id: 'WQ003', location: '黄浦江段C区', parameter: '浊度', value: '15NTU', threshold: '<10NTU', level: 'low', time: '1小时前'}
|
|
|
];
|
|
|
|
|
|
container.innerHTML = warnings.map(warning => `
|
|
|
<div class="warning-item ${warning.level}">
|
|
|
<div class="warning-header">
|
|
|
<span class="warning-location">${warning.location}</span>
|
|
|
<span class="warning-time">${warning.time}</span>
|
|
|
</div>
|
|
|
<div class="warning-content">
|
|
|
<div class="warning-parameter">${warning.parameter}: ${warning.value}</div>
|
|
|
<div class="warning-threshold">标准: ${warning.threshold}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
function loadDeviceWarnings() {
|
|
|
const container = document.getElementById('deviceWarnings');
|
|
|
if (!container) return;
|
|
|
|
|
|
const warnings = [
|
|
|
{id: 'DW001', device: '清理无人机-01', issue: '电量不足', level: 'high', location: '苏州河段B区', time: '5分钟前'},
|
|
|
{id: 'DW002', device: '水质传感器-02', issue: '通信异常', level: 'critical', location: '黄浦江段D区', time: '15分钟前'},
|
|
|
{id: 'DW003', device: '监控摄像头-03', issue: '图像模糊', level: 'medium', location: '苏州河段F区', time: '30分钟前'}
|
|
|
];
|
|
|
|
|
|
container.innerHTML = warnings.map(warning => `
|
|
|
<div class="warning-item ${warning.level}">
|
|
|
<div class="warning-header">
|
|
|
<span class="warning-device">${warning.device}</span>
|
|
|
<span class="warning-time">${warning.time}</span>
|
|
|
</div>
|
|
|
<div class="warning-content">
|
|
|
<div class="warning-issue">${warning.issue}</div>
|
|
|
<div class="warning-location">${warning.location}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
// 加载统计数据
|
|
|
function loadStatisticsData() {
|
|
|
// 统计数据已在HTML中静态显示,这里可以添加动态更新逻辑
|
|
|
}
|
|
|
|
|
|
// 全局图表实例存储
|
|
|
window.chartInstances = window.chartInstances || {};
|
|
|
|
|
|
// 销毁图表实例的辅助函数
|
|
|
function destroyChart(chartId) {
|
|
|
if (window.chartInstances[chartId]) {
|
|
|
window.chartInstances[chartId].destroy();
|
|
|
delete window.chartInstances[chartId];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 设置统计图表
|
|
|
function setupStatisticsCharts() {
|
|
|
setupCleanupTrendChart();
|
|
|
setupDeviceUsageChart();
|
|
|
setupAreaDistributionChart();
|
|
|
setupEfficiencyChart();
|
|
|
}
|
|
|
|
|
|
function setupCleanupTrendChart() {
|
|
|
const canvas = document.getElementById('cleanupTrendChart');
|
|
|
if (!canvas) return;
|
|
|
|
|
|
// 销毁已存在的图表实例
|
|
|
destroyChart('cleanupTrendChart');
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
window.chartInstances['cleanupTrendChart'] = new Chart(ctx, {
|
|
|
type: 'line',
|
|
|
data: {
|
|
|
labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
|
|
datasets: [{
|
|
|
label: '清理量(吨)',
|
|
|
data: [120, 150, 180, 220, 190, 250],
|
|
|
borderColor: '#64b5f6',
|
|
|
backgroundColor: 'rgba(100, 181, 246, 0.1)',
|
|
|
tension: 0.4
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
labels: { color: '#e0e0e0' }
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: { ticks: { color: '#b0bec5' } },
|
|
|
y: { ticks: { color: '#b0bec5' } }
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function setupDeviceUsageChart() {
|
|
|
const canvas = document.getElementById('deviceUsageChart');
|
|
|
if (!canvas) return;
|
|
|
|
|
|
// 销毁已存在的图表实例
|
|
|
destroyChart('deviceUsageChart');
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
window.chartInstances['deviceUsageChart'] = new Chart(ctx, {
|
|
|
type: 'doughnut',
|
|
|
data: {
|
|
|
labels: ['摄像头', '无人机', '无人船', '传感器'],
|
|
|
datasets: [{
|
|
|
data: [35, 25, 20, 20],
|
|
|
backgroundColor: ['#64b5f6', '#42a5f5', '#2196f3', '#1976d2']
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
labels: { color: '#e0e0e0' }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function setupAreaDistributionChart() {
|
|
|
const canvas = document.getElementById('areaDistributionChart');
|
|
|
if (!canvas) return;
|
|
|
|
|
|
// 销毁已存在的图表实例
|
|
|
destroyChart('areaDistributionChart');
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
window.chartInstances['areaDistributionChart'] = new Chart(ctx, {
|
|
|
type: 'bar',
|
|
|
data: {
|
|
|
labels: ['黄浦江段', '苏州河段', '长江口段', '其他水域'],
|
|
|
datasets: [{
|
|
|
label: '清理次数',
|
|
|
data: [45, 38, 22, 15],
|
|
|
backgroundColor: '#64b5f6'
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
labels: { color: '#e0e0e0' }
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: { ticks: { color: '#b0bec5' } },
|
|
|
y: { ticks: { color: '#b0bec5' } }
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function setupEfficiencyChart() {
|
|
|
const canvas = document.getElementById('efficiencyChart');
|
|
|
if (!canvas) return;
|
|
|
|
|
|
// 销毁已存在的图表实例
|
|
|
destroyChart('efficiencyChart');
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
window.chartInstances['efficiencyChart'] = new Chart(ctx, {
|
|
|
type: 'radar',
|
|
|
data: {
|
|
|
labels: ['响应速度', '清理效率', '设备利用率', '成本控制', '环保指标'],
|
|
|
datasets: [{
|
|
|
label: '当前指标',
|
|
|
data: [85, 92, 78, 88, 95],
|
|
|
borderColor: '#64b5f6',
|
|
|
backgroundColor: 'rgba(100, 181, 246, 0.2)'
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
labels: { color: '#e0e0e0' }
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
r: {
|
|
|
ticks: { color: '#b0bec5' },
|
|
|
grid: { color: '#2a3f5f' }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 设置事件监听器
|
|
|
function setupWorkorderEvents() {
|
|
|
// 工单相关事件处理
|
|
|
const createBtn = document.getElementById('createWorkorder');
|
|
|
if (createBtn) {
|
|
|
createBtn.addEventListener('click', () => {
|
|
|
console.log('创建新工单');
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function setupDeviceEvents() {
|
|
|
// 设备分类按钮事件
|
|
|
const categoryBtns = document.querySelectorAll('.category-btn');
|
|
|
categoryBtns.forEach(btn => {
|
|
|
btn.addEventListener('click', (e) => {
|
|
|
categoryBtns.forEach(b => b.classList.remove('active'));
|
|
|
e.target.classList.add('active');
|
|
|
filterDevices(e.target.dataset.category);
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function setupWarningEvents() {
|
|
|
// 告警相关事件处理
|
|
|
}
|
|
|
|
|
|
// 工具函数
|
|
|
function editWorkorder(id) {
|
|
|
console.log('编辑工单:', id);
|
|
|
}
|
|
|
|
|
|
function controlDevice(id) {
|
|
|
console.log('控制设备:', id);
|
|
|
}
|
|
|
|
|
|
function deviceDetail(id) {
|
|
|
console.log('设备详情:', id);
|
|
|
}
|
|
|
|
|
|
function filterDevices(category) {
|
|
|
console.log('筛选设备类型:', category);
|
|
|
}
|
|
|
|
|
|
// 导出全局函数供HTML调用
|
|
|
window.switchTab = switchTab;
|
|
|
window.closeModal = closeModal;
|
|
|
window.viewWorkorder = viewWorkorder;
|
|
|
window.editWorkorder = editWorkorder;
|
|
|
window.controlDevice = controlDevice;
|
|
|
window.deviceDetail = deviceDetail;
|
|
|
window.filterDevices = filterDevices;
|
|
|
window.editWorkorder = editWorkorder;
|
|
|
window.viewDevice = viewDevice;
|
|
|
window.controlDevice = controlDevice;
|
|
|
window.maintainDevice = maintainDevice;
|
|
|
window.showNotification = showNotification;
|
|
|
|
|
|
console.log('智慧河道管理平台初始化完成'); |