当前位置: 首页 > 文档资料 > vue-amap 中文文档 >

1.8.1 指南

优质
小牛编辑
129浏览
2023-12-01

实际开发中面对复杂业务,库中现有的几个基础组件很多时候无法满足我们的业务需求,另一个方面在于高德的sdk也在疯狂更新,一味的包装也不是长久之计,所以这里提供一个方法 -- createCustomComponent,让用户自己开发并维护自己特定业务组件,同时也希望通过社区成员一起建设公共组件。

先看个例子

<vuep template="#example"></vuep>

<script v-pre type="text/x-template" id="example">
  <template>
    <div class="amap-page-container">
      <el-amap vid="amapDemo" :zoom="zoom" :center="center" class="amap-demo">
        <amap-canvas-markers
          :data="markerData"
          :get-position="markerOptions.getPosition"
          :get-hover-title="markerOptions.getHoverTitle"
          :visible="markerOptions.visible"
          render-constructor="PointSimplifier.Render.Canvas"
          :render-options="markerOptions.renderOptions"
          :events="markerOptions.events"
          ></amap-canvas-markers>
      </el-amap>
      <div class="toolbar">
        <button type="button" name="button" @click="toggleVisible">toggle visible</button>
      </div>
    </div>
  </template>

  <style>
    .amap-demo {
      height: 300px;
    }
  </style>
  <script>
    // import {createCustomComponent} from 'vue-amap' 
    const { createCustomComponent } = VueAMap;
    // 组件定义
    const AmapCanvasMarkers = createCustomComponent({
      name: 'amap-canvas-marker',
      props: [
        'visible',
        'zIndex',
        'data',
        'getPosition',
        'getHoverTitle',
        'compareDataItem',
        'autoSetFitView',
        'renderConstructor',
        'renderOptions',
        'maxChildrenOfQuadNode',
        'maxDepthOfQuadTree',
        'badBoundsAspectRatio'
      ],
      contextReady() {
        console.log('context ready', AMap);
      },
      init(options, map) {
        return new Promise((resolve, reject) => {
          AMapUI.loadUI(['misc/PointSimplifier'], PointSimplifier => {
            const {renderConstructor: renderStr, renderOptions } = options;
            // console.log(renderStr);
            if (renderStr) options.renderConstructor = renderStr.split('.').reduce((pre, cur) =>  pre[cur], {PointSimplifier});
            if (options.renderOptions && options.renderOptions.pointStyle) {
              const {pointStyle} = options.renderOptions;
              if (pointStyle.contentImg) pointStyle.content = PointSimplifier.Render.Canvas.getImageContent(pointStyle.contentImg, () => this.$amapComponent.renderLater()),
              e => console.error(e)
            }
            resolve(new PointSimplifier(options))
          });
        })
      },
      converters: {},
      handlers: {
        zIndex(index) {
          this.setzIndex(index);
        },
        visible(flag) {
          flag === false ? this.hide() : this.show();
        }
      }
    });
    const center = [121.5273285, 31.21515044];
    const markerData = Array.from({length: 10000},(x, index) => ({position: [
      center[0] + (Math.random() > 0.5 ? 1 : -1) * Math.random() * 0.6,
      center[1] + (Math.random() > 0.5 ? 1 : -1) * Math.random() * 0.6
    ], title: `小点坐标-${index}`}));
    module.exports = {
      components: {AmapCanvasMarkers},
      data() {
        return {
          zoom: 14,
          center,
          markerData,
          markerOptions: {
            visible: true,
            getPosition(dateItem) {
              return dateItem.position
            },
            getHoverTitle(dateItem) {
              return dateItem.title
            },
            renderOptions: {
              pointStyle: {
                contentImg: 'http://webapi.amap.com/theme/v1.3/markers/n/mark_b1.png',
                width: 19,
                height: 31,
                offset: ['-50%', '-100%'],
                fillStyle: null,
                strokeStyle: null
              }
            },
            events: {
              pointClick(e, point) {
                console.log('event pointClick', e, point)
              },
              pointMouseover(e, point) {
                console.log('event pointMouseover', e, point);
              },
              pointMouseout(e, point) {
                console.log('event pointMouseout', e, point)
              }
            }
          }
        }
      },
      methods: {
        toggleVisible() {
          this.markerOptions.visible = !this.markerOptions.visible;
        }
      }
    }
  </script>
</script>

再看第二个

<vuep template="#example2"></vuep>

<script v-pre type="text/x-template" id="example2">
<style>
  .amap-demo {
    height: 300px;
  }
  .container {
    position: relative;
  }
  .tip {
      background-color: #ddf;
      color: #333;
      border: 1px solid silver;
      box-shadow: 3px 4px 3px 0px silver;
      position: absolute;
      top: 10px;
      right: 10px;
      border-radius: 5px;
      overflow: hidden;
      line-height: 20px;
      z-index: 99;
  }
  .tip input {
      height: 25px;
      border: 0;
      padding-left: 5px;
      width: 280px;
      border-radius: 3px;
      outline: none;
  }
</style>
<template>
  <div class="container">
  <div class="tip">
    <input class="custom-componet-input" id="custom-componet-input" />
  </div>
    <el-amap vid="xxx" :zoom="zoom" :center="center" class="amap-demo">
      <custom-map-searchbox @select="selectSearch" input="custom-componet-input" ></custom-map-searchbox>
      <el-amap-marker v-if="selectMarker" :position="selectMarker.position" :label="selectMarker.label"></el-amap-marker>
    </el-amap>
  </div>
</template>

<script>

const customMapSearchbox = VueAMap.createCustomComponent({
  props: {
    input: String
  },
  init(options, map) {
    return new Promise(resolve => {
      AMap.plugin(['AMap.Autocomplete','AMap.PlaceSearch'], () => {
        const autocomplete = new AMap.Autocomplete(options)
        AMap.event.addListener(autocomplete, 'select', (e) => {
          this.$emit('select', e.poi)
        });
        resolve(autocomplete)
      })
    });
  }
})

module.exports = {
  data() {
    return {
      selectMarker: null,
      zoom: 14,
      center: [121.5273285, 31.21515044]
    }
  },
  components: {customMapSearchbox},
  methods: {
    selectSearch(poi) {
      console.log(poi)
      const {location, name, adcode, district, address, } = poi
      const center = [location.lng, location.lat];
      console.log(center)
      this.selectMarker = {
        label: {content: `<div>
          <div>${name}</div>
          <div>${district}</div>
        </div>`, offset: [20, 20]},
        position: [...center]
      },
      console.log(this.selectMarker);
      this.center = center;
    }
  }
}
</script>

再看第三个

<vuep template="#example3"></vuep>

<script v-pre type="text/x-template" id="example3">
<style>
.xxconatiner {
  position: relative;
  padding: 60px 10px
}
.tip {
    background-color: #ddf;
    color: #333;
    border: 1px solid silver;
    box-shadow: 3px 4px 3px 0px silver;
    display: inline-block;

    border-radius: 5px;
    overflow: hidden;
    line-height: 20px;
    z-index: 99;
}
.tip input {
    height: 25px;
    border: 0;
    padding-left: 5px;
    width: 280px;
    border-radius: 3px;
    outline: none;
}
</style>

<template>
<div class="xxconatiner">
  <custom-search @select="select">
  </custom-search>
</div>
</template>

<script>
const customSearch = VueAMap.createCustomComponent({
  template: `<div class="tip">
    <input class="custom-componet-input" :id="id" />
  </div>`,
  data() {
    return {
      id: `custom-componet-input-${Math.random()}`
    }
  },
  contextReady(_options) {
    const options = {
      ..._options,
      input: this.id
    }
    AMap.plugin(['AMap.Autocomplete','AMap.PlaceSearch'], () => {
      const autocomplete = new AMap.Autocomplete(options)
      AMap.event.addListener(autocomplete, 'select', (e) => {
        this.$emit('select', e.poi)
      })
      this.$amapComponent = autocomplete
    })
  }
})
module.exports = {
  components: {customSearch},
  methods: {
    select(poi) {
      console.log(poi)
    }
  }
}
</script>
import {createCustomComponent} from 'vue-amap'
const customComponent = createCustomComponent({
  name: 'custom-component-name',
  init() {  // required
    ...
    return amapInstance // required
    // or
    return new Promise(resolve => {
      ...
      resolve(amapInstance)
    })
    ...
  },
  converters: {
    [propKey](propVal) {
      return customConvert(propVal)
    }
  },
  handlers: {
    [propKey]() {
      // callback
    }
  },
  contextReady() {},
  created() {},
  mounted() {},
  destoryed() {},
  // other hooks
})

Vue.use(customComponent) // registered as a component named [custom-component-name]

以上是一个简单的组件的例子,关键函数就是 createCustomComponent(VueComponentOptionsPlus),返回是一个完整的 VueComponentOptions, 入参除了具有基本的 VueComponentOptions 的相关属性外,还有以下的关键属性

名称类型说明
nameStringcomponent Name
initFunction(options, AMapInstance)该 hook 是父组件vue-amap中地图实例初始化后进行调用的, 所以依赖于父组件,该函数的作用是组件对应的高德实例进行初始化,关键步骤 return componentInstance / resolve(componentInstance),返回个实例,异步初始化需要返回一个 Promise,将实例 resolve 出来;
contextReadyFucntion该 hook 在组件 mounted 中并且浏览器上下文完成高德脚本加载后执行,该 hook 内部可安全使用高德API,该 hook 依赖于内部的 lazyAMapApiLoaderInstance, 请务必保证在该组件 mounted 前完成初始化函数 initAMapApiLoader 调用。该 hook 并不依赖父组件,可独立存在,适用于一些简单无交互的工具组件
convertersObject: {[propKey]:Function}对于组件原始入参的转换函数,像一些坐标类型数据需要从原始数组转为 AMap.LngLat,本库内部内置了 position: toLngLat, offset: toPixel, bounds: toBounds 的转换
handlersObject: {[propKey]: Function}自定义组件属性值变化后对应的回调,内置的回调指定规则是 自定义 handler -> 高德实例 set[PropKey] 方法 -> setOptions 方法 从左到右的依次判空,如果存在则指定调用该回调,注:回调函数的默认 this 是该 高德实例

原理其实很简单,内部 watch 了所有提前申明的 prop,propValue 改变时执行对应回调,规则如上。自定义组件的实例上维护了有两个特殊的属性 $amap 高德地图实例(父组件为 vue-amap 才会初始化)和 $amapComponent高德组件实例;

关于 destoryed 内置了通用的回收方法 ,如果不满足请务必自行回收。