vue3 高德地图弹窗选址功能

import { defineComponent, h } from 'vue';
import AMapLoader from '@amap/amap-jsapi-loader';
import { Input, AutoComplete, Modal, message } from 'ant-design-vue';
// 首先需要引入 Vue3 的 shallowRef 方法(使用 shallowRef 进行非深度监听,因为在 Vue3 所使用的 Proxy 拦截操作会改变 JSAPI 原生对象,所以此处需要区别 Vue2 使用方式对地图对象行非深度监听,否则会出现问题,建议 JSAPI 相关对象采用非响应式的普通对象来存储)。
import { shallowRef } from '@vue/reactivity';
import { debounce } from 'lodash';
import './index.less';
import Service from './service';

export default defineComponent({
  name: 'amap',
  props: {
    visible: {
      type: Boolean,
      required: true,
    },
    bindCancel: {
      type: Function,
      required: true,
    },
    bindOk: {
      type: Function,
      required: true,
    },
    width: {
      type: Number,
    },
  },
  components: {
    antModal: Modal,
    antInput: Input,
    antAutoComplete: AutoComplete,
  },
  data() {
    const instance = shallowRef(null);
    const map = shallowRef(null);
    const marker = shallowRef(null);
    const geocoder = shallowRef(null);
    const placeSearch = shallowRef(null);
    return {
      instance,
      map,
      marker,
      geocoder,
      placeSearch,
      options: [],
      value: '',
      address: {},
    } as any;
  },
  watch: {
    visible(val) {
      if (val && !this.instance) {
        this.initMap();
      }
    },
  },
  methods: {
    initMap() {
      AMapLoader.load({
        key: '******', // 申请好的Web端开发者Key,首次调用 load 时必填
        version: '1.4.15', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
        plugins: ['AMap.ToolBar', 'AMap.Scale', 'AMap.PlaceSearch', 'AMap.Geocoder'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
      })
        .then((AMap) => {
          this.instance = AMap;
          this.geocoder = new AMap.Geocoder({ extensions: 'all' });
          this.placeSearch = new AMap.PlaceSearch({ extensions: 'all', pageSize: 15 });
          this.map = new AMap.Map('amap', {
            //设置地图容器id
            viewMode: '3D', //是否为3D地图模式
            zoom: 12, //初始化地图级别
            animateEnable: false,
          });
          const ToolBar = new AMap.ToolBar();
          const Scale = new AMap.Scale();

          this.map.addControl(ToolBar);
          this.map.addControl(Scale);

          const marker = new this.instance.Marker({
            icon: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',
            position: [22, 22],
            offset: new this.instance.Pixel(-10, -35),
            visible: false,
          });
          this.marker = marker;
          this.map.add(marker);

          this.map.on('click', (e) => {
            this.setPosition(e.lnglat);
          });
        })
        .catch((e) => {
          console.log(e);
        });
    },
    search(str) {
      this.placeSearch.search(str, (status, result) => {
        if (status === 'complete' && result.info === 'OK') {
          this.options = result.poiList.pois.map((ps, idx) => {
            const item = (
              <div>
                <span>
                  {idx + 1}. {ps.name}
                </span>
                <div style={{ fontSize: '13px', color: '#999999' }}>
                  地址:{ps.pname}
                  {ps.cityname === ps.pname ? undefined : ps.cityname}
                  {ps.adname}
                  {ps.address}
                </div>
              </div>
            );
            return {
              obj: ps,
              label: item,
              name: ps.name,
              value: `${ps.location.lng}-${ps.location.lat}`,
            };
          });
        } else {
          this.options = [];
        }
        //console.log(str, status, result);
      });
    },
    setPosition(posi) {
      this.geocoder.getAddress(posi, async (status, result) => {
        //console.log(status, result);
        if (status === 'complete' && result.info === 'OK') {
          const { addressComponent, formattedAddress } = result.regeocode;
          const { province, city, district } = addressComponent;
          this.address = { province, city, district, detail: formattedAddress };
          this.marker.setPosition(posi);
          this.marker.show();

          const infoWindow = new this.instance.InfoWindow({
            anchor: 'bottom-center',
            content: `<div style="padding: 10px;">${result.regeocode.formattedAddress}</div>`,
            offset: new this.instance.Pixel(0, -38),
          });
          infoWindow.open(this.map, posi);
          this.address = await this.translateAddress(this.address);
          //console.log(this.address);
        }
      });
    },
    //获取省市区数据源方法,具体看后端接口,可自定义
    async getDataSource(params) {
      try {
        //这一步主要是获取对应省市区的数据源
        const res = await Service.getDataSource(params);
        return res;
        // return (res.records || []).map((r) => {
        //   return { ...r, label: r.codeName, value: r.code };
        // });
      } catch (e) {
        message.error((e as any).message || '获取数据异常');
        return [];
      }
    },
    //根据所选省市区翻译code
    async translateAddress(address) {
      const { province, city, district, detail } = address || {};
      //坑:不判断!city,因为存在直辖市的情况,直辖市的city为空,province===city
      if (!province || !district) {
        return;
      }
      const provinceSource = await this.getDataSource({ codeType: 'sheng' });
      const provinceCode = (provinceSource.find((ps) => ps.label === province) || {}).value;

      const citySource = await this.getDataSource({ codeType: 'shi', sheng: provinceCode });
      const cityCode = (citySource.find((ps) => ps.label === (city || province)) || {}).value;

      const districtSource = await this.getDataSource({ codeType: 'qu', shi: cityCode });
      const districtCode = (districtSource.find((ps) => ps.label === district) || {}).value;
      const addressDetail = detail.replace(`${province}${city}${district}`, '');
      address = { ...address, provinceCode, cityCode, districtCode, addressDetail };
      return address;
    },
    onCancel() {
      this.bindCancel();
    },
    onOk() {
      if (Object.keys(this.address).length === 0) {
        message.warn('请选择一个地址');
        return;
      }
      this.bindOk(this.address);
      this.onCancel();
    },
  },
  render() {
    return (
      <antModal
        title="电子地图"
        visible={this.visible}
        onCancel={() => this.onCancel()}
        onOk={() => this.onOk()}
        style={{ top: '20px' }}
        width={this.width || 800}>
        <antAutoComplete
          allowClear={true}
          onSearch={debounce(this.search, 300)}
          onChange={(val) => (this.value = val)}
          onSelect={async (val, option) => {
            this.value = option.name;
            const posi = val.split('-');
            this.map.setZoomAndCenter(16, posi);
            // this.setPosition(posi);

            const { pname, cityname, adname, address, name } = option.obj;
            this.address = { province: pname, city: cityname, district: adname, detail: address + name };
            this.marker.setPosition(posi);
            this.marker.show();

            const infoWindow = new this.instance.InfoWindow({
              anchor: 'bottom-center',
              content: `<div style="padding: 10px;">${
                pname + (cityname === pname ? '' : cityname) + adname + address + name
              }</div>`,
              offset: new this.instance.Pixel(0, -38),
            });
            infoWindow.open(this.map, posi);
            this.address = await this.translateAddress(this.address);
            console.log(this.address);
          }}
          value={this.value}
          options={this.options}
          style={{ width: '100%' }}
        />
        <div id="amap"></div>
      </antModal>
    );
  },
});
posted @ 2023-04-10 14:02  你风致  阅读(398)  评论(0编辑  收藏  举报