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

220
mobile/alerts.html Normal file
View File

@@ -0,0 +1,220 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>报警管理 - 危化品车辆管理系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Noto Sans SC', sans-serif; }
.dashboard-bg {
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
background-image: radial-gradient(circle at 1px 1px, rgba(30,58,138,0.1) 1px, transparent 0);
background-size: 20px 20px;
}
.nav-item.active { color: #1e3a8a; }
.nav-item.active svg { fill: #1e3a8a; }
.alert-card { transition: all 0.3s ease; }
.alert-card:active { transform: scale(0.98); }
.alert-critical { border-left: 4px solid #ef4444; }
.alert-warning { border-left: 4px solid #f59e0b; }
.alert-info { border-left: 4px solid #3b82f6; }
.pulse-red { animation: pulse-red 1.5s infinite; }
@keyframes pulse-red {
0%, 100% { background-color: #ef4444; }
50% { background-color: #f87171; }
}
</style>
</head>
<body class="dashboard-bg min-h-screen">
<!-- Header -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="px-4 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<button onclick="goBack()" class="p-1">
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
<div>
<h1 class="text-lg font-bold text-gray-900">报警管理</h1>
<p class="text-xs text-gray-500">紧急事件处理</p>
</div>
</div>
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-red-500 rounded-full pulse-red"></div>
<span class="text-xs text-gray-600">1条未处理</span>
</div>
</div>
</div>
</header>
<!-- Alert Statistics -->
<section class="px-4 py-4">
<div class="bg-white rounded-xl shadow-sm p-4">
<h2 class="text-base font-semibold text-gray-900 mb-4">报警统计</h2>
<div class="grid grid-cols-3 gap-4">
<div class="text-center">
<p class="text-2xl font-bold text-red-600" id="pending-count">3</p>
<p class="text-xs text-gray-500">待处理</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-yellow-600" id="processing-count">2</p>
<p class="text-xs text-gray-500">处理中</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-green-600" id="resolved-count">15</p>
<p class="text-xs text-gray-500">已解决</p>
</div>
</div>
</div>
</section>
<!-- Filter Tabs -->
<section class="px-4 pb-4">
<div class="bg-white rounded-xl shadow-sm p-2">
<div class="grid grid-cols-4 gap-1">
<button onclick="filterAlerts('all')" class="filter-tab active px-3 py-2 rounded-lg text-xs font-medium text-center bg-blue-100 text-blue-800">
全部 <span class="ml-1 bg-blue-200 px-1 rounded" id="count-all">20</span>
</button>
<button onclick="filterAlerts('pending')" class="filter-tab px-3 py-2 rounded-lg text-xs font-medium text-center text-gray-600">
待处理 <span class="ml-1 bg-gray-200 px-1 rounded" id="count-pending">3</span>
</button>
<button onclick="filterAlerts('processing')" class="filter-tab px-3 py-2 rounded-lg text-xs font-medium text-center text-gray-600">
处理中 <span class="ml-1 bg-gray-200 px-1 rounded" id="count-processing">2</span>
</button>
<button onclick="filterAlerts('resolved')" class="filter-tab px-3 py-2 rounded-lg text-xs font-medium text-center text-gray-600">
已解决 <span class="ml-1 bg-gray-200 px-1 rounded" id="count-resolved">15</span>
</button>
</div>
</div>
</section>
<!-- Alert List -->
<main class="px-4 pb-20">
<div id="alert-list" class="space-y-3">
<!-- Alert cards will be populated by JavaScript -->
</div>
<!-- Empty State -->
<div id="empty-state" class="hidden text-center py-12">
<svg class="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<p class="text-gray-500 text-sm">暂无报警信息</p>
</div>
</main>
<!-- Alert Detail Modal -->
<div id="alert-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
<div class="absolute bottom-0 left-0 right-0 bg-white rounded-t-xl max-h-[80vh] overflow-y-auto">
<div class="p-4">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-gray-900" id="modal-title">报警详情</h3>
<button onclick="closeAlertModal()" class="p-1 text-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div id="alert-modal-content">
<!-- Modal content will be populated by JavaScript -->
</div>
</div>
</div>
</div>
<!-- Processing Modal -->
<div id="processing-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
<div class="absolute bottom-0 left-0 right-0 bg-white rounded-t-xl">
<div class="p-4">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-gray-900">处理报警</h3>
<button onclick="closeProcessingModal()" class="p-1 text-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">处理措施</label>
<div class="grid grid-cols-2 gap-2">
<button onclick="selectAction('contact')" class="action-btn p-3 border border-gray-300 rounded-lg text-sm text-left">
<div class="font-medium">联系驾驶员</div>
<div class="text-xs text-gray-500">直接通话确认情况</div>
</button>
<button onclick="selectAction('dispatch')" class="action-btn p-3 border border-gray-300 rounded-lg text-sm text-left">
<div class="font-medium">派遣救援</div>
<div class="text-xs text-gray-500">安排应急处理团队</div>
</button>
<button onclick="selectAction('reroute')" class="action-btn p-3 border border-gray-300 rounded-lg text-sm text-left">
<div class="font-medium">调整路线</div>
<div class="text-xs text-gray-500">重新规划安全路线</div>
</button>
<button onclick="selectAction('monitor')" class="action-btn p-3 border border-gray-300 rounded-lg text-sm text-left">
<div class="font-medium">持续监控</div>
<div class="text-xs text-gray-500">加强跟踪观察</div>
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">处理备注</label>
<textarea id="processing-note" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" placeholder="请输入处理过程和结果..."></textarea>
</div>
<div class="flex space-x-3">
<button onclick="submitProcessing()" class="flex-1 bg-blue-600 text-white py-2 px-4 rounded-lg text-sm font-medium">
提交处理结果
</button>
<button onclick="closeProcessingModal()" class="px-4 py-2 border border-gray-300 rounded-lg text-sm text-gray-700">
取消
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Bottom Navigation -->
<nav class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-50">
<div class="grid grid-cols-4 h-16">
<button onclick="navigateToHome()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
</svg>
<span class="text-xs">首页</span>
</button>
<button onclick="navigateToMonitor()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span class="text-xs">监控</span>
</button>
<button onclick="navigateToAlerts()" class="nav-item active flex flex-col items-center justify-center space-y-1">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<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"/>
</svg>
<span class="text-xs">报警</span>
</button>
<button onclick="navigateToTracking()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" 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>
<span class="text-xs">轨迹</span>
</button>
</div>
</nav>
<script src="main.js"></script>
<script>
// Initialize alerts page
document.addEventListener('DOMContentLoaded', function() {
loadAlerts();
updateAlertCounts();
});
</script>
</body>
</html>

117
mobile/design.md Normal file
View File

@@ -0,0 +1,117 @@
# 危化品车辆管理应用 - 设计风格指南
## 设计理念
### 核心设计原则
**专业可靠**:体现工业级安全管理的专业性,建立用户信任感
**清晰高效**:信息层次清晰,操作流程简洁,适合移动端快速操作
**安全警示**:通过颜色和视觉元素强调安全重要性,突出关键信息
**现代简洁**:采用现代化设计语言,避免过度装饰,专注功能性
### 色彩系统
**主色调**:深海蓝 (#1e3a8a) - 体现专业性和信任感
**辅助色**
- 安全绿 (#10b981) - 正常状态、成功操作
- 警告橙 (#f59e0b) - 注意事项、中等风险
- 危险红 (#ef4444) - 报警状态、高风险
- 中性灰 (#6b7280) - 辅助信息、禁用状态
**背景色系**
- 主背景:浅灰白 (#f9fafb)
- 卡片背景:纯白 (#ffffff)
- 分割线:浅灰 (#e5e7eb)
### 字体系统
**标题字体**Noto Sans SC Bold - 用于页面标题和重要信息
**正文字体**Noto Sans SC Regular - 用于一般内容和描述文字
**数字字体**:等宽字体用于数据显示,确保对齐和易读性
**字体大小规范**
- 大标题24px (1.5rem)
- 中标题18px (1.125rem)
- 正文14px (0.875rem)
- 小字12px (0.75rem)
### 视觉语言
**图标风格**线性图标2px描边圆角处理保持简洁现代感
**卡片设计**:轻微圆角 (8px),微妙阴影,突出层次感
**按钮设计**:圆角按钮,明确的视觉反馈,适合触摸操作
**状态指示**:使用颜色、图标和文字组合,确保信息传达清晰
## 视觉效果
### 动画效果库选择
**Anime.js**:用于页面切换和元素动画
- 卡片进入动画:淡入 + 上移效果
- 状态切换动画:颜色渐变和尺寸变化
- 加载动画:脉冲效果和进度指示
**ECharts.js**:用于数据可视化
- 车辆状态分布饼图
- 报警趋势折线图
- 实时数据仪表盘
### 背景效果
**主背景**:纯色背景配合微妙的几何图案
- 使用CSS生成的点阵网格作为装饰背景
- 保持低对比度,不影响内容可读性
**卡片效果**
- 轻微阴影box-shadow: 0 1px 3px rgba(0,0,0,0.1)
- 悬浮状态:轻微上浮效果(仅桌面端)
### 交互反馈
**触摸反馈**
- 按钮按下时轻微缩放效果 (scale: 0.95)
- 列表项点击时背景色变化
- 开关组件的平滑过渡动画
**状态动画**
- 实时数据更新时的脉冲效果
- 报警状态的闪烁提醒
- 加载状态的旋转动画
### 图标和图形元素
**功能图标**
- 车辆监控:卡车图标
- 报警管理:警告三角形图标
- 轨迹回放:路径箭头图标
- 系统设置:齿轮图标
**状态图标**
- 在线状态:绿色圆点
- 离线状态:灰色圆点
- 报警状态:红色感叹号
- 警告状态:黄色三角形
### 布局设计原则
**信息层次**
- 重要信息使用大字体和醒目颜色
- 次要信息使用小字体和中性色
- 相关信息分组展示,使用卡片容器
**空间利用**
- 移动端优先,确保内容在小屏幕上清晰可读
- 合理的留白,避免信息过于密集
- 关键操作按钮放置在拇指易触及区域
**响应式设计**
- 适配不同屏幕尺寸
- 横竖屏切换时的布局调整
- 考虑刘海屏和导航栏的适配
### 特殊视觉元素
**报警通知**
- 顶部横幅通知,使用渐变背景
- 紧急报警使用全屏遮罩和震动效果
- 声音提醒配合视觉闪烁
**数据展示**
- 使用进度条和仪表盘展示关键指标
- 数字数据使用等宽字体,便于对比
- 趋势图使用品牌色彩,突出变化
**加载状态**
- 骨架屏替代传统loading动画
- 分页加载时的无限滚动效果
- 数据为空时的友好提示插图

222
mobile/index.html Normal file
View File

@@ -0,0 +1,222 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>危化品车辆监控系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Noto Sans SC', sans-serif; }
.dashboard-bg {
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
background-image: radial-gradient(circle at 1px 1px, rgba(30,58,138,0.1) 1px, transparent 0);
background-size: 20px 20px;
}
.nav-item.active { color: #1e3a8a; }
.nav-item.active svg { fill: #1e3a8a; }
.stat-card { transition: all 0.3s ease; }
.stat-card:active { transform: scale(0.98); }
.alert-unread { border-left: 4px solid #ef4444; }
.alert-read { border-left: 4px solid #6b7280; }
.pulse-dot { animation: pulse 2s infinite; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
</head>
<body class="dashboard-bg min-h-screen">
<!-- Header -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="px-4 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-blue-800 rounded-lg flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"/>
</svg>
</div>
<div>
<h1 class="text-lg font-bold text-gray-900">危化品车辆监控</h1>
<p class="text-xs text-gray-500">园区安全管理系统</p>
</div>
</div>
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-green-500 rounded-full pulse-dot"></div>
<span class="text-xs text-gray-600">在线</span>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="px-4 py-6 pb-20">
<!-- Vehicle Statistics -->
<section class="mb-6">
<h2 class="text-base font-semibold text-gray-900 mb-4">园区车辆概览</h2>
<div class="grid grid-cols-1 gap-4">
<!-- Total Vehicles -->
<div class="stat-card bg-white rounded-xl shadow-sm p-4" onclick="showVehicleDetail('total')">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 8h-3V4H3c-1.1 0-2 .9-2 2v11h2c0 1.66 1.34 3 3 3s3-1.34 3-3h6c0 1.66 1.34 3 3 3s3-1.34 3-3h2v-5l-3-4zM6 18.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm13.5-9l1.96 2.5H17V9.5h2.5zm-1.5 9c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/>
</svg>
</div>
<div>
<p class="text-2xl font-bold text-blue-600" id="total-vehicles">156</p>
<p class="text-sm text-gray-500">园区车辆总数</p>
</div>
</div>
<div class="text-right">
<p class="text-xs text-green-600">+5 今日新增</p>
<p class="text-xs text-gray-400">较前日</p>
</div>
</div>
</div>
<!-- Today's Applications -->
<div class="stat-card bg-white rounded-xl shadow-sm p-4" onclick="showVehicleDetail('applications')">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<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"/>
</svg>
</div>
<div>
<p class="text-2xl font-bold text-green-600" id="today-applications">23</p>
<p class="text-sm text-gray-500">今日入园申请</p>
</div>
</div>
<div class="text-right">
<p class="text-xs text-blue-600">18 已批准</p>
<p class="text-xs text-yellow-600">5 待审批</p>
</div>
</div>
</div>
<!-- Active Orders -->
<div class="stat-card bg-white rounded-xl shadow-sm p-4" onclick="showVehicleDetail('orders')">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 bg-orange-100 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-orange-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
</svg>
</div>
<div>
<p class="text-2xl font-bold text-orange-600" id="active-orders">89</p>
<p class="text-sm text-gray-500">有运单车辆数</p>
</div>
</div>
<div class="text-right">
<p class="text-xs text-green-600">67 运输中</p>
<p class="text-xs text-gray-400">22 待装卸</p>
</div>
</div>
</div>
</div>
</section>
<!-- Recent Alerts -->
<section class="mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-base font-semibold text-gray-900">最新报警</h2>
<button onclick="navigateToMonitor()" class="text-xs text-blue-600 font-medium">查看全部</button>
</div>
<div class="space-y-3" id="recent-alerts">
<!-- Alerts will be populated by JavaScript -->
</div>
</section>
<!-- Quick Actions -->
<section class="mb-6">
<h2 class="text-base font-semibold text-gray-900 mb-4">快速操作</h2>
<div class="grid grid-cols-2 gap-3">
<button onclick="navigateToMonitor()" class="bg-white rounded-xl shadow-sm p-4 text-left hover:shadow-md transition-shadow">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 bg-red-100 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-red-600" fill="currentColor" viewBox="0 0 24 24">
<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"/>
</svg>
</div>
<div>
<p class="font-medium text-gray-900">报警管理</p>
<p class="text-xs text-gray-500">处理报警事件</p>
</div>
</div>
</button>
<button onclick="navigateToTracking()" class="bg-white rounded-xl shadow-sm p-4 text-left hover:shadow-md transition-shadow">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
<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>
</div>
<div>
<p class="font-medium text-gray-900">轨迹查询</p>
<p class="text-xs text-gray-500">车辆轨迹回放</p>
</div>
</div>
</button>
</div>
</section>
</main>
<!-- Statistics Detail Modal -->
<div id="stats-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
<div class="absolute bottom-0 left-0 right-0 bg-white rounded-t-xl max-h-[70vh] overflow-y-auto">
<div class="p-4">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-gray-900" id="stats-modal-title">统计详情</h3>
<button onclick="closeStatsModal()" class="p-1 text-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div id="stats-modal-content">
<!-- Modal content will be populated by JavaScript -->
</div>
</div>
</div>
</div>
<!-- Bottom Navigation -->
<nav class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-50">
<div class="grid grid-cols-3 h-16">
<button onclick="navigateToHome()" class="nav-item active flex flex-col items-center justify-center space-y-1">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
</svg>
<span class="text-xs">首页</span>
</button>
<button onclick="navigateToMonitor()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span class="text-xs">监控</span>
</button>
<button onclick="navigateToTracking()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" 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>
<span class="text-xs">轨迹</span>
</button>
</div>
</nav>
<script src="main.js"></script>
<script>
// Initialize home page
document.addEventListener('DOMContentLoaded', function() {
loadRecentAlerts();
animateStatistics();
});
</script>
</body>
</html>

85
mobile/interaction.md Normal file
View File

@@ -0,0 +1,85 @@
# 危化品车辆监控系统(简化版)- 交互设计
## 核心交互功能
### 1. 园区车辆概览交互
**主要交互流程:**
- 用户进入首页,直接显示三个核心统计数字
- 数字采用大字体显示,配合图标增强视觉效果
- 点击统计卡片可查看详细信息(模态窗口显示)
- 数据每30秒自动刷新用户可手动下拉刷新
**交互细节:**
- 使用数字动画效果从0递增到目标数值
- 统计卡片使用不同颜色区分:蓝色(总数)、绿色(申请)、橙色(运单)
- 卡片点击时轻微缩放反馈,增强交互体验
### 2. 报警信息管理交互
**主要交互流程:**
- 报警信息以列表形式展示,按时间倒序排列
- 每条报警显示车牌号、报警类型、位置、时间
- 点击报警项可切换"未查看"/"已查看"状态
- 支持按状态筛选报警(全部、未查看、已查看)
**报警类型和显示:**
- **违停报警**: 红色标识,显示"XXX车牌违停在XX位置XX时间触发"
- **超速报警**: 橙色标识,显示"XXX车牌超速在XX位置XX时间触发"
- **其他报警**: 黄色标识,根据具体情况显示
**状态管理:**
- **未查看**: 红色圆点标识,文字加粗显示
- **已查看**: 灰色圆点标识,文字正常显示
- 状态切换通过点击实现,切换后更新视觉样式
### 3. 车辆轨迹查询交互
**主要交互流程:**
- 用户在输入框中输入车牌号(支持模糊匹配)
- 点击查询按钮,系统显示该车辆的轨迹信息
- 轨迹信息包括:行驶路线、停留点、报警记录
- 支持查看最近7天的轨迹记录
**查询功能:**
- 支持车牌号前缀搜索(如输入"京A"显示所有京A车辆
- 输入框提供历史查询记录快捷选择
- 查询结果以时间轴形式展示关键轨迹点
- 每个轨迹点显示时间、位置、状态信息
### 4. 页面导航交互
**底部导航栏:**
- **首页**: 显示园区概览和最新报警
- **监控**: 专门的报警管理页面,功能更完整
- **轨迹**: 车辆轨迹查询专用页面
**导航特性:**
- 当前页面图标高亮显示
- 页面切换使用平滑过渡动画
- 保持用户操作上下文,避免数据丢失
## 简化交互原则
### 操作简化
- 减少多步骤操作,一键完成主要功能
- 避免复杂的表单填写,使用选择代替输入
- 关键操作提供确认提示,防止误操作
### 信息显示
- 突出关键信息,次要信息可折叠显示
- 使用图标和颜色编码,减少文字阅读负担
- 重要数据使用大字体和醒目颜色
### 响应速度
- 所有操作提供即时视觉反馈
- 数据加载使用骨架屏,避免空白等待
- 优化动画时长,确保流畅体验
## 移动端适配
### 触摸交互
- 所有可点击元素最小44px×44px触摸区域
- 支持滑动查看更多信息
- 长按显示快捷操作菜单
### 屏幕适配
- 适配不同屏幕尺寸和分辨率
- 横竖屏切换保持功能可用性
- 考虑刘海屏和底部指示条适配

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;

135
mobile/monitor.html Normal file
View File

@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>报警监控 - 危化品车辆监控系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Noto Sans SC', sans-serif; }
.dashboard-bg {
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
background-image: radial-gradient(circle at 1px 1px, rgba(30,58,138,0.1) 1px, transparent 0);
background-size: 20px 20px;
}
.nav-item.active { color: #1e3a8a; }
.nav-item.active svg { fill: #1e3a8a; }
.alert-card { transition: all 0.3s ease; }
.alert-card:active { transform: scale(0.98); }
.alert-unread { border-left: 4px solid #ef4444; }
.alert-read { border-left: 4px solid #6b7280; }
.filter-tab.active { background: #dbeafe; color: #1e40af; }
</style>
</head>
<body class="dashboard-bg min-h-screen">
<!-- Header -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="px-4 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<button onclick="goBack()" class="p-1">
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
<div>
<h1 class="text-lg font-bold text-gray-900">报警监控</h1>
<p class="text-xs text-gray-500">实时报警管理</p>
</div>
</div>
<div class="flex items-center space-x-2">
<span class="text-xs text-gray-600" id="unread-count">3条未查看</span>
</div>
</div>
</div>
</header>
<!-- Alert Statistics -->
<section class="px-4 py-4">
<div class="bg-white rounded-xl shadow-sm p-4">
<h2 class="text-base font-semibold text-gray-900 mb-4">报警统计</h2>
<div class="grid grid-cols-3 gap-4">
<div class="text-center">
<p class="text-2xl font-bold text-red-600" id="total-alerts">12</p>
<p class="text-xs text-gray-500">今日报警</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-orange-600" id="unread-alerts">3</p>
<p class="text-xs text-gray-500">未查看</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-green-600" id="read-alerts">9</p>
<p class="text-xs text-gray-500">已查看</p>
</div>
</div>
</div>
</section>
<!-- Filter Tabs -->
<section class="px-4 pb-4">
<div class="bg-white rounded-xl shadow-sm p-2">
<div class="grid grid-cols-3 gap-1">
<button onclick="filterAlerts('all')" class="filter-tab active px-3 py-2 rounded-lg text-xs font-medium text-center">
全部 <span class="ml-1 bg-blue-200 px-1 rounded" id="count-all">12</span>
</button>
<button onclick="filterAlerts('unread')" class="filter-tab px-3 py-2 rounded-lg text-xs font-medium text-center text-gray-600">
未查看 <span class="ml-1 bg-gray-200 px-1 rounded" id="count-unread">3</span>
</button>
<button onclick="filterAlerts('read')" class="filter-tab px-3 py-2 rounded-lg text-xs font-medium text-center text-gray-600">
已查看 <span class="ml-1 bg-gray-200 px-1 rounded" id="count-read">9</span>
</button>
</div>
</div>
</section>
<!-- Alert List -->
<main class="px-4 pb-20">
<div id="alert-list" class="space-y-3">
<!-- Alert cards will be populated by JavaScript -->
</div>
<!-- Empty State -->
<div id="empty-state" class="hidden text-center py-12">
<svg class="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<p class="text-gray-500 text-sm">暂无报警信息</p>
</div>
</main>
<!-- Bottom Navigation -->
<nav class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-50">
<div class="grid grid-cols-3 h-16">
<button onclick="navigateToHome()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
</svg>
<span class="text-xs">首页</span>
</button>
<button onclick="navigateToMonitor()" class="nav-item active flex flex-col items-center justify-center space-y-1">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span class="text-xs">监控</span>
</button>
<button onclick="navigateToTracking()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" 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>
<span class="text-xs">轨迹</span>
</button>
</div>
</nav>
<script src="main.js"></script>
<script>
// Initialize monitor page
document.addEventListener('DOMContentLoaded', function() {
loadAllAlerts();
updateAlertStats();
});
</script>
</body>
</html>

46
mobile/outline.md Normal file
View File

@@ -0,0 +1,46 @@
# 危化品车辆监控系统(简化版)- 项目概述
## 项目目标
创建一个简洁高效的移动端危化品车辆监控系统,专注于核心监控功能,提供园区车辆概览、报警信息展示和轨迹查询功能。
## 核心功能模块
### 1. 园区车辆概览
- **园区车辆总数**: 实时显示园区内车辆总数
- **当日入园申请车辆数**: 显示今日申请入园的车辆数量
- **有运单车辆数**: 显示正在执行运输任务的车辆数量
### 2. 报警信息管理
- **报警列表**: 显示所有报警事件
- **报警详情**: 显示报警车辆、类型、位置、时间
- **状态管理**: 未查看/已查看状态切换
- **报警类型**: 违停、超速等常见报警
### 3. 车辆轨迹查询
- **车牌号输入**: 支持手动输入车牌号查询
- **轨迹展示**: 显示车辆历史轨迹信息
- **简化查询**: 快速查询最近轨迹记录
## 页面结构规划
### 主页面 (index.html)
- 顶部:应用标题和在线状态
- 中部:三个核心统计卡片(园区车辆总数、入园申请、运单车辆)
- 下部:最新报警信息列表
- 底部:导航栏(首页、监控、轨迹)
### 车辆监控页面 (monitor.html)
- 车辆统计概览
- 报警信息列表
- 报警状态切换功能
### 轨迹查询页面 (tracking.html)
- 车牌号输入区域
- 轨迹查询结果展示
- 轨迹详情信息
## 技术实现要点
- 响应式移动端设计
- 简化数据结构
- 快速查询功能
- 清晰的视觉层次

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

190
mobile/tracking.html Normal file
View File

@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>轨迹查询 - 危化品车辆监控系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Noto Sans SC', sans-serif; }
.dashboard-bg {
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
background-image: radial-gradient(circle at 1px 1px, rgba(30,58,138,0.1) 1px, transparent 0);
background-size: 20px 20px;
}
.nav-item.active { color: #1e3a8a; }
.nav-item.active svg { fill: #1e3a8a; }
.search-btn:active { transform: scale(0.95); }
.result-card { transition: all 0.3s ease; }
.result-card:active { transform: scale(0.98); }
.timeline-item { position: relative; }
.timeline-item::before {
content: '';
position: absolute;
left: 15px;
top: 40px;
bottom: -10px;
width: 2px;
background: #e5e7eb;
}
.timeline-item:last-child::before {
display: none;
}
</style>
</head>
<body class="dashboard-bg min-h-screen">
<!-- Header -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="px-4 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<button onclick="goBack()" class="p-1">
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
<div>
<h1 class="text-lg font-bold text-gray-900">轨迹查询</h1>
<p class="text-xs text-gray-500">车辆轨迹回放</p>
</div>
</div>
</div>
</div>
</header>
<!-- Search Section -->
<section class="px-4 py-4">
<div class="bg-white rounded-xl shadow-sm p-4">
<h2 class="text-base font-semibold text-gray-900 mb-4">输入车牌号查询轨迹</h2>
<div class="space-y-4">
<div class="relative">
<input type="text" id="license-plate-input" placeholder="请输入车牌号京A12345"
class="w-full px-4 py-3 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
onkeyup="handleInputChange()">
<svg class="absolute right-3 top-3 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
</div>
<!-- Quick Search Suggestions -->
<div class="flex flex-wrap gap-2" id="search-suggestions">
<button onclick="quickSearch('京A')" class="px-3 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs">京A</button>
<button onclick="quickSearch('京B')" class="px-3 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs">京B</button>
<button onclick="quickSearch('京C')" class="px-3 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs">京C</button>
<button onclick="quickSearch('京D')" class="px-3 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs">京D</button>
</div>
<button onclick="searchTrajectory()" class="search-btn w-full bg-blue-600 text-white py-3 px-4 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
查询轨迹
</button>
</div>
</div>
</section>
<!-- Search Results -->
<section id="results-section" class="px-4 pb-20 hidden">
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-base font-semibold text-gray-900">查询结果</h2>
<button onclick="clearResults()" class="text-xs text-gray-500">清除结果</button>
</div>
<div id="search-results">
<!-- Results will be populated by JavaScript -->
</div>
</div>
<!-- Trajectory Details -->
<div id="trajectory-details" class="bg-white rounded-xl shadow-sm p-4 hidden">
<h3 class="text-base font-semibold text-gray-900 mb-4">轨迹详情</h3>
<div class="space-y-3" id="trajectory-timeline">
<!-- Timeline will be populated by JavaScript -->
</div>
</div>
</section>
<!-- Recent Searches -->
<section class="px-4 pb-20" id="recent-searches-section">
<div class="bg-white rounded-xl shadow-sm p-4">
<h2 class="text-base font-semibold text-gray-900 mb-4">最近查询</h2>
<div class="space-y-2" id="recent-searches">
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div>
<p class="text-sm font-medium text-gray-900">京A12345</p>
<p class="text-xs text-gray-500">2024-01-15 14:30</p>
</div>
</div>
<button onclick="quickSearch('京A12345')" class="text-xs text-blue-600">查询</button>
</div>
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-green-100 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<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"/>
</svg>
</div>
<div>
<p class="text-sm font-medium text-gray-900">京B67890</p>
<p class="text-xs text-gray-500">2024-01-15 13:15</p>
</div>
</div>
<button onclick="quickSearch('京B67890')" class="text-xs text-blue-600">查询</button>
</div>
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-orange-100 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-orange-600" fill="currentColor" viewBox="0 0 20 20">
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
</svg>
</div>
<div>
<p class="text-sm font-medium text-gray-900">京C24680</p>
<p class="text-xs text-gray-500">2024-01-15 11:20</p>
</div>
</div>
<button onclick="quickSearch('京C24680')" class="text-xs text-blue-600">查询</button>
</div>
</div>
</div>
</section>
<!-- Bottom Navigation -->
<nav class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-50">
<div class="grid grid-cols-3 h-16">
<button onclick="navigateToHome()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
</svg>
<span class="text-xs">首页</span>
</button>
<button onclick="navigateToMonitor()" class="nav-item flex flex-col items-center justify-center space-y-1 text-gray-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span class="text-xs">监控</span>
</button>
<button onclick="navigateToTracking()" class="nav-item active flex flex-col items-center justify-center space-y-1">
<svg class="w-5 h-5" 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>
<span class="text-xs">轨迹</span>
</button>
</div>
</nav>
<script src="main.js"></script>
<script>
// Initialize tracking page
document.addEventListener('DOMContentLoaded', function() {
loadRecentSearches();
});
</script>
</body>
</html>