1291 lines
46 KiB
HTML
1291 lines
46 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>低空无人机物流控制平台</title>
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
:root {
|
||
--primary: #1a73e8;
|
||
--primary-dark: #1557b0;
|
||
--success: #34a853;
|
||
--warning: #fbbc04;
|
||
--danger: #ea4335;
|
||
--info: #4285f4;
|
||
--dark: #1f2937;
|
||
--gray: #6b7280;
|
||
--light: #f3f4f6;
|
||
--white: #ffffff;
|
||
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
body {
|
||
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
|
||
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
||
min-height: 100vh;
|
||
color: var(--white);
|
||
}
|
||
|
||
.app-container {
|
||
display: flex;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* Sidebar */
|
||
.sidebar {
|
||
width: 260px;
|
||
background: rgba(15, 23, 42, 0.95);
|
||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||
padding: 20px 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.logo {
|
||
padding: 0 24px 30px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.logo h1 {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
background: linear-gradient(90deg, #60a5fa, #a78bfa);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.logo-icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.nav-menu {
|
||
flex: 1;
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 14px 16px;
|
||
margin: 4px 0;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
color: #94a3b8;
|
||
font-size: 14px;
|
||
gap: 12px;
|
||
}
|
||
|
||
.nav-item:hover {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
color: var(--white);
|
||
}
|
||
|
||
.nav-item.active {
|
||
background: linear-gradient(90deg, rgba(59, 130, 246, 0.3), rgba(139, 92, 246, 0.3));
|
||
color: var(--white);
|
||
border: 1px solid rgba(139, 92, 246, 0.3);
|
||
}
|
||
|
||
.nav-icon {
|
||
width: 20px;
|
||
text-align: center;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.nav-badge {
|
||
margin-left: auto;
|
||
background: var(--danger);
|
||
color: white;
|
||
font-size: 11px;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* Main Content */
|
||
.main-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.header {
|
||
background: rgba(15, 23, 42, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
padding: 16px 30px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
}
|
||
|
||
.header-title {
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.header-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
background: transparent;
|
||
color: var(--white);
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.header-btn:hover {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.header-btn.primary {
|
||
background: linear-gradient(135deg, #3b82f6, #6366f1);
|
||
border: none;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #10b981, #3b82f6);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* Page Content */
|
||
.page {
|
||
display: none;
|
||
padding: 24px 30px;
|
||
}
|
||
|
||
.page.active {
|
||
display: block;
|
||
}
|
||
|
||
/* Dashboard */
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 20px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 16px;
|
||
padding: 20px;
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
}
|
||
|
||
.stat-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.stat-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.stat-label {
|
||
color: #94a3b8;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.stat-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.stat-trend {
|
||
font-size: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.stat-trend.up { color: var(--success); }
|
||
.stat-trend.down { color: var(--danger); }
|
||
|
||
/* Dashboard Grid */
|
||
.dashboard-grid {
|
||
display: grid;
|
||
grid-template-columns: 2fr 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.panel {
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.panel-header {
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.panel-title {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.panel-body {
|
||
padding: 16px 20px;
|
||
}
|
||
|
||
/* Alert List */
|
||
.alert-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px;
|
||
border-radius: 10px;
|
||
margin-bottom: 10px;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
gap: 12px;
|
||
cursor: pointer;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.alert-item:hover {
|
||
background: rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.alert-icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.alert-icon.warning { background: rgba(251, 188, 4, 0.2); color: var(--warning); }
|
||
.alert-icon.danger { background: rgba(234, 67, 53, 0.2); color: var(--danger); }
|
||
.alert-icon.info { background: rgba(66, 133, 244, 0.2); color: var(--info); }
|
||
|
||
.alert-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.alert-title {
|
||
font-size: 13px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.alert-time {
|
||
font-size: 11px;
|
||
color: #64748b;
|
||
}
|
||
|
||
/* Drone Status */
|
||
.drone-list {
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.drone-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px;
|
||
border-radius: 10px;
|
||
margin-bottom: 8px;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
gap: 12px;
|
||
}
|
||
|
||
.drone-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 10px;
|
||
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.drone-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.drone-name {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.drone-location {
|
||
font-size: 11px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.drone-status {
|
||
padding: 4px 10px;
|
||
border-radius: 20px;
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.drone-status.online { background: rgba(52, 168, 83, 0.2); color: var(--success); }
|
||
.drone-status.flying { background: rgba(66, 133, 244, 0.2); color: var(--info); }
|
||
.drone-status.charging { background: rgba(251, 188, 4, 0.2); color: var(--warning); }
|
||
.drone-status.offline { background: rgba(107, 114, 128, 0.2); color: var(--gray); }
|
||
|
||
/* Tasks Page */
|
||
.toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
|
||
.search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 10px;
|
||
padding: 0 14px;
|
||
width: 280px;
|
||
}
|
||
|
||
.search-box input {
|
||
background: transparent;
|
||
border: none;
|
||
padding: 10px;
|
||
color: var(--white);
|
||
width: 100%;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.search-box input::placeholder {
|
||
color: #64748b;
|
||
}
|
||
|
||
.search-box input:focus {
|
||
outline: none;
|
||
}
|
||
|
||
.filter-btns {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.filter-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
background: transparent;
|
||
color: #94a3b8;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.filter-btn:hover {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
color: var(--white);
|
||
}
|
||
|
||
.filter-btn.active {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: var(--white);
|
||
}
|
||
|
||
/* Task Table */
|
||
.task-table {
|
||
width: 100%;
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
}
|
||
|
||
.task-table th {
|
||
text-align: left;
|
||
padding: 14px 16px;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
color: #64748b;
|
||
font-weight: 500;
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.task-table td {
|
||
padding: 16px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.task-table tr:hover td {
|
||
background: rgba(255, 255, 255, 0.02);
|
||
}
|
||
|
||
.task-id {
|
||
font-family: monospace;
|
||
color: #60a5fa;
|
||
}
|
||
|
||
.task-route {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.route-arrow {
|
||
color: #64748b;
|
||
}
|
||
|
||
.task-status {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.task-status.pending { background: rgba(107, 114, 128, 0.2); color: #94a3b8; }
|
||
.task-status.running { background: rgba(66, 133, 244, 0.2); color: #60a5fa; }
|
||
.task-status.completed { background: rgba(52, 168, 83, 0.2); color: var(--success); }
|
||
.task-status.cancelled { background: rgba(234, 67, 53, 0.2); color: var(--danger); }
|
||
.task-status.exception { background: rgba(251, 188, 4, 0.2); color: var(--warning); }
|
||
|
||
.task-priority {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.priority-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.priority-dot.high { background: var(--danger); }
|
||
.priority-dot.medium { background: var(--warning); }
|
||
.priority-dot.low { background: var(--success); }
|
||
|
||
.task-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.action-btn.view {
|
||
background: rgba(66, 133, 244, 0.2);
|
||
color: #60a5fa;
|
||
}
|
||
|
||
.action-btn.view:hover {
|
||
background: rgba(66, 133, 244, 0.3);
|
||
}
|
||
|
||
/* Map Page */
|
||
.map-container {
|
||
height: calc(100vh - 180px);
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
#map {
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
|
||
.map-overlay {
|
||
position: absolute;
|
||
top: 20px;
|
||
left: 20px;
|
||
z-index: 1000;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.map-toolbar {
|
||
background: rgba(15, 23, 42, 0.95);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.toolbar-title {
|
||
font-size: 13px;
|
||
color: #94a3b8;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.toolbar-btns {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.toolbar-btn {
|
||
padding: 8px 14px;
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
background: transparent;
|
||
color: #94a3b8;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.toolbar-btn:hover {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: var(--white);
|
||
}
|
||
|
||
.toolbar-btn.active {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: var(--white);
|
||
}
|
||
|
||
.warehouse-legend {
|
||
background: rgba(15, 23, 42, 0.95);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 12px;
|
||
padding: 14px;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.legend-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.legend-dot {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.legend-dot.center { background: #3b82f6; }
|
||
.legend-dot.station { background: #10b981; }
|
||
.legend-dot.landing { background: #f59e0b; }
|
||
|
||
/* Warehouse Info Panel */
|
||
.warehouse-panel {
|
||
position: absolute;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
width: 320px;
|
||
background: rgba(15, 23, 42, 0.98);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 16px;
|
||
z-index: 1000;
|
||
display: none;
|
||
}
|
||
|
||
.warehouse-panel.show {
|
||
display: block;
|
||
}
|
||
|
||
.panel-close {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border: none;
|
||
color: #94a3b8;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.warehouse-header {
|
||
padding: 20px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.warehouse-name {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.warehouse-type {
|
||
display: inline-block;
|
||
padding: 4px 10px;
|
||
border-radius: 20px;
|
||
font-size: 11px;
|
||
background: rgba(59, 130, 246, 0.2);
|
||
color: #60a5fa;
|
||
}
|
||
|
||
.warehouse-body {
|
||
padding: 16px 20px;
|
||
}
|
||
|
||
.warehouse-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.ws-stat {
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border-radius: 10px;
|
||
padding: 12px;
|
||
}
|
||
|
||
.ws-label {
|
||
font-size: 11px;
|
||
color: #64748b;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.ws-value {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.warehouse-info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.warehouse-info-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.wi-label {
|
||
color: #64748b;
|
||
}
|
||
|
||
/* Scrollbar */
|
||
::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
/* Animations */
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.page.active {
|
||
animation: fadeIn 0.3s ease-out;
|
||
}
|
||
|
||
.stat-card, .panel, .task-table {
|
||
animation: fadeIn 0.4s ease-out;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app-container">
|
||
<!-- Sidebar -->
|
||
<div class="sidebar">
|
||
<div class="logo">
|
||
<h1>
|
||
<span class="logo-icon">🚁</span>
|
||
无人机物流控制台
|
||
</h1>
|
||
</div>
|
||
<div class="nav-menu">
|
||
<div class="nav-item active" data-page="dashboard">
|
||
<span class="nav-icon">📊</span>
|
||
<span>物流看板</span>
|
||
</div>
|
||
<div class="nav-item" data-page="tasks">
|
||
<span class="nav-icon">📋</span>
|
||
<span>任务管理</span>
|
||
<span class="nav-badge">12</span>
|
||
</div>
|
||
<div class="nav-item" data-page="warehouse">
|
||
<span class="nav-icon">🗺️</span>
|
||
<span>仓库GIS</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Content -->
|
||
<div class="main-content">
|
||
<!-- Header -->
|
||
<div class="header">
|
||
<div class="header-title">低空无人机物流控制平台</div>
|
||
<div class="header-actions">
|
||
<button class="header-btn">🔔 通知</button>
|
||
<button class="header-btn primary">+ 新建任务</button>
|
||
<div class="user-avatar">A</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Dashboard Page -->
|
||
<div class="page active" id="page-dashboard">
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<span class="stat-label">今日任务</span>
|
||
<div class="stat-icon" style="background: rgba(59, 130, 246, 0.2);">📦</div>
|
||
</div>
|
||
<div class="stat-value">156</div>
|
||
<div class="stat-trend up">↑ 12% 较昨日</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<span class="stat-label">进行中</span>
|
||
<div class="stat-icon" style="background: rgba(66, 133, 244, 0.2);">🚁</div>
|
||
</div>
|
||
<div class="stat-value">23</div>
|
||
<div class="stat-trend up">↑ 5% 较昨日</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<span class="stat-label">配送完成率</span>
|
||
<div class="stat-icon" style="background: rgba(52, 168, 83, 0.2);">✓</div>
|
||
</div>
|
||
<div class="stat-value">94.2%</div>
|
||
<div class="stat-trend up">↑ 2.1% 较昨日</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<span class="stat-label">在线无人机</span>
|
||
<div class="stat-icon" style="background: rgba(139, 92, 246, 0.2);">⚡</div>
|
||
</div>
|
||
<div class="stat-value">48/56</div>
|
||
<div class="stat-trend">正常运行</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dashboard-grid">
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<span class="panel-title">实时预警</span>
|
||
<span style="color: #64748b; font-size: 12px;">查看全部 →</span>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="alert-item">
|
||
<div class="alert-icon danger">⚠️</div>
|
||
<div class="alert-content">
|
||
<div class="alert-title">设备异常: Drone-023 电池温度过高</div>
|
||
<div class="alert-time">2026-05-16 10:32:15</div>
|
||
</div>
|
||
</div>
|
||
<div class="alert-item">
|
||
<div class="alert-icon warning">🌧️</div>
|
||
<div class="alert-content">
|
||
<div class="alert-title">天气预警:朝阳区预计有雷暴天气</div>
|
||
<div class="alert-time">2026-05-16 10:15:00</div>
|
||
</div>
|
||
</div>
|
||
<div class="alert-item">
|
||
<div class="alert-icon info">⏰</div>
|
||
<div class="alert-content">
|
||
<div class="alert-title">任务超时:TASK-20260516-089 等待超2小时</div>
|
||
<div class="alert-time">2026-05-16 09:45:22</div>
|
||
</div>
|
||
</div>
|
||
<div class="alert-item">
|
||
<div class="alert-icon warning">📍</div>
|
||
<div class="alert-content">
|
||
<div class="alert-title">空域限制:亦庄区域暂时禁飞</div>
|
||
<div class="alert-time">2026-05-16 09:20:00</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<span class="panel-title">无人机状态</span>
|
||
<span style="color: #64748b; font-size: 12px;">在线 48 架</span>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="drone-list">
|
||
<div class="drone-item">
|
||
<div class="drone-avatar">🚁</div>
|
||
<div class="drone-info">
|
||
<div class="drone-name">Drone-001</div>
|
||
<div class="drone-location">望京配送站 → 798园区</div>
|
||
</div>
|
||
<span class="drone-status flying">执飞中</span>
|
||
</div>
|
||
<div class="drone-item">
|
||
<div class="drone-avatar">🚁</div>
|
||
<div class="drone-info">
|
||
<div class="drone-name">Drone-012</div>
|
||
<div class="drone-location">亦庄中心仓</div>
|
||
</div>
|
||
<span class="drone-status charging">充电中</span>
|
||
</div>
|
||
<div class="drone-item">
|
||
<div class="drone-avatar">🚁</div>
|
||
<div class="drone-info">
|
||
<div class="drone-name">Drone-008</div>
|
||
<div class="drone-location">通州配送站</div>
|
||
</div>
|
||
<span class="drone-status online">待命</span>
|
||
</div>
|
||
<div class="drone-item">
|
||
<div class="drone-avatar">🚁</div>
|
||
<div class="drone-info">
|
||
<div class="drone-name">Drone-035</div>
|
||
<div class="drone-location">顺义起降点</div>
|
||
</div>
|
||
<span class="drone-status offline">离线</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel" style="margin-top: 20px;">
|
||
<div class="panel-header">
|
||
<span class="panel-title">近期任务趋势</span>
|
||
</div>
|
||
<div class="panel-body" style="height: 200px; display: flex; align-items: center; justify-content: center; color: #64748b;">
|
||
[趋势图表区域 - 可集成 ECharts]
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tasks Page -->
|
||
<div class="page" id="page-tasks">
|
||
<div class="toolbar">
|
||
<div class="search-box">
|
||
<span>🔍</span>
|
||
<input type="text" placeholder="搜索任务ID、目的地...">
|
||
</div>
|
||
<div class="filter-btns">
|
||
<button class="filter-btn active">全部</button>
|
||
<button class="filter-btn">待执行</button>
|
||
<button class="filter-btn">进行中</button>
|
||
<button class="filter-btn">已完成</button>
|
||
<button class="filter-btn">异常</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<table class="task-table">
|
||
<thead>
|
||
<tr>
|
||
<th>任务ID</th>
|
||
<th>航线</th>
|
||
<th>货物信息</th>
|
||
<th>优先级</th>
|
||
<th>状态</th>
|
||
<th>执行无人机</th>
|
||
<th>创建时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><span class="task-id">TASK-0892</span></td>
|
||
<td>
|
||
<div class="task-route">
|
||
<span>望京站</span>
|
||
<span class="route-arrow">→</span>
|
||
<span>798园区</span>
|
||
</div>
|
||
</td>
|
||
<td>文件 / 2.3kg</td>
|
||
<td><span class="task-priority"><span class="priority-dot high"></span>高</span></td>
|
||
<td><span class="task-status running">进行中</span></td>
|
||
<td>Drone-001</td>
|
||
<td>10:32</td>
|
||
<td>
|
||
<div class="task-actions">
|
||
<button class="action-btn view">详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="task-id">TASK-0891</span></td>
|
||
<td>
|
||
<div class="task-route">
|
||
<span>亦庄仓</span>
|
||
<span class="route-arrow">→</span>
|
||
<span>通州站</span>
|
||
</div>
|
||
</td>
|
||
<td>生鲜 / 5.8kg</td>
|
||
<td><span class="task-priority"><span class="priority-dot high"></span>高</span></td>
|
||
<td><span class="task-status pending">待执行</span></td>
|
||
<td>Drone-012</td>
|
||
<td>10:28</td>
|
||
<td>
|
||
<div class="task-actions">
|
||
<button class="action-btn view">详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="task-id">TASK-0890</span></td>
|
||
<td>
|
||
<div class="task-route">
|
||
<span>顺义起降点</span>
|
||
<span class="route-arrow">→</span>
|
||
<span>机场物流园</span>
|
||
</div>
|
||
</td>
|
||
<td>药品 / 1.2kg</td>
|
||
<td><span class="task-priority"><span class="priority-dot medium"></span>中</span></td>
|
||
<td><span class="task-status completed">已完成</span></td>
|
||
<td>Drone-008</td>
|
||
<td>10:15</td>
|
||
<td>
|
||
<div class="task-actions">
|
||
<button class="action-btn view">详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="task-id">TASK-0889</span></td>
|
||
<td>
|
||
<div class="task-route">
|
||
<span>朝阳站</span>
|
||
<span class="route-arrow">→</span>
|
||
<span>国贸商圈</span>
|
||
</div>
|
||
</td>
|
||
<td>外卖 / 3.5kg</td>
|
||
<td><span class="task-priority"><span class="priority-dot low"></span>低</span></td>
|
||
<td><span class="task-status exception">异常</span></td>
|
||
<td>Drone-023</td>
|
||
<td>10:05</td>
|
||
<td>
|
||
<div class="task-actions">
|
||
<button class="action-btn view">详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="task-id">TASK-0888</span></td>
|
||
<td>
|
||
<div class="task-route">
|
||
<span>海淀中心</span>
|
||
<span class="route-arrow">→</span>
|
||
<span>中关村</span>
|
||
</div>
|
||
</td>
|
||
<td>电子产品 / 4.2kg</td>
|
||
<td><span class="task-priority"><span class="priority-dot medium"></span>中</span></td>
|
||
<td><span class="task-status completed">已完成</span></td>
|
||
<td>Drone-015</td>
|
||
<td>09:48</td>
|
||
<td>
|
||
<div class="task-actions">
|
||
<button class="action-btn view">详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="task-id">TASK-0887</span></td>
|
||
<td>
|
||
<div class="task-route">
|
||
<span>丰台站</span>
|
||
<span class="route-arrow">→</span>
|
||
<span>西局小区</span>
|
||
</div>
|
||
</td>
|
||
<td>包裹 / 2.0kg</td>
|
||
<td><span class="task-priority"><span class="priority-dot low"></span>低</span></td>
|
||
<td><span class="task-status completed">已完成</span></td>
|
||
<td>Drone-022</td>
|
||
<td>09:32</td>
|
||
<td>
|
||
<div class="task-actions">
|
||
<button class="action-btn view">详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Warehouse GIS Page -->
|
||
<div class="page" id="page-warehouse">
|
||
<div class="map-container">
|
||
<div id="map"></div>
|
||
<div class="map-overlay">
|
||
<div class="map-toolbar">
|
||
<div class="toolbar-title">视图控制</div>
|
||
<div class="toolbar-btns">
|
||
<button class="toolbar-btn active" id="btn-all">全部</button>
|
||
<button class="toolbar-btn" id="btn-center">中心仓</button>
|
||
<button class="toolbar-btn" id="btn-station">配送站</button>
|
||
<button class="toolbar-btn" id="btn-landing">起降点</button>
|
||
</div>
|
||
</div>
|
||
<div class="warehouse-legend">
|
||
<div class="legend-item">
|
||
<span class="legend-dot center"></span>
|
||
<span>中心仓 (3)</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<span class="legend-dot station"></span>
|
||
<span>配送站 (8)</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<span class="legend-dot landing"></span>
|
||
<span>起降点 (12)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="warehouse-panel" id="warehouse-panel">
|
||
<button class="panel-close" onclick="closeWarehousePanel()">×</button>
|
||
<div class="warehouse-header">
|
||
<div class="warehouse-name" id="wh-name">亦庄物流中心</div>
|
||
<span class="warehouse-type" id="wh-type">中心仓</span>
|
||
</div>
|
||
<div class="warehouse-body">
|
||
<div class="warehouse-stats">
|
||
<div class="ws-stat">
|
||
<div class="ws-label">在仓包裹</div>
|
||
<div class="ws-value">1,284</div>
|
||
</div>
|
||
<div class="ws-stat">
|
||
<div class="ws-label">今日出库</div>
|
||
<div class="ws-value">856</div>
|
||
</div>
|
||
<div class="ws-stat">
|
||
<div class="ws-label">无人机</div>
|
||
<div class="ws-value">12</div>
|
||
</div>
|
||
<div class="ws-stat">
|
||
<div class="ws-label">员工</div>
|
||
<div class="ws-value">28</div>
|
||
</div>
|
||
</div>
|
||
<div class="warehouse-info-row">
|
||
<span class="wi-label">地址</span>
|
||
<span>北京经济技术开发区荣华路15号</span>
|
||
</div>
|
||
<div class="warehouse-info-row">
|
||
<span class="wi-label">负责人</span>
|
||
<span>张建国</span>
|
||
</div>
|
||
<div class="warehouse-info-row">
|
||
<span class="wi-label">联系方式</span>
|
||
<span>138****8888</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<script>
|
||
// Navigation
|
||
document.querySelectorAll('.nav-item').forEach(item => {
|
||
item.addEventListener('click', () => {
|
||
const page = item.dataset.page;
|
||
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
|
||
item.classList.add('active');
|
||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||
document.getElementById('page-' + page).classList.add('active');
|
||
|
||
if (page === 'warehouse') {
|
||
setTimeout(initMap, 100);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Map
|
||
let map;
|
||
const warehouseData = [
|
||
{ id: 1, name: '亦庄物流中心', type: 'center', lat: 39.795, lng: 116.528, address: '北京经济技术开发区荣华路15号', packages: 1284, drones: 12, staff: 28, leader: '张建国', phone: '138****8888' },
|
||
{ id: 2, name: '望京配送站', type: 'station', lat: 39.985, lng: 116.470, address: '朝阳区望京SOHO', packages: 456, drones: 6, staff: 12, leader: '李明', phone: '139****6666' },
|
||
{ id: 3, name: '通州配送站', type: 'station', lat: 39.907, lng: 116.656, address: '通州区新华大街', packages: 328, drones: 5, staff: 10, leader: '王芳', phone: '137****5555' },
|
||
{ id: 4, name: '顺义起降点', type: 'landing', lat: 40.130, lng: 116.635, address: '顺义区南法信镇', packages: 120, drones: 3, staff: 5, leader: '赵强', phone: '136****4444' },
|
||
{ id: 5, name: '朝阳站', type: 'station', lat: 39.928, lng: 116.457, address: '朝阳区建国路', packages: 512, drones: 7, staff: 15, leader: '刘洋', phone: '135****3333' },
|
||
{ id: 6, name: '海淀中心', type: 'center', lat: 39.983, lng: 116.312, address: '海淀区中关村', packages: 980, drones: 10, staff: 22, leader: '陈静', phone: '134****2222' },
|
||
{ id: 7, name: '丰台站', type: 'station', lat: 39.858, lng: 116.286, address: '丰台区西局', packages: 284, drones: 4, staff: 8, leader: '周磊', phone: '133****1111' },
|
||
{ id: 8, name: '亦庄起降点', type: 'landing', lat: 39.812, lng: 116.545, address: '亦庄经济区', packages: 86, drones: 2, staff: 4, leader: '吴涛', phone: '132****0000' },
|
||
];
|
||
|
||
const typeColors = {
|
||
center: '#3b82f6',
|
||
station: '#10b981',
|
||
landing: '#f59e0b'
|
||
};
|
||
|
||
const typeIcons = {
|
||
center: '🏭',
|
||
station: '📦',
|
||
landing: '🛫'
|
||
};
|
||
|
||
function initMap() {
|
||
if (map) {
|
||
map.remove();
|
||
}
|
||
|
||
map = L.map('map').setView([39.904, 116.407], 11);
|
||
|
||
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
||
attribution: '© OpenStreetMap, © CARTO',
|
||
subdomains: 'abcd',
|
||
maxZoom: 19
|
||
}).addTo(map);
|
||
|
||
warehouseData.forEach(wh => {
|
||
const marker = L.circleMarker([wh.lat, wh.lng], {
|
||
radius: 12,
|
||
fillColor: typeColors[wh.type],
|
||
color: '#fff',
|
||
weight: 2,
|
||
opacity: 1,
|
||
fillOpacity: 0.9
|
||
}).addTo(map);
|
||
|
||
marker.bindTooltip(`${typeIcons[wh.type]} ${wh.name}`, {
|
||
permanent: false,
|
||
direction: 'top',
|
||
className: 'custom-tooltip'
|
||
});
|
||
|
||
marker.on('click', () => showWarehousePanel(wh));
|
||
});
|
||
|
||
const routes = [
|
||
[[39.795, 116.528], [39.985, 116.470]],
|
||
[[39.795, 116.528], [39.907, 116.656]],
|
||
[[39.983, 116.312], [39.928, 116.457]],
|
||
];
|
||
|
||
routes.forEach(route => {
|
||
L.polyline(route, {
|
||
color: 'rgba(139, 92, 246, 0.6)',
|
||
weight: 2,
|
||
dashArray: '5, 10'
|
||
}).addTo(map);
|
||
});
|
||
}
|
||
|
||
function showWarehousePanel(wh) {
|
||
document.getElementById('wh-name').textContent = wh.name;
|
||
document.getElementById('wh-type').textContent = wh.type === 'center' ? '中心仓' : wh.type === 'station' ? '配送站' : '起降点';
|
||
document.querySelector('.ws-stat:nth-child(1) .ws-value').textContent = wh.packages.toLocaleString();
|
||
document.querySelector('.ws-stat:nth-child(2) .ws-value').textContent = Math.floor(wh.packages * 0.67).toLocaleString();
|
||
document.querySelector('.ws-stat:nth-child(3) .ws-value').textContent = wh.drones;
|
||
document.querySelector('.ws-stat:nth-child(4) .ws-value').textContent = wh.staff;
|
||
document.querySelector('.warehouse-info-row:nth-child(1) span:last-child').textContent = wh.address;
|
||
document.querySelector('.warehouse-info-row:nth-child(2) span:last-child').textContent = wh.leader;
|
||
document.querySelector('.warehouse-info-row:nth-child(3) span:last-child').textContent = wh.phone;
|
||
document.getElementById('warehouse-panel').classList.add('show');
|
||
}
|
||
|
||
function closeWarehousePanel() {
|
||
document.getElementById('warehouse-panel').classList.remove('show');
|
||
}
|
||
|
||
// Filter buttons
|
||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.toolbar-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
document.querySelectorAll('.toolbar-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |