高德地图撒点组件

高德地图撒点组件_第1张图片

一、引入amap地图库 - public/index.html

<script type="text/javascript">
    window._AMapSecurityConfig = {
      securityJsCode: '地图密钥' 
    }
  </script>
  <script
    type="text/javascript"
    src="https://webapi.amap.com/maps?v=1.4.8&key=111111111111111111111111"
  ></script>

二、组件 - DzvScatterMap

<template>
  <div class="container-box">
    <div id="container"></div>
  </div>
</template>
<script>
import { containerType, getLocation } from '@/utils/alipay'
import locationImg from '@/assets/images/location.png'

const { AMap } = window

const iconSize = 56
const defaultZoom = 12

export default {
  name: 'DzvScatterMap',
  props: {
    lngLat: {
      type: Array,
      default: () => []
    },
    // 点位列表
    list: {
      type: Array,
      default: () => []
    },
    // 样式列表
    styles: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      map: null,
      cluster: null,
      mass: null,
      styleList: []
    }
  },
  watch: {
    lngLat: function(value) {
      if (value && value.length > 0) {
        this.initAMap()
      }
    },
    list: {
      deep: true,
      handler: function(newVal) {
        if (this.map) {
          this.markerMyPosition(this.lngLat)
        }
      }
    },
    styles: {
      deep: true,
      handler: function(newVal) {
        this.styleList = newVal?.map(it => ({
          ...it,
          url: it?.src,
          anchor: new AMap.Pixel(6, 6),
          size: new AMap.Size(iconSize, 56),
          zIndex: 3
        }))
      }
    }
  },
  created() {},
  mounted() {
    this.initAMap()
  },
  methods: {
    // 初始化地图
    initAMap() {
      this.map = new AMap.Map('container', {
        zoom: defaultZoom, // 级别
        resizeEnable: true,
        center: [113.67621, 34.74499], // 默认中心点坐标
        viewMode: '2D' // 使用2D视图,使用3D在amap2.0中会旋转
      })
      this.markerMyPosition(this.lngLat)
    },
    // 标记自己的位置
    markerMyPosition(lngLat) {
      if (lngLat && lngLat.length > 0) {
        if (this.locationMarker) {
          this.map.remove(this.locationMarker)
        }

        this.locationMarker = new AMap.Marker({
          offset: new AMap.Pixel(-10, -10),
          position: new AMap.LngLat(lngLat[0], lngLat[1]),
          zoom: 13,
          content: `
${locationImg});">
`
}) if (this.map) { this.map.add(this.locationMarker) // 添加位置marker this.map.setCenter(lngLat) // 设置中心 this.map.setZoom(defaultZoom) // 重置缩放 this.panBy() // 设置地图平移量,将marker移到中心 } } // 地图加载完成后,进行撒点 this.asyncMarker() }, // 地图的平移 panBy() { const refList = document.getElementById('refList') const height = refList?.offsetHeight if (height > 50) { // 地图中心点平移 this.map.panTo(this.lngLat) // 像素值平移 this.map.panBy(0, -height / 2) } }, // 撒点 getMarkerList(allPoints = {}, currentType = '') { // 洒点之前清除上次的洒点 if (this.mass) this.mass.clear() if (this.list) { const serviceHall = this.list.map(item => { return { ...item, lnglat: [item.siteCoordinateY, item.siteCoordinateX], style: item.typeId } }) this.mass = new AMap.MassMarks(serviceHall, { opacity: 0.8, zIndex: 111, cursor: 'pointer', style: this.styleList }) this.mass.on('click', this.markerClick) this.mass.setMap(this.map) this.$forceUpdate() } }, // 类型和全部定位数据修改的时候进行延迟撒点,展示列表中的loading效果 asyncMarker() { const _this = this setTimeout(() => { const { allPoints, currentType } = _this _this.getMarkerList(allPoints, currentType) }, 5) }, // 点击事件 markerClick(e) { // const curentPoint = e.target.getExtData() || {} const currentPoint = e.data || {} this.$emit('changePoint', currentPoint) }, // 获取当前位置 async location() { if (containerType() === 'xcx' || containerType() === 'wechat') { // this.$emit('changeLngLat', this.lngLat) this.markerMyPosition(this.lngLat) } else { const lngLat = await getLocation() // this.$emit('changeLngLat', lngLat) this.markerMyPosition(lngLat) } } } } </script> <style lang="scss" scoped> .container-box { padding: 0px; margin: 0px; width: 100vw; height: calc(100vh - 50px); position: relative; } #container { padding: 0px; margin: 0px; width: 100%; height: 100%; position: absolute; } .location-icon { width: 40px; height: 40px; background: $white; border-radius: 10px; display: flex; justify-content: center; align-items: center; position: absolute; z-index: 20; right: 10px; top: 10px; } h3 { position: absolute; left: 10px; z-index: 2; color: white; } ::v-deep { .amap-logo { z-index: -1; } .amap-copyright { z-index: -1; } } </style>

三、使用

2.1.home/index.vue

<template>
  <div>
    <HomePickers
      :types="types"
      @updateInfo="updateInfo"
      :class="{ 'home-pickers': true, 'fade-out-picker': fadeOutPicker, 'fade-in-picker': fadeInPicker }"
    />
    <DzvScatterMap
      ref="map"
      :isInit="isInit"
      :lngLat="lngLat"
      :styles="styles"
      :list="itemList"
      @changePoint="changePoint"
    >
      <div id="container" slot="container"></div>
    </DzvScatterMap>
    <HomeBtns @btn="btnClick" ref="refList" id="refList" />
    <HomeSearch :class="{ 'home-search': true, 'fade-out-search': fadeOutSearch, 'fade-in-search': fadeInSearch }" />
    <HomeModal v-if="showModal" @changeShow="changeShow" :item="currentPoint" />
  </div>
</template>

<script>
import { getItemListApi, getClassListApi } from '@/api/home'
import { getLocation, checkAppPermissionHandler } from '@/utils/alipay'
import { countDistance } from '@/utils/index'
import HomePickers from './@component/pickers'
import HomeBtns from './@component/btns'
import HomeSearch from './@component/searches'
import HomeModal from './@component/modal'

export default {
  components: { HomePickers, HomeBtns, HomeSearch, HomeModal },
  data() {
    return {
      isInit: true,
      currentPoint: null, // 当前点击的点位
      itemList: [],
      types: [], // 分类类型
      styles: [], // 地图撒点样式类型
      lngLat: null, // 自身
      params: {},
      pickerInfo: {},
      fadeOutPicker: false,
      fadeInPicker: false,
      fadeOutSearch: false,
      fadeInSearch: false,
      showModal: false // 是否显示选中区域模态框
    }
  },
  computed: {},
  created() {
    this.getClassList() // 查询分类
    this.getItemList() // 查询点位,郑州市不传areaCode
    this.location() // 获取当前位置
  },
  mounted() {},
  methods: {
    getClassList() {
      getClassListApi().then(res => {
        if (res?.code === 0) {
          const types = res?.data?.map(it => ({ text: it?.name, value: it?.id }))
          types?.unshift({ text: '全部类型', value: undefined })
          this.types = types
          this.styles = res?.data
          this.$refs.map.initAMap(this.lngLat) // 初始化地图
        }
      })
    },
    // 获取分类下10公里以内的数据,地图滑动重新请求
    getItemList() {
      checkAppPermissionHandler({ allowLocation: 'must' }, ({ status, location }) => {
        // const { longitude, latitude } = location
        const longitude = 113.58762
        const latitude = 37.86236
        getItemListApi({ ...this.params, longitude, latitude }).then(res => {
          if (res?.code === 0) {
            this.itemList = Object.freeze(
              res?.data.map(item => {
                const { meter, result } = countDistance(longitude, latitude, item.siteCoordinateY, item.siteCoordinateX)
                return {
                  ...item,
                  distance: result,
                  meter
                }
              })
            )
            this.$refs.map.initAMap(this.lngLat) // 初始化地图
          } else {
            this.$toast(res.message)
          }
        })
      })
    },
    changeShow() {
      this.showModal = !this.showModal
    },
    // 修改分类切换
    updateInfo(item) {
      this.params = { ...item }
      this.getItemList()
    },
    // 修改定位
    async changeLngLat(lngLat) {
      this.lngLat = lngLat
      this.$forceUpdate()
    },
    // 获取当前定位并设置自己的位置
    async location() {
      getLocation(res => {
        const longitude = 113.77855
        const latitude = 34.759108
        const lngLat = [longitude, latitude]
        this.changeLngLat(lngLat)
      })
    },
    changePoint(val) {
      this.currentPoint = val
      this.showModal = true
    },
    btnClick(val) {
      // 淡出
      if (val === 1) {
        this.fadeOutPicker = true
        this.fadeOutSearch = true
        this.fadeInPicker = false
        this.fadeInSearch = false
      }
      // 淡入
      if (val === 2) {
        this.fadeInPicker = true
        this.fadeInSearch = true
        this.fadeOutPicker = false
        this.fadeOutSearch = false
      }
      if (val === 3) {
        console.log('val', this.$refs.map.panBy, this.lngLat)
        // 回到当前位置
        this.$refs.map.panBy(this.lngLat)
      }
    }
  }
}
</script>
<style lang="scss" scoped>
.home-pickers {
  width: 100%;
  position: absolute;
  top: 0;
  z-index: 1000;
}
.home-search {
  width: 100%;
  position: absolute;
  bottom: 0;
  z-index: 999;
}
.fade-out-picker {
  animation: fadeOutPicker 1s ease forwards;
}

@keyframes fadeOutPicker {
  0% {
    opacity: 1;
    transform: translateY(0);
  }
  100% {
    opacity: 0;
    transform: translateY(-100%);
  }
}

.fade-out-search {
  animation: fadeOutSearch 1s ease forwards;
}

@keyframes fadeOutSearch {
  0% {
    opacity: 1;
    transform: translateY(0);
  }
  100% {
    opacity: 0;
    transform: translateY(100%);
  }
}

.fade-in-picker {
  animation: fadeInPicker 1s ease forwards;
}

@keyframes fadeInPicker {
  0% {
    opacity: 0;
    transform: translateY(-100%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}
.fade-in-search {
  animation: fadeInSearch 1s ease forwards;
}

@keyframes fadeInSearch {
  0% {
    opacity: 0;
    transform: translateY(100%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}
</style>

2.2.顶部选择器 - home/@component/pickers/index.vue

<template>
  <div class="pickers">
    <van-dropdown-menu active-color="#2689FF">
      <van-dropdown-item v-model="area" :options="areas" @change="menuChange" />
      <van-dropdown-item v-model="type" :options="types" @change="menuChange" />
      <van-dropdown-item v-model="charge" :options="charges" @change="menuChange" />
    </van-dropdown-menu>
  </div>
</template>
<script>
import { charges, areas } from '@/utils/constant'

export default {
  name: 'HomePickers',
  props: {
    types: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      area: undefined,
      type: undefined,
      charge: undefined,
      areas,
      charges
    }
  },
  created() {},
  methods: {
    // 修改信息
    menuChange() {
      this.$emit('updateInfo', { area: this.area, type: this.type, charge: this.charge })
    }
  }
}
</script>
<style lang="scss">
.pickers {
  .van-dropdown-menu {
    .van-dropdown-menu__bar {
      background: linear-gradient(360deg, #d6fcff 0%, #ffffff 100%);
      box-shadow: 0px 2px 6px 0px rgba(129, 163, 203, 0.15);
    }
    .van-ellipsis {
      font-size: 16px;
      font-weight: 400;
      color: yellow;
      line-height: 22px;
      text-shadow: 0px 2px 12px rgba(100, 101, 102, 0.08);
      background: linear-gradient(307deg, #32b6ff 0%, #3562fe 100%);
      font-family: MaokenZhuyuanTi;
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }
    .van-dropdown-menu__title::after {
      border-color: transparent transparent #32b6ff #32b6ff;
    }
  }
}
</style>

2.3.底部搜索页 - home/@component/search/index.vue

<template>
  <div class="searches">
    <div class="searches-box">
      <div class="search" @click="onSearch">
        <van-image width="18" height="18" :src="require('@/assets/images/search.png')" />
        <span class="text">搜索</span>
      </div>
      <div class="item">
        <div v-for="it in item" :key="it.value">
          <div class="it" @click="onJump(it)">
            <van-image :src="it.icon" width="50" height="50" />
            <span class="text">{{ it.text }}</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'HomeSearch',
  data() {
    return {
      item: [
        { value: 1, text: '我要去', icon: require('@/assets/images/go.png'), url: '/go' },
        { value: 2, text: '动态资讯', icon: require('@/assets/images/info.png'), url: '/news' },
        { value: 3, text: '疫苗预约', icon: require('@/assets/images/vaccinum.png'), url: '' },
        { value: 4, text: '入驻服务', icon: require('@/assets/images/enter.png'), url: '' }
      ]
    }
  },
  created() {},
  methods: {
    onSearch() {
      this.$router.push({ path: '/search' })
    },
    onJump(item) {
      if (['http', 'https'].includes(item?.path?.split(':')?.[0])) {
        window.open(item.url)
      } else {
        this.$router.push({ path: item?.url })
      }
    }
  }
}
</script>
<style lang="scss">
.searches {
  height: 266px;
  width: 100%;
  background-image: url('~@/assets/images/search-bg.png');
  background-repeat: no-repeat;
  background-size: 100% 100%;
  &-box {
    width: 100%;
    height: 141px;
    margin-top: 125px;
    background: linear-gradient(360deg, #d6fcff 0%, #ffffff 100%);
    box-shadow: 0px 2px 6px 0px rgba(129, 163, 203, 0.15);
    border-radius: 24px 24px 0px 0px;
    .search {
      position: relative;
      top: 8px;
      margin: 0 17px;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 8px 0;
      background: rgba(67, 191, 243, 0.1);
      border-radius: 18px;
      border: 1px solid #43bff3;
      .text {
        margin-left: 4px;
        font-size: 14px;
        font-weight: 400;
        color: #43bff3;
        line-height: 20px;
      }
    }
    .item {
      position: relative;
      top: 12px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0 32px;
      .it {
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        .text {
          font-size: 13px;
          font-weight: 700;
          color: #3562fe;
          line-height: 19px;
          background: linear-gradient(307deg, #32b6ff 0%, #3562fe 100%);
          -webkit-background-clip: text;
          -webkit-text-fill-color: transparent;
        }
      }
    }
  }
}
</style>

2.4.按钮 - home/@component/btns/index.vue

放大,缩小,当前位置定位

<template>
  <div class="btns">
    <van-image
      :src="require('@/assets/images/enlarge.png')"
      width="67"
      height="67"
      @click="btnClick(1)"
      :style="{ display: show ? 'block' : 'none' }"
    />
    <van-image
      :src="require('@/assets/images/lessen.png')"
      width="67"
      height="67"
      @click="btnClick(2)"
      :style="{ display: !show ? 'block' : 'none' }"
    />
    <van-image :src="require('@/assets/images/focus.png')" width="67" height="67" @click="btnClick(3)" />
  </div>
</template>
<script>
export default {
  name: 'HomeBtns',
  data() {
    return {
      show: true
    }
  },
  methods: {
    btnClick(val) {
      if (val === 1 || val === 2) {
        this.show = !this.show
      }
      this.$emit('btn', val)
    }
  }
}
</script>
<style lang="scss">
.btns {
  position: absolute;
  bottom: 212px;
  z-index: 1100;
  right: 10px;
  display: flex;
  flex-direction: column;
}
</style>

2.5.home/@component/modal/index.vue

点击地图撒点目标点位弹框展示

<template>
  <div class="modal">
    <van-overlay :show="true" class="overlay" @click="showModal">
      <div class="wrapper" @click="showModal">
        <div class="content">
          <div class="img"><van-image width="287" height="161" :src="item.url" /></div>
          <div :class="{ text: true, name: true }">{{ item.name }}</div>
          <div :class="{ text: true, intro: true }">{{ item.intro }}</div>
          <div class="btn">
            <div class="bg" @click.stop="onCall(item.phone)">打电话</div>
            <div class="bg" @click.stop="onMap(_, { ...item, lng: 30, lat: 113, siteName: '郑州东站' })">去这里</div>
          </div>
        </div>
      </div>
    </van-overlay>
  </div>
</template>
<script>
import { onCall, onMap } from '@/utils/index'
export default {
  name: 'HomeModal',
  props: {
    item: {
      type: Object,
      default: () => ({ url: '' })
    }
  },
  data() {
    return { onCall, onMap }
  },
  methods: {
    showModal(val) {
      this.$emit('changeShow')
    }
  }
}
</script>
<style lang="scss">
.modal {
  .overlay {
    z-index: 1999;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .wrapper {
    width: 389px;
    height: 457px;
    background: url('~@/assets/images/modal_bg.png') -12px center / auto 100% no-repeat;
    .content {
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      margin-top: 133px;
      .img {
        background: #d8d8d8;
        border-radius: 8px;
        overflow: hidden;
      }
      .text {
        font-family: PingFangSC-Medium, PingFang SC;
        max-width: 263px;
      }
      .name {
        font-size: 16px;
        font-weight: 500;
        color: #323233;
        line-height: 22px;
        margin: 12px 0 8px 0;
      }
      .intro {
        font-size: 14px;
        font-weight: 400;
        color: #969799;
        line-height: 20px;
        text-overflow: ellipsis;
        overflow: hidden;
        display: -webkit-box; /* 将此元素作为弹性伸缩盒模型展示 */
        -webkit-line-clamp: 2; /* 块级元素显示文本行数 */
        -webkit-box-orient: vertical; /* 设置或检索弹性伸缩盒子对象子元素的排列方式 */
        word-break: break-all; /* 文本存在英文单词时进行换行拆分 */
      }
      .btn {
        display: flex;
        justify-content: space-between;
        align-items: center;
        .bg {
          display: flex;
          justify-content: center;
          align-items: center;
          width: 154px;
          height: 61px;
          background: url('~@/assets/images/btn_bg.png') center center / auto 100% no-repeat;
          font-size: 16px;
          font-family: KingnamBobo-Bold, KingnamBobo;
          font-weight: bold;
          color: #ffffff;
          line-height: 25px;
          text-shadow: 0px 2px 4px rgba(101, 191, 255, 0.3), 0px 2px 2px rgba(18, 68, 221, 0.52);
        }
      }
    }
  }
}
</style>

你可能感兴趣的:(前端,vue,高德地图撒点)