فهرست منبع

feat(map): 实现地图多边形绘制与编辑功能

- 新增多边形绘制、编辑、删除功能
- 支持撤销/重做操作
- 实现地图卫星图与矢量图切换
- 添加多边形选择模式与顶点拖拽调整
- 支持多边形数据保存与加载
- 显示当前操作模式提示信息
nahida 6 ماه پیش
والد
کامیت
ea49d26686
1فایلهای تغییر یافته به همراه262 افزوده شده و 0 حذف شده
  1. 262 0
      src/pages/test/index.vue

+ 262 - 0
src/pages/test/index.vue

@@ -0,0 +1,262 @@
+<template>
+  <view class="relative h-screen">
+    <!-- 顶部工具栏 -->
+    <view class="absolute top-2 left-2 right-2 flex gap-1.5 flex-wrap z-20">
+      <button class="px-2 py-1.5 bg-white rounded-md" @click="startNewPolygon">新建多边形</button>
+      <button class="px-2 py-1.5 bg-white rounded-md" @click="finishPolygon">完成当前多边形</button>
+      <button class="px-2 py-1.5 bg-white rounded-md" @click="toggleSatellite">{{ isSatellite ? '矢量图' : '卫星图' }}</button>
+      <button class="px-2 py-1.5 bg-white rounded-md" @click="undo">撤销</button>
+      <button class="px-2 py-1.5 bg-white rounded-md" @click="redo">重做</button>
+      <button class="px-2 py-1.5 bg-white rounded-md" @click="savePolygons">保存(打印)</button>
+      <button class="px-2 py-1.5 bg-white rounded-md" @click="loadPolygons">加载(从控制台最后保存)</button>
+      <button class="px-2 py-1.5 bg-red-500 text-white rounded-md" @click="deleteSelectedPolygon">删除选中多边形</button>
+      <button class="px-2 py-1.5 bg-white rounded-md" @click="toggleSelectMode">{{ mode === 'select' ? '退出选择模式' : '选择多边形' }}</button>
+    </view>
+
+    <!-- 地图 -->
+    <map
+        class="w-screen h-screen"
+        :latitude="center.lat"
+        :longitude="center.lng"
+        :enable-satellite="isSatellite"
+        :polygons="mapPolygons"
+        :markers="allMarkers"
+        @tap="onMapTap"
+        @markertap="onMarkerTap"
+        @markerdragend="onMarkerDragEnd"
+        @polygontap="onPolygonTap"
+    ></map>
+
+    <!-- 提示 -->
+    <view class="absolute left-3 bottom-4.5 bg-black bg-op-50 text-white px-2.5 py-1.5 rounded-md">当前模式:{{ modeText }}</view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import {computed, ref} from 'vue'
+
+interface LatLng {
+  latitude: number;
+  longitude: number
+}
+
+interface PolygonItem {
+  id: number
+  title?: string
+  points: LatLng[]
+  fillColor: string
+  strokeColor: string
+  strokeWidth: number
+}
+
+const center = ref({lat: 28.21, lng: 112.89})
+const isSatellite = ref(false)
+const polygons = ref<PolygonItem[]>([])
+const editingId = ref<number | null>(null)
+const mode = ref<'idle' | 'editing' | 'select'>('idle')
+const undoStack = ref<string[]>([])
+const redoStack = ref<string[]>([])
+const lastSaved = ref<string | null>(null)
+
+// ---------- 选择模式支持 ----------
+const toggleSelectMode = () => {
+  mode.value = mode.value === 'select' ? 'idle' : 'select'
+  editingId.value = null
+}
+
+const mapPolygons = computed(() => {
+  return polygons.value.map(p => {
+    const pts = p.points.map(pt => ({latitude: pt.latitude, longitude: pt.longitude}))
+    if (pts.length > 2) pts.push({...pts[0]})
+    return {
+      points: pts,
+      fillColor: editingId.value === p.id ? '#00FF0033' : p.fillColor, // 选中高亮
+      strokeColor: editingId.value === p.id ? '#00FF00' : p.strokeColor,
+      strokeWidth: p.strokeWidth
+    }
+  })
+})
+
+const allMarkers = computed(() => {
+  const markers: any[] = []
+  polygons.value.forEach(p => {
+    p.points.forEach((pt, idx) => {
+      markers.push({
+        id: Number(`${p.id}${idx}`),
+        latitude: pt.latitude,
+        longitude: pt.longitude,
+        width: 22,
+        height: 22,
+        iconPath: '/static/point.png',
+        draggable: true,
+        callout: {content: String(idx + 1), color: '#ffffff', bgColor: '#007aff', padding: 4}
+      })
+    })
+  })
+  return markers
+})
+
+const modeText = computed(() => {
+  if (mode.value === 'idle') return '空闲(点击 新建 多边形 开始绘制)'
+  if (mode.value === 'editing') return `编辑中:ID=${editingId.value}`
+  if (mode.value === 'select') return '选择模式:点击多边形选中'
+  return ''
+})
+
+const pushHistory = () => {
+  undoStack.value.push(JSON.stringify(polygons.value));
+  redoStack.value = []
+}
+const undo = () => {
+  if (undoStack.value.length) {
+    redoStack.value.push(JSON.stringify(polygons.value));
+    polygons.value = JSON.parse(undoStack.value.pop()!)
+  } else uni.showToast({title: '无法撤销', icon: 'none'})
+}
+const redo = () => {
+  if (redoStack.value.length) {
+    // 当前状态入撤销栈,但不要清空 redoStack
+    undoStack.value.push(JSON.stringify(polygons.value));
+    // 从 redoStack 弹出一个快照恢复
+    polygons.value = JSON.parse(redoStack.value.pop()!);
+  } else {
+    uni.showToast({title: '无法重做', icon: 'none'});
+  }
+}
+
+const startNewPolygon = () => {
+  const id = Date.now();
+  const newPoly: PolygonItem = {
+    id,
+    title: `多边形-${polygons.value.length + 1}`,
+    points: [],
+    fillColor: '#FF000033',
+    strokeColor: '#FF0000',
+    strokeWidth: 2
+  };
+  pushHistory();
+  polygons.value.push(newPoly);
+  editingId.value = id;
+  mode.value = 'editing'
+}
+const finishPolygon = () => {
+  if (!editingId.value) return uni.showToast({title: '当前没有正在编辑的多边形', icon: 'none'});
+  const p = polygons.value.find(x => x.id === editingId.value);
+  if (!p) return;
+  if (p.points.length < 3) return uni.showToast({title: '至少需要 3 个点才能完成', icon: 'none'});
+  editingId.value = null;
+  mode.value = 'idle'
+}
+const deleteSelectedPolygon = () => {
+  if (!editingId.value) return uni.showToast({title: '请先选中一个多边形(在编辑状态)', icon: 'none'});
+  pushHistory();
+  polygons.value = polygons.value.filter(p => p.id !== editingId.value);
+  editingId.value = null;
+  mode.value = 'idle'
+}
+
+const onMapTap = (e: any) => {
+  const {latitude, longitude} = e.detail;
+  if (mode.value === 'editing' && editingId.value != null) {
+    pushHistory();
+    const p = polygons.value.find(x => x.id === editingId.value);
+    if (!p) return;
+    p.points.push({latitude, longitude})
+  }
+}
+const onMarkerDragEnd = (e: any) => {
+  const {markerId, latitude, longitude} = e.detail;
+  let found = false;
+  for (const p of polygons.value) {
+    for (let idx = 0; idx < p.points.length; idx++) {
+      if (Number(`${p.id}${idx}`) === markerId) {
+        pushHistory();
+        p.points[idx].latitude = latitude;
+        p.points[idx].longitude = longitude;
+        found = true;
+        break
+      }
+    }
+    if (found) break
+  }
+}
+const onMarkerTap = (e: any) => {
+  const markerId = e.detail.markerId;
+  for (const p of polygons.value) {
+    for (let idx = 0; idx < p.points.length; idx++) {
+      if (Number(`${p.id}${idx}`) === markerId) {
+        if (mode.value === 'select') {
+          editingId.value = p.id;
+          return
+        }
+        editingId.value = p.id;
+        mode.value = 'editing';
+        uni.showModal({
+          title: '顶点操作',
+          content: `你点击了多边形 "${p.title}" 的第 ${idx + 1} 个顶点。`,
+          showCancel: true,
+          cancelText: '删除点',
+          confirmText: '取消',
+          success(res) {
+            if (res.cancel) {
+              if (p.points.length <= 3) {
+                uni.showModal({
+                  title: '提示', content: '删除会导致顶点不足(<3),是否删除整 polygon?', success(r) {
+                    if (r.confirm) {
+                      pushHistory();
+                      polygons.value = polygons.value.filter(pp => pp.id !== p.id);
+                      editingId.value = null;
+                      mode.value = 'idle'
+                    }
+                  }
+                });
+                return
+              }
+              pushHistory();
+              p.points.splice(idx, 1)
+            }
+          }
+        });
+        return
+      }
+    }
+  }
+}
+const onPolygonTap = (e: any) => {
+  if (mode.value === 'select') {
+    const idx = e.detail.index;
+    const p = polygons.value[idx];
+    if (p) {
+      editingId.value = p.id
+    }
+  }
+}
+
+const toggleSatellite = () => {
+  isSatellite.value = !isSatellite.value
+}
+const savePolygons = () => {
+  const payload = polygons.value.map(p => ({id: p.id, title: p.title, points: p.points}));
+  const json = JSON.stringify(payload, null, 2);
+  console.log('保存多边形:', json);
+  lastSaved.value = json;
+  uni.showToast({title: '已打印到控制台(并保存到内存)', icon: 'none'})
+}
+const loadPolygons = () => {
+  if (!lastSaved.value) {
+    return uni.showToast({title: '没有保存记录,请先点击 保存(打印)', icon: 'none'})
+  }
+  pushHistory();
+  const arr = JSON.parse(lastSaved.value);
+  polygons.value = arr.map((x: any, idx: number) => ({
+    id: x.id ?? Date.now() + idx,
+    title: x.title ?? `多边形-${idx + 1}`,
+    points: x.points,
+    fillColor: '#FF000033',
+    strokeColor: '#FF0000',
+    strokeWidth: 2
+  }));
+  editingId.value = null;
+  mode.value = 'idle'
+}
+</script>