一、引入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: `<div style="background-size: 100% 100%; height: 30px; width: 30px; background-image:url(${locationImg});"></div>`
})
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>