LAE-log/platform/index.html
2026-05-23 11:32:49 +08:00

2160 lines
77 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>空运宝 - 无人机物流供需匹配平台</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #0066ff;
--primary-dark: #0052cc;
--success: #00a854;
--warning: #faad14;
--danger: #ff4d4f;
--info: #1890ff;
--dark: #1f2937;
--gray: #8c8c8c;
--light: #f5f5f5;
--white: #ffffff;
--gradient: linear-gradient(135deg, #0066ff 0%, #00d4ff 100%);
}
body {
font-family: 'Noto Sans SC', -apple-system, sans-serif;
background: #0a0a0f;
color: var(--white);
min-height: 100vh;
}
/* Login/Landing Page */
.landing-page {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="%230066ff20"/><stop offset="100%" stop-color="%2300d4ff10"/></linearGradient></defs><circle cx="50" cy="50" r="40" fill="url(%23g)"/></svg>') no-repeat center center;
background-size: cover;
}
.landing-logo {
font-size: 48px;
margin-bottom: 20px;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.landing-title {
font-size: 42px;
font-weight: 700;
margin-bottom: 10px;
background: var(--gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.landing-subtitle {
font-size: 18px;
color: #666;
margin-bottom: 50px;
}
.role-selection {
display: flex;
gap: 40px;
flex-wrap: wrap;
justify-content: center;
}
.role-card {
width: 280px;
padding: 40px 30px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 24px;
cursor: pointer;
transition: all 0.3s;
text-align: center;
}
.role-card:hover {
transform: translateY(-10px);
border-color: var(--primary);
box-shadow: 0 20px 60px rgba(0, 102, 255, 0.3);
}
.role-icon {
font-size: 64px;
margin-bottom: 20px;
}
.role-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 10px;
}
.role-desc {
font-size: 14px;
color: #888;
line-height: 1.6;
}
.role-btn {
margin-top: 20px;
padding: 12px 30px;
background: var(--gradient);
border: none;
border-radius: 30px;
color: white;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s;
}
.role-btn:hover {
transform: scale(1.05);
}
/* App Container */
.app-container {
display: none;
min-height: 100vh;
}
.app-container.active {
display: flex;
}
/* Sidebar */
.sidebar {
width: 260px;
background: #111118;
border-right: 1px solid rgba(255, 255, 255, 0.06);
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
}
.sidebar-logo {
padding: 24px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
display: flex;
align-items: center;
gap: 12px;
font-size: 20px;
font-weight: 700;
color: var(--primary);
}
.sidebar-logo span {
font-size: 28px;
}
.sidebar-nav {
flex: 1;
padding: 16px 12px;
overflow-y: auto;
}
.nav-section {
margin-bottom: 20px;
}
.nav-section-title {
font-size: 11px;
color: #555;
text-transform: uppercase;
letter-spacing: 1px;
padding: 8px 12px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
color: #888;
font-size: 14px;
}
.nav-item:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--white);
}
.nav-item.active {
background: var(--gradient);
color: var(--white);
}
.nav-item .icon {
font-size: 18px;
}
.nav-badge {
margin-left: auto;
background: var(--danger);
color: white;
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
}
.sidebar-user {
padding: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
display: flex;
align-items: center;
gap: 12px;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--gradient);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
}
.user-info {
flex: 1;
}
.user-name {
font-size: 14px;
font-weight: 500;
}
.user-role {
font-size: 12px;
color: #666;
}
.logout-btn {
padding: 8px 16px;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
color: #888;
cursor: pointer;
font-size: 12px;
}
.logout-btn:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--white);
}
/* Main Content */
.main-content {
flex: 1;
margin-left: 260px;
min-height: 100vh;
}
.page {
display: none;
padding: 24px 30px;
}
.page.active {
display: block;
}
/* Header */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.page-title {
font-size: 24px;
font-weight: 600;
}
.page-actions {
display: flex;
gap: 12px;
}
.btn {
padding: 10px 20px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: var(--gradient);
color: white;
}
.btn-primary:hover {
box-shadow: 0 8px 20px rgba(0, 102, 255, 0.4);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: var(--white);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.15);
}
/* Stats Cards */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 16px;
padding: 24px;
}
.stat-label {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.stat-value {
font-size: 32px;
font-weight: 700;
}
.stat-trend {
font-size: 12px;
margin-top: 8px;
}
.stat-trend.up { color: var(--success); }
.stat-trend.down { color: var(--danger); }
/* Panel */
.panel {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 16px;
overflow: hidden;
margin-bottom: 24px;
}
.panel-header {
padding: 20px 24px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
display: flex;
justify-content: space-between;
align-items: center;
}
.panel-title {
font-size: 16px;
font-weight: 600;
}
.panel-body {
padding: 20px 24px;
}
/* Order List */
.order-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.order-item {
display: flex;
align-items: center;
padding: 16px 20px;
background: rgba(255, 255, 255, 0.02);
border-radius: 12px;
gap: 16px;
transition: background 0.2s;
}
.order-item:hover {
background: rgba(255, 255, 255, 0.05);
}
.order-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--gradient);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.order-info {
flex: 1;
}
.order-title {
font-size: 15px;
font-weight: 500;
margin-bottom: 4px;
}
.order-route {
font-size: 13px;
color: #888;
}
.order-meta {
display: flex;
gap: 20px;
align-items: center;
}
.order-price {
font-size: 20px;
font-weight: 600;
color: var(--success);
}
.order-status {
padding: 6px 14px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.order-status.pending { background: rgba(136, 136, 136, 0.2); color: #aaa; }
.order-status.matching { background: rgba(0, 102, 255, 0.2); color: #66b3ff; }
.order-status.accepted { background: rgba(0, 168, 84, 0.2); color: var(--success); }
.order-status.in_transit { background: rgba(250, 173, 20, 0.2); color: var(--warning); }
.order-status.completed { background: rgba(0, 168, 84, 0.3); color: var(--success); }
.order-status.cancelled { background: rgba(255, 77, 79, 0.2); color: var(--danger); }
/* Drone Card */
.drone-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.drone-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 16px;
padding: 24px;
transition: all 0.3s;
}
.drone-card:hover {
border-color: var(--primary);
transform: translateY(-4px);
}
.drone-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 16px;
}
.drone-icon {
width: 56px;
height: 56px;
border-radius: 14px;
background: var(--gradient);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
}
.drone-name {
font-size: 18px;
font-weight: 600;
}
.drone-model {
font-size: 13px;
color: #666;
}
.drone-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.drone-stat {
background: rgba(255, 255, 255, 0.03);
border-radius: 10px;
padding: 12px;
}
.drone-stat-label {
font-size: 11px;
color: #666;
}
.drone-stat-value {
font-size: 18px;
font-weight: 600;
margin-top: 4px;
}
.drone-status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
}
.drone-status-badge.available {
background: rgba(0, 168, 84, 0.2);
color: var(--success);
}
.drone-status-badge.busy {
background: rgba(250, 173, 20, 0.2);
color: var(--warning);
}
.drone-status-badge.offline {
background: rgba(136, 136, 136, 0.2);
color: #888;
}
.drone-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
/* Marketplace - Create Order Form */
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
color: #aaa;
}
.form-input {
width: 100%;
padding: 14px 16px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: var(--white);
font-size: 15px;
transition: all 0.2s;
}
.form-input:focus {
outline: none;
border-color: var(--primary);
background: rgba(255, 255, 255, 0.08);
}
.form-input::placeholder {
color: #555;
}
.form-row {
display: flex;
gap: 16px;
}
.form-row .form-group {
flex: 1;
}
/* Quote Cards */
.quote-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.quote-card {
display: flex;
align-items: center;
padding: 20px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 16px;
gap: 20px;
transition: all 0.2s;
}
.quote-card:hover {
border-color: rgba(0, 102, 255, 0.3);
}
.quote-provider {
width: 60px;
text-align: center;
}
.provider-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--gradient);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
margin: 0 auto 8px;
}
.provider-name {
font-size: 13px;
font-weight: 500;
}
.provider-rating {
font-size: 12px;
color: var(--warning);
}
.quote-details {
flex: 1;
}
.quote-drone {
font-size: 16px;
font-weight: 500;
margin-bottom: 6px;
}
.quote-info {
display: flex;
gap: 20px;
font-size: 13px;
color: #888;
}
.quote-price-section {
text-align: right;
}
.quote-price {
font-size: 28px;
font-weight: 700;
color: var(--success);
}
.quote-etd {
font-size: 12px;
color: #666;
margin-top: 4px;
}
.quote-action {
padding: 12px 24px;
background: var(--gradient);
border: none;
border-radius: 10px;
color: white;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.quote-action:hover {
box-shadow: 0 8px 20px rgba(0, 102, 255, 0.4);
}
/* Map */
.map-container {
height: calc(100vh - 200px);
border-radius: 16px;
overflow: hidden;
position: relative;
}
#market-map {
height: 100%;
width: 100%;
}
.map-panel {
position: absolute;
top: 20px;
left: 20px;
width: 360px;
background: rgba(17, 17, 24, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
z-index: 1000;
max-height: calc(100% - 40px);
overflow-y: auto;
}
.map-panel-header {
padding: 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.map-panel-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
}
.map-filters {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.filter-chip {
padding: 6px 12px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.filter-chip:hover, .filter-chip.active {
background: var(--primary);
border-color: var(--primary);
}
.map-panel-body {
padding: 16px 20px;
}
.supply-item {
display: flex;
align-items: center;
padding: 12px;
background: rgba(255, 255, 255, 0.03);
border-radius: 10px;
margin-bottom: 10px;
gap: 12px;
cursor: pointer;
transition: all 0.2s;
}
.supply-item:hover {
background: rgba(255, 255, 255, 0.08);
}
.supply-icon {
width: 40px;
height: 40px;
border-radius: 10px;
background: var(--gradient);
display: flex;
align-items: center;
justify-content: center;
}
.supply-info {
flex: 1;
}
.supply-name {
font-size: 14px;
font-weight: 500;
}
.supply-meta {
font-size: 12px;
color: #666;
}
.supply-price {
font-size: 16px;
font-weight: 600;
color: var(--success);
}
/* Matching Animation */
.matching-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
}
.matching-overlay.active {
display: flex;
}
.matching-content {
text-align: center;
}
.matching-spinner {
width: 100px;
height: 100px;
border: 4px solid rgba(0, 102, 255, 0.2);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 30px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.matching-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 10px;
}
.matching-subtitle {
color: #888;
margin-bottom: 30px;
}
.matching-progress {
width: 300px;
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
margin: 0 auto;
overflow: hidden;
}
.matching-progress-bar {
height: 100%;
background: var(--gradient);
width: 0%;
transition: width 0.3s;
}
.matching-result {
display: none;
}
.matching-result.show {
display: block;
}
.match-success {
font-size: 60px;
margin-bottom: 20px;
}
/* Order Detail Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
}
.modal-overlay.active {
display: flex;
}
.modal {
background: #15151d;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
width: 90%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
padding: 24px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 20px;
font-weight: 600;
}
.modal-close {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
border: none;
color: #888;
font-size: 20px;
cursor: pointer;
}
.modal-body {
padding: 24px;
}
.detail-row {
display: flex;
justify-content: space-between;
padding: 14px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.detail-label {
color: #666;
font-size: 14px;
}
.detail-value {
font-weight: 500;
}
.modal-footer {
padding: 20px 24px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* Toast */
.toast {
position: fixed;
bottom: 30px;
right: 30px;
padding: 16px 24px;
background: #1a1a24;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
display: flex;
align-items: center;
gap: 12px;
z-index: 3000;
transform: translateY(100px);
opacity: 0;
transition: all 0.3s;
}
.toast.show {
transform: translateY(0);
opacity: 1;
}
.toast-icon {
font-size: 24px;
}
.toast.success .toast-icon { color: var(--success); }
.toast.error .toast-icon { color: var(--danger); }
.toast.info .toast-icon { color: var(--info); }
/* Responsive */
@media (max-width: 1200px) {
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.drone-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.sidebar { display: none; }
.main-content { margin-left: 0; }
.stats-grid { grid-template-columns: 1fr; }
.drone-grid { grid-template-columns: 1fr; }
.form-grid { grid-template-columns: 1fr; }
}
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 3px; }
</style>
</head>
<body>
<!-- Landing Page -->
<div class="landing-page" id="landing-page">
<div class="landing-logo">🚁</div>
<h1 class="landing-title">空运宝</h1>
<p class="landing-subtitle">无人机物流供需匹配平台 | 连接天空与地面</p>
<div class="role-selection">
<div class="role-card" onclick="loginAs('demander')">
<div class="role-icon">📦</div>
<div class="role-title">我要发货</div>
<div class="role-desc">需要使用无人机物流服务<br>发布运货需求,快速匹配无人机</div>
<button class="role-btn">立即发货</button>
</div>
<div class="role-card" onclick="loginAs('provider')">
<div class="role-icon">🚁</div>
<div class="role-title">我要接单</div>
<div class="role-desc">拥有无人机并提供物流服务<br>接单配送,获取收益</div>
<button class="role-btn">入驻接单</button>
</div>
</div>
</div>
<!-- App Container -->
<div class="app-container" id="app-container">
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-logo">
<span>🚁</span>
空运宝
</div>
<div class="sidebar-nav" id="sidebar-nav">
<!-- Nav items will be dynamically inserted -->
</div>
<div class="sidebar-user">
<div class="user-avatar" id="user-avatar">U</div>
<div class="user-info">
<div class="user-name" id="user-name">用户</div>
<div class="user-role" id="user-role">发货方</div>
</div>
<button class="logout-btn" onclick="logout()">退出</button>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Provider Dashboard -->
<div class="page" id="page-provider-dashboard">
<div class="page-header">
<h1 class="page-title">运营概览</h1>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">今日订单</div>
<div class="stat-value">12</div>
<div class="stat-trend up">↑ 较昨日 +3</div>
</div>
<div class="stat-card">
<div class="stat-label">本月收入</div>
<div class="stat-value">¥8,640</div>
<div class="stat-trend up">↑ 12%</div>
</div>
<div class="stat-card">
<div class="stat-label">飞行时长</div>
<div class="stat-value">48.5h</div>
<div class="stat-trend up">↑ 8%</div>
</div>
<div class="stat-card">
<div class="stat-label">客户评分</div>
<div class="stat-value">4.9 ⭐</div>
<div class="stat-trend">稳定</div>
</div>
</div>
</div>
<!-- Provider Orders -->
<div class="page" id="page-provider-orders">
<div class="page-header">
<h1 class="page-title">订单管理</h1>
</div>
<div class="panel">
<div class="panel-body">
<div class="order-list" id="provider-all-orders">
<!-- All orders -->
</div>
</div>
</div>
</div>
<!-- Provider Task Hall -->
<div class="page" id="page-task-hall">
<div class="page-header">
<h1 class="page-title">任务大厅</h1>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">待接订单</span>
<span style="color: #666; font-size: 13px;" id="pending-count">0 单可接</span>
</div>
<div class="panel-body">
<div class="order-list" id="task-hall-orders">
<!-- Orders -->
</div>
</div>
</div>
</div>
<!-- Provider Drone Management -->
<div class="page" id="page-drone-management">
<div class="page-header">
<h1 class="page-title">无人机管理</h1>
<div class="page-actions">
<button class="btn btn-primary" onclick="showAddDroneModal()">+ 添加无人机</button>
</div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">我的无人机</span>
<span style="color: #666; font-size: 13px;" id="drone-count">共 0 架</span>
</div>
<div class="panel-body">
<div class="drone-grid" id="drone-management-list">
<!-- Drone cards -->
</div>
</div>
</div>
</div>
<!-- Provider Earnings -->
<div class="page" id="page-provider-earnings">
<div class="page-header">
<h1 class="page-title">收入明细</h1>
<div class="page-actions">
<button class="btn btn-secondary">提现</button>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">账户余额</div>
<div class="stat-value">¥2,840</div>
</div>
<div class="stat-card">
<div class="stat-label">待结算</div>
<div class="stat-value">¥1,200</div>
</div>
<div class="stat-card">
<div class="stat-label">本月收入</div>
<div class="stat-value">¥8,640</div>
</div>
<div class="stat-card">
<div class="stat-label">累计收入</div>
<div class="stat-value">¥42,800</div>
</div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">收入记录</span>
</div>
<div class="panel-body">
<div class="order-list">
<div class="order-item">
<div class="order-icon"></div>
<div class="order-info">
<div class="order-title">订单 #ORDER-0892</div>
<div class="order-route">望京 → 798园区</div>
</div>
<div class="order-meta">
<div class="order-price">+¥128</div>
<span class="order-status completed">已完成</span>
</div>
</div>
<div class="order-item">
<div class="order-icon"></div>
<div class="order-info">
<div class="order-title">订单 #ORDER-0889</div>
<div class="order-route">亦庄 → 通州</div>
</div>
<div class="order-meta">
<div class="order-price">+¥256</div>
<span class="order-status completed">已完成</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Demander Dashboard -->
<div class="page" id="page-demander-dashboard">
<div class="page-header">
<h1 class="page-title">发货大厅</h1>
<div class="page-actions">
<button class="btn btn-primary" onclick="showCreateOrderModal()">+ 发布运货需求</button>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">进行中订单</div>
<div class="stat-value">3</div>
</div>
<div class="stat-card">
<div class="stat-label">本月发货</div>
<div class="stat-value">28 单</div>
</div>
<div class="stat-card">
<div class="stat-label">累计运费</div>
<div class="stat-value">¥4,680</div>
</div>
<div class="stat-card">
<div class="stat-label">平均时效</div>
<div class="stat-value">42 分钟</div>
</div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">实时运力</span>
<span style="color: #666; font-size: 13px;">附近 8 架可用</span>
</div>
<div class="panel-body">
<div class="drone-grid" id="available-drones">
<!-- Available drones -->
</div>
</div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">我的订单</span>
</div>
<div class="panel-body">
<div class="order-list" id="demander-orders">
<!-- Orders -->
</div>
</div>
</div>
</div>
<!-- Create Order -->
<div class="page" id="page-create-order">
<div class="page-header">
<h1 class="page-title">发布运货需求</h1>
</div>
<div class="panel">
<div class="panel-body">
<div class="form-grid">
<div class="form-row">
<div class="form-group">
<label class="form-label">起运地</label>
<input type="text" class="form-input" placeholder="例如朝阳区望京SOHO" id="order-from">
</div>
<div class="form-group">
<label class="form-label">目的地</label>
<input type="text" class="form-input" placeholder="例如:海淀区中关村" id="order-to">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">货物类型</label>
<select class="form-input" id="order-cargo-type">
<option value="">选择类型</option>
<option value="文件">文件/合同</option>
<option value="生鲜">生鲜/食品</option>
<option value="药品">药品/医疗</option>
<option value="电子产品">电子产品</option>
<option value="包裹">普通包裹</option>
<option value="其他">其他</option>
</select>
</div>
<div class="form-group">
<label class="form-label">货物重量 (kg)</label>
<input type="number" class="form-input" placeholder="例如5" id="order-weight">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">期望送达时间</label>
<select class="form-input" id="order-urgency">
<option value="normal">普通时效 (2小时内)</option>
<option value="urgent">加急 (1小时内)</option>
<option value=" ASAP">立即配送</option>
</select>
</div>
<div class="form-group">
<label class="form-label">保价金额 (元)</label>
<input type="number" class="form-input" placeholder="可选" id="order-insurance">
</div>
</div>
<div class="form-group">
<label class="form-label">备注说明</label>
<input type="text" class="form-input" placeholder="特殊要求、联系方式等" id="order-note">
</div>
</div>
<div style="text-align: center; margin-top: 30px;">
<button class="btn btn-primary" style="padding: 16px 60px; font-size: 16px;" onclick="submitOrder()">
发布需求
</button>
</div>
</div>
</div>
</div>
<!-- Matching -->
<div class="page" id="page-matching">
<div class="page-header">
<h1 class="page-title">匹配运力</h1>
</div>
<div class="panel">
<div class="panel-body">
<div class="quote-list" id="quote-list">
<!-- Quote cards -->
</div>
</div>
</div>
</div>
<!-- Marketplace Map -->
<div class="page" id="page-marketplace">
<div class="page-header">
<h1 class="page-title">运力地图</h1>
</div>
<div class="map-container">
<div id="market-map"></div>
<div class="map-panel">
<div class="map-panel-header">
<div class="map-panel-title">附近运力</div>
<div class="map-filters">
<span class="filter-chip active">全部</span>
<span class="filter-chip">_available</span>
<span class="filter-chip">配送中</span>
</div>
</div>
<div class="map-panel-body" id="supply-list">
<!-- Supply items -->
</div>
</div>
</div>
</div>
<!-- My Orders -->
<div class="page" id="page-my-orders">
<div class="page-header">
<h1 class="page-title">我的订单</h1>
</div>
<div class="panel">
<div class="panel-body">
<div class="order-list" id="my-orders-list">
<!-- Orders -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Matching Overlay -->
<div class="matching-overlay" id="matching-overlay">
<div class="matching-content">
<div class="matching-spinner"></div>
<div class="matching-title" id="matching-title">正在匹配运力</div>
<div class="matching-subtitle" id="matching-subtitle">系统正在为您寻找附近最合适的无人机...</div>
<div class="matching-progress">
<div class="matching-progress-bar" id="matching-progress"></div>
</div>
<div class="matching-result" id="matching-result">
<div class="match-success">🎉</div>
<div class="matching-title">匹配成功!</div>
<div class="matching-subtitle">无人机已接单预计12分钟后送达</div>
<button class="btn btn-primary" style="margin-top: 20px;" onclick="closeMatching()">查看订单详情</button>
</div>
</div>
</div>
<!-- Create Order Modal -->
<div class="modal-overlay" id="create-order-modal">
<div class="modal">
<div class="modal-header">
<span class="modal-title">发布运货需求</span>
<button class="modal-close" onclick="closeModal('create-order-modal')">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">起运地</label>
<input type="text" class="form-input" placeholder="输入起运地址" id="modal-from">
</div>
<div class="form-group">
<label class="form-label">目的地</label>
<input type="text" class="form-input" placeholder="输入目的地地址" id="modal-to">
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">货物类型</label>
<select class="form-input" id="modal-cargo-type">
<option value="文件">文件/合同</option>
<option value="生鲜">生鲜/食品</option>
<option value="药品">药品/医疗</option>
<option value="包裹">普通包裹</option>
</select>
</div>
<div class="form-group">
<label class="form-label">重量 (kg)</label>
<input type="number" class="form-input" placeholder="例如5" id="modal-weight">
</div>
</div>
<div class="form-group">
<label class="form-label">期望送达时间</label>
<select class="form-input" id="modal-urgency">
<option value="normal">普通时效 (2小时内)</option>
<option value="urgent">加急 (1小时内)</option>
</select>
</div>
<div class="form-group">
<label class="form-label">备注</label>
<input type="text" class="form-input" placeholder="特殊要求" id="modal-note">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('create-order-modal')">取消</button>
<button class="btn btn-primary" onclick="confirmCreateOrder()">发布需求</button>
</div>
</div>
</div>
<!-- Order Detail Modal -->
<div class="modal-overlay" id="order-detail-modal">
<div class="modal">
<div class="modal-header">
<span class="modal-title">订单详情</span>
<button class="modal-close" onclick="closeModal('order-detail-modal')">×</button>
</div>
<div class="modal-body" id="order-detail-content">
<!-- Order details -->
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('order-detail-modal')">关闭</button>
<button class="btn btn-primary" id="order-action-btn" onclick="">确认收货</button>
</div>
</div>
</div>
<!-- Add Drone Modal -->
<div class="modal-overlay" id="add-drone-modal">
<div class="modal">
<div class="modal-header">
<span class="modal-title">添加无人机</span>
<button class="modal-close" onclick="closeModal('add-drone-modal')">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">无人机名称</label>
<input type="text" class="form-input" placeholder="例如大疆M30T" id="drone-name-input">
</div>
<div class="form-group">
<label class="form-label">型号</label>
<input type="text" class="form-input" placeholder="例如:行业级" id="drone-model-input">
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">最大载重 (kg)</label>
<input type="number" class="form-input" placeholder="15" id="drone-weight-input">
</div>
<div class="form-group">
<label class="form-label">航程 (km)</label>
<input type="number" class="form-input" placeholder="10" id="drone-range-input">
</div>
</div>
<div class="form-group">
<label class="form-label">位置</label>
<input type="text" class="form-input" placeholder="例如:朝阳区望京" id="drone-location-input">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('add-drone-modal')">取消</button>
<button class="btn btn-primary" onclick="addDrone()">确认添加</button>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast">
<span class="toast-icon"></span>
<span class="toast-message">操作成功</span>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// ── API Service Layer ──
const API = {
base: '',
enabled: false,
async init() {
try {
const r = await fetch('/api/health');
if (r.ok) { this.enabled = true; console.log('[API] 后端已连接'); }
} catch { console.log('[API] 后端未连接,使用离线数据'); }
},
async request(method, path, body) {
if (!this.enabled) throw new Error('API不可用');
const opts = { method, headers: { 'Content-Type': 'application/json' } };
if (body) opts.body = JSON.stringify(body);
const r = await fetch(this.base + path, opts);
const j = await r.json();
if (j.code !== 0) throw new Error(j.msg || 'API错误');
return j.data;
},
// Auth
login(phone, role) { return this.request('POST', '/api/login', { phone, role }); },
// Drones
getDrones(params) { return this.request('GET', '/api/drones?' + new URLSearchParams(params)); },
getAvailableDrones() { return this.request('GET', '/api/drones/available'); },
addDrone(data) { return this.request('POST', '/api/drones', data); },
// Orders
getOrders(params) { return this.request('GET', '/api/orders?' + new URLSearchParams(params)); },
getPendingOrders() { return this.request('GET', '/api/orders/pending'); },
createOrder(data) { return this.request('POST', '/api/orders', data); },
getOrder(id) { return this.request('GET', `/api/orders/${id}`); },
acceptOrder(id, data) { return this.request('POST', `/api/orders/${id}/accept`, data); },
startTransit(id) { return this.request('POST', `/api/orders/${id}/transit`); },
completeOrder(id) { return this.request('POST', `/api/orders/${id}/complete`); },
cancelOrder(id) { return this.request('POST', `/api/orders/${id}/cancel`); },
rateOrder(id, data) { return this.request('POST', `/api/orders/${id}/rate`, data); },
// Quotes
getQuotes(orderId) { return this.request('GET', '/api/quotes?order_id=' + orderId); },
acceptQuote(quoteId) { return this.request('POST', `/api/quotes/${quoteId}/accept`); },
// Dashboard
getStats(params) { return this.request('GET', '/api/dashboard/stats?' + new URLSearchParams(params)); },
// Agent
agentAnalyze(data) { return this.request('POST', '/api/agent/analyze', data); },
getAgentSettings() { return this.request('GET', '/api/agent/settings'); },
};
// ── Data ──
let currentUser = null;
let currentRole = null;
let orders = [
{ id: 'ORDER-001', from: '望京SOHO', to: '798艺术区', cargo: '文件', weight: 2.5, price: 128, status: 'in_transit', provider: '张师傅', time: '10:32', providerDrone: '大疆M30T' },
{ id: 'ORDER-002', from: '亦庄物流中心', to: '通州配送站', cargo: '包裹', weight: 8.0, price: 256, status: 'accepted', provider: '李师傅', time: '10:28', providerDrone: '大疆M3E' },
{ id: 'ORDER-003', from: '顺义机场', to: '平谷物流园', cargo: '生鲜', weight: 15.0, price: 380, status: 'pending', provider: null, time: '10:15', providerDrone: null },
{ id: 'ORDER-004', from: '海淀中关村', to: '朝阳国贸', cargo: '电子产品', weight: 3.2, price: 188, status: 'completed', provider: '王师傅', time: '09:45', providerDrone: '大疆M30T' },
{ id: 'ORDER-005', from: '丰台站', to: '石景山', cargo: '药品', weight: 1.0, price: 156, status: 'completed', provider: '赵师傅', time: '09:20', providerDrone: '极飞P100' },
];
let drones = [
{ id: 'D001', name: '大疆M30T', model: '行业级', maxWeight: 15, range: 15, status: 'available', totalFlights: 856, location: '朝阳区望京' },
{ id: 'D002', name: '大疆M3E', model: '入门级', maxWeight: 5, range: 10, status: 'busy', totalFlights: 1243, location: '海淀区中关村' },
{ id: 'D003', name: '极飞P100', model: '农业级', maxWeight: 25, range: 8, status: 'available', totalFlights: 567, location: '通州区' },
{ id: 'D004', name: '纵横CW-15', model: '测绘级', maxWeight: 20, range: 20, status: 'offline', totalFlights: 234, location: '顺义区' },
];
let quotes = [
{ id: 'Q1', provider: '张师傅', avatar: '张', drone: '大疆M30T', rating: 4.9, price: 128, distance: 3.2, eta: 12, totalFlights: 856 },
{ id: 'Q2', provider: '李师傅', avatar: '李', drone: '大疆M3E', rating: 4.8, price: 98, distance: 5.1, eta: 18, totalFlights: 1243 },
{ id: 'Q3', provider: '王师傅', avatar: '王', drone: '极飞P100', rating: 4.7, price: 156, distance: 2.8, eta: 10, totalFlights: 567 },
];
// Init API on load
API.init();
// Navigation Data
const providerNav = [
{ section: '运营', items: [
{ id: 'dashboard', icon: '📊', label: '运营概览', page: 'page-provider-dashboard' },
{ id: 'map', icon: '🗺️', label: '运力地图', page: 'page-marketplace' },
]},
{ section: '任务', items: [
{ id: 'taskhall', icon: '📋', label: '任务大厅', page: 'page-task-hall' },
{ id: 'orders', icon: '📑', label: '订单管理', page: 'page-provider-orders' },
]},
{ section: '无人机', items: [
{ id: 'drones', icon: '🚁', label: '无人机管理', page: 'page-drone-management' },
]},
{ section: '财务', items: [
{ id: 'earnings', icon: '💰', label: '收入明细', page: 'page-provider-earnings' },
]},
];
const demanderNav = [
{ section: '发货', items: [
{ id: 'dashboard', icon: '📦', label: '发货大厅', page: 'page-demander-dashboard' },
{ id: 'create', icon: '', label: '发布需求', page: 'page-create-order' },
{ id: 'matching', icon: '🔍', label: '匹配运力', page: 'page-matching' },
]},
{ section: '订单', items: [
{ id: 'myorders', icon: '📋', label: '我的订单', page: 'page-my-orders' },
]},
{ section: '运力', items: [
{ id: 'map', icon: '🗺️', label: '运力地图', page: 'page-marketplace' },
]},
];
// Functions
async function loginAs(role) {
currentRole = role;
const phone = role === 'demander' ? '13800138001' : '13900139002';
const name = role === 'demander' ? '王先生' : '张师傅';
if (API.enabled) {
try {
currentUser = await API.login(phone, role);
} catch {
currentUser = { id: role === 'demander' ? 'U001' : 'U002', name, role, phone, avatar: name[0], company: role === 'demander' ? '星耀科技' : '飞驰无人机物流', credit_score: 850 };
}
} else {
currentUser = { id: role === 'demander' ? 'U001' : 'U002', name, role, phone, avatar: name[0], company: role === 'demander' ? '星耀科技' : '飞驰无人机物流', credit_score: 850 };
}
document.getElementById('landing-page').style.display = 'none';
document.getElementById('app-container').classList.add('active');
document.getElementById('user-name').textContent = currentUser.name;
document.getElementById('user-role').textContent = role === 'demander' ? '发货方' : '运营方';
document.getElementById('user-avatar').textContent = currentUser.name.charAt(0);
renderNav();
const defaultPage = role === 'demander' ? 'page-demander-dashboard' : 'page-provider-dashboard';
showPage(defaultPage);
showToast('登录成功' + (API.enabled ? ' (后端已连接)' : ''), 'success');
if (role === 'provider') {
await renderProviderContent();
} else {
await renderDemanderContent();
}
}
function logout() {
currentUser = null;
currentRole = null;
document.getElementById('landing-page').style.display = 'flex';
document.getElementById('app-container').classList.remove('active');
}
function renderNav() {
const nav = currentRole === 'provider' ? providerNav : demanderNav;
const container = document.getElementById('sidebar-nav');
container.innerHTML = '';
nav.forEach(section => {
const sectionDiv = document.createElement('div');
sectionDiv.className = 'nav-section';
sectionDiv.innerHTML = `<div class="nav-section-title">${section.section}</div>`;
section.items.forEach(item => {
const itemDiv = document.createElement('div');
itemDiv.className = 'nav-item';
itemDiv.innerHTML = `<span class="icon">${item.icon}</span><span>${item.label}</span>`;
itemDiv.onclick = () => showPage(item.page);
sectionDiv.appendChild(itemDiv);
});
container.appendChild(sectionDiv);
});
}
function showPage(pageId) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(pageId).classList.add('active');
document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
event?.target?.closest('.nav-item')?.classList.add('active');
if (pageId === 'page-marketplace') {
setTimeout(initMarketMap, 100);
}
}
function renderProviderContent() {
renderProviderAllOrders();
renderTaskHallOrders();
renderDroneManagement();
}
function renderProviderAllOrders() {
const container = document.getElementById('provider-all-orders');
container.innerHTML = orders.map(order => `
<div class="order-item">
<div class="order-icon">${order.status === 'completed' ? '✓' : '🚁'}</div>
<div class="order-info">
<div class="order-title">${order.id}</div>
<div class="order-route">${order.from}${order.to}</div>
</div>
<div class="order-meta">
<div class="order-price">¥${order.price}</div>
<span class="order-status ${order.status}">${getStatusText(order.status)}</span>
</div>
</div>
`).join('');
}
function renderDemanderContent() {
renderAvailableDrones();
renderDemanderOrders();
renderQuoteList();
renderMyOrders();
renderSupplyList();
}
function renderAvailableDrones() {
const container = document.getElementById('available-drones');
const availableDrones = drones.filter(d => d.status === 'available');
container.innerHTML = availableDrones.map(drone => `
<div class="drone-card">
<div class="drone-header">
<div class="drone-icon">🚁</div>
<div>
<div class="drone-name">${drone.name}</div>
<div class="drone-model">${drone.model}</div>
</div>
</div>
<div class="drone-stats">
<div class="drone-stat">
<div class="drone-stat-label">载重上限</div>
<div class="drone-stat-value">${drone.maxWeight}kg</div>
</div>
<div class="drone-stat">
<div class="drone-stat-label">服务范围</div>
<div class="drone-stat-value">${drone.range}km</div>
</div>
</div>
<div class="drone-status-badge available">
<span class="drone-dot"></span> 可用
</div>
</div>
`).join('');
}
function renderDemanderOrders() {
const container = document.getElementById('demander-orders');
const myOrders = orders.slice(0, 3);
container.innerHTML = myOrders.map(order => `
<div class="order-item" onclick="showOrderDetail('${order.id}')">
<div class="order-icon">📦</div>
<div class="order-info">
<div class="order-title">${order.id}</div>
<div class="order-route">${order.from}${order.to}</div>
</div>
<div class="order-meta">
<div class="order-price">¥${order.price}</div>
<span class="order-status ${order.status}">${getStatusText(order.status)}</span>
</div>
</div>
`).join('');
}
function renderMyOrders() {
const container = document.getElementById('my-orders-list');
container.innerHTML = orders.map(order => `
<div class="order-item" onclick="showOrderDetail('${order.id}')">
<div class="order-icon">${order.status === 'completed' ? '✓' : '🚁'}</div>
<div class="order-info">
<div class="order-title">${order.id}</div>
<div class="order-route">${order.from}${order.to}</div>
</div>
<div class="order-meta">
<div class="order-price">¥${order.price}</div>
<span class="order-status ${order.status}">${getStatusText(order.status)}</span>
</div>
</div>
`).join('');
}
function renderQuoteList() {
const container = document.getElementById('quote-list');
container.innerHTML = quotes.map(quote => `
<div class="quote-card">
<div class="quote-provider">
<div class="provider-avatar">${quote.avatar}</div>
<div class="provider-name">${quote.provider}</div>
<div class="provider-rating">⭐ ${quote.rating}</div>
</div>
<div class="quote-details">
<div class="quote-drone">${quote.drone}</div>
<div class="quote-info">
<span>📍 ${quote.distance}km</span>
<span>🚁 ${quote.totalFlights}单</span>
</div>
</div>
<div class="quote-price-section">
<div class="quote-price">¥${quote.price}</div>
<div class="quote-etd">预计${quote.eta}分钟送达</div>
</div>
<button class="quote-action" onclick="bookQuote('${quote.id}')">立即下单</button>
</div>
`).join('');
}
function renderSupplyList() {
const container = document.getElementById('supply-list');
container.innerHTML = drones.map(drone => `
<div class="supply-item">
<div class="supply-icon">🚁</div>
<div class="supply-info">
<div class="supply-name">${drone.name}</div>
<div class="supply-meta">${drone.location} · ${drone.maxWeight}kg载重</div>
</div>
<div class="supply-price">${drone.status === 'available' ? '可接单' : '配送中'}</div>
</div>
`).join('');
}
function renderTaskHallOrders() {
const container = document.getElementById('task-hall-orders');
const pendingOrders = orders.filter(o => o.status === 'pending');
document.getElementById('pending-count').textContent = pendingOrders.length + ' 单可接';
container.innerHTML = pendingOrders.map(order => `
<div class="order-item">
<div class="order-icon">📦</div>
<div class="order-info">
<div class="order-title">${order.id}</div>
<div class="order-route">${order.from}${order.to}</div>
</div>
<div class="order-meta">
<div class="order-price">¥${order.price}</div>
<button class="btn btn-primary" style="padding: 8px 16px;" onclick="acceptOrder('${order.id}')">接单</button>
</div>
</div>
`).join('');
}
function renderDroneManagement() {
const container = document.getElementById('drone-management-list');
document.getElementById('drone-count').textContent = '共 ' + drones.length + ' 架';
container.innerHTML = drones.map((drone, index) => `
<div class="drone-card">
<div class="drone-header">
<div class="drone-icon">🚁</div>
<div>
<div class="drone-name">${drone.name}</div>
<div class="drone-model">${drone.model}</div>
</div>
</div>
<div class="drone-stats">
<div class="drone-stat">
<div class="drone-stat-label">载重</div>
<div class="drone-stat-value">${drone.maxWeight}kg</div>
</div>
<div class="drone-stat">
<div class="drone-stat-label">航程</div>
<div class="drone-stat-value">${drone.range}km</div>
</div>
<div class="drone-stat">
<div class="drone-stat-label">飞行次</div>
<div class="drone-stat-value">${drone.totalFlights}</div>
</div>
<div class="drone-stat">
<div class="drone-stat-label">位置</div>
<div class="drone-stat-value">${drone.location}</div>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div class="drone-status-badge ${drone.status}">
<span class="drone-dot"></span>
${drone.status === 'available' ? '可接单' : drone.status === 'busy' ? '配送中' : '离线'}
</div>
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 12px;" onclick="removeDrone(${index})">移除</button>
</div>
</div>
`).join('');
}
function showAddDroneModal() {
document.getElementById('drone-name-input').value = '';
document.getElementById('drone-model-input').value = '';
document.getElementById('drone-weight-input').value = '';
document.getElementById('drone-range-input').value = '';
document.getElementById('drone-location-input').value = '';
document.getElementById('add-drone-modal').classList.add('active');
}
function addDrone() {
const name = document.getElementById('drone-name-input').value.trim();
const model = document.getElementById('drone-model-input').value.trim();
const maxWeight = parseFloat(document.getElementById('drone-weight-input').value);
const range = parseFloat(document.getElementById('drone-range-input').value);
const location = document.getElementById('drone-location-input').value.trim();
if (!name || !model || !maxWeight || !range || !location) {
showToast('请填写完整信息', 'error');
return;
}
drones.push({
id: 'D' + String(drones.length + 1).padStart(3, '0'),
name: name,
model: model,
maxWeight: maxWeight,
range: range,
status: 'available',
totalFlights: 0,
location: location
});
closeModal('add-drone-modal');
showToast('无人机添加成功', 'success');
renderDroneManagement();
renderAvailableDrones();
renderSupplyList();
}
function removeDrone(index) {
drones.splice(index, 1);
showToast('无人机已移除', 'info');
renderDroneManagement();
renderAvailableDrones();
renderSupplyList();
}
function getStatusText(status) {
const map = {
pending: '待接单',
matching: '匹配中',
accepted: '已接单',
in_transit: '配送中',
completed: '已完成',
cancelled: '已取消'
};
return map[status] || status;
}
function acceptOrder(orderId) {
const order = orders.find(o => o.id === orderId);
if (order) {
order.status = 'accepted';
order.provider = '张师傅';
showToast('接单成功', 'success');
renderProviderContent();
}
}
function showCreateOrderModal() {
document.getElementById('create-order-modal').classList.add('active');
}
function confirmCreateOrder() {
closeModal('create-order-modal');
showMatching();
}
function showMatching() {
document.getElementById('matching-overlay').classList.add('active');
document.getElementById('matching-result').classList.remove('show');
let progress = 0;
const interval = setInterval(() => {
progress += 10;
document.getElementById('matching-progress').style.width = progress + '%';
if (progress >= 100) {
clearInterval(interval);
document.getElementById('matching-spinner').style.display = 'none';
document.getElementById('matching-result').classList.add('show');
}
}, 300);
}
function closeMatching() {
document.getElementById('matching-overlay').classList.remove('active');
showToast('订单已确认', 'success');
}
function bookQuote(quoteId) {
showMatching();
}
function showOrderDetail(orderId) {
const order = orders.find(o => o.id === orderId);
if (!order) return;
const content = document.getElementById('order-detail-content');
content.innerHTML = `
<div class="detail-row">
<span class="detail-label">订单编号</span>
<span class="detail-value">${order.id}</span>
</div>
<div class="detail-row">
<span class="detail-label">起运地</span>
<span class="detail-value">${order.from}</span>
</div>
<div class="detail-row">
<span class="detail-label">目的地</span>
<span class="detail-value">${order.to}</span>
</div>
<div class="detail-row">
<span class="detail-label">货物类型</span>
<span class="detail-value">${order.cargo}</span>
</div>
<div class="detail-row">
<span class="detail-label">订单金额</span>
<span class="detail-value" style="color: var(--success);">¥${order.price}</span>
</div>
<div class="detail-row">
<span class="detail-label">订单状态</span>
<span class="detail-value"><span class="order-status ${order.status}">${getStatusText(order.status)}</span></span>
</div>
${order.provider ? `
<div class="detail-row">
<span class="detail-label">承运司机</span>
<span class="detail-value">${order.provider}</span>
</div>
<div class="detail-row">
<span class="detail-label">使用机型</span>
<span class="detail-value">${order.providerDrone}</span>
</div>
` : ''}
<div class="detail-row">
<span class="detail-label">创建时间</span>
<span class="detail-value">${order.time}</span>
</div>
`;
const actionBtn = document.getElementById('order-action-btn');
if (order.status === 'in_transit') {
actionBtn.style.display = 'block';
actionBtn.textContent = '确认收货';
} else {
actionBtn.style.display = 'none';
}
document.getElementById('order-detail-modal').classList.add('active');
}
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
function submitOrder() {
const from = document.getElementById('order-from').value;
const to = document.getElementById('order-to').value;
const cargo = document.getElementById('order-cargo-type').value;
const weight = document.getElementById('order-weight').value;
if (!from || !to || !cargo || !weight) {
showToast('请填写完整信息', 'error');
return;
}
showMatching();
}
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
toast.querySelector('.toast-message').textContent = message;
toast.className = 'toast ' + type;
toast.querySelector('.toast-icon').textContent = type === 'success' ? '✓' : type === 'error' ? '✕' : '';
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
}
// Map
let marketMap;
function initMarketMap() {
if (marketMap) {
marketMap.remove();
}
marketMap = L.map('market-map').setView([39.904, 116.407], 11);
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '© OpenStreetMap',
maxZoom: 19
}).addTo(marketMap);
const droneLocations = [
{ lat: 39.985, lng: 116.470, name: '大疆M30T', status: 'available' },
{ lat: 39.907, lng: 116.656, name: '大疆M3E', status: 'busy' },
{ lat: 40.130, lng: 116.635, name: '极飞P100', status: 'available' },
{ lat: 39.983, lng: 116.312, name: '纵横CW-15', status: 'available' },
];
droneLocations.forEach(drone => {
const color = drone.status === 'available' ? '#00a854' : '#faad14';
L.circleMarker([drone.lat, drone.lng], {
radius: 10,
fillColor: color,
color: '#fff',
weight: 2,
fillOpacity: 0.9
}).addTo(marketMap).bindTooltip(`🚁 ${drone.name}`);
});
}
// Initialize
document.querySelectorAll('.filter-chip').forEach(chip => {
chip.addEventListener('click', function() {
document.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active'));
this.classList.add('active');
});
});
</script>
</body>
</html>