wlzboy
2025-10-18 b46065a201c09ce69f111806f2bda4a5f476bc4e
app/components/map-selector.vue
@@ -5,15 +5,56 @@
        class="search-input" 
        placeholder="请输入地址" 
        v-model="searchKeyword" 
        @input="onSearchInput"
        @focus="onSearchFocus"
      />
      <button class="search-btn" @click="searchAddress">搜索</button>
      <button class="location-btn" @click="getCurrentLocation" :disabled="isGettingLocation">
        <uni-icons v-if="!isGettingLocation" type="location" size="18" color="white"></uni-icons>
        <uni-icons v-else type="spinner-cycle" size="18" color="white"></uni-icons>
      </button>
    </view>
    
    <!-- 微信小程序使用原生map组件 -->
    <!-- 选中地址界面 - 显示在列表上方 -->
    <view class="selected-address" v-if="selectedAddress">
      <view class="selected-header">
        <uni-icons type="checkmarkempty" size="20" color="#007AFF"></uni-icons>
        <text class="address-title">已选中地址</text>
        <view class="cancel-btn" @click="cancelSelection">
          <uni-icons type="closeempty" size="16" color="#999"></uni-icons>
        </view>
      </view>
      <view class="address-info">
        <view class="address-name">{{ selectedAddress.title }}</view>
        <view class="address-detail">{{ selectedAddress.address }}</view>
      </view>
      <button class="confirm-btn" @click="confirmAddress">
        <uni-icons type="checkmarkempty" size="18" color="white"></uni-icons>
        <text>确认选择此地址</text>
      </button>
    </view>
    <!-- 地址搜索结果列表 -->
    <view class="address-list" v-if="showSearchResults && searchResults.length > 0">
      <view
        class="address-item"
        v-for="(item, index) in searchResults"
        :key="'search-' + index"
        @click="selectAddress(item)"
      >
        <!-- 确保使用插值语法 -->
        <view class="address-title">{{ item.title }}</view>
        <view class="address-detail">{{ item.address }}</view>
      </view>
    </view>
    <!-- 搜索无结果提示 -->
    <view class="no-results" v-if="showSearchResults && searchResults.length === 0 && searchCompleted">
      <text>未找到相关地址</text>
    </view>
    <!-- UniApp原生地图组件,支持多平台 -->
    <view class="map-container" id="mapContainer">
      <!-- #ifdef MP-WEIXIN -->
      <!-- 所有平台统一使用uniapp原生map组件 -->
      <map 
        id="map" 
        :longitude="longitude" 
@@ -24,6 +65,14 @@
        :controls="controls"
        :include-points="includePoints"
        :show-location="true"
        :enable-3D="false"
        :show-compass="false"
        :enable-overlooking="false"
        :enable-zoom="true"
        :enable-scroll="true"
        :enable-rotate="false"
        :enable-satellite="false"
        :enable-traffic="false"
        @markertap="onMarkerTap"
        @callouttap="onCalloutTap"
        @controltap="onControlTap"
@@ -31,45 +80,17 @@
        @tap="onMapTap"
        class="map-webview"
      ></map>
      <!-- #endif -->
      <!-- H5平台使用web-view加载百度地图 -->
      <!-- #ifdef H5 -->
      <web-view
        v-if="baiduMapUrl"
        :src="baiduMapUrl"
        class="map-webview"
        @message="onWebviewMessage"
      ></web-view>
      <!-- #endif -->
      
      <view class="map-placeholder" v-if="!isMapLoaded">
        <text>地图加载中...</text>
      </view>
    </view>
    <view class="address-list" v-if="searchResults.length > 0">
      <view
        class="address-item"
        v-for="(item, index) in searchResults"
        :key="index"
        @click="selectAddress(item)"
      >
        <view class="address-title">{{ item.title }}</view>
        <view class="address-detail">{{ item.address }}</view>
      </view>
    </view>
    <view class="selected-address" v-if="selectedAddress">
      <view class="address-title">选中地址:</view>
      <view class="address-detail">{{ selectedAddress.title }}</view>
      <view class="address-detail">{{ selectedAddress.address }}</view>
      <button class="confirm-btn" @click="confirmAddress">确认选择</button>
    </view>
  </view>
</template>
<script>
  import { searchAddress, reverseGeocoder } from '@/api/map'
  export default {
    name: 'MapSelector',
    props: {
@@ -82,75 +103,149 @@
    data() {
      return {
        searchKeyword: '',
        searchResults: [],
        searchResults: [], // 确保这是响应式数据
        selectedAddress: null,
        isMapLoaded: false,
        // H5平台相关
        baiduMapUrl: '',
        ak: '您的百度地图AK',
        // 微信小程序相关
        showSearchResults: false, // 控制是否显示搜索结果
        searchCompleted: false, // 标记搜索是否完成
        showDebug: true, // 调试开关
        // 地图相关
        longitude: 113.324520,
        latitude: 23.099994,
        markers: [],
        polyline: [],
        circles: [],
        controls: [],
        includePoints: []
        includePoints: [],
        isGettingLocation: false, // 是否正在获取位置
        // 防止自动聚焦的标志
        shouldPreventAutoFocus: true
      }
    },
    mounted() {
      // #ifdef H5
      // 初始化百度地图
      this.initBaiduMap()
      // #endif
      console.log('地图选择器组件已挂载');
      
      // #ifdef MP-WEIXIN
      // 初始化微信小程序地图
      this.initWechatMap()
      // #endif
      // 检查API Key是否已配置
      if (!this.isTencentMapKeyConfigured()) {
        console.error('[system] Map key not configured.')
        this.$modal.showToast('地图API Key未配置,请联系管理员')
        return
      }
      
      // 初始化地图
      this.initMap()
      // 延迟处理初始地址,避免跨域问题
      if (this.initialAddress) {
        this.searchKeyword = this.initialAddress
        // 延迟设置焦点,避免跨域问题
        setTimeout(() => {
          // 不自动聚焦,让用户手动点击
        }, 500)
        // 不再尝试自动聚焦,避免跨域问题
        // 用户需要手动点击输入框才能聚焦
      }
      // 延迟一段时间后允许自动操作(如果需要的话)
      setTimeout(() => {
        this.shouldPreventAutoFocus = false
      }, 1000)
    },
    methods: {
      // 检查腾讯地图API Key是否已配置
      isTencentMapKeyConfigured() {
        // 对于后端代理方式,前端不再需要检查Key配置
        return true
      },
      // 搜索框获得焦点事件
      onSearchFocus() {
        // 用户主动聚焦,不会触发跨域问题
        console.log('搜索框获得焦点')
        // 重置防聚焦标志
        this.shouldPreventAutoFocus = false
      },
      
      // #ifdef H5
      // 处理web-view消息
      onWebviewMessage(e) {
        // 处理来自web-view的消息
        console.log('收到来自web-view的消息:', e)
      },
      // 初始化百度地图(H5平台)
      initBaiduMap() {
        // 构造百度地图URL
        this.baiduMapUrl = `https://api.map.baidu.com/mapjs?v=3.0&ak=${this.ak}&callback=initMap`
        this.isMapLoaded = true
      // 获取当前位置
      getCurrentLocation() {
        if (this.isGettingLocation) {
          return
        }
        
        // 延迟加载地图,避免阻塞
        setTimeout(() => {
          this.isMapLoaded = true
        }, 1000)
        this.isGettingLocation = true
        this.$modal.showToast('正在获取当前位置...')
        uni.getLocation({
          type: 'gcj02',
          success: (res) => {
            console.log('获取位置成功:', res)
            this.handleLocationSuccess(res.longitude, res.latitude)
            this.isGettingLocation = false
          },
          fail: (error) => {
            console.error('获取位置失败:', error)
            this.$modal.showToast('获取位置失败,请检查定位权限')
            this.isGettingLocation = false
          }
        })
      },
      // #endif
      
      // #ifdef MP-WEIXIN
      // 初始化微信小程序地图
      initWechatMap() {
      // 处理位置获取成功
      handleLocationSuccess(longitude, latitude) {
        // 参数验证
        if (longitude === undefined || longitude === null || latitude === undefined || latitude === null) {
          console.error('位置获取失败,参数不完整', { longitude, latitude });
          this.$modal.showToast('位置获取失败');
          return;
        }
        // 检查参数有效性
        if (isNaN(longitude) || isNaN(latitude)) {
          console.error('位置获取失败,坐标包含非法值', { longitude, latitude });
          this.$modal.showToast('位置获取失败,坐标无效');
          return;
        }
        this.longitude = longitude
        this.latitude = latitude
        // 设置标记
        this.markers = [{
          id: Date.now(),
          longitude: longitude,
          latitude: latitude,
          title: '当前位置',
          iconPath: '/static/icons/location.png',
          width: 30,
          height: 30
        }]
        // 逆地址解析
        this.reverseGeocode(latitude, longitude, true)
        this.$modal.showToast('已定位到当前位置')
        this.isMapLoaded = true
      },
      // 初始化地图
      initMap() {
        // 获取用户位置
        uni.getLocation({
          type: 'gcj02',
          success: (res) => {
            // 参数验证
            if (res.longitude === undefined || res.longitude === null ||
                res.latitude === undefined || res.latitude === null) {
              console.error('初始化地图时位置获取失败,参数不完整', res);
              this.$modal.showToast('位置获取失败');
              this.isMapLoaded = true;
              return;
            }
            // 检查参数有效性
            if (isNaN(res.longitude) || isNaN(res.latitude)) {
              console.error('初始化地图时位置获取失败,坐标包含非法值', res);
              this.$modal.showToast('位置获取失败,坐标无效');
              this.isMapLoaded = true;
              return;
            }
            this.longitude = res.longitude
            this.latitude = res.latitude
            
@@ -165,16 +260,12 @@
              height: 30
            }]
            
            // 延迟设置加载状态,确保地图完全初始化
            setTimeout(() => {
              this.isMapLoaded = true
            }, 500)
            // 设置加载状态
            this.isMapLoaded = true
          },
          fail: () => {
            // 延迟设置加载状态
            setTimeout(() => {
              this.isMapLoaded = true
            }, 500)
            // 设置加载状态
            this.isMapLoaded = true
            this.$modal.showToast('获取位置失败')
          }
        })
@@ -184,6 +275,14 @@
      onMapTap(e) {
        // 在点击位置添加标记
        const { longitude, latitude } = e.detail
        // 参数验证
        if (longitude === undefined || longitude === null || latitude === undefined || latitude === null) {
          console.error('地图点击事件参数不完整', { longitude, latitude });
          this.$modal.showToast('位置信息获取失败');
          return;
        }
        this.markers = [{
          id: Date.now(),
          longitude,
@@ -199,84 +298,148 @@
      },
      
      // 逆地址解析
      reverseGeocode(lat, lng) {
        // 这里应该调用后台API进行逆地址解析
        // 模拟数据
        this.selectedAddress = {
          title: '选中位置',
          address: `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
          lat: lat,
          lng: lng
      reverseGeocode(lat, lng, isCurrentLocation = false) {
        // 参数验证
        if (lat === undefined || lat === null || lng === undefined || lng === null) {
          console.error('逆地址解析参数不完整,缺少经纬度坐标', { lat, lng });
          this.$modal.showToast('参数错误:缺少经纬度坐标');
          return;
        }
        // 检查参数有效性
        if (isNaN(lat) || isNaN(lng)) {
          console.error('逆地址解析参数无效,坐标包含非法值', { lat, lng });
          this.$modal.showToast('参数错误:坐标格式无效');
          return;
        }
        // 使用后端代理接口进行逆地址解析
        reverseGeocoder(lat, lng).then(res => {
          if (res.code === 200) {
            // 解析后端返回的腾讯地图API响应
            const responseData = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
            if (responseData && responseData.status === 0 && responseData.result) {
              const result = responseData.result
              this.selectedAddress = {
                title: isCurrentLocation ? '当前位置' : (result.address || '选中位置'),
                address: result.address || `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
                lat: lat,
                lng: lng
              }
            } else {
              this.selectedAddress = {
                title: isCurrentLocation ? '当前位置' : '选中位置',
                address: `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
                lat: lat,
                lng: lng
              }
            }
          } else {
            this.selectedAddress = {
              title: isCurrentLocation ? '当前位置' : '选中位置',
              address: `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
              lat: lat,
              lng: lng
            }
          }
        }).catch(error => {
          console.error('逆地址解析失败:', error)
          this.$modal.showToast('地址解析失败:' + (error.message || '请稍后重试'))
          this.selectedAddress = {
            title: isCurrentLocation ? '当前位置' : '选中位置',
            address: `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
            lat: lat,
            lng: lng
          }
        })
      },
      // #endif
      
      // 搜索地址
      // 搜索地址 - 只在点击搜索按钮时调用
      searchAddress() {
        console.log('执行搜索,关键词:', this.searchKeyword);
        if (!this.searchKeyword) {
          this.$modal.showToast('请输入地址')
          return
        }
        
        // 在实际项目中,这里应该调用对应平台的地图API
        // 例如H5平台调用百度地图API,微信小程序调用微信地图API
        // 模拟搜索结果
        this.searchResults = [
          {
            title: this.searchKeyword + '附近地点1',
            address: '广东省广州市天河区' + this.searchKeyword + '123号',
            lat: 23.123 + Math.random() * 0.1,
            lng: 113.321 + Math.random() * 0.1
          },
          {
            title: this.searchKeyword + '附近地点2',
            address: '广东省广州市越秀区' + this.searchKeyword + '456号',
            lat: 23.145 + Math.random() * 0.1,
            lng: 113.289 + Math.random() * 0.1
          },
          {
            title: this.searchKeyword + '附近地点3',
            address: '广东省广州市白云区' + this.searchKeyword + '789号',
            lat: 23.167 + Math.random() * 0.1,
            lng: 113.345 + Math.random() * 0.1
          }
        ]
        // 使用后端代理接口进行地址搜索
        this.searchAddressViaBackend()
      },
      
      // 输入框输入事件
      onSearchInput() {
        // 防抖搜索
      // 通过后端代理搜索地址
      searchAddressViaBackend() {
        // 显示搜索结果区域
        this.showSearchResults = true;
        this.searchCompleted = false;
        console.log('开始搜索地址:', this.searchKeyword);
        searchAddress(this.searchKeyword, '广州').then(res => {
          console.log('地址搜索返回结果:', res);
          // 确保正确更新搜索结果
          this.searchResults = [];
          if (res.code === 200) {
            // 解析后端返回的腾讯地图API响应
            const responseData = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
            console.log('解析后的响应数据:', responseData);
            if (responseData && responseData.status === 0 && responseData.data && responseData.data.length > 0) {
              // 直接赋值数组,确保响应式更新
              this.searchResults = responseData.data.map(item => ({
                title: item.title,
                address: item.address,
                lat: item.location.lat,
                lng: item.location.lng
              }));
              console.log('处理后的搜索结果:', this.searchResults);
              // 添加调试信息
              console.log('搜索结果数量:', this.searchResults.length);
              console.log('showSearchResults:', this.showSearchResults);
              console.log('v-if条件结果:', this.showSearchResults && this.searchResults.length > 0);
            } else {
              console.log('未找到相关地址');
              // 即使没有数据也要显示"未找到相关地址"
              this.searchCompleted = true;
            }
          } else {
            console.log('搜索失败,错误代码:', res.code);
            this.$modal.showToast('搜索失败,请重试')
          }
          // 标记搜索完成
          this.searchCompleted = true;
        }).catch(error => {
          console.error('搜索失败:', error)
          this.$modal.showToast('搜索失败,请重试')
          // 标记搜索完成
          this.searchCompleted = true;
        })
      },
      
      // 选择地址
      selectAddress(item) {
        this.selectedAddress = item
        this.markLocation(item.lat, item.lng)
        // 选中地址后,保持搜索结果列表显示
        console.log('地址已选中:', item);
      },
      
      // 在地图上标记位置
      markLocation(lat, lng) {
        // #ifdef H5
        // H5平台标记位置逻辑
        console.log(`H5平台标记位置: ${lat}, ${lng}`)
        this.$modal.showToast(`已标记位置`)
        // #endif
        // #ifdef MP-WEIXIN
        // 微信小程序标记位置逻辑
        this.longitude = lng
        this.latitude = lat
        this.markers = [{
          id: Date.now(),
          longitude: lng,
          latitude: lat,
          title: item.title,
          title: this.selectedAddress?.title || '选中位置',
          iconPath: '/static/icons/location-selected.png',
          width: 30,
          height: 30
        }]
        // #endif
      },
      
      // 确认选择地址
@@ -286,6 +449,9 @@
          return
        }
        
        // 显示确认提示
        this.$modal.showToast('地址选择成功')
        // 触发事件,将选中的地址传递给父组件
        this.$emit('addressSelected', {
          title: this.selectedAddress.title,
@@ -293,6 +459,21 @@
          lat: this.selectedAddress.lat,
          lng: this.selectedAddress.lng
        })
        // 确认后隐藏搜索结果和选中地址界面
        setTimeout(() => {
          this.selectedAddress = null
          this.showSearchResults = false
          this.searchResults = []
        }, 1000)
      },
      // 取消选择地址
      cancelSelection() {
        this.selectedAddress = null
        // 只清空选中地址,不清空搜索结果
        // this.showSearchResults = false
        // this.searchResults = []
      }
    }
  }
@@ -301,14 +482,17 @@
<style lang="scss">
  .map-selector-container {
    width: 100%;
    height: 100%;
    height: 100vh;
    display: flex;
    flex-direction: column;
    
    .search-bar {
      display: flex;
      padding: 20rpx;
      padding: 15rpx 20rpx;
      background-color: white;
      flex-shrink: 0;
      position: relative;
      z-index: 100; /* 提高层级 */
      
      .search-input {
        flex: 1;
@@ -328,18 +512,136 @@
        border-radius: 10rpx;
        font-size: 28rpx;
      }
      .location-btn {
        width: 70rpx;
        height: 70rpx;
        margin-left: 10rpx;
        background-color: #1AAD19;
        color: white;
        border-radius: 10rpx;
        display: flex;
        align-items: center;
        justify-content: center;
        &:active {
          background-color: #179B16;
        }
        &[disabled] {
          background-color: #ccc;
          opacity: 0.6;
        }
      }
    }
    .debug-panel {
      padding: 15rpx 20rpx;
      background-color: #fff3cd;
      border: 1rpx solid #ffeaa7;
      z-index: 100; /* 提高层级 */
      position: relative;
      .debug-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 10rpx;
        .debug-title {
          font-weight: bold;
          color: #856404;
        }
        .debug-toggle {
          font-size: 24rpx;
          padding: 5rpx 10rpx;
          background-color: #ffc107;
          border: none;
          border-radius: 5rpx;
        }
      }
      .debug-content {
        font-size: 24rpx;
        color: #856404;
        text {
          display: block;
          margin-bottom: 5rpx;
          word-break: break-all;
        }
      }
    }
    /* 地址搜索结果列表 - 调整位置 */
    .address-list {
      max-height: 40vh;
      min-height: 200rpx;
      overflow-y: auto;
      background-color: white;
      position: absolute;
      top: 300rpx; /* 在选中地址界面下方显示 */
      left: 0;
      right: 0;
      z-index: 90; /* 确保在地图上方 */
      box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
      .address-item {
        padding: 25rpx 30rpx;
        border-bottom: 1rpx solid #f0f0f0;
        min-height: 100rpx;
        display: flex;
        flex-direction: column;
        justify-content: center;
        .address-title {
          font-size: 32rpx;
          font-weight: bold;
          margin-bottom: 8rpx;
          color: #333;
          line-height: 1.3;
        }
        .address-detail {
          font-size: 28rpx;
          color: #666;
          line-height: 1.4;
        }
      }
    }
    /* 无结果提示 - 调整位置 */
    .no-results {
      padding: 30rpx;
      text-align: center;
      background-color: white;
      color: #999;
      font-size: 28rpx;
      position: absolute;
      top: 150rpx; /* 调整位置 */
      left: 0;
      right: 0;
      z-index: 90; /* 确保在地图上方 */
      box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
      min-height: 200rpx;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .map-container {
      flex: 1;
      height: 400rpx;
      min-height: 50vh;
      max-height: 70vh;
      position: relative;
      
      .map-webview {
        width: 100%;
        height: 100%;
      }
      
      // 微信小程序地图样式
      // 地图样式
      map {
        width: 100%;
        height: 100%;
@@ -356,43 +658,65 @@
      }
    }
    
    .address-list {
      max-height: 300rpx;
      overflow-y: auto;
      background-color: white;
      .address-item {
        padding: 20rpx 30rpx;
        border-bottom: 1rpx solid #f0f0f0;
        .address-title {
          font-size: 30rpx;
          font-weight: bold;
          margin-bottom: 10rpx;
        }
        .address-detail {
          font-size: 26rpx;
          color: #666;
        }
      }
    }
    .selected-address {
      padding: 20rpx 30rpx;
      background-color: white;
      border-top: 1rpx solid #f0f0f0;
      flex-shrink: 0;
      position: absolute;
      top: 150rpx; /* 在搜索栏下方显示 */
      left: 0;
      right: 0;
      z-index: 95; /* 确保在地图上方 */
      box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
      
      .address-title {
        font-size: 30rpx;
        font-weight: bold;
        margin-bottom: 10rpx;
      .selected-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 15rpx;
        .address-title {
          font-size: 28rpx;
          font-weight: bold;
          color: #007AFF;
          margin-left: 10rpx;
        }
        .cancel-btn {
          width: 40rpx;
          height: 40rpx;
          display: flex;
          align-items: center;
          justify-content: center;
          border-radius: 50%;
          background-color: #f5f5f5;
          &:active {
            background-color: #e0e0e0;
          }
        }
      }
      
      .address-detail {
        font-size: 26rpx;
        color: #666;
        margin-bottom: 10rpx;
      .address-info {
        background-color: #f8f9fa;
        padding: 25rpx;
        border-radius: 12rpx;
        margin-bottom: 20rpx;
        .address-name {
          font-size: 32rpx;
          font-weight: bold;
          color: #333;
          margin-bottom: 10rpx;
          line-height: 1.3;
        }
        .address-detail {
          font-size: 28rpx;
          color: #666;
          line-height: 1.5;
        }
      }
      
      .confirm-btn {
@@ -402,7 +726,17 @@
        color: white;
        border-radius: 10rpx;
        font-size: 32rpx;
        margin-top: 20rpx;
        display: flex;
        align-items: center;
        justify-content: center;
        text {
          margin-left: 10rpx;
        }
        &:active {
          background-color: #0056CC;
        }
      }
    }
  }