From 3fc27febccd04e2fcffbd2cdd16d2daafd0b3ca3 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Sat, 11 Oct 2025 17:23:27 +0800
Subject: [PATCH] feat:首页地图相关调整

---
 src/pages/map/index.vue           |  230 +++++++++++++++++++++++++---
 src/pages.json                    |    7 
 src/pages/map/drag.vue            |  176 ++++++++++++++++++++++
 package.json                      |    7 
 src/subPackages/browser/index.vue |   39 ++++
 pnpm-lock.yaml                    |    7 
 6 files changed, 436 insertions(+), 30 deletions(-)

diff --git a/package.json b/package.json
index 9020a56..6879d02 100644
--- a/package.json
+++ b/package.json
@@ -75,16 +75,17 @@
     "@dcloudio/uni-mp-weixin": "3.0.0-4070520250711001",
     "@dcloudio/uni-mp-xhs": "3.0.0-4070520250711001",
     "@dcloudio/uni-quickapp-webview": "3.0.0-4070520250711001",
+    "@dcloudio/uni-ui": "^1.5.11",
     "dayjs": "^1.11.18",
+    "js-base64": "^3.7.4",
+    "js-md5": "^0.7.3",
     "leaflet": "^1.9.4",
     "pinia": "2.2.4",
     "pinia-plugin-persistedstate": "4.1.3",
     "uview-plus": "^3.5.41",
     "vue": "3.4.21",
     "vue-i18n": "9.1.9",
-    "z-paging": "^2.8.8",
-    "js-md5": "^0.7.3",
-    "js-base64": "^3.7.4"
+    "z-paging": "^2.8.8"
   },
   "devDependencies": {
     "@antfu/eslint-config": "5.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4377ecf..36c9401 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -56,6 +56,9 @@
   '@dcloudio/uni-quickapp-webview':
     specifier: 3.0.0-4070520250711001
     version: 3.0.0-4070520250711001(postcss@8.5.6)(vue@3.4.21)
+  '@dcloudio/uni-ui':
+    specifier: ^1.5.11
+    version: 1.5.11
   dayjs:
     specifier: ^1.11.18
     version: 1.11.18
@@ -2272,6 +2275,10 @@
       - vue
     dev: false
 
+  /@dcloudio/uni-ui@1.5.11:
+    resolution: {integrity: sha512-DBtk046ofmeFd82zRI7d89SoEwrAxYzUN3WVPm1DIBkpLPG5F5QDNkHMnZGu2wNrMEmGBjBpUh3vqEY1L3jaMw==}
+    dev: false
+
   /@dcloudio/vite-plugin-uni@3.0.0-4070520250711001(postcss@8.5.6)(vite@5.2.8)(vue@3.4.21):
     resolution: {integrity: sha512-Pcd1YIPP+0hyC64oh0P3EBZGF8YHsScUS7R0wjlDGkRMsGowil0IbBE5DrmqjZ7QE+0Lau77yxfZdkSjY3gbvA==}
     engines: {node: ^14.18.0 || >=16.0.0}
diff --git a/src/pages.json b/src/pages.json
index d826fa4..4b0c6d4 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -121,6 +121,13 @@
         "style": {
           "navigationBarTitleText": "修改密码"
         }
+      },
+
+      {
+        "path": "browser/index",
+        "style": {
+          "navigationBarTitleText": "地图"
+        }
       }
     ]
   }],
diff --git a/src/pages/map/drag.vue b/src/pages/map/drag.vue
new file mode 100644
index 0000000..c6e581c
--- /dev/null
+++ b/src/pages/map/drag.vue
@@ -0,0 +1,176 @@
+<template>
+  <view
+    class="panel-container"
+    :style="{
+      transform: `translateY(${translateY}px)`,
+      transition: isDragging ? 'none' : 'transform 0.3s ease',
+    }"
+    @touchstart="onTouchStart"
+    @touchmove="onTouchMove"
+    @touchend="onTouchEnd"
+  >
+    <!-- 顶部拖拽提示条 -->
+    <view class="drag-bar"></view>
+
+    <!-- 搜索框(带毛玻璃) -->
+    <view class="search-box" :style="searchBoxStyle">
+      <slot name="searchBox"></slot>
+    </view>
+
+    <!-- 内容区 -->
+    <scroll-view scroll-y class="panel-content">
+      <view class="dummy-content">
+        <text>这里可以放搜索结果、推荐地点等内容</text>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, computed, onMounted } from 'vue'
+
+  // 获取屏幕高度
+  let screenHeight = 0
+
+  // 拖拽状态
+  const startY = ref(0)
+  const currentY = ref(0)
+  const translateY = ref(0)
+  const isDragging = ref(false)
+
+  const initialPosition = ref(0.8)
+  const oneSlidePosition = ref(0.6)
+  const twoSlidePosition = ref(0.1)
+
+  // 毛玻璃效果
+  const searchBoxStyle = computed(() => {
+    return translateY.value <= screenHeight * oneSlidePosition.value
+      ? {
+          backdropFilter: 'blur(10px)',
+          background: 'rgba(255, 255, 255, 0.7)',
+        }
+      : {
+          background: '#fff',
+        }
+  })
+
+  // 获取屏幕高度
+  onMounted(() => {
+    const info = uni.getSystemInfoSync()
+    screenHeight = info.windowHeight
+
+    // #ifdef APP-PLUS
+    initialPosition.value = 0.8
+    // #endif
+
+    // #ifdef H5
+    initialPosition.value = 0.95
+
+    if (screenHeight < 500) {
+       initialPosition.value = 0.86
+    }
+
+    if (screenHeight > 575) {
+      initialPosition.value = 0.85
+    }
+
+    if (screenHeight > 642) {
+       initialPosition.value = 0.865
+    }
+    // #endif
+
+    translateY.value = screenHeight * initialPosition.value
+  })
+
+  // 拖拽开始
+  const onTouchStart = (e) => {
+    // 只在拖拽时,阻止页面滚动
+    startY.value = e.touches[0].clientY
+    currentY.value = translateY.value
+    isDragging.value = true
+  }
+
+  // 拖拽移动
+  const onTouchMove = (e) => {
+    // 只在拖拽时,阻止页面滚动
+    if (isDragging.value) {
+      e.preventDefault() // 阻止滚动行为
+      e.stopPropagation() // 阻止事件冒泡
+    }
+
+    const delta = e.touches[0].clientY - startY.value
+    let nextY = currentY.value + delta
+
+    // 限制拖拽范围 [0, 90% 屏幕高度]
+    nextY = Math.min(screenHeight * (1 - twoSlidePosition.value), Math.max(0, nextY))
+
+    translateY.value = nextY
+  }
+
+  // 拖拽结束后自动吸附
+  const onTouchEnd = () => {
+    isDragging.value = false
+    const percentFromTop = 100 - (translateY.value / screenHeight) * 100
+
+    if (percentFromTop < 20) translateY.value = screenHeight * initialPosition.value
+    else if (percentFromTop < 40) translateY.value = screenHeight * oneSlidePosition.value
+    else if (percentFromTop < 75) translateY.value = screenHeight * oneSlidePosition.value
+    else if (percentFromTop < 90) translateY.value = screenHeight * twoSlidePosition.value
+    else translateY.value = screenHeight * twoSlidePosition.value
+  }
+</script>
+
+<style scoped>
+  .panel-container {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 90vh;
+    background: #fff;
+    border-top-left-radius: 20rpx;
+    border-top-right-radius: 20rpx;
+    box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.15);
+    display: flex;
+    flex-direction: column;
+    z-index: 997;
+    touch-action: none; /* 禁止默认的滚动行为 */
+  }
+
+  .drag-bar {
+    width: 80rpx;
+    height: 8rpx;
+    background: #ccc;
+    border-radius: 4rpx;
+    margin: 16rpx auto;
+  }
+
+  .search-box {
+    padding: 0 24rpx;
+    margin-bottom: 12rpx;
+    border-radius: 40rpx;
+    transition: backdrop-filter 0.3s ease, background 0.3s ease;
+  }
+
+  .search-input {
+    width: 100%;
+    height: 80rpx;
+    border-radius: 40rpx;
+    background: #f2f2f2;
+    padding: 0 24rpx;
+    font-size: 28rpx;
+    border: none;
+    outline: none;
+  }
+
+  .panel-content {
+    flex: 1;
+    padding: 20rpx;
+  }
+
+  .dummy-content {
+    text-align: center;
+    color: #999;
+    margin-top: 100rpx;
+  }
+</style>
diff --git a/src/pages/map/index.vue b/src/pages/map/index.vue
index ba2a595..97a5f8b 100644
--- a/src/pages/map/index.vue
+++ b/src/pages/map/index.vue
@@ -5,6 +5,10 @@
     <view id="map" class="map" :prop="setSelectMapLayerKey" :location="location" :change:prop="leaflet.initLayer"
       :change:location="leaflet.setView"></view>
 
+    <view class="weather-box">
+      <up-icon customPrefix="xyicon" name="tuceng" size="24" color="#000"></up-icon>
+    </view>
+
     <view class="layer-btn" @click="show = true">
       <up-icon customPrefix="xyicon" name="tuceng" size="24" color="#000"></up-icon>
     </view>
@@ -13,19 +17,44 @@
       <up-icon customPrefix="xyicon" name="dingwei" size="24" color="#000"></up-icon>
     </view>
 
-    <u-input placeholder="搜索" border="surround" v-model="value" @change="change" color="#fff">
-      <template #prefix>
-        <view class="search-left-box">
-          <u-icon color="#fff" name="arrow-left"></u-icon> 地址
-        </view>
-      </template>
+    <drag-ele>
+      <template #searchBox>
+        <u-input placeholder="搜索" border="surround" v-model="value" @change="change" color="#fff">
+          <template #prefix>
+            <view class="search-left-box">
+              <u-icon color="#fff" name="arrow-left"></u-icon>
 
-      <template #suffix>
-        <view class="search-right-box">
-          <u-icon color="#fff" name="scan"></u-icon>
+              <up-tooltip ref="addressTooltip" text="text5" color="#fff" bgColor="#333" popupBgColor="#333"
+                triggerMode="click" :forcePosition="{right: '0px', top: '0px'}" direction="top">
+                <template #trigger>
+                  <up-button iconColor="transparent" color="transparent" :hairline="false" size="mini" :stop="false"
+                    type="text">{{ searchModeTextType === 0 ? '地址' : '机巢'}}</up-button>
+                </template>
+                <template #content>
+                  <view style="padding: 8rpx 0; text-align: right;">
+                    <!-- 按钮1 -->
+                    <up-button type="text" size="mini" @click="selectAddress"
+                      style="margin: 4rpx 0; width: 100%; text-align: center;">{{ searchModeTextType === 0 ? '机巢' : '地址'}}</up-button>
+                  </view>
+                </template>
+              </up-tooltip>
+            </view>
+          </template>
+
+          <template #suffix>
+            <view class="search-right-box">
+              <u-icon color="#fff" name="scan" @click="scanCode"></u-icon>
+            </view>
+          </template>
+        </u-input>
+
+      </template>
+      <template #content>
+        <view class="search-content">
+
         </view>
       </template>
-    </u-input>
+    </drag-ele>
 
     <up-popup v-model:show="show">
       <view class="popup-container">
@@ -50,6 +79,7 @@
 </template>
 
 <script setup>
+  import DragEle from './drag.vue'
   import {
     useMapStore
   } from "@/store/index.js"
@@ -68,6 +98,7 @@
     }
   ]
 
+  const addressTooltip = ref(null)
   const show = ref(false)
 
   const value = ref('')
@@ -94,13 +125,48 @@
         console.log('定位失败:', err);
       }
     });
-
   }
 
+  const searchModeTextType = ref(0)
+
+  const selectAddress = () => {
+    searchModeTextType.value = searchModeTextType.value === 0 ? 1 : 0
+    addressTooltip.value.showTooltip = false
+  }
+
+  const scanCode = () => {
+    // 只允许通过相机扫码
+    uni.scanCode({
+      onlyFromCamera: true,
+      success: function(res) {
+        console.log('条码类型:' + res.scanType);
+        console.log('条码内容:' + res.result);
+
+        // 获取扫码结果
+        let url = res.result;
+
+        // 跳转到B页面,并传递URL参数
+        uni.navigateTo({
+          url: '/subPackages/browser/index?url=' + encodeURIComponent(url)
+        });
+      },
+      fail: function(err) {
+        console.log('扫码失败:', err);
+        uni.showToast({
+          title: '扫码失败',
+          icon: 'none'
+        });
+      }
+    });
+  }
+
+  const createWorkOrder = (id) => {
+    console.log(`创建工单:标注 ID 为 ${id}`);
+    // 在这里添加工单创建逻辑
+  }
 
   onShow(() => {
     // #ifdef APP-PLUS
-
     // plus.screen.lockOrientation("landscape-primary");
     // #endif
   });
@@ -117,28 +183,28 @@
 
   var basemapLayer0 = L.tileLayer(
     'http://t1.tianditu.com/vec_c/wmts?layer=vec&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', {
-      maxZoom: 18,
+      maxZoom: 20,
       minZoom: 1,
       tileSize: 256,
       zoomOffset: 1
     });
   var basemapLayer1 = L.tileLayer(
     'http://t1.tianditu.com/cva_c/wmts?layer=cva&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', {
-      maxZoom: 18,
+      maxZoom: 20,
       minZoom: 1,
       tileSize: 256,
       zoomOffset: 1
     });
   var basemapLayer2 = L.tileLayer(
     'http://t1.tianditu.com/img_c/wmts?layer=img&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', {
-      maxZoom: 18,
+      maxZoom: 20,
       minZoom: 1,
       tileSize: 256,
       zoomOffset: 1
     });
   var basemapLayer3 = L.tileLayer(
     'http://t1.tianditu.com/cia_c/wmts?layer=cia&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', {
-      maxZoom: 18,
+      maxZoom: 20,
       minZoom: 1,
       tileSize: 256,
       zoomOffset: 1
@@ -165,7 +231,7 @@
   export default {
     data() {
       return {
-        setSelectMapLayerKey: 1
+        curSelectMapLayerKey: 1
       }
     },
 
@@ -175,6 +241,8 @@
 
     mounted() {
       this.initMap()
+
+      this.initDronePositions()
     },
 
     methods: {
@@ -192,15 +260,14 @@
       },
 
       initLayer(value) {
-
         this.initMap()
 
         this.$nextTick(() => {
-          map.removeLayer(layers.find(i => i.key === this.setSelectMapLayerKey).map);
+          map.removeLayer(layers.find(i => i.key === this.curSelectMapLayerKey).map);
 
           map.addLayer(layers.find(i => i.key === value).map);
 
-          this.setSelectMapLayerKey = value
+          this.curSelectMapLayerKey = value
         })
       },
 
@@ -214,6 +281,97 @@
             animate: false // 使用动画过渡
           });
         })
+      },
+
+      // 初始化无人机位置
+      initDronePositions() {
+        // 假设你有一个包含多个标注位置的数组
+        const markersData = [{
+            lat: 25.992338,
+            lng: 114.823254,
+            id: 1
+          },
+          {
+            lat: 26,
+            lng: 114.823255,
+            id: 2
+          },
+          {
+            lat: 25.992338,
+            lng: 115,
+            id: 3
+          },
+        ];
+
+        // 创建一个自定义的图片图标
+        const customIcon = L.icon({
+          iconUrl: './static/images/logo.png', // 替换为你的图片路径
+          iconSize: [32, 32], // 图标大小
+          iconAnchor: [16, 16], // 图标的锚点(设置图片的底部中心)
+        });
+
+        // 批量添加标注
+        const markersLayer = L.layerGroup().addTo(map); // 创建一个标注层,便于管理和移除
+
+        markersData.forEach((data) => {
+          const marker = L.marker([data.lat, data.lng], {
+            icon: customIcon
+          }).addTo(markersLayer);
+
+          marker.on('click', () => {
+            marker.unbindPopup();
+
+            const popupContent = `
+              <view>
+                <button data-type="addWork">创建工单</button>
+              </view>
+             `;
+
+            marker.bindPopup(popupContent).openPopup();
+
+            marker.getPopup()._contentNode.addEventListener("click", e => {
+              if (e.target.dataset.type === 'addWork') {
+                // 跳转到tabBar页面
+                uni.switchTab({
+                  url: '/pages/work/index', // 需要跳转的tabBar页面的路径
+                });
+
+                marker.closePopup();
+                marker.unbindPopup();
+              }
+            })
+          });
+
+          // 使用 marker 的 id 作为标识符
+          marker.options.id = data.id;
+        });
+
+        this.buildCirclePolygon(25.992338, 114.823254, 'rgba(255, 0, 0, 1)').addTo(map)
+      },
+
+      buildCirclePolygon(lat, lng, color) {
+        var radius = 5000 / 100460; // 5000米转为大约的纬度差(纬度上的1度大约是111km)
+        var parts = [];
+
+        for (var i = 0; i < 360; i++) {
+          var radians = (i + 1) * Math.PI / 180;
+
+          // 计算新的点
+          var circlePoint = [
+            Math.cos(radians) * radius + lat, // 纬度偏移
+            Math.sin(radians) * radius + lng // 经度偏移,需考虑纬度的影响
+          ];
+
+          parts[i] = circlePoint;
+        }
+
+        // 生成多边形,近似圆形
+        var polygon = L.polygon(parts, {
+          color: color
+        });
+
+        return polygon;
+
       }
     }
   }
@@ -226,12 +384,9 @@
     height: 100%;
 
     .u-input {
-      position: absolute;
-      left: 16rpx;
-      bottom: 16rpx;
+      margin-left: 16rpx;
       width: calc(100% - 32rpx);
       height: 64rpx;
-      z-index: 999;
       box-sizing: border-box;
       background: rgba(0, 0, 0, .4);
 
@@ -246,6 +401,27 @@
       }
     }
 
+    .search-content {
+      width: 100%;
+      height: 360rpx;
+      background: #fff;
+      box-shadow: 0 -80rpx 80rpx -80rpx #fff;
+    }
+
+    .weather-box {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      position: absolute;
+      top: 16rpx;
+      left: 16rpx;
+      width: 64rpx;
+      height: 64rpx;
+      background: #fff;
+      z-index: 996;
+      border-radius: 8rpx;
+    }
+
     .location-btn,
     .layer-btn {
       display: flex;
@@ -256,16 +432,16 @@
       width: 64rpx;
       height: 64rpx;
       background: #fff;
-      z-index: 999;
+      z-index: 996;
       border-radius: 8rpx;
     }
 
     .layer-btn {
-      bottom: 176rpx;
+      bottom: 236rpx;
     }
 
     .location-btn {
-      bottom: 96rpx;
+      bottom: 156rpx;
     }
   }
 
diff --git a/src/subPackages/browser/index.vue b/src/subPackages/browser/index.vue
new file mode 100644
index 0000000..bd22b80
--- /dev/null
+++ b/src/subPackages/browser/index.vue
@@ -0,0 +1,39 @@
+<!-- 跳转第三方应用 -->
+<template>
+  <view class="content">
+    <!-- WebView组件 -->
+    <web-view :src="webViewUrl"></web-view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      webViewUrl: '' // WebView要加载的URL
+    };
+  },
+  onLoad(options) {
+    // 从导航参数中获取URL
+    if (options.url) {
+      this.webViewUrl = decodeURIComponent(options.url);
+    } else {
+      uni.showToast({
+        title: '未获取到URL',
+        icon: 'none'
+      });
+      // 如果没有URL,可以返回上一页或做其他处理
+      setTimeout(() => {
+        uni.navigateBack();
+      }, 1500);
+    }
+  }
+};
+</script>
+
+<style>
+.content {
+  width: 100%;
+  height: 100%;
+}
+</style>

--
Gitblit v1.9.3