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

8.3 KiB
Raw Blame History

地图页面设计文档

1. 页面概述

地图页面是 EMS 系统的核心可视化界面,提供基于地理位置的直观信息展示。它主要用于显示城市的环境网格划分、实时污染数据(如热力图)、网格员位置以及特定任务的地理标记。此页面帮助主管和决策者监控区域状态,并为网格员提供清晰的工作区域指引。

2. 页面布局

地图页面布局示意图

2.1 布局结构

页面采用全屏地图为主体的布局,辅以侧边栏和浮动控件。

  • 主区域: 全屏交互式地图,作为所有地理信息的载体。
  • 左侧边栏: 用于控制地图上显示的图层,如网格边界、热力图、网格员位置等。同时,也可能包含一个可搜索的兴趣点列表。
  • 地图控件: 地图右上角或左上角放置缩放、定位、全屏等标准地图控件。
  • 信息弹窗 (Popup): 点击地图上的特定元素(如网格、标记点)时,会弹出信息窗口,显示该元素的详细信息。

2.2 响应式设计

  • 桌面端: 显示完整的左侧边栏和地图。
  • 平板端/移动端: 左侧边栏默认收起,可通过按钮展开为抽屉或浮动面板,以最大化地图可视区域。

3. 组件结构

<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. 数据结构

// 网格数据结构
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. 状态管理

// 地图服务实例
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 地图初始化

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 图层控制

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 面板交互

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 调用

// 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. 样式设计

.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)进行高效更新,而不是频繁重绘整个图层。