huangxw 1 месяц назад
Родитель
Сommit
438f99dcb1
4 измененных файлов с 214 добавлено и 0 удалено
  1. 21 0
      src/pages.json
  2. 188 0
      src/tools/map-draw-area/index.vue
  3. 5 0
      src/tools/map-gd/index.vue
  4. 0 0
      stats.html

+ 21 - 0
src/pages.json

@@ -52,6 +52,27 @@
                     }
                     }
                 }
                 }
             ]
             ]
+        },
+        {
+            "root": "tools",
+            "pages": [
+               {
+                    "path": "map-draw-area/index",
+                    "style": {
+                        "navigationBarTitleText": "地图绘制面积图",
+                        "disableScroll": true,
+                        "enablePullDownRefresh": false
+                    }
+               },
+               {
+                    "path": "map-gd/index",
+                    "style": {
+                        "navigationBarTitleText": "地图绘制面积图",
+                        "disableScroll": true,
+                        "enablePullDownRefresh": false
+                    }
+               }
+            ]
         }
         }
     ],
     ],
     "tabBar": {
     "tabBar": {

+ 188 - 0
src/tools/map-draw-area/index.vue

@@ -0,0 +1,188 @@
+<template>
+    <!-- 仅微信小程序端 -->
+    <!-- #ifdef MP-WEIXIN -->
+    <view class="wx-map-draw-area">
+        <up-navbar title="地图绘制" @leftClick="navigateBackOrHome()" :fixed="false"></up-navbar>
+        <view class="flex1 ov-hd">
+            <map class="wx-map" :latitude="center[0]" :longitude="center[1]"  enable-rotate :scale="zoomToScale(zoom)"
+                :polygons="wxPolygons" :polyline="wxPolylines" :show-location="false" @tap="onWxMapTap"
+                enable-satellite />
+
+        </view>
+        <view class="wx-toolbar">
+            <u-button size="small" type="primary" @click="startOrFinish">
+                {{ isDrawing ? '完成' : '开始绘制' }}
+            </u-button>
+            <u-button size="small" class="mt-8" @click="undoPoint" :disabled="wxPoints.length === 0">撤销</u-button>
+            <u-button size="small" class="mt-8" type="warning" @click="clearAll"
+                :disabled="wxPoints.length === 0">清除</u-button>
+        </view>
+
+        <view class="wx-info" v-if="showInfo">
+            <text>点数:{{ wxPoints.length }}</text>
+            <text class="ml-12">面积:{{ formattedWxArea }}</text>
+            <text class="ml-12" v-if="isClosed">(已闭合)</text>
+        </view>
+    </view>
+    <!-- #endif -->
+
+    <!-- 非微信端兜底提示(可选) -->
+    <!-- #ifndef MP-WEIXIN -->
+    <view class="map-draw-area__unsupported">
+        <text>该组件仅支持微信小程序端绘制。</text>
+    </view>
+    <!-- #endif -->
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+
+type LatLng = [number, number]
+type WxPoint = { latitude: number; longitude: number }
+
+const props = defineProps({
+    center: { type: Array as () => LatLng, default: () => [23.1291, 113.2644] },
+    zoom: { type: Number, default: 13 },
+    showInfo: { type: Boolean, default: true },
+})
+
+const emits = defineEmits<{
+    (e: 'change', payload: { area: number; latlngs: LatLng[]; closed: boolean }): void
+}>()
+
+// 绘制状态
+const wxPoints = ref<WxPoint[]>([])
+const isDrawing = ref(false)
+const isClosed = ref(false)
+const wxArea = ref(0)
+
+// 可视化:多边形(填充)、折线(路径)
+const wxPolygons = computed<any[]>(() => {
+    if (wxPoints.value.length < 3) return []
+    return [
+        {
+            points: wxPoints.value,
+            strokeColor: '#18A058',
+            strokeWidth: 2,
+            fillColor: '#18A05833',
+            zIndex: 2,
+        },
+    ]
+})
+
+const wxPolylines = computed<any[]>(() => {
+    if (wxPoints.value.length < 2) return []
+    const lines: any[] = [
+        { points: wxPoints.value, color: '#18A058', width: 2, dottedLine: false, zIndex: 3 },
+    ]
+    if (wxPoints.value.length >= 2) {
+        const first = wxPoints.value[0]
+        const last = wxPoints.value[wxPoints.value.length - 1]
+        lines.push({ points: [last, first], color: '#18A058', width: 2, dottedLine: true, zIndex: 3 })
+    }
+    return lines
+})
+
+const formattedWxArea = computed(() => formatArea(wxArea.value))
+
+function onWxMapTap(e: any) {
+    if (!isDrawing.value) return
+    const { latitude, longitude } = e?.detail || {}
+    if (typeof latitude === 'number' && typeof longitude === 'number') {
+        wxPoints.value = [...wxPoints.value, { latitude, longitude }]
+        computeWxArea()
+        emitChange()
+    }
+}
+
+function startOrFinish() {
+    if (!isDrawing.value) {
+        isDrawing.value = true
+        isClosed.value = false
+        wxPoints.value = []
+        wxArea.value = 0
+    } else {
+        isDrawing.value = false
+        isClosed.value = wxPoints.value.length >= 3
+        computeWxArea()
+        emitChange()
+    }
+}
+
+function undoPoint() {
+    if (wxPoints.value.length === 0) return
+    wxPoints.value = wxPoints.value.slice(0, -1)
+    computeWxArea()
+    emitChange()
+}
+
+function clearAll() {
+    wxPoints.value = []
+    wxArea.value = 0
+    isDrawing.value = false
+    isClosed.value = false
+    emitChange()
+}
+
+function emitChange() {
+    emits('change', {
+        area: wxArea.value,
+        latlngs: wxPoints.value.map((p) => [p.latitude, p.longitude]) as LatLng[],
+        closed: isClosed.value,
+    })
+}
+
+function computeWxArea() {
+    if (wxPoints.value.length < 3) {
+        wxArea.value = 0
+        return
+    }
+    wxArea.value = calcSphericalPolygonAreaLngLat(
+        wxPoints.value.map((p) => [p.longitude, p.latitude])
+    )
+}
+
+// 球面多边形面积(近似,WGS84)输入 [lng, lat]
+function calcSphericalPolygonAreaLngLat(coords: [number, number][]): number {
+    const n = coords.length
+    if (n < 3) return 0
+    const rad = (d: number) => (d * Math.PI) / 180
+    const R = 6378137
+    let area = 0
+    for (let i = 0; i < n; i++) {
+        const lower = coords[(i + n - 1) % n]
+        const middle = coords[i]
+        const upper = coords[(i + 1) % n]
+        area += (rad(upper[0]) - rad(lower[0])) * Math.sin(rad(middle[1]))
+    }
+    area = (area * R * R) / 2
+    return Math.abs(area)
+}
+
+function zoomToScale(zoom: number) {
+    const z = Math.max(3, Math.min(20, Math.round(zoom)))
+    return z
+}
+
+function formatArea(m2: number): string {
+    if (!m2) return '0 m²'
+    if (m2 < 100000) return `${m2.toFixed(2)} m²`
+    const km2 = m2 / 1_000_000
+    return `${km2.toFixed(4)} km²`
+}
+
+defineExpose({ startOrFinish, undoPoint, clearAll })
+</script>
+
+<style scoped lang="scss">
+.wx-map-draw-area {
+    width: 100%;
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+}
+.wx-map {
+    width: 750rpx;
+    height: 100%;
+}
+</style>

+ 5 - 0
src/tools/map-gd/index.vue

@@ -0,0 +1,5 @@
+<template>
+    <div>
+        <web-view src="https://www.shuziyunyao.com/admin/client/report-list?tlk=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxOTIxMDQzNjEwNjY5MTk5MzYxIiwicm5TdHIiOiJHNzZNSlgxbjZUOFBiVEdYQnk4RnBXU3FyRDFTZkRsYSIsImNsaWVudGlkIjoiZTVjZDdlNDg5MWJmOTVkMWQxOTIwNmNlMjRhN2IzMmUiLCJjcHlpZCI6IjkxNTMwMTAyTUFDQTk2TUo5SCIsImNweU5hbWUiOiLmvJTnpLrkvIHkuJoiLCJ0ZW5hbnRJZCI6IjAwMDAwMCIsInVzZXJJZCI6MTkyMTA0MzYxMDY2OTE5OTM2MSwiZGVwdElkIjoxNzQ2Nzg0MjA3MjM5MDczNzk0fQ.Fq6ebfuf_galuCRUEVbZyj0lcXuqp9a0e7vBvSjlQKk&state=1766373817974"></web-view>
+    </div>
+</template>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
stats.html


Некоторые файлы не были показаны из-за большого количества измененных файлов