| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- <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>
|