wlzboy
2025-09-26 97db9d11ff425583d2dece82a842a7766bb5e7e4
feat: 添加map
1个文件已添加
5个文件已修改
594 ■■■■■ 已修改文件
app/components/map-selector.vue 409 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/manifest.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/create.vue 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/static/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/task/vehicle/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/components/map-selector.vue
New file
@@ -0,0 +1,409 @@
<template>
  <view class="map-selector-container">
    <view class="search-bar">
      <input
        class="search-input"
        placeholder="请输入地址"
        v-model="searchKeyword"
        @input="onSearchInput"
        @focus="onSearchFocus"
      />
      <button class="search-btn" @click="searchAddress">搜索</button>
    </view>
    <!-- 微信小程序使用原生map组件 -->
    <view class="map-container" id="mapContainer">
      <!-- #ifdef MP-WEIXIN -->
      <map
        id="map"
        :longitude="longitude"
        :latitude="latitude"
        :markers="markers"
        :polyline="polyline"
        :circles="circles"
        :controls="controls"
        :include-points="includePoints"
        :show-location="true"
        @markertap="onMarkerTap"
        @callouttap="onCalloutTap"
        @controltap="onControlTap"
        @regionchange="onRegionChange"
        @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>
  export default {
    name: 'MapSelector',
    props: {
      // 初始地址
      initialAddress: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        searchKeyword: '',
        searchResults: [],
        selectedAddress: null,
        isMapLoaded: false,
        // H5平台相关
        baiduMapUrl: '',
        ak: '您的百度地图AK',
        // 微信小程序相关
        longitude: 113.324520,
        latitude: 23.099994,
        markers: [],
        polyline: [],
        circles: [],
        controls: [],
        includePoints: []
      }
    },
    mounted() {
      // #ifdef H5
      // 初始化百度地图
      this.initBaiduMap()
      // #endif
      // #ifdef MP-WEIXIN
      // 初始化微信小程序地图
      this.initWechatMap()
      // #endif
      if (this.initialAddress) {
        this.searchKeyword = this.initialAddress
        // 延迟设置焦点,避免跨域问题
        setTimeout(() => {
          // 不自动聚焦,让用户手动点击
        }, 500)
      }
    },
    methods: {
      // 搜索框获得焦点事件
      onSearchFocus() {
        // 用户主动聚焦,不会触发跨域问题
        console.log('搜索框获得焦点')
      },
      // #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
        // 延迟加载地图,避免阻塞
        setTimeout(() => {
          this.isMapLoaded = true
        }, 1000)
      },
      // #endif
      // #ifdef MP-WEIXIN
      // 初始化微信小程序地图
      initWechatMap() {
        // 获取用户位置
        uni.getLocation({
          type: 'gcj02',
          success: (res) => {
            this.longitude = res.longitude
            this.latitude = res.latitude
            // 设置默认标记
            this.markers = [{
              id: 0,
              longitude: this.longitude,
              latitude: this.latitude,
              title: '当前位置',
              iconPath: '/static/icons/location.png',
              width: 30,
              height: 30
            }]
            // 延迟设置加载状态,确保地图完全初始化
            setTimeout(() => {
              this.isMapLoaded = true
            }, 500)
          },
          fail: () => {
            // 延迟设置加载状态
            setTimeout(() => {
              this.isMapLoaded = true
            }, 500)
            this.$modal.showToast('获取位置失败')
          }
        })
      },
      // 地图点击事件
      onMapTap(e) {
        // 在点击位置添加标记
        const { longitude, latitude } = e.detail
        this.markers = [{
          id: Date.now(),
          longitude,
          latitude,
          title: '选中位置',
          iconPath: '/static/icons/location-selected.png',
          width: 30,
          height: 30
        }]
        // 逆地址解析获取地址信息
        this.reverseGeocode(latitude, longitude)
      },
      // 逆地址解析
      reverseGeocode(lat, lng) {
        // 这里应该调用后台API进行逆地址解析
        // 模拟数据
        this.selectedAddress = {
          title: '选中位置',
          address: `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
          lat: lat,
          lng: lng
        }
      },
      // #endif
      // 搜索地址
      searchAddress() {
        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
          }
        ]
      },
      // 输入框输入事件
      onSearchInput() {
        // 防抖搜索
      },
      // 选择地址
      selectAddress(item) {
        this.selectedAddress = item
        this.markLocation(item.lat, item.lng)
      },
      // 在地图上标记位置
      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,
          iconPath: '/static/icons/location-selected.png',
          width: 30,
          height: 30
        }]
        // #endif
      },
      // 确认选择地址
      confirmAddress() {
        if (!this.selectedAddress) {
          this.$modal.showToast('请先选择地址')
          return
        }
        // 触发事件,将选中的地址传递给父组件
        this.$emit('addressSelected', {
          title: this.selectedAddress.title,
          address: this.selectedAddress.address,
          lat: this.selectedAddress.lat,
          lng: this.selectedAddress.lng
        })
      }
    }
  }
</script>
<style lang="scss">
  .map-selector-container {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    .search-bar {
      display: flex;
      padding: 20rpx;
      background-color: white;
      .search-input {
        flex: 1;
        height: 70rpx;
        padding: 0 20rpx;
        border: 1rpx solid #eee;
        border-radius: 10rpx;
        font-size: 28rpx;
      }
      .search-btn {
        width: 120rpx;
        height: 70rpx;
        margin-left: 20rpx;
        background-color: #007AFF;
        color: white;
        border-radius: 10rpx;
        font-size: 28rpx;
      }
    }
    .map-container {
      flex: 1;
      height: 400rpx;
      .map-webview {
        width: 100%;
        height: 100%;
      }
      // 微信小程序地图样式
      map {
        width: 100%;
        height: 100%;
      }
      .map-placeholder {
        display: flex;
        align-items: center;
        justify-content: center;
        height: 100%;
        background-color: #f5f5f5;
        font-size: 28rpx;
        color: #999;
      }
    }
    .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;
      .address-title {
        font-size: 30rpx;
        font-weight: bold;
        margin-bottom: 10rpx;
      }
      .address-detail {
        font-size: 26rpx;
        color: #666;
        margin-bottom: 10rpx;
      }
      .confirm-btn {
        width: 100%;
        height: 80rpx;
        background-color: #007AFF;
        color: white;
        border-radius: 10rpx;
        font-size: 32rpx;
        margin-top: 20rpx;
      }
    }
  }
</style>
app/manifest.json
@@ -51,7 +51,14 @@
        "optimization" : {
            "subPackages" : true
        },
        "usingComponents" : true
        "usingComponents" : true,
        "sdkConfigs" : {
            "maps" : {
                "baidu" : {
                    "appkey" : "n5z5pKfAnaP3fYMR4RJOAQsR1wQ2avAn"
                }
            }
        }
    },
    "vueVersion" : "2",
    "h5" : {
@@ -64,6 +71,13 @@
        "router" : {
            "mode" : "hash",
            "base" : "./"
        },
        "sdkConfigs" : {
            "maps" : {
                "baidu" : {
                    "appkey" : "您的百度地图AK"
                }
            }
        }
    }
}
app/pages/login.vue
@@ -3,7 +3,7 @@
    <view class="logo-content align-center justify-center flex">
      <image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
      </image>
      <text class="title">若依移动端登录</text>
      <text class="title">民航调度系统</text>
    </view>
    <view class="login-form-content">
      <view class="input-item flex align-center">
app/pages/task/create.vue
@@ -475,16 +475,37 @@
        </view>
      </view>
    </view>
    <!-- 地图选择器弹窗 -->
    <uni-popup ref="mapPopup" type="bottom" :mask-click="false">
      <view class="map-popup-container">
        <view class="popup-header">
          <view class="popup-title">选择地址</view>
          <view class="close-btn" @click="closeMapSelector">
            <uni-icons type="closeempty" size="20" color="#999"></uni-icons>
          </view>
        </view>
        <map-selector
          :initial-address="getInitialAddress()"
          @addressSelected="onAddressSelected"
        ></map-selector>
      </view>
    </uni-popup>
  </scroll-view>
</template>
<script>
  import { mapState } from 'vuex'
  import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue'
  import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
  import { getUserProfile } from "@/api/system/user"
  import MapSelector from '@/components/map-selector.vue'
  
  export default {
    components: {
      uniDatetimePicker
      uniDatetimePicker,
      uniPopup,
      MapSelector
    },
    data() {
      return {
@@ -492,6 +513,10 @@
        selectedVehicle: '',
        selectedOrganization: '',
        selectedEmergencyTaskType: '',
        boundVehicle: '', // 用户绑定的车辆
        // 地图选择相关
        showMapSelector: false,
        mapSelectorType: '', // 标识当前选择的是哪个地址字段
        taskForm: {
          description: '',
          startLocation: '',
@@ -575,9 +600,34 @@
        })
      })
    },
    onLoad() {
      // 获取用户绑定的车辆信息
      this.getUserBoundVehicle()
    },
    methods: {
      // 获取用户绑定的车辆信息
      getUserBoundVehicle() {
        getUserProfile().then(response => {
          // 这里模拟从用户信息中获取绑定车辆,实际项目中应该从response.data中获取
          // 假设用户信息中有boundVehicle字段
          this.boundVehicle = '粤A12345' // 模拟值,实际应从response.data.boundVehicle获取
          // 如果有绑定车辆,则默认选中
          if (this.boundVehicle) {
            this.selectedVehicle = this.boundVehicle
          }
        }).catch(() => {
          // 获取用户信息失败时使用默认值
          this.boundVehicle = ''
        })
      },
      selectTaskCategory(category) {
        this.selectedTaskCategory = category
        // 当选择任务类型时,如果是普通任务且用户有绑定车辆,则默认选中绑定车辆
        if (category.type === 'normal' && this.boundVehicle) {
          this.selectedVehicle = this.boundVehicle
        }
      },
      
      backToCategory() {
@@ -596,28 +646,94 @@
        this.selectedEmergencyTaskType = this.emergencyTaskTypes[e.detail.value]
      },
      
      // 显示地图选择器 - 任务出发地
      selectStartLocation() {
        this.$modal.showToast('选择出发地功能开发中')
        this.mapSelectorType = 'startLocation'
        this.$refs.mapPopup.open()
      },
      
      // 显示地图选择器 - 任务目的地
      selectEndLocation() {
        this.$modal.showToast('选择目的地功能开发中')
        this.mapSelectorType = 'endLocation'
        this.$refs.mapPopup.open()
      },
      
      // 显示地图选择器 - 转出医院地址
      selectHospitalOutAddress() {
        this.$modal.showToast('选择转出医院地址功能开发中')
        this.mapSelectorType = 'hospitalOutAddress'
        this.$refs.mapPopup.open()
      },
      
      // 显示地图选择器 - 转入医院地址
      selectHospitalInAddress() {
        this.$modal.showToast('选择转入医院地址功能开发中')
        this.mapSelectorType = 'hospitalInAddress'
        this.$refs.mapPopup.open()
      },
      
      // 显示地图选择器 - 福祉车出发地址
      selectStartAddress() {
        this.$modal.showToast('选择出发地址功能开发中')
        this.mapSelectorType = 'startAddress'
        this.$refs.mapPopup.open()
      },
      
      // 显示地图选择器 - 福祉车目的地址
      selectEndAddress() {
        this.$modal.showToast('选择目的地址功能开发中')
        this.mapSelectorType = 'endAddress'
        this.$refs.mapPopup.open()
      },
      // 获取初始地址用于地图搜索
      getInitialAddress() {
        switch (this.mapSelectorType) {
          case 'startLocation':
            return this.taskForm.startLocation
          case 'endLocation':
            return this.taskForm.endLocation
          case 'hospitalOutAddress':
            return this.taskForm.hospitalOut.address
          case 'hospitalInAddress':
            return this.taskForm.hospitalIn.address
          case 'startAddress':
            return this.taskForm.startAddress
          case 'endAddress':
            return this.taskForm.endAddress
          default:
            return ''
        }
      },
      // 地图选择器地址选择回调
      onAddressSelected(address) {
        // 根据不同的地址类型设置对应的表单字段
        switch (this.mapSelectorType) {
          case 'startLocation':
            this.taskForm.startLocation = address.title + ' - ' + address.address
            break
          case 'endLocation':
            this.taskForm.endLocation = address.title + ' - ' + address.address
            break
          case 'hospitalOutAddress':
            this.taskForm.hospitalOut.address = address.title + ' - ' + address.address
            break
          case 'hospitalInAddress':
            this.taskForm.hospitalIn.address = address.title + ' - ' + address.address
            break
          case 'startAddress':
            this.taskForm.startAddress = address.title + ' - ' + address.address
            break
          case 'endAddress':
            this.taskForm.endAddress = address.title + ' - ' + address.address
            break
        }
        // 关闭地图选择器
        this.closeMapSelector()
      },
      // 关闭地图选择器
      closeMapSelector() {
        this.$refs.mapPopup.close()
        this.mapSelectorType = ''
      },
      
      addStaff() {
@@ -852,5 +968,36 @@
        }
      }
    }
    // 地图选择器弹窗样式
    .map-popup-container {
      height: 80vh;
      background-color: white;
      border-top-left-radius: 20rpx;
      border-top-right-radius: 20rpx;
      overflow: hidden;
      .popup-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 20rpx 30rpx;
        border-bottom: 1rpx solid #f0f0f0;
        .popup-title {
          font-size: 32rpx;
          font-weight: bold;
          color: #333;
        }
        .close-btn {
          width: 50rpx;
          height: 50rpx;
          display: flex;
          align-items: center;
          justify-content: center;
        }
      }
    }
  }
</style>
app/static/logo.png

ruoyi-ui/src/views/task/vehicle/index.vue
@@ -33,7 +33,7 @@
        <el-select v-model="queryParams.status" placeholder="请选择关联状态" clearable>
          <el-option
            v-for="dict in dict.type.sys_task_vehicle_status"
            :key="dict.value"
            :key="'search-' + dict.value"
            :label="dict.label"
            :value="dict.value"
          />
@@ -210,7 +210,7 @@
          <el-select v-model="form.status" placeholder="请选择关联状态" style="width: 100%">
            <el-option
              v-for="dict in dict.type.sys_task_vehicle_status"
              :key="dict.value"
              :key="'form-' + dict.value"
              :label="dict.label"
              :value="dict.value"
            ></el-option>
@@ -236,7 +236,7 @@
          <el-select v-model="statusForm.newStatus" placeholder="请选择新状态">
            <el-option
              v-for="dict in dict.type.sys_task_vehicle_status"
              :key="dict.value"
              :key="'status-' + dict.value"
              :label="dict.label"
              :value="dict.value"
            ></el-option>