【新增】高德地图组件 AMap

pull/147/MERGE
这么诚实 1 year ago committed by 小诺
parent 154d2e1675
commit 7a3b988a44

@ -17,6 +17,7 @@
"prod": "vite --mode production"
},
"dependencies": {
"@amap/amap-jsapi-loader": "1.0.1",
"@ant-design/colors": "7.0.0",
"@ant-design/icons-vue": "6.1.0",
"@antv/g2plot": "2.4.28",

@ -0,0 +1,198 @@
AMap
====
> 高德地图组件,常用于地图展示使用
该组件由 [小诺开源技术](https://www.xiaonuo.vip) 封装
### 使用方式
```vue
<template>
<a-map ref="map" api-key="******" @complete="handleComplete"
@marker-click="handleMarkerClick"></a-map>
</template>
<script setup name="exmAMap">
import AMap from '@/components/Map/aMap/index.vue'
const map = ref(null)
const handleComplete = () => {
// 渲染 点标记
map.value.renderMarker(
[
{
position: [116.39, 39.9],
title: 'TA',
content: 'CA',
label: {
content: 'LCA'
}
},
{
position: [116.33, 39.5],
title: 'TB',
icon: '//vdata.amap.com/icons/b18/1/2.png'
}
]
)
}
const handleMarkerClick = (position) => {
map.value.openInfoWindow(position)
}
</script>
```
### Prop属性
| 名称 | 说明 | 类型 | 默认值 |
|---------------|---------------------|---------|--------|
| mid | 容器ID | String | 时间戳 |
| apiKey | 地图Key | String | |
| center | 地图中心点 | String | 自动定位 |
| plugins | 地图控件 | Array | |
| viewMode | 效果2D3D | String | 3D |
| zoom | 地图缩放比例 | Number | 12 |
| pitch | 地图俯仰角度,有效范围 0-83 | String | 50 |
| mapStyle | 地图样式 | String | normal |
| markerCluster | 点聚合 | Boolean | true |
#### 地图控件
- AMap.ToolBar缩放工具条
- AMap.Scale比例尺
- AMap.HawkEye鹰眼
- AMap.MapType图层切换
- AMap.Geolocation定位
- AMap.MarkerCluster点聚合
#### 地图样式
- normal
- macaron
- dark
- fresh
- grey
### 事件
| 名称 | 说明 | 参数 | 参数类型 |
|-------------|---------------|----------|-------|
| complete | 当地图初始化完成时触发 | - | - |
| markerClick | 当点击了点覆盖物时触发 | position | Array |
### 方法
| 名称 | 说明 | 参数 | 参数类型 |
|---------------------|----------|---------------------|-------------------|
| renderMarker | 渲染 点标记 | dataArr | Array |
| renderCircleMarker | 渲染 圆点标记 | dataArr | Array |
| renderSimpleMarker | 渲染 简单点标记 | dataArr, theme | Array, String |
| renderAwesomeMarker | 渲染 字体点标记 | dataArr | Array |
| renderPolyline | 渲染 线 | dataArr,opt | Array,JSON |
| renderCircle | 渲染 圆 | position,radius,opt | Array,Number,JSON |
| renderPolygon | 渲染 面 | dataArr,opt | Array,JSON |
| renderInfoWindow | 渲染 信息窗体 | dataArr | Array |
| openInfoWindow | 打开 信息窗体 | position | Array |
| clearOverlay | 清理 覆盖物 | | |
### 方法参数```dataArr```结构
> 点标记
```json
[{
"position": "坐标数组",
"title": "鼠标滑过点标记时的文字提示",
"content": "显示内容content有效时icon属性将被覆盖",
"icon": "图标",
"label": {
"content": "文本标注"
}
}]
```
> 圆点标记
```json
[{
"position": "坐标数组,圆心位置",
"radius": "圆点半径",
"strokeColor": "线条颜色,默认#006600",
"fillColor": "填充颜色,默认#006600"
}]
```
> 简单点标记
```json
[{
"position": "坐标数组",
"label": "前景文字",
"labelStyle": {
"color": "颜色",
"fontSize": "字体大小"
},
"style": "背景图标样式"
}]
```
> 字体点标记
```json
[{
"position": "坐标数组",
"awesomeIcon": "图标参见http://fontawesome.io/icons/",
"labelStyle": {
"color": "颜色",
"fontSize": "字体大小"
},
"style": "背景图标样式"
}]
```
> 线、面
```json
[{
"position": "坐标数组"
}]
```
> 信息窗体
```json
[{
"position": "坐标数组",
"content": "显示内容,文本数组,会以换行进行连接"
}]
```
### 方法参数```opt```结构
> 线
```json
{
"strokeColor": "边线颜色默认blue",
"strokeWeight": "边线宽度默认2",
"strokeOpacity": "边线透明度默认0.5",
"isOutline": "是否显示描边默认false",
"borderWeight": "描边宽度默认1"
}
```
> 圆、面
```json
{
"strokeColor": "边线颜色默认blue",
"strokeWeight": "边线宽度默认2",
"strokeOpacity": "边线透明度默认0.5",
"fillColor": "填充颜色默认blue",
"fllOpacity": "填充透明度默认0.5"
}
```

@ -0,0 +1,410 @@
<template>
<div class="aMap">
<div :id="`container-${mid}`" style="width: 100%;height: 100%;">
地图资源加载中...
</div>
</div>
</template>
<!--AMap官网https://lbs.amap.com/api/javascript-api-v2/summary-->
<script setup name="AMap">
import {onMounted, onUnmounted, shallowRef} from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
const props = defineProps(
{
mid: {
type: Number,
default: new Date().getTime()
},
apiKey: {
type: String,
required: true
},
center: {
type: Array
},
plugins: {
type: Array,
default: [
'AMap.ToolBar',
'AMap.Scale',
'AMap.HawkEye',
'AMap.MapType',
'AMap.Geolocation',
'AMap.MarkerCluster'
]
},
viewMode: {
type: String,
default: '3D',
validator(value) {
return ['2D', '3D'].includes(value)
}
},
zoom: {
type: Number,
default: 12
},
pitch: {
type: Number,
default: 50
},
mapStyle: {
type: String,
default: 'normal',
validator(value) {
return ['normal', 'macaron', 'dark', 'fresh', 'grey'].includes(value)
}
},
markerCluster: {
type: Boolean,
default: true
}
}
)
const emits = defineEmits(['complete', 'markerClick'])
const aMap = shallowRef(null)
const aMapMarkerArr = ref([])
const aMapInfoWindowObj = ref({})
const init = () => {
AMapLoader.load({
key: props.apiKey,
version: '2.0',
plugins: props.plugins,
AMapUI: {
version: '1.1',
plugins: ['overlay/SimpleMarker', 'overlay/AwesomeMarker']
}
}).then(() => {
initMap()
}).catch(e => {
console.error(e);
})
}
/**
* 初始化 地图
*/
const initMap = () => {
aMap.value = new AMap.Map(`container-${props.mid}`, {
viewMode: props.viewMode,
zoom: props.zoom,
//
pitch: props.pitch,
mapStyle: `amap://styles/${props.mapStyle}`
})
//
if (props.center) {
aMap.value.setCenter(props.center)
}
//
props.plugins.length > 0 && initControlPlugin()
//
aMap.value.on('complete', () => {
emits('complete')
})
}
/**
* 初始化 控制控件
*/
const initControlPlugin = () => {
//
props.plugins.includes('AMap.ToolBar') && aMap.value.addControl(new AMap.ToolBar({}))
//
props.plugins.includes('AMap.Scale') && aMap.value.addControl(new AMap.Scale())
//
props.plugins.includes('AMap.HawkEye') && aMap.value.addControl(new AMap.HawkEye({isOpen: true}))
//
props.plugins.includes('AMap.MapType') && aMap.value.addControl(new AMap.MapType({}))
//
props.plugins.includes('AMap.Geolocation') && aMap.value.addControl(new AMap.Geolocation({}))
}
/**
* 渲染 点标记
* @param dataArr
*/
const renderMarker = (dataArr) => {
dataArr.forEach(d => {
const marker = new AMap.Marker({
map: aMap.value,
position: d.position,
//
title: d.title,
// contenticon
content: d.content,
//
icon: d.icon ? d.icon : null,
//
label: d.label
})
marker.on('click', () => {
emits('markerClick', d.position)
})
aMapMarkerArr.value.push(marker)
})
setFitView()
}
/**
* 渲染 圆点标记
* @param dataArr
*/
const renderCircleMarker = (dataArr) => {
dataArr.forEach(d => {
const marker = new AMap.CircleMarker({
map: aMap.value,
//
center: d.position,
//
radius: d.radius ? d.radius : 20,
// 线
strokeColor: d.strokeColor ? d.strokeColor : '#006600',
// 线
strokeOpacity: 0.5,
// 线
strokeWeight: 2,
//
fillColor: d.fillColor ? d.fillColor : '#006600',
//
fillOpacity: 0.5,
cursor: 'pointer'
})
marker.on('click', () => {
emits('markerClick', d.position)
})
aMapMarkerArr.value.push(marker)
})
setFitView()
}
/**
* 渲染 简单点标记
* @param dataArr
* @param theme
*/
const renderSimpleMarker = (dataArr, theme = 'default') => {
dataArr.forEach(d => {
const marker = new AMapUI.SimpleMarker({
map: aMap.value,
position: d.position,
//
iconLabel: {
//
innerHTML: d.label,
//
style: d.labelStyle ? d.labelStyle : {
color: '#333',
fontSize: '12px'
}
},
// defaultfreshnumv1numv2
iconTheme: theme,
//
iconStyle: d.style
})
marker.on('click', () => {
emits('markerClick', d.position)
})
aMapMarkerArr.value.push(marker)
})
setFitView()
}
/**
* 渲染 字体点标记
* @param dataArr
*/
const renderAwesomeMarker = (dataArr) => {
dataArr.forEach(d => {
const marker = new AMapUI.AwesomeMarker({
map: aMap.value,
position: d.position,
// http://fontawesome.io/icons/
awesomeIcon: d.awesomeIcon,
//
iconLabel: {
style: d.labelStyle ? d.labelStyle : {
color: '#333',
fontSize: '12px'
}
},
//
iconStyle: d.style
})
marker.on('click', () => {
emits('markerClick', d.position)
})
aMapMarkerArr.value.push(marker)
})
setFitView()
}
/**
* 设置 视图级别
*/
const setFitView = () => {
//
props.markerCluster && new AMap.MarkerCluster(aMap.value, aMapMarkerArr.value)
//
aMap.value.setFitView(aMapMarkerArr.value)
}
/**
* 渲染 线
* @param dataArr
* @param option
*/
const renderPolyline = (dataArr, option = {}) => {
const path = [];
dataArr.forEach(d => {
path.push(new AMap.LngLat(d.position[0], d.position[1]))
})
const polyline = new AMap.Polyline({
path: path,
strokeColor: option.strokeColor || 'blue',
strokeWeight: option.strokeWeight || 2,
strokeOpacity: option.strokeOpacity || 0.5,
isOutline: option.isOutline || false,
borderWeight: option.borderWeight || 1,
// 线
lineJoin: 'round'
})
aMap.value.add(polyline)
aMap.value.setFitView([polyline])
}
/**
* 渲染
* @param position
* @param radius
* @param option
*/
const renderCircle = (position, radius, option) => {
const circle = new AMap.Circle({
center: new AMap.LngLat(position[0], position[1]),
radius: radius,
strokeColor: option.strokeColor || 'blue',
strokeWeight: option.strokeWeight || 2,
strokeOpacity: option.strokeOpacity || 0.5,
fillColor: option.fillColor || 'blue',
fillOpacity: option.fillOpacity || 0.5,
strokeStyle: 'solid'
})
aMap.value.add(circle)
aMap.value.setFitView([circle])
}
/**
* 渲染
* @param dataArr
* @param option
*/
const renderPolygon = (dataArr, option = {}) => {
const path = [];
dataArr.forEach(d => {
path.push(new AMap.LngLat(d.position[0], d.position[1]))
})
const polygon = new AMap.Polygon({
path: path,
strokeColor: option.strokeColor || 'blue',
strokeWeight: option.strokeWeight || 2,
strokeOpacity: option.strokeOpacity || 0.5,
fillColor: option.fillColor || 'blue',
fillOpacity: option.fillOpacity || 0.5,
strokeStyle: 'solid'
})
aMap.value.add(polygon)
aMap.value.setFitView([polygon])
}
/**
* 渲染 信息窗体
* @param dataArr
*/
const renderInfoWindow = (dataArr) => {
dataArr.forEach(d => {
aMapInfoWindowObj.value[d.position] = new AMap.InfoWindow({
//
content: d.content.join('<br>'),
//
offset: new AMap.Pixel(0, -20),
//
closeWhenClickMap: true
})
})
}
/**
* 打开 信息窗体
* @param position
*/
const openInfoWindow = (position) => {
const infoWindow = aMapInfoWindowObj.value[position]
if (infoWindow) {
infoWindow.open(aMap.value, position)
}
}
/**
* 清理 覆盖物
*/
const clearOverlay = () => {
aMap.value.clearMap()
}
onMounted(() => {
init()
})
onUnmounted(() => {
aMap.value && aMap.value.destroy()
})
defineExpose({
renderMarker,
renderCircleMarker,
renderSimpleMarker,
renderAwesomeMarker,
renderPolyline,
renderCircle,
renderPolygon,
renderInfoWindow,
openInfoWindow,
clearOverlay
})
</script>
<style lang="less">
.aMap {
padding: 0;
margin: 0;
width: 100%;
height: 800px;
input[type=radio] {
-webkit-appearance: radio;
}
input[type=checkbox] {
-webkit-appearance: checkbox;
}
}
</style>

@ -0,0 +1,143 @@
<template>
<a-map ref="map" api-key="87528cfa68513cbc7574ff94704664fc" @complete="handleComplete"
@marker-click="handleMarkerClick"></a-map>
</template>
<script setup name="exmAMap">
import AMap from '@/components/Map/aMap/index.vue'
const map = ref(null)
const handleComplete = () => {
console.log('complete')
//
map.value.renderMarker(
[
{
position: [116.39, 39.9],
title: 'TA',
content: 'CA',
label: {
content: 'LCA'
}
},
{
position: [116.33, 39.5],
title: 'TB',
icon: '//vdata.amap.com/icons/b18/1/2.png'
}
]
)
//
// map.value.renderCircleMarker(
// [
// {
// position: [116.39, 39.9],
// radius: 30,
// strokeColor: 'green',
// fillColor: 'green'
// },
// {
// position: [116.33, 39.5],
// radius: 10,
// strokeColor: 'orange',
// fillColor: 'orange'
// }
// ]
// )
//
// map.value.renderSimpleMarker(
// [
// {
// position: [116.39, 39.9],
// label: 'A',
// labelStyle: {
// color: '#333',
// fontSize: '15px'
// },
// style: 'green'
// },
// {
// position: [116.33, 39.5],
// label: 'B',
// labelStyle: {
// color: '#555',
// fontSize: '15px'
// },
// style: 'orange'
// }
// ]
// )
//
// map.value.renderAwesomeMarker(
// [
// {
// position: [116.39, 39.9],
// awesomeIcon: 'address-book-o',
// labelStyle: {
// color: '#333',
// fontSize: '15px'
// },
// style: 'green'
// },
// {
// position: [116.33, 39.5],
// awesomeIcon: 'anchor',
// labelStyle: {
// color: '#333',
// fontSize: '15px'
// },
// style: 'orange'
// }
// ]
// )
//
// map.value.renderPolygon(
// [
// {
// position: [116.39, 39.9]
// },
// {
// position: [116.47, 39.8]
// },
// {
// position: [116.46, 39.7]
// },
// {
// position: [116.35, 39.6]
// }
// ]
// )
//
map.value.renderInfoWindow(
[
{
position: [116.39, 39.9],
content: [
"<div style='padding:0'><b>Snowy-小诺开源技术</b>",
"网站 : https://www.xiaonuo.vip",
"Snowy是一款国内首例国产密码算法加密框架采用Vue3.0+AntDesignVue3.0+SpringBoot2.8前后分离技术打造,技术框架与密码的结合,让前后分离‘密’不可分!</div>"
]
},
{
position: [116.33, 39.5],
content: [
"<div style='padding:0'><b>Snowy-小诺开源技术</b>",
"网站 : https://www.xiaonuo.vip",
"Snowy是一款国内首例国产密码算法加密框架采用Vue3.0+AntDesignVue3.0+SpringBoot2.8前后分离技术打造,技术框架与密码的结合,让前后分离‘密’不可分!</div>"
]
}
]
)
}
const handleMarkerClick = (position) => {
map.value.openInfoWindow(position)
console.log('marker click', position)
}
</script>
<style lang="less">
</style>
Loading…
Cancel
Save