Files
Environment-Monitoring-System/Design/frontend_Design/MapPage_Design.md
ChuXun 4ce487588a 1
2025-10-19 20:31:01 +08:00

296 lines
8.3 KiB
Markdown
Raw 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.
# 地图页面设计文档
## 1. 页面概述
地图页面是 EMS 系统的核心可视化界面,提供基于地理位置的直观信息展示。它主要用于显示城市的环境网格划分、实时污染数据(如热力图)、网格员位置以及特定任务的地理标记。此页面帮助主管和决策者监控区域状态,并为网格员提供清晰的工作区域指引。
## 2. 页面布局
![地图页面布局示意图](https://placeholder-for-map-page-mockup.png)
### 2.1 布局结构
页面采用全屏地图为主体的布局,辅以侧边栏和浮动控件。
- **主区域**: 全屏交互式地图,作为所有地理信息的载体。
- **左侧边栏**: 用于控制地图上显示的图层,如网格边界、热力图、网格员位置等。同时,也可能包含一个可搜索的兴趣点列表。
- **地图控件**: 地图右上角或左上角放置缩放、定位、全屏等标准地图控件。
- **信息弹窗 (Popup)**: 点击地图上的特定元素(如网格、标记点)时,会弹出信息窗口,显示该元素的详细信息。
### 2.2 响应式设计
- **桌面端**: 显示完整的左侧边栏和地图。
- **平板端/移动端**: 左侧边栏默认收起,可通过按钮展开为抽屉或浮动面板,以最大化地图可视区域。
## 3. 组件结构
```vue
<template>
<div class="map-page">
<!-- 地图容器 -->
<div ref="mapContainer" class="map-container"></div>
<!-- 左侧图层控制面板 -->
<el-card class="layer-control-panel">
<template #header>
<div class="panel-header">
<span>图层控制</span>
<el-button link type="primary" @click="togglePanel" class="panel-toggle-btn">
<el-icon><ArrowLeft v-if="isPanelVisible" /><ArrowRight v-else /></el-icon>
</el-button>
</div>
</template>
<div v-show="isPanelVisible">
<el-checkbox-group v-model="visibleLayers">
<el-checkbox label="grids">显示网格</el-checkbox>
<el-checkbox label="heatmap">显示污染热力图</el-checkbox>
<el-checkbox label="workers">显示网格员位置</el-checkbox>
<el-checkbox label="tasks">显示任务标记</el-checkbox>
</el-checkbox-group>
<el-divider />
<h4>搜索网格</h4>
<el-input v-model="gridSearch" placeholder="输入网格名称/编号搜索" @keyup.enter="searchGrid" />
</div>
</el-card>
<!-- 地图加载状态 -->
<div v-if="mapLoading" class="map-loading-overlay">
<el-icon class="is-loading" size="30"><Loading /></el-icon>
<span>地图加载中...</span>
</div>
</div>
</template>
```
## 4. 数据结构
```typescript
// 网格数据结构
interface GridData {
id: number;
name: string;
// GeoJSON 格式的多边形坐标
geometry: {
type: 'Polygon';
coordinates: number[][][];
};
manager: string; // 网格负责人
}
// 热力图数据点
interface HeatmapPoint {
lat: number;
lng: number;
value: number; // 污染指数
}
// 网格员位置
interface WorkerPosition {
id: number;
name: string;
lat: number;
lng: number;
lastUpdated: string;
}
// 任务标记
interface TaskMarker {
id: number;
title: string;
lat: number;
lng: number;
status: string;
}
```
## 5. 状态管理
```typescript
// 地图服务实例
let mapInstance = null;
let gridLayerGroup = null;
let heatmapLayer = null;
let workerMarkersGroup = null;
// 组件内状态
const mapContainer = ref<HTMLElement | null>(null);
const mapLoading = ref<boolean>(true);
const isPanelVisible = ref<boolean>(true);
const visibleLayers = ref<string[]>(['grids']); // 默认显示的图层
const gridSearch = ref<string>('');
// 全局状态 (Pinia Store)
const mapStore = useMapStore();
const { grids, heatmapData, workers } = storeToRefs(mapStore);
// 监听可见图层变化,动态更新地图
watch(visibleLayers, (newLayers, oldLayers) => {
// ... 调用地图服务方法显示/隐藏图层
});
```
## 6. 交互逻辑
### 6.1 地图初始化
```typescript
onMounted(() => {
initializeMap();
mapStore.fetchInitialMapData();
});
const initializeMap = () => {
if (mapContainer.value) {
mapInstance = L.map(mapContainer.value).setView([39.9042, 116.4074], 10); // 默认北京
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
}).addTo(mapInstance);
mapLoading.value = false;
}
};
```
### 6.2 图层控制
```typescript
const updateLayers = () => {
// 网格图层
if (visibleLayers.value.includes('grids') && !mapInstance.hasLayer(gridLayerGroup)) {
gridLayerGroup.addTo(mapInstance);
} else if (!visibleLayers.value.includes('grids') && mapInstance.hasLayer(gridLayerGroup)) {
gridLayerGroup.removeFrom(mapInstance);
}
// 其他图层同理...
};
// 监听 Pinia store 中数据的变化,并更新地图
watch(grids, (newGrids) => {
if (gridLayerGroup) {
gridLayerGroup.clearLayers();
// 添加新的网格到图层
newGrids.forEach(grid => {
const polygon = L.geoJSON(grid.geometry);
polygon.bindPopup(`<b>${grid.name}</b><br>负责人: ${grid.manager}`);
gridLayerGroup.addLayer(polygon);
});
}
});
```
### 6.3 面板交互
```typescript
const togglePanel = () => {
isPanelVisible.value = !isPanelVisible.value;
};
const searchGrid = () => {
const targetGrid = grids.value.find(g => g.name === gridSearch.value);
if (targetGrid) {
// 飞到目标网格位置
const bounds = L.geoJSON(targetGrid.geometry).getBounds();
mapInstance.flyToBounds(bounds);
} else {
ElMessage.info('未找到该网格');
}
};
```
## 7. API 调用
```typescript
// api/map.ts
export const mapApi = {
getGrids: () => apiClient.get<GridData[]>('/map/grids'),
getHeatmapData: () => apiClient.get<HeatmapPoint[]>('/map/heatmap'),
getWorkerPositions: () => apiClient.get<WorkerPosition[]>('/map/workers'),
};
// stores/map.ts
export const useMapStore = defineStore('map', {
state: () => ({
grids: [] as GridData[],
heatmapData: [] as HeatmapPoint[],
workers: [] as WorkerPosition[],
}),
actions: {
async fetchInitialMapData() {
// 并发请求所有地图数据
const [gridsRes, heatmapRes, workersRes] = await Promise.all([
mapApi.getGrids(),
mapApi.getHeatmapData(),
mapApi.getWorkerPositions(),
]);
this.grids = gridsRes.data;
this.heatmapData = heatmapRes.data;
this.workers = workersRes.data;
}
}
});
```
## 8. 样式设计
```scss
.map-page {
position: relative;
width: 100%;
height: calc(100vh - 50px); /* 减去顶部导航栏高度 */
.map-container {
width: 100%;
height: 100%;
z-index: 1;
}
.layer-control-panel {
position: absolute;
top: 20px;
left: 20px;
z-index: 2;
width: 250px;
background: white;
transition: transform 0.3s ease;
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
}
}
.map-loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 3;
flex-direction: column;
gap: 10px;
}
}
```
## 9. 测试用例
1. **单元测试**:
- 测试 Pinia store 的 action 是否能正确获取和存储地图数据。
- 测试地图交互函数,如 `searchGrid` 的逻辑。
2. **集成测试**:
- 验证地图是否能成功初始化并显示底图。
- 验证图层控制复选框是否能正确显示/隐藏对应的地理要素。
- 验证点击网格是否能弹出正确的信息窗口。
- 验证搜索功能是否能正确地将地图视图定位到指定网格。
## 10. 性能优化
1. **大数据渲染**:
- 对于大量的标记点(如网格员、任务),使用 `L.markerClusterGroup` 插件进行聚合,提高性能。
- 对于复杂的网格边界Polygon在低缩放级别下使用简化的几何图形可通过后端服务实现
2. **懒加载**: 地图库Leaflet和相关插件热力图、聚合应按需异步加载。
3. **数据更新**: 网格员位置等实时数据应使用 WebSocket 或定时轮询(配合 `requestAnimationFrame`)进行高效更新,而不是频繁重绘整个图层。