This commit is contained in:
MeSHard
2025-11-14 17:34:39 +08:00
commit ca6f1086b0
18 changed files with 3297 additions and 0 deletions

543
mobile/main.js Normal file
View File

@@ -0,0 +1,543 @@
// 危化品车辆监控系统(简化版)- 主要JavaScript文件
// 模拟数据
const vehicleStats = {
total: 156,
todayApplications: 23,
activeOrders: 89,
todayNew: 5,
approved: 18,
pending: 5,
transporting: 67,
loading: 22
};
const alertsData = [
{ id: 1, vehicleId: '京A12345', type: '违停', location: '化工园区A区停车场', time: '2024-01-15 14:25', description: '京A12345违停在化工园区A区停车场2024-01-15 14:25触发', status: 'unread' },
{ id: 2, vehicleId: '京B67890', type: '超速', location: 'G6高速K125+300', time: '2024-01-15 13:40', description: '京B67890超速在G6高速K125+3002024-01-15 13:40触发', status: 'unread' },
{ id: 3, vehicleId: '京C24680', type: '违停', location: '危险品仓库门口', time: '2024-01-15 12:15', description: '京C24680违停在危险品仓库门口2024-01-15 12:15触发', status: 'unread' },
{ id: 4, vehicleId: '京D13579', type: '超速', location: 'S12省道K45+200', time: '2024-01-15 11:30', description: '京D13579超速在S12省道K45+2002024-01-15 11:30触发', status: 'read' },
{ id: 5, vehicleId: '京E86420', type: '违停', location: '化学品储罐区', time: '2024-01-15 10:45', description: '京E86420违停在化学品储罐区2024-01-15 10:45触发', status: 'read' },
{ id: 6, vehicleId: '京F97531', type: '超速', location: '化工园区主干道', time: '2024-01-15 09:20', description: '京F97531超速在化工园区主干道2024-01-15 09:20触发', status: 'read' }
];
const trajectoryData = {
'京A12345': [
{ time: '08:30', location: '化工园区A区', status: '出发', type: 'normal' },
{ time: '09:15', location: '高速入口', status: '停留15分钟', type: 'stop' },
{ time: '10:45', location: '服务区', status: '停留30分钟', type: 'stop' },
{ time: '11:00', location: 'G6高速K125', status: '超速报警', type: 'alert' },
{ time: '11:30', location: '检查站', status: '停留20分钟', type: 'stop' },
{ time: '12:00', location: '危险品仓库', status: '到达', type: 'normal' }
],
'京B67890': [
{ time: '09:00', location: '化工厂', status: '出发', type: 'normal' },
{ time: '09:45', location: '化工园区B区', status: '装卸货物', type: 'stop' },
{ time: '10:30', location: '高速入口', status: '正常行驶', type: 'normal' },
{ time: '11:15', location: '服务区', status: '停留10分钟', type: 'stop' },
{ time: '12:30', location: '储运中心', status: '到达', type: 'normal' }
],
'京C24680': [
{ time: '07:30', location: '储运中心', status: '出发', type: 'normal' },
{ time: '08:15', location: '检查站', status: '检查通过', type: 'normal' },
{ time: '09:00', location: '高速入口', status: '正常行驶', type: 'normal' },
{ time: '10:20', location: '化工园区', status: '违停报警', type: 'alert' },
{ time: '11:00', location: '化学品仓库', status: '到达', type: 'normal' }
]
};
// 全局变量
let currentAlertFilter = 'all';
let recentSearches = ['京A12345', '京B67890', '京C24680'];
// 导航函数
function navigateToHome() {
window.location.href = 'index.html';
}
function navigateToMonitor() {
window.location.href = 'monitor.html';
}
function navigateToTracking() {
window.location.href = 'tracking.html';
}
function goBack() {
window.history.back();
}
// 首页功能
function loadRecentAlerts() {
const container = document.getElementById('recent-alerts');
if (!container) return;
const recentAlerts = alertsData.slice(0, 4);
container.innerHTML = recentAlerts.map(alert => {
const statusColor = alert.status === 'unread' ? 'text-red-600' : 'text-gray-500';
const statusDot = alert.status === 'unread' ? 'bg-red-500' : 'bg-gray-300';
const statusText = alert.status === 'unread' ? '未查看' : '已查看';
return `
<div class="bg-white rounded-lg shadow-sm p-3 ${alert.status === 'unread' ? 'alert-unread' : 'alert-read'}" onclick="toggleAlertStatus(${alert.id})">
<div class="flex items-start space-x-3">
<div class="w-2 h-2 ${statusDot} rounded-full mt-2"></div>
<div class="flex-1">
<div class="flex items-center justify-between mb-1">
<span class="text-sm font-medium ${statusColor}">${alert.vehicleId}</span>
<span class="text-xs ${statusColor}">${statusText}</span>
</div>
<p class="text-sm text-gray-700 mb-1">${alert.description}</p>
<div class="flex items-center justify-between text-xs text-gray-400">
<span>${alert.location}</span>
<span>${alert.time.split(' ')[1]}</span>
</div>
</div>
</div>
</div>
`;
}).join('');
}
function animateStatistics() {
const stats = [
{ id: 'total-vehicles', value: vehicleStats.total },
{ id: 'today-applications', value: vehicleStats.todayApplications },
{ id: 'active-orders', value: vehicleStats.activeOrders }
];
stats.forEach(stat => {
const element = document.getElementById(stat.id);
if (element) {
anime({
targets: { value: 0 },
value: stat.value,
duration: 2000,
easing: 'easeOutQuart',
update: function(anim) {
element.textContent = Math.round(anim.animatables[0].target.value);
}
});
}
});
}
function showVehicleDetail(type) {
const modal = document.getElementById('stats-modal');
const title = document.getElementById('stats-modal-title');
const content = document.getElementById('stats-modal-content');
let titleText, contentHTML;
switch (type) {
case 'total':
titleText = '园区车辆详情';
contentHTML = `
<div class="space-y-4">
<div class="flex justify-between items-center p-3 bg-blue-50 rounded-lg">
<span class="text-sm text-gray-700">总车辆数</span>
<span class="text-lg font-bold text-blue-600">${vehicleStats.total}</span>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="text-center p-3 bg-green-50 rounded-lg">
<p class="text-lg font-bold text-green-600">${vehicleStats.todayNew}</p>
<p class="text-xs text-gray-500">今日新增</p>
</div>
<div class="text-center p-3 bg-gray-50 rounded-lg">
<p class="text-lg font-bold text-gray-600">${vehicleStats.total - vehicleStats.todayNew}</p>
<p class="text-xs text-gray-500">原有车辆</p>
</div>
</div>
</div>
`;
break;
case 'applications':
titleText = '入园申请详情';
contentHTML = `
<div class="space-y-4">
<div class="flex justify-between items-center p-3 bg-green-50 rounded-lg">
<span class="text-sm text-gray-700">今日申请总数</span>
<span class="text-lg font-bold text-green-600">${vehicleStats.todayApplications}</span>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="text-center p-3 bg-blue-50 rounded-lg">
<p class="text-lg font-bold text-blue-600">${vehicleStats.approved}</p>
<p class="text-xs text-gray-500">已批准</p>
</div>
<div class="text-center p-3 bg-yellow-50 rounded-lg">
<p class="text-lg font-bold text-yellow-600">${vehicleStats.pending}</p>
<p class="text-xs text-gray-500">待审批</p>
</div>
</div>
</div>
`;
break;
case 'orders':
titleText = '运单车辆详情';
contentHTML = `
<div class="space-y-4">
<div class="flex justify-between items-center p-3 bg-orange-50 rounded-lg">
<span class="text-sm text-gray-700">运单车辆总数</span>
<span class="text-lg font-bold text-orange-600">${vehicleStats.activeOrders}</span>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="text-center p-3 bg-green-50 rounded-lg">
<p class="text-lg font-bold text-green-600">${vehicleStats.transporting}</p>
<p class="text-xs text-gray-500">运输中</p>
</div>
<div class="text-center p-3 bg-gray-50 rounded-lg">
<p class="text-lg font-bold text-gray-600">${vehicleStats.loading}</p>
<p class="text-xs text-gray-500">待装卸</p>
</div>
</div>
</div>
`;
break;
}
title.textContent = titleText;
content.innerHTML = contentHTML;
modal.classList.remove('hidden');
// 添加显示动画
anime({
targets: modal.querySelector('.absolute'),
translateY: [100, 0],
duration: 300,
easing: 'easeOutQuart'
});
}
function closeStatsModal() {
const modal = document.getElementById('stats-modal');
modal.classList.add('hidden');
}
// 报警管理页面功能
function loadAllAlerts() {
const container = document.getElementById('alert-list');
if (!container) return;
const filteredAlerts = filterAlertsByStatus(alertsData, currentAlertFilter);
if (filteredAlerts.length === 0) {
container.innerHTML = '';
document.getElementById('empty-state').classList.remove('hidden');
return;
}
document.getElementById('empty-state').classList.add('hidden');
container.innerHTML = filteredAlerts.map(alert => {
const statusColor = alert.status === 'unread' ? 'text-red-600' : 'text-gray-500';
const statusDot = alert.status === 'unread' ? 'bg-red-500' : 'bg-gray-300';
const statusText = alert.status === 'unread' ? '未查看' : '已查看';
const borderClass = alert.status === 'unread' ? 'alert-unread' : 'alert-read';
return `
<div class="alert-card ${borderClass} bg-white rounded-xl shadow-sm p-4" onclick="toggleAlertStatus(${alert.id})">
<div class="flex items-start space-x-3">
<div class="w-3 h-3 ${statusDot} rounded-full mt-2"></div>
<div class="flex-1">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-semibold ${statusColor}">${alert.vehicleId}</h3>
<span class="text-xs px-2 py-1 rounded ${alert.status === 'unread' ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600'}">${statusText}</span>
</div>
<p class="text-sm text-gray-700 mb-2">${alert.description}</p>
<div class="flex items-center justify-between text-xs text-gray-400">
<span class="flex items-center">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"/>
</svg>
${alert.location}
</span>
<span>${alert.time}</span>
</div>
</div>
</div>
</div>
`;
}).join('');
// 添加进入动画
anime({
targets: '.alert-card',
translateY: [20, 0],
opacity: [0, 1],
delay: anime.stagger(100),
duration: 600,
easing: 'easeOutQuart'
});
}
function filterAlerts(status) {
currentAlertFilter = status;
// 更新筛选标签状态
document.querySelectorAll('.filter-tab').forEach(tab => {
tab.classList.remove('active');
tab.classList.add('text-gray-600');
});
event.target.classList.remove('text-gray-600');
event.target.classList.add('active');
loadAllAlerts();
}
function filterAlertsByStatus(alerts, status) {
if (status === 'all') return alerts;
return alerts.filter(alert => alert.status === status);
}
function toggleAlertStatus(alertId) {
const alert = alertsData.find(a => a.id === alertId);
if (alert) {
alert.status = alert.status === 'unread' ? 'read' : 'unread';
loadAllAlerts();
updateAlertStats();
// 显示状态更新提示
const statusText = alert.status === 'read' ? '已标记为已查看' : '已标记为未查看';
showToast(statusText);
}
}
function updateAlertStats() {
const totalAlerts = alertsData.length;
const unreadAlerts = alertsData.filter(a => a.status === 'unread').length;
const readAlerts = totalAlerts - unreadAlerts;
const elements = {
'total-alerts': totalAlerts,
'unread-alerts': unreadAlerts,
'read-alerts': readAlerts,
'count-all': totalAlerts,
'count-unread': unreadAlerts,
'count-read': readAlerts,
'unread-count': `${unreadAlerts}条未查看`
};
Object.entries(elements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) {
if (typeof value === 'string' && value.includes('条未查看')) {
element.textContent = value;
} else {
element.textContent = value;
}
}
});
}
// 轨迹查询页面功能
function handleInputChange() {
const input = document.getElementById('license-plate-input');
const value = input.value.trim();
if (value.length > 0) {
document.getElementById('search-suggestions').style.display = 'none';
} else {
document.getElementById('search-suggestions').style.display = 'flex';
}
}
function quickSearch(platePrefix) {
document.getElementById('license-plate-input').value = platePrefix;
handleInputChange();
}
function searchTrajectory() {
const plateNumber = document.getElementById('license-plate-input').value.trim();
if (!plateNumber) {
alert('请输入车牌号');
return;
}
// 模拟查询过程
showToast('正在查询轨迹...');
setTimeout(() => {
const trajectory = trajectoryData[plateNumber];
if (trajectory) {
displaySearchResults(plateNumber, trajectory);
addToRecentSearches(plateNumber);
} else {
showToast('未找到该车辆的轨迹记录');
}
}, 1000);
}
function displaySearchResults(plateNumber, trajectory) {
document.getElementById('recent-searches-section').classList.add('hidden');
document.getElementById('results-section').classList.remove('hidden');
// 显示搜索结果
const resultsContainer = document.getElementById('search-results');
resultsContainer.innerHTML = `
<div class="result-card p-4 bg-gray-50 rounded-lg">
<div class="flex items-center justify-between mb-3">
<h3 class="text-lg font-semibold text-gray-900">${plateNumber}</h3>
<span class="px-2 py-1 bg-green-100 text-green-800 rounded text-xs">有轨迹记录</span>
</div>
<div class="grid grid-cols-2 gap-4 text-sm">
<div>
<span class="text-gray-500">轨迹点数:</span>
<span class="text-gray-900 ml-1">${trajectory.length}</span>
</div>
<div>
<span class="text-gray-500">报警次数:</span>
<span class="text-red-600 ml-1">${trajectory.filter(t => t.type === 'alert').length}</span>
</div>
<div>
<span class="text-gray-500">开始时间:</span>
<span class="text-gray-900 ml-1">${trajectory[0].time}</span>
</div>
<div>
<span class="text-gray-500">结束时间:</span>
<span class="text-gray-900 ml-1">${trajectory[trajectory.length - 1].time}</span>
</div>
</div>
<button onclick="showTrajectoryDetails('${plateNumber}')" class="w-full mt-4 bg-blue-600 text-white py-2 px-4 rounded-lg text-sm font-medium">
查看详细轨迹
</button>
</div>
`;
}
function showTrajectoryDetails(plateNumber) {
const trajectory = trajectoryData[plateNumber];
const detailsContainer = document.getElementById('trajectory-details');
const timelineContainer = document.getElementById('trajectory-timeline');
timelineContainer.innerHTML = trajectory.map((point, index) => {
const iconColor = point.type === 'alert' ? 'text-red-600' : point.type === 'stop' ? 'text-yellow-600' : 'text-green-600';
const iconBg = point.type === 'alert' ? 'bg-red-100' : point.type === 'stop' ? 'bg-yellow-100' : 'bg-green-100';
return `
<div class="timeline-item flex items-start space-x-4">
<div class="w-8 h-8 ${iconBg} rounded-full flex items-center justify-center flex-shrink-0">
<svg class="w-4 h-4 ${iconColor}" fill="currentColor" viewBox="0 0 20 20">
${point.type === 'alert' ?
'<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>' :
point.type === 'stop' ?
'<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>' :
'<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>'
}
</svg>
</div>
<div class="flex-1 pb-4">
<div class="flex items-center justify-between mb-1">
<h4 class="text-sm font-medium text-gray-900">${point.location}</h4>
<span class="text-xs text-gray-500">${point.time}</span>
</div>
<p class="text-xs ${point.type === 'alert' ? 'text-red-600' : 'text-gray-600'}">${point.status}</p>
</div>
</div>
`;
}).join('');
detailsContainer.classList.remove('hidden');
// 添加显示动画
anime({
targets: detailsContainer,
opacity: [0, 1],
translateY: [20, 0],
duration: 500,
easing: 'easeOutQuart'
});
}
function clearResults() {
document.getElementById('results-section').classList.add('hidden');
document.getElementById('trajectory-details').classList.add('hidden');
document.getElementById('recent-searches-section').classList.remove('hidden');
document.getElementById('license-plate-input').value = '';
handleInputChange();
}
function addToRecentSearches(plateNumber) {
// 移除已存在的相同车牌号
recentSearches = recentSearches.filter(plate => plate !== plateNumber);
// 添加到开头
recentSearches.unshift(plateNumber);
// 保持最多3条记录
recentSearches = recentSearches.slice(0, 3);
}
function loadRecentSearches() {
// 这个功能在简化版中用于显示预设的最近查询
// 实际应用中会使用本地存储来保存用户的查询历史
}
// 通用工具函数
function showToast(message) {
// 创建toast元素
const toast = document.createElement('div');
toast.className = 'fixed top-20 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-lg text-sm z-50';
toast.textContent = message;
document.body.appendChild(toast);
// 显示动画
anime({
targets: toast,
translateY: [-20, 0],
opacity: [0, 1],
duration: 300,
easing: 'easeOutQuart',
complete: () => {
setTimeout(() => {
anime({
targets: toast,
translateY: [0, -20],
opacity: [1, 0],
duration: 300,
easing: 'easeInQuart',
complete: () => {
document.body.removeChild(toast);
}
});
}, 2000);
}
});
}
// 页面初始化
document.addEventListener('DOMContentLoaded', function() {
// 根据当前页面初始化相应功能
const currentPage = window.location.pathname.split('/').pop();
switch (currentPage) {
case 'index.html':
case '':
loadRecentAlerts();
animateStatistics();
break;
case 'monitor.html':
loadAllAlerts();
updateAlertStats();
break;
case 'tracking.html':
loadRecentSearches();
break;
}
});
// 导出函数供HTML调用
window.navigateToHome = navigateToHome;
window.navigateToMonitor = navigateToMonitor;
window.navigateToTracking = navigateToTracking;
window.goBack = goBack;
window.showVehicleDetail = showVehicleDetail;
window.closeStatsModal = closeStatsModal;
window.filterAlerts = filterAlerts;
window.toggleAlertStatus = toggleAlertStatus;
window.handleInputChange = handleInputChange;
window.quickSearch = quickSearch;
window.searchTrajectory = searchTrajectory;
window.showTrajectoryDetails = showTrajectoryDetails;
window.clearResults = clearResults;