地图实例全局唯一化,在上一篇文章中,有需要的可自行查看

地图实例唯一化

先上图!!!

要实现以上功能,请看下面的代码,这个是一个map的子组件,直接在父组件引用就行,里面涉及到的图片可以自行去网上下载,很多的

<script setup lang="ts">
import {FormInstance} from "element-plus";
import StartImg from "@/static/images/start_icon.png"
import EndImg from "@/static/images/end_icon.png"
import WayPointImg from "@/static/images/wayPoint.png"
import {AddLocation, CircleCloseFilled} from "@element-plus/icons-vue";

/**
 * 线路规划
 */
interface MapApiKey {
  lng: number;
  lat: number;
}
interface AddFormVo {
  lineName:string,
  startAddr:string,
  startAddrText:string,
  startAddrCode:any[],
  endAddr:string,
  endAddrText:string,
  endAddrCode:any[],
  passAreaCode:string,
  lineWight:string,
  lineRange:string,
  lineColor:string,
}

const props = defineProps({
  dialogVisible: {
    type: Boolean,
    default: false
  },
  openType:{
    type:String,
    default:'add'
  },
  detailInfo:{
    type:Object,
    default:() => {
      return {
        startAddrCode:[],
        endAddrCode:[],
        passAreaCode:'',
        lineColor:'',
      }
    }
  }
})
const emit = defineEmits(['update:dialogVisible','success'])
const dialogVisible = computed({
  get: () => props.dialogVisible,
  set: (val) => emit('update:dialogVisible', val)
})
const handleClose = (done) => {
  destroyMap();
  emit('update:dialogVisible', false)
  done();
}

const mapRef = ref() // 对应地图渲染的 ref 元素 <div ref="mapRef" />
const markerData = ref<any[]>([]);
const aMapPromise = inject('aMap') as Promise<unknown>;
const map = ref();
const form = reactive<MapApiKey>({
  lng: 116.401889,
  lat: 39.923154,
})
const waypointsArr = ref<any[]>([]);
const addFormRef = ref<FormInstance>();
const addForm = reactive<AddFormVo>({
  lineName:'',
  startAddr:'',
  startAddrText:'',
  startAddrCode:[],
  endAddr:'',
  endAddrText:'',
  endAddrCode:[],
  passAreaCode:'',
  lineWight:'',
  lineRange:'',
  lineColor:'#578bff',
})
const addFormRules = ref({
  lineName:[
    { required: true, message: '请输入线路名称', trigger: 'blur' },
  ],
  startAddr:[
    { required: true, message: '请输入起点搜索', trigger: 'change' },
  ],
  endAddr:[
    { required: true, message: '请输入终点搜索', trigger: 'change' },
  ],
  lineColor:[
    { required: true, message: '请选择线路颜色', trigger: 'change' },
  ]
})
const startLoading = ref(false);
const startAddrOpt = ref<any[]>([]);
const endLoading = ref(false);
const endAddrOpt = ref<any[]>([]);
const mapLoading = ref(false);

watch(() => props.dialogVisible, (val) => {
  if(val){
    initMapView()
  }
})

//创建地图
const initMapView =  () => {
  mapLoading.value = true;
  aMapPromise.then((AMap:any) => {
    map.value = new AMap.Map(mapRef.value, {
      viewMode: '3D', // 是否为3D地图模式
      zoom: 11, // 初始化地图级别
      center: [form.lng, form.lat],
      resizeEnable: true
    });
    //开启定位,由于众多浏览器已不再支持非安全域的定位请求,为保位成功率和精度,请升级您的站点到HTTPS。
    let geolocation = new AMap.Geolocation({
      enableHighAccuracy: true,//是否使用高精度定位,默认:true
      timeout: 10000,          //超过10秒后停止定位,默认:5s
      buttonPosition:'RB',    //定位按钮的停靠位置
      buttonOffset: new AMap.Pixel(10, 20),//定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
      zoomToAccuracy: true,   //定位成功后是否自动调整地图视野到定位点

    });
    map.value.addControl(geolocation);
    mapLoading.value = false;
    geolocation.getCurrentPosition(function(status,result){
      if(status=='complete'){
        console.log(result,'定位成功!');
      }else{
        console.log(result,'定位失败!');
      }
    });
    //查看详情时,渲染图层
    if(props.openType === 'detail'){
      addForm.startAddrCode = props.detailInfo.startAddrCode;
      addForm.endAddrCode = props.detailInfo.endAddrCode;
      addForm.lineColor = props.detailInfo.lineColor;
      props.detailInfo.passAreaCode.split(';').forEach((item:any) => {
        waypointsArr.value.push({
          lng:item.split(',')[0],
          lat:item.split(',')[1]
        })
      })
      repeatDrawMap();
    }
  })
}

//删除地图
const destroyMap = () => {
  Object.keys(addForm).forEach(key => {
    if(key === 'lineColor'){
      addForm[key] = "#578bff";
    }else{
      addForm[key] = "";
    }
    //删除动态添加的途经点
    if(key.indexOf('wayPoint') > -1){
      delete addForm[key];
    }
  })
  startAddrOpt.value = [];
  endAddrOpt.value = [];
  waypointsArr.value = [];
  addFormRef.value?.resetFields();
  map.value && map.value.destroy();
}

//重新绘制
const repeatDrawMap = () => {
  map.value.clearMap();
  markerData.value = [];
  if(addForm.startAddrCode.length > 0){
    addMarkerFn(addForm.startAddrCode,'start');
  }
  if(addForm.endAddrCode.length > 0){
    addMarkerFn(addForm.endAddrCode,'end');
  }
  waypointsArr.value.forEach((val,idx) => {
    addMarkerFn([val.lng,val.lat],'wayPoint'+idx);
  })
  if(addForm.startAddrCode.length>0 && addForm.endAddrCode.length>0){
    drawLineFn(addForm.startAddrCode,addForm.endAddrCode,waypointsArr.value)
  }
}

// 实例化点标记
function addMarkerFn(code:any[],type:string) {
  aMapPromise.then((AMap:any) => {
    let _icon = null;
    let isDrag = false; //标记点是否拖拽
    if(type === 'start'){
      _icon = StartImg;
    }else if(type === 'end'){
      _icon = EndImg;
    }else if(type.indexOf('wayPoint') > -1){
      isDrag = true;
      _icon = WayPointImg;
    }
    const Icon = new AMap.Icon({
      size: new AMap.Size(50, 68), //图标尺寸
      image: _icon, //Icon 的图像
      imageOffset: new AMap.Pixel(-5, 0), //图像相对展示区域的偏移量,适于雪碧图等
      imageSize: new AMap.Size(50, 68), //根据所设置的大小拉伸或压缩图片
    });
    let _maker:any = new AMap.Marker({
      icon:Icon,
      position: code,
      map:map.value,
      offset: new AMap.Pixel(-13, -30),
      // 设置是否可以拖拽
      draggable: isDrag,
      raiseOnDrag:isDrag
    });
    _maker['type2'] = type;
    if(isDrag){
      _maker.on("dragend",async function (result: any) {
        const _type = result.target.type2;
        const _data = result.lnglat;
        const _ret:any = await getAddrFn([_data.lng, _data.lat]);
        waypointsArr.value.forEach((val, idx) => {
          let _idx = _type.split('t')[1];
          if (Number(_idx) === idx) {
            addForm['wayPoint'+idx] = _ret.addr;
            val.lng = _data.lng;
            val.lat = _data.lat;
          }
        })
        repeatDrawMap();
      })
    }
    map.value.setFitView();
    map.value.add(_maker);
    markerData.value.push({
      type:type,
      marker:_maker
    })
  })
}

//删除标记点
function delMarkerFn(type:string) {
  markerData.value.forEach((item,index) => {
    if(item.type === type){
      map.value.remove(item.marker);
      markerData.value.splice(index,1);
    }
  })
}

//地址转坐标
async function getAddrCodeFn(addr: string, code:string): Promise<{ lng: number; lat: number } | null> {
  try {
    return new Promise((resolve, reject) => {
      aMapPromise.then((AMap:any) => {
        const geocoder = new AMap.Geocoder({
          city: code // 城市设为北京
        });

        geocoder.getLocation(addr, (status: string, result: any) => {
          if (status === 'complete' && result.geocodes && result.geocodes.length) {
            const location = result.geocodes[0].location;
            resolve({
              lng: parseFloat(location.lng),
              lat: parseFloat(location.lat)
            });
          } else {
            ElMessage.error('根据地址查询位置失败');
            reject(new Error('根据地址查询位置失败'));
          }
        });
      })
    });
  } catch (error) {
    console.error('AMap加载或地理编码错误:', error);
    ElMessage.error('地图服务初始化失败');
    return null;
  }
}

//坐标转地址
async function getAddrFn(adCode:any[]): Promise<{ addr: string } | null>  {
  try {
    return new Promise((resolve, reject) => {
      aMapPromise.then((AMap:any) => {
        const geocoder = new AMap.Geocoder({
          city: "010", //城市设为北京,默认:“全国”
          radius: 1000 //范围,默认:500
        });
        geocoder.getAddress(adCode, function(status, result) {
          if (status === 'complete'&&result.regeocode) {
            const address = result.regeocode.formattedAddress;
            resolve({
              addr:address
            })
          }else{
            ElMessage.error('根据经纬度查询地址失败');
            reject(new Error('根据地址查询位置失败'));
          }
        });
      })
    });
  } catch (error) {
    console.error('AMap加载或地理编码错误:', error);
    ElMessage.error('地图服务初始化失败');
    return null;
  }
}

//根据起始坐标画线
function drawLineFn(start:any,end:any, wayPoint?:any[]) {
  aMapPromise.then((AMap:any) => {
    let wayPointOpt:any[] = [];
    //判断是否含有途经点
    if(wayPoint && wayPoint.length > 0){
      wayPoint.forEach(item => {
        wayPointOpt.push(new AMap.LngLat(item.lng, item.lat))
      })
    }
    //构造路线导航类
    const driving = new AMap.Driving({
      policy: AMap.DrivingPolicy.LEAST_TIME,
      ferry: 1, // 是否可以使用轮渡
      province: '京', // 车牌省份的汉字缩写
    });
    // 根据起终点经纬度规划驾车导航路线
    driving.search(new AMap.LngLat(start[0], start[1]), new AMap.LngLat(end[0], end[1]),{
      waypoints:wayPointOpt
    }, function(status:any, result:any) {
      if (status === 'complete') {
        if (result.routes && result.routes.length) {
          // 绘制第一条路线,也可以按需求绘制其它几条路线
          let path = parseRouteToPath(result.routes[0]);
          addForm.lineRange = String((result.routes[0].distance/1000).toFixed(2));
          let routeLine = new AMap.Polyline({
            path: path,
            isOutline: true,
            outlineColor: '#ffeeee',
            borderWeight: 2,
            strokeWeight: 5,
            strokeColor: addForm.lineColor,
            strokeOpacity:'1',
            lineJoin: 'round'
          })
          // 调整视野达到最佳显示区域
          routeLine.setMap(map.value)
          map.value.setFitView([ markerData.value[0].marker, markerData.value[1].marker, routeLine ])
        }
      } else {
        console.log('获取驾车数据失败:' + result)
      }
    });
  })
}

// 解析DrivingRoute对象,构造成AMap.Polyline的path参数需要的格式
function parseRouteToPath(route:any) {
  let path:any[] = []
  for (let i = 0, l = route.steps.length; i < l; i++) {
    let step = route.steps[i]
    for (let j = 0, n = step.path.length; j < n; j++) {
      path.push(step.path[j])
    }
  }
  return path
}


//起点搜索事件
const handleStartAddrSearch = (val:string) => {
  if (val) {
    startLoading.value = true;
    aMapPromise.then((AMap:any) => {
      // 实例化Autocomplete
      let autoOptions = {
        city: '全国'
      }
      let autoComplete = new AMap.Autocomplete(autoOptions);
      autoComplete.search(val, function(status:any, result:any) {
        // 搜索成功时,result即是对应的匹配数据
        if(status === 'complete'){
          startLoading.value = false;
          startAddrOpt.value = result.tips.map((item:any) => {
            return {
              value:item.id || String(Math.random()*100),
              label:item.name,
              adCode:item.adcode,
            }
          })
        }
      })
    })
  }else{
    startAddrOpt.value = [];
  }
}
//起点选中事件
const handleStartAddrChange = async (val:any) => {
  let _addr:any = {};
  startAddrOpt.value.forEach(item => {
    if(item.value === val){
      _addr = item.label;
      _addr = {addr:item.label,code:item.adCode}
      addForm.startAddrText = item.label;
    }
  })
  const result = await getAddrCodeFn(_addr.addr,_addr.code);
  if (result) {
    addForm.startAddrCode = [result.lng,result.lat];
    if(addForm.endAddrCode.length > 0){
      repeatDrawMap()
    }else{
      delMarkerFn('start');
      addMarkerFn([result.lng,result.lat],'start');
    }
  }
}

//途经点添加事件
const handleWayPointClick = () => {
  if(addForm.startAddrCode.length === 0 || addForm.endAddrCode.length === 0){
    ElMessage.error("请先选择起点和终点!");
    return false;
  }
  addForm['wayPoint'+waypointsArr.value.length] = "";
  let _center = map.value.getCenter()
  waypointsArr.value.push({
    lng:_center.lng,
    lat:_center.lat,
  });
}
const handleAddPoint = async (idx: any) => {
  const _ret:any = await getAddrFn([waypointsArr.value[idx].lng, waypointsArr.value[idx].lat]);
  addForm['wayPoint'+idx] = _ret.addr;
  repeatDrawMap()
}
const handleRemoveWayPoint = (idx:any) => {
  waypointsArr.value.forEach((item,index) => {
    if(index === idx) {
      waypointsArr.value.splice(index,1);
    }
  })
  repeatDrawMap()
}

//终点搜索事件
const handleEndAddrSearch = (val:string) => {
  if (val) {
    endLoading.value = true;
    aMapPromise.then((AMap:any) => {
      // 实例化Autocomplete
      let autoOptions = {
        city: '全国'
      }
      let autoComplete = new AMap.Autocomplete(autoOptions);
      autoComplete.search(val, function(status:any, result:any) {
        // 搜索成功时,result即是对应的匹配数据
        if(status === 'complete'){
          endLoading.value = false;
          endAddrOpt.value = result.tips.map((item:any) => {
            return {
              value:item.id || String(Math.random()*100),
              label:item.name,
              adCode:item.adcode,
            }
          })
        }
      })
    })
  }else{
    endAddrOpt.value = [];
  }
}
//终点选中事件
const handleEndAddrChange = async (val:any) => {
  let _addr:any = {};
  endAddrOpt.value.forEach(item => {
    if(item.value === val){
      _addr = item.label;
      _addr = {addr:item.label,code:item.adCode};
      addForm.endAddrText = item.label;
    }
  })
  const result = await getAddrCodeFn(_addr.addr,_addr.code);
  if (result) {
    addForm.endAddrCode = [result.lng,result.lat];
    if(addForm.startAddrCode.length > 0){
      repeatDrawMap()
    }else{
      delMarkerFn('end');
      addMarkerFn([result.lng,result.lat],'end');
    }
  }
}

//颜色选中事件
const handleLineColorChange = (val:string) => {
  repeatDrawMap()
}

//保存
const handleSubmit = () => {
  addFormRef.value?.validate(valid => {
    if(valid){
      console.log(addForm,'打印提交的数据');
    }
  })
}
const handleCancel = () => {
  dialogVisible.value = false;
  destroyMap();
}

</script>

<template>
  <el-dialog
    v-model="dialogVisible"
    :title="openType === 'add'?'新增线路':'查看线路'"
    width="80%"
    top="5vh"
    :before-close="handleClose"
  >
    <div class="map_wrap" v-loading="mapLoading" element-loading-text="加载中...">
      <div class="map_search" v-show="openType === 'add'">
        <el-form
          ref="addFormRef"
          :model="addForm"
          :rules="addFormRules"
          label-width="auto"
        >
          <el-form-item label="线路名称" prop="lineName">
            <el-input v-model="addForm.lineName" placeholder="请输入线路名称" />
          </el-form-item>
          <el-form-item label="起点" prop="startAddr">
            <el-select
              v-model="addForm.startAddr"
              filterable
              remote
              reserve-keyword
              placeholder="请输入起点搜索"
              :remote-method="handleStartAddrSearch"
              @change="handleStartAddrChange"
              :loading="startLoading"
              style="width: 80%"
            >
              <el-option
                v-for="item in startAddrOpt"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
            <div class="way_point" @click="handleWayPointClick">途</div>
          </el-form-item>
          <el-form-item v-for="(item,index) in waypointsArr" :key="index" :label="'途经点'+(index+1)" :prop="'wayPoint'+index">
            <el-input v-model="addForm['wayPoint'+index]" readonly style="width: 210px" />
            <el-button type="primary" style="margin-left: 10px;" @click="handleRemoveWayPoint(index)">
              <el-icon><CircleCloseFilled /></el-icon>
            </el-button>
            <el-button type="primary" @click="handleAddPoint(index)">
              <el-icon><AddLocation /></el-icon>
            </el-button>
          </el-form-item>
          <el-form-item label="终点" prop="startAddr">
            <el-select
              v-model="addForm.endAddr"
              filterable
              remote
              reserve-keyword
              placeholder="请输入终点搜索"
              :remote-method="handleEndAddrSearch"
              @change="handleEndAddrChange"
              :loading="endLoading"
              style="width: 100%"
            >
              <el-option
                v-for="item in endAddrOpt"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="距离" prop="lineRange">
            <el-input v-model="addForm.lineRange" readonly>
              <template #append>
                <span>公里</span>
              </template>
            </el-input>
          </el-form-item>
          <el-form-item label="线路颜色" prop="lineColor">
            <el-color-picker v-model="addForm.lineColor" @change="handleLineColorChange" />
          </el-form-item>
          <div style="display: flex;justify-content: flex-end">
            <el-button type="primary" @click="handleSubmit">保存</el-button>
            <el-button @click="handleCancel">取消</el-button>
          </div>
        </el-form>
      </div>
      <div ref="mapRef" id="container"></div>
    </div>
  </el-dialog>
</template>

<style scoped lang="scss">
.map_wrap{
  width: 100%;
  height: calc(100vh - 200px);
  background-color: #ffffff;
  .map_search{
    position: absolute;
    top: 80px;
    right: 40px;
    background-color: #ffffff;
    z-index: 2;
    box-sizing: border-box;
    padding: 10px;
    border-radius: 8px;
    box-shadow: 4px 4px 4px #e4e4e4;
    .way_point{
      margin-left: 10px;
      box-sizing: border-box;
      padding: 0 10px;
      background-color: #578bff;
      color: #ffffff;
      font-size: 16px;
      border-radius: 4px;
      cursor: pointer;
    }
  }
  #container {
    width: 100%;
    height: calc(100vh - 200px);
  }
}
</style>

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐