Vue开发高德地图应用的最佳实践

小村上空升起袅袅炊烟,好像一个身穿白纱的少女在翩翩起舞,在夕阳的照耀下婀娜多姿。

前言

之前做不过不少关于地图交互的产品系统,目前国内主流的地图应用 SDK 只有几家:高德、百度和腾讯。所以个人觉得在 PC 应用上高德地图开发相对好一些,至少体验起来没有很明显的坑。这篇文章算是总结下开发地图应用总结吧。

异步加载

因为使用 js sdk 应用,脚本文件本身体积很大,所以要注意下加载的白屏时间,解决用户体验问题,目前绝大部分产品应用都是 SPA 单页面应用系统,所以我封装一个异步加载的方法:

const loadScripts = async scripts => {
  const get = src => {
    return new Promise(function(resolve, reject) {
      const el = document.createElement('script')
      el.addEventListener('load', function() {
        resolve(src)
      }, false)
      el.addEventListener('error', function() {
        reject(src)
      }, false)
      el.id = src.id
      el.src = src.url
      document.getElementsByTagName('body')[0].appendChild(el) || document.getElementsByTagName('head')[0].appendChild(el)
    })
  }

  const myPromises = scripts.map(async script => {
    if (document.getElementById(script.id) === null) {
      return await get(script)
    }
  })

  return await Promise.all(myPromises)
}

export default loadScripts

这个方法在加载脚本的时候先去判断页面是否存在该脚本,如果存在就不会加载第二次,然后再利用加载完毕回调执行相关方法。

封装组件

如果系统中有多个页面需要地图应用业务,那么需要封装一个通用型的地图组件,提高项目可维护性,我这边就简单的封装下地图应用:

<template>
  <div
    :style="{
      width: width,
      height: height
    }"
    class="amap-container"
  >
    <div ref="amap" class="amap">
      <slot />
    </div>
  </div>
</template>

<style lang="scss" scoped>
    .amap-container {
    .amap {
        width: 100%;
        height: 100%;
    }
    }
</style>

指定一个地图应用容器,外面包裹一层指定高宽,高宽作为外部变量传入,业务逻辑如下:

import loadScripts from '@/loadScripts'
export default {
  name: 'AMapContainer',
  props: {
    width: {
      require: false,
      type: String,
      default: '100%'
    },
    height: {
      require: false,
      type: String,
      default: '600px'
    },
    options: {
      require: false,
      type: Object,
      default: () => {}
    }
  },
  data: () => ({
    amap: null,
    amapInfo: {
      key: 'xxxxxxxxxxxxxx'
    }
  }),
  created() {
    this.initAMap()
  },
  beforeDestroy() {
    // 销毁地图
    if (!this.amap) {
      return
    }
    this.amap.destroy()
    this.amap = null
  },
  methods: {
    initAMap() {
      loadScripts([{
        id: 'ampa',
        url: `https://webapi.amap.com/maps?v=2.0&key=${this.amapInfo.key}&plugin=AMap.PolygonEditor`
      }]).then(() => {
        this.amap = new window.AMap.Map(this.$refs['amap'], this.options)
        this.$emit('map', this.amap, window.AMap)
      })
    }
  }
}

应用加载的时候初始化地图容器:异步加载高德地图 js sdk 然后回调方法里进行实例化地图应用,并且把地图实例化的对象传入 $emit 事件里,方便父类组件需要。另外注意要在销毁生命周期里对地图应用进行销毁,否则会占用大量的系统内存。

使用组件

封装好组件后就可以在对应的页面进行引入组件使用即可:

<template>
    <amap-container height="100%" :options="amapOptions" @map="getAMapData" />
</template>

<script>
    import AMap from '@/components/AMap'

    export default {
        name: 'AMapDemo',
        components: {
            'amap-container': AMap
        },
        data: () => ({
            amapOptions: {
                zoom: 14,
                resizeEnable: true,
                viewMode: '3D',
                mapStyle: 'amap://styles/normal'
            },
            AMap: null, // 地图对象
            amap: null // 当前地图实例
        }),
        methods: {
            /**
             * 地图加载完毕回调
             * @param amap
             * @param AMap
             */
            getAMapData(amap, AMap) {
                // 从组件获取地图 amap 对象
                this.amap = amap
                // 从组件获取地图 AMap 静态对象
                this.AMap = AMap
            }
        }
    }
</script>

然后在上面基础上展开相关业务。对于地图应用来说,最核心的数据就是地图应用中的坐标,无论是地图的标记元素,折线元素(轨迹等),绘制图元素等,只需要获取对应的经纬度数据存到数据库即可,至于怎么获取这边不再详述。

自定义界面最佳实践

之前制作的地图应用,在标记的详细界面(选择某个标记左键打开界面),这个界面是需要传入原生 document 对象,但是在 vue 对象里面不符合这种写法,所以导致之前很多系统都是花大量的时间去编写 dom 结构,甚是头疼,后续为了解决这个问题,vue 是否有相关方法挂载组件获取真实的 document 对象,查阅相关文档后,确实有这个 api : Vue.extend,利用这个 api 挂载组件对象即可得到实例化组件的对象。

import ContextCard from './components/ContextCard'

// 创建标记
const marker = new this.AMap.Marker({
  map: this.amap,
  position: [119.058904, 33.537069]
})
// 绑定点击事件
marker.on('click', this.markerInfoWindow)

// 点击打开弹窗
const markerInfoWindow = () => {
  // 引入 Vue 组件构造器实例化
  const ContextCardContent = Vue.extend(ContextCard)
  // 挂载组件
  const contextCardContent = new ContextCardContent().$mount()
  // 实例化窗口对象
  this.amapInfoWindow = new this.AMap.InfoWindow({
    isCustom: true,
    content: contextCardContent.$el,
    offset: new this.AMap.Pixel(0, -40)
  })
  // 打开窗口
  this.amapInfoWindow.open(this.amap, marker.getPosition())
  // 监听组件事件关闭窗口
  contextCardContent.$on('closeWindow', () => this.amapInfoWindow.close())
}

ContextCard.vue 组件:

<template>
  <el-card class="context-box-card box-card">
    <div slot="header" class="header">
      <span>卡片名称</span>
      <el-button type="text" class="close-btn" @click="closeWindow">关闭</el-button>
    </div>
    <div v-for="o in 4" :key="o" class="text item">
      {{ '列表内容 ' + o }}
    </div>
  </el-card>
</template>

<script>
export default {
  name: 'AMapContextCard',
  methods: {
    closeWindow() {
      this.$emit('closeWindow')
    }
  }
}
</script>

<style lang="scss" scoped>
.context-box-card {
  width: 320px;
  height: 200px;

  .header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  ::v-deep .el-card__header {
    padding: 5px 20px;
  }
}
</style>

上面就是一个标点点击打开标记弹窗的详细信息,利用 Vue.extend 构造器进行实例化组件。这样很大程度上提高项目健壮性。

import Vue from "vue";
import App from "./App.vue";

import Element from "element-ui";

import "normalize.css/normalize.css";
import "element-ui/lib/theme-chalk/index.css";

Vue.config.productionTip = false;

Vue.use(Element);

new Vue({
  render: (h) => h(App)
}).$mount("#app");

总结

到此这篇关于Vue开发高德地图应用的文章就介绍到这了,更多相关Vue高德地图应用内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

您可能有感兴趣的文章
Vue路由参数的传递与获取方式详细介绍

vue学习记录之动态组件浅析

vue如何实现列表固定列滚动

vue如何实现伸缩菜单功能

vue项目中canvas如何实现截图功能