649 lines
15 KiB
Markdown
649 lines
15 KiB
Markdown
# 区域侦查任务前端集成文档
|
||
|
||
## 📋 概述
|
||
|
||
本文档详细说明前端如何集成区域侦查任务API,包括接口调用、数据格式、错误处理等完整的前端集成方案。
|
||
|
||
## 🚀 快速开始
|
||
|
||
### 基础配置
|
||
|
||
```javascript
|
||
// API基础配置
|
||
const API_BASE_URL = 'http://localhost:8080/api/v1/region-reconnaissance';
|
||
|
||
// 动态获取鉴权头(如有需要)
|
||
function getAuthHeaders() {
|
||
const token = window.localStorage.getItem('token');
|
||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||
}
|
||
|
||
// 统一请求方法(基于 fetch)
|
||
async function request(path, options = {}) {
|
||
const url = path.startsWith('http') ? path : `${API_BASE_URL}${path}`;
|
||
const headers = {
|
||
'Content-Type': 'application/json',
|
||
...getAuthHeaders(),
|
||
...(options.headers || {})
|
||
};
|
||
|
||
const response = await fetch(url, { ...options, headers });
|
||
|
||
// 尝试解析响应体(可能为 204 无内容)
|
||
let payload = null;
|
||
if (response.status !== 204) {
|
||
try {
|
||
payload = await response.json();
|
||
} catch {
|
||
// 非 JSON 响应体容忍
|
||
}
|
||
}
|
||
|
||
if (!response.ok) {
|
||
const message = payload?.message || `HTTP ${response.status}`;
|
||
throw new Error(message);
|
||
}
|
||
|
||
// 兼容 204 的删除等请求
|
||
return payload ?? { code: 200, message: 'OK', data: null };
|
||
}
|
||
```
|
||
|
||
## 📡 API接口集成
|
||
|
||
### 1. 创建区域侦查任务
|
||
|
||
#### 接口信息
|
||
- **URL**: `POST /api/v1/region-reconnaissance/tasks`
|
||
- **功能**: 创建新的区域侦查任务
|
||
- **返回**: 任务ID和创建结果
|
||
|
||
#### 前端实现
|
||
|
||
```javascript
|
||
/**
|
||
* 创建区域侦查任务
|
||
* @param {Object} taskData - 任务数据
|
||
* @returns {Promise<Object>} 创建结果
|
||
*/
|
||
async function createRegionReconnaissanceTask(taskData) {
|
||
try {
|
||
const result = await request('/tasks', {
|
||
method: 'POST',
|
||
body: JSON.stringify(taskData)
|
||
});
|
||
console.log('任务创建成功:', result);
|
||
return result;
|
||
} catch (error) {
|
||
console.error('创建任务失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 请求数据格式
|
||
|
||
```javascript
|
||
// 完整的任务创建数据示例(区域闭合由后端自动处理,前端无需首尾重复)
|
||
const taskData = {
|
||
taskName: "测试区域侦查任务",
|
||
taskType: 1, // 1-区域侦查, 2-航线侦查
|
||
regionZone: {
|
||
pointList: [
|
||
{ latitude: 39.9042, longitude: 116.4074, altitude: 100.0 },
|
||
{ latitude: 39.9142, longitude: 116.4174, altitude: 100.0 },
|
||
{ latitude: 39.9242, longitude: 116.4274, altitude: 100.0 }
|
||
]
|
||
},
|
||
description: "这是一个测试任务",
|
||
executeTime: "2025-01-27T10:00:00Z" // 使用 ISO 8601(UTC)
|
||
};
|
||
```
|
||
|
||
> 说明
|
||
> - 区域闭合规则:后端会基于点列表自动闭合多边形,前端不需要重复首尾点。
|
||
> - 时间与时区:所有时间字段使用 ISO 8601 UTC(例如 `2025-01-27T10:00:00Z`)。服务端按 UTC 存储与计算,前端展示时可按本地时区格式化。
|
||
|
||
### 2. 获取任务列表
|
||
|
||
#### 接口信息
|
||
- **URL**: `GET /api/v1/region-reconnaissance/tasks`
|
||
- **功能**: 获取区域侦查任务列表
|
||
- **支持分页**: 是
|
||
|
||
#### 前端实现
|
||
|
||
```javascript
|
||
/**
|
||
* 获取任务列表
|
||
* @param {Object} params - 查询参数
|
||
* @returns {Promise<Object>} 任务列表
|
||
*/
|
||
async function getRegionReconnaissanceTasks(params = {}) {
|
||
try {
|
||
const qp = Object.fromEntries(
|
||
Object.entries({ page: params.page ?? 1, size: params.size ?? 10, ...params })
|
||
.filter(([, v]) => v !== undefined && v !== null && v !== '')
|
||
);
|
||
const queryParams = new URLSearchParams(qp).toString();
|
||
const result = await request(`/tasks?${queryParams}`, { method: 'GET' });
|
||
return result;
|
||
} catch (error) {
|
||
console.error('获取任务列表失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 获取任务详情
|
||
|
||
#### 接口信息
|
||
- **URL**: `GET /api/v1/region-reconnaissance/tasks/{taskId}`
|
||
- **功能**: 获取指定任务的详细信息
|
||
|
||
#### 前端实现
|
||
|
||
```javascript
|
||
/**
|
||
* 获取任务详情
|
||
* @param {string} taskId - 任务ID
|
||
* @returns {Promise<Object>} 任务详情
|
||
*/
|
||
async function getRegionReconnaissanceTaskDetail(taskId) {
|
||
try {
|
||
const result = await request(`/tasks/${taskId}`, { method: 'GET' });
|
||
return result;
|
||
} catch (error) {
|
||
console.error('获取任务详情失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. 更新任务状态
|
||
|
||
#### 接口信息
|
||
- **URL**: `PUT /api/v1/region-reconnaissance/tasks/{taskId}/status`
|
||
- **功能**: 更新任务状态
|
||
|
||
#### 前端实现
|
||
|
||
```javascript
|
||
/**
|
||
* 更新任务状态
|
||
* @param {string} taskId - 任务ID
|
||
* @param {number} status - 新状态
|
||
* @returns {Promise<Object>} 更新结果
|
||
*/
|
||
async function updateTaskStatus(taskId, status) {
|
||
try {
|
||
const result = await request(`/tasks/${taskId}/status`, {
|
||
method: 'PUT',
|
||
body: JSON.stringify({ status })
|
||
});
|
||
return result;
|
||
} catch (error) {
|
||
console.error('更新任务状态失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5. 删除任务
|
||
|
||
#### 接口信息
|
||
- **URL**: `DELETE /api/v1/region-reconnaissance/tasks/{taskId}`
|
||
- **功能**: 删除指定任务
|
||
|
||
#### 前端实现
|
||
|
||
```javascript
|
||
/**
|
||
* 删除任务
|
||
* @param {string} taskId - 任务ID
|
||
* @returns {Promise<Object>} 删除结果
|
||
*/
|
||
async function deleteRegionReconnaissanceTask(taskId) {
|
||
try {
|
||
const result = await request(`/tasks/${taskId}`, { method: 'DELETE' });
|
||
return result; // 兼容 204,无内容时返回 { code: 200, message: 'OK', data: null }
|
||
} catch (error) {
|
||
console.error('删除任务失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🎨 前端组件示例
|
||
|
||
### React组件示例
|
||
|
||
```jsx
|
||
import React, { useState, useEffect } from 'react';
|
||
|
||
const RegionReconnaissanceTaskManager = () => {
|
||
const [tasks, setTasks] = useState([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState(null);
|
||
|
||
// 获取任务列表
|
||
const fetchTasks = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const result = await getRegionReconnaissanceTasks();
|
||
setTasks(result.data?.records ?? []);
|
||
} catch (err) {
|
||
setError(err.message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
// 创建任务
|
||
const handleCreateTask = async (taskData) => {
|
||
try {
|
||
await createRegionReconnaissanceTask(taskData);
|
||
await fetchTasks(); // 刷新列表
|
||
alert('任务创建成功!');
|
||
} catch (err) {
|
||
alert(`创建失败: ${err.message}`);
|
||
}
|
||
};
|
||
|
||
// 删除任务
|
||
const handleDeleteTask = async (taskId) => {
|
||
if (window.confirm('确定要删除这个任务吗?')) {
|
||
try {
|
||
await deleteRegionReconnaissanceTask(taskId);
|
||
await fetchTasks(); // 刷新列表
|
||
alert('任务删除成功!');
|
||
} catch (err) {
|
||
alert(`删除失败: ${err.message}`);
|
||
}
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
fetchTasks();
|
||
}, []);
|
||
|
||
return (
|
||
<div className="task-manager">
|
||
<h2>区域侦查任务管理</h2>
|
||
|
||
{loading && <div>加载中...</div>}
|
||
{error && <div className="error">错误: {error}</div>}
|
||
|
||
<div className="task-list">
|
||
{tasks.map(task => (
|
||
<div key={task.taskId} className="task-item">
|
||
<h3>{task.taskName}</h3>
|
||
<p>类型: {task.taskType === 1 ? '区域侦查' : '航线侦查'}</p>
|
||
<p>状态: {getStatusText(task.status)}</p>
|
||
<p>创建时间: {new Date(task.createTime).toLocaleString()}</p>
|
||
<button onClick={() => handleDeleteTask(task.taskId)}>
|
||
删除
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// 状态文本转换
|
||
const getStatusText = (status) => {
|
||
const statusMap = {
|
||
0: '待执行',
|
||
1: '执行中',
|
||
2: '已完成',
|
||
3: '已取消',
|
||
4: '执行失败'
|
||
};
|
||
return statusMap[status] || '未知状态';
|
||
};
|
||
|
||
export default RegionReconnaissanceTaskManager;
|
||
```
|
||
|
||
### Vue组件示例
|
||
|
||
```vue
|
||
<template>
|
||
<div class="task-manager">
|
||
<h2>区域侦查任务管理</h2>
|
||
|
||
<div v-if="loading" class="loading">加载中...</div>
|
||
<div v-if="error" class="error">错误: {{ error }}</div>
|
||
|
||
<div class="task-list">
|
||
<div
|
||
v-for="task in tasks"
|
||
:key="task.taskId"
|
||
class="task-item"
|
||
>
|
||
<h3>{{ task.taskName }}</h3>
|
||
<p>类型: {{ task.taskType === 1 ? '区域侦查' : '航线侦查' }}</p>
|
||
<p>状态: {{ getStatusText(task.status) }}</p>
|
||
<p>创建时间: {{ formatDate(task.createTime) }}</p>
|
||
<button @click="handleDeleteTask(task.taskId)">
|
||
删除
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'RegionReconnaissanceTaskManager',
|
||
data() {
|
||
return {
|
||
tasks: [],
|
||
loading: false,
|
||
error: null
|
||
};
|
||
},
|
||
async mounted() {
|
||
await this.fetchTasks();
|
||
},
|
||
methods: {
|
||
async fetchTasks() {
|
||
this.loading = true;
|
||
try {
|
||
const result = await getRegionReconnaissanceTasks();
|
||
this.tasks = result.data?.records ?? [];
|
||
} catch (err) {
|
||
this.error = err.message;
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
async handleCreateTask(taskData) {
|
||
try {
|
||
await createRegionReconnaissanceTask(taskData);
|
||
await this.fetchTasks();
|
||
alert('任务创建成功!');
|
||
} catch (err) {
|
||
alert(`创建失败: ${err.message}`);
|
||
}
|
||
},
|
||
|
||
async handleDeleteTask(taskId) {
|
||
if (confirm('确定要删除这个任务吗?')) {
|
||
try {
|
||
await deleteRegionReconnaissanceTask(taskId);
|
||
await this.fetchTasks();
|
||
alert('任务删除成功!');
|
||
} catch (err) {
|
||
alert(`删除失败: ${err.message}`);
|
||
}
|
||
}
|
||
},
|
||
|
||
getStatusText(status) {
|
||
const statusMap = {
|
||
0: '待执行',
|
||
1: '执行中',
|
||
2: '已完成',
|
||
3: '已取消',
|
||
4: '执行失败'
|
||
};
|
||
return statusMap[status] || '未知状态';
|
||
},
|
||
|
||
formatDate(dateString) {
|
||
return new Date(dateString).toLocaleString();
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
## 📊 数据格式说明
|
||
|
||
### 任务对象结构
|
||
|
||
```typescript
|
||
interface RegionReconnaissanceTask {
|
||
taskId: string; // 任务ID
|
||
taskName: string; // 任务名称
|
||
taskType: number; // 任务类型 (1-区域侦查, 2-航线侦查)
|
||
status: number; // 任务状态 (0-待执行, 1-执行中, 2-已完成, 3-已取消, 4-执行失败)
|
||
regionZone: RegionZone; // 区域信息
|
||
description?: string; // 任务描述
|
||
executeTime?: string; // 执行时间
|
||
createTime: string; // 创建时间
|
||
updateTime: string; // 更新时间
|
||
createUserId: string; // 创建用户ID
|
||
mqttSendStatus: number; // MQTT发送状态
|
||
}
|
||
|
||
interface RegionZone {
|
||
pointList: Point[]; // 区域点列表
|
||
}
|
||
|
||
interface Point {
|
||
latitude: number; // 纬度
|
||
longitude: number; // 经度
|
||
altitude: number; // 高度
|
||
}
|
||
```
|
||
|
||
### 响应数据格式
|
||
|
||
```typescript
|
||
// 成功响应
|
||
interface ApiResponse<T> {
|
||
code: number; // 响应码 (200-成功)
|
||
message: string; // 响应消息
|
||
data: T; // 响应数据
|
||
timestamp: string; // 时间戳
|
||
}
|
||
|
||
// 分页响应
|
||
interface PageResponse<T> {
|
||
code: number;
|
||
message: string;
|
||
data: {
|
||
records: T[]; // 数据列表
|
||
total: number; // 总记录数
|
||
size: number; // 每页大小
|
||
current: number; // 当前页码
|
||
pages: number; // 总页数
|
||
};
|
||
timestamp: string;
|
||
}
|
||
```
|
||
|
||
## ⚠️ 错误处理
|
||
|
||
### 常见错误码
|
||
|
||
```javascript
|
||
const ERROR_CODES = {
|
||
400: '请求参数错误',
|
||
401: '未授权访问',
|
||
403: '禁止访问',
|
||
404: '资源不存在',
|
||
500: '服务器内部错误',
|
||
1001: '任务名称不能为空',
|
||
1002: '区域点列表不能为空',
|
||
1003: '任务不存在',
|
||
1004: '任务状态不允许此操作'
|
||
};
|
||
```
|
||
|
||
### 错误处理示例
|
||
|
||
```javascript
|
||
/**
|
||
* 统一错误处理(配合 fetch 请求助手)
|
||
* @param {Error} error - 错误对象
|
||
* @returns {string} 用户友好的错误信息
|
||
*/
|
||
function handleApiError(error) {
|
||
// request() 已将后端 message 或 HTTP 状态转为 Error(message)
|
||
return error?.message || '请求失败';
|
||
}
|
||
```
|
||
|
||
## 🔧 工具函数
|
||
|
||
### 数据验证
|
||
|
||
```javascript
|
||
/**
|
||
* 验证任务数据
|
||
* @param {Object} taskData - 任务数据
|
||
* @returns {Object} 验证结果
|
||
*/
|
||
function validateTaskData(taskData) {
|
||
const errors = [];
|
||
|
||
if (!taskData.taskName || taskData.taskName.trim() === '') {
|
||
errors.push('任务名称不能为空');
|
||
}
|
||
|
||
if (![1, 2].includes(taskData.taskType)) {
|
||
errors.push('任务类型必须是1(区域侦查)或2(航线侦查)');
|
||
}
|
||
|
||
if (!taskData.regionZone || !Array.isArray(taskData.regionZone.pointList)) {
|
||
errors.push('区域信息不能为空');
|
||
} else if (taskData.regionZone.pointList.length < 3) {
|
||
errors.push('区域至少需要3个点');
|
||
}
|
||
|
||
return {
|
||
isValid: errors.length === 0,
|
||
errors
|
||
};
|
||
}
|
||
```
|
||
|
||
### 数据转换
|
||
|
||
```javascript
|
||
/**
|
||
* 转换区域数据为API格式
|
||
* @param {Array} points - 前端点数据
|
||
* @returns {Object} API格式的区域数据
|
||
*/
|
||
function convertRegionData(points) {
|
||
return {
|
||
pointList: points.map(point => ({
|
||
latitude: parseFloat(point.latitude),
|
||
longitude: parseFloat(point.longitude),
|
||
altitude: parseFloat(point.altitude) || 0
|
||
}))
|
||
};
|
||
}
|
||
```
|
||
|
||
## 📱 移动端适配
|
||
|
||
### 响应式设计
|
||
|
||
```css
|
||
/* 移动端适配 */
|
||
@media (max-width: 768px) {
|
||
.task-manager {
|
||
padding: 10px;
|
||
}
|
||
|
||
.task-item {
|
||
margin-bottom: 15px;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.task-item h3 {
|
||
font-size: 16px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.task-item p {
|
||
font-size: 14px;
|
||
margin: 4px 0;
|
||
}
|
||
|
||
.task-item button {
|
||
width: 100%;
|
||
padding: 8px;
|
||
margin-top: 10px;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🧪 测试示例
|
||
|
||
### 单元测试
|
||
|
||
```javascript
|
||
// Jest测试示例
|
||
describe('RegionReconnaissanceTask API', () => {
|
||
test('创建任务', async () => {
|
||
const taskData = {
|
||
taskName: '测试任务',
|
||
taskType: 1,
|
||
regionZone: {
|
||
pointList: [
|
||
{ latitude: 39.9042, longitude: 116.4074, altitude: 100 },
|
||
{ latitude: 39.9142, longitude: 116.4174, altitude: 100 },
|
||
{ latitude: 39.9242, longitude: 116.4274, altitude: 100 }
|
||
]
|
||
}
|
||
};
|
||
|
||
const result = await createRegionReconnaissanceTask(taskData);
|
||
expect(result.code).toBe(200);
|
||
expect(result.data.taskId).toBeDefined();
|
||
});
|
||
|
||
test('获取任务列表', async () => {
|
||
const result = await getRegionReconnaissanceTasks();
|
||
expect(result.code).toBe(200);
|
||
expect(Array.isArray(result.data.records)).toBe(true);
|
||
});
|
||
});
|
||
```
|
||
|
||
## 📚 最佳实践
|
||
|
||
### 1. 错误处理
|
||
- 始终使用try-catch包装API调用
|
||
- 提供用户友好的错误信息
|
||
- 记录详细的错误日志用于调试
|
||
|
||
### 2. 数据验证
|
||
- 在发送请求前验证数据格式
|
||
- 使用TypeScript或PropTypes进行类型检查
|
||
- 提供实时验证反馈
|
||
|
||
### 3. 用户体验
|
||
- 显示加载状态
|
||
- 提供操作确认
|
||
- 使用乐观更新提升响应速度
|
||
|
||
### 4. 性能优化
|
||
- 实现数据缓存
|
||
- 使用分页加载大量数据
|
||
- 防抖处理用户输入
|
||
|
||
## 🔗 相关资源
|
||
|
||
- [后端API接口说明](./后端API接口说明.md)
|
||
- [前端对接指南](./前端对接指南.md)
|
||
- [前端集成说明](./前端集成说明.md)
|
||
|
||
## 📞 技术支持
|
||
|
||
如有问题,请联系开发团队或查看项目文档。
|
||
|
||
---
|
||
|
||
*最后更新: 2025-01-27*
|