微信小程序 Map 组件点聚合里的弯弯绕绕
微信小程序 Map 组件点聚合里的弯弯绕绕
总所周知,微信小程序的文档看了等于没看,它总能很巧妙的避开你会遇到的问题,所以很多时候只能你自己慢慢的去摸索(人工适配)。
本文为我在开发时遇到各种问题的总结,也会尝试带你实现 Map 组件的点聚合功能。
解决 Map 组件的类型定义
!在 Typescript 5.1.3 中,此定义无效
<map></map>
组件默认是 html 中用于定义一个图像映射(一个可点击的链接区域)的标准组件,详见 MDN - <map>。
这在没有使用 ts 开发的项目中不会产生什么问题,
但在诸如 uni-app-ts-vue3 等 ts 项目中,map
组件可能就会导致类型检查错误(一般来说都是由于 vue vscode 插件 volar
产生的,建议在出现问题的时候切换版本为 v1.2.0 发布版本)
那么我们就需要扩展 map
组件为小程序中应有的类型定义。
从微信文档中定义 Map 组件
虽然吐槽了微信小程序的文档,但开发的过程却也逃不出它的魔爪…
我在 npm 上检索了一下,没有发现有现有的类型 package,所以就只能手写加一些 HTML 数据处理(dom.query)了。 最终得到了以下的类型定义,虽然不是全部,但实现点聚合应该是没有遗漏了:
import type { SVGAttributes } from 'vue';
export interface Point {
latitude: number;
longitude: number;
}
export interface MarkerCallout {
/** 文本 */
content?: string;
/** 文本颜色 */
color?: string;
/** 文字大小 */
fontSize?: number;
/** 边框圆角 */
borderRadius?: number;
/** 边框宽度 */
borderWidth?: number;
/** 边框颜色 */
borderColor?: string;
/** 背景色 */
bgColor?: string;
/** 文本边缘留白 */
padding?: number;
/** 'BYCLICK':点击显示; 'ALWAYS':常显 */
display?: 'BYCLICK' | 'ALWAYS';
/** 文本对齐方式。有效值: left, right, center */
textAlign?: string;
/** 横向偏移量,向右为正数 */
anchorX?: number;
/** 纵向偏移量,向下为正数 */
anchorY?: number;
}
export interface MarkerCustomCallout {
/** 'BYCLICK':点击显示; 'ALWAYS':常显 */
display?: 'BYCLICK' | 'ALWAYS';
/** 横向偏移量,向右为正数 */
anchorX?: number;
/** 纵向偏移量,向下为正数 */
anchorY?: number;
}
export interface MarkerLabel {
width?: number;
height?: number;
/** 文本 */
content?: string;
/** 文本颜色 */
color?: string;
/** 文字大小 */
fontSize?: number;
/** label的坐标(废弃) */
x?: number;
/** label的坐标(废弃) */
y?: number;
/** label的坐标,原点是 marker 对应的经纬度 */
anchorX?: number;
/** label的坐标,原点是 marker 对应的经纬度 */
anchorY?: number;
/** 边框宽度 */
borderWidth?: number;
/** 边框颜色 */
borderColor?: string;
/** 边框圆角 */
borderRadius?: number;
/** 背景色 */
bgColor?: string;
/** 文本边缘留白 */
padding?: number;
/** 文本对齐方式。有效值: left, right, center */
textAlign?: string;
}
export interface Marker {
/** 标记点 id */
id?: number;
/** 聚合簇的 id */
clusterId?: Number;
/** 是否参与点聚合 */
joinCluster?: Boolean;
/** 纬度 */
latitude: number;
/** 经度 */
longitude: number;
/** 标注点名 */
title?: string;
/** 显示层级 */
zIndex?: number;
/** 显示的图标 */
iconPath?: string;
/** 旋转角度 */
rotate?: number;
/** 标注的透明度 */
alpha?: number;
/** 标注图标宽度 */
width?: number | string;
/** 标注图标高度 */
height?: number | string;
/** 标记点上方的气泡窗口 */
callout?: MarkerCallout;
/** 自定义气泡窗口 */
customCallout?: MarkerCustomCallout;
/** 为标记点旁边增加标签 */
label?: MarkerLabel;
/** 经纬度在标注图标的锚点,默认底边中点 */
anchor?: {
/** x 表示横向(0-1) */
x: number;
/** y 表示纵向(0-1) */
y: number;
};
/** 无障碍访问,(属性)元素的额外描述 */
ariaLabel?: string;
}
export interface TextStyle {
/** 文本颜色 */
textColor?: string;
/** 描边颜色 */
strokeColor?: string;
/** 文本大小 */
fontSize?: number;
}
export interface SegmentText {
/** 名称 */
name?: string;
/** 起点 */
startIndex?: number;
/** 终点 */
endIndex?: number;
}
export interface Polyline {
/** 经纬度数组 */
points: Point[];
/** 线的颜色 */
color?: string;
/** 彩虹线 */
colorList?: string[];
/** 线的宽度 */
width?: number;
/** 是否虚线 */
dottedLine?: boolean;
/** 带箭头的线 */
arrowLine?: boolean;
/** 更换箭头图标 */
arrowIconPath?: string;
/** 线的边框颜色 */
borderColor?: string;
/** 线的厚度 */
borderWidth?: number;
/** 压盖关系 */
level?: string;
/** 文字样式 */
textStyle?: TextStyle;
/** 分段文本 */
segmentTexts?: SegmentText[];
}
export interface Polygon {
/** 边线虚线 */
dashArray?: number[];
/** 经纬度数组 */
points: Point[];
/** 描边的宽度 */
strokeWidth?: number;
/** 描边的颜色 */
strokeColor?: string;
/** 填充颜色 */
fillColor?: string;
/** 设置多边形 Z 轴数值 */
zIndex?: number;
/** 压盖关系 */
level?: string;
}
export interface Circle {
/** 纬度 */
latitude: number;
/** 经度 */
longitude: number;
/** 描边的颜色 */
color?: string;
/** 填充颜色 */
fillColor?: string;
/** 半径 */
radius: number;
/** 描边的宽度 */
strokeWidth?: number;
/** 压盖关系 */
level?: string;
}
export interface Control {
/** 控件id */
id?: number;
/** 控件在地图的位置 */
position: object;
/** 显示的图标 */
iconPath: string;
/** 是否可点击 */
clickable?: boolean;
}
export interface Position {
/** 距离地图的左边界多远 */
left?: number;
/** 距离地图的上边界多远 */
top?: number;
/** 控件宽度 */
width?: number;
/** 控件高度 */
height?: number;
}
export interface MarkerEvent {
detail: {
markerId: number;
};
}
export interface TapEvent {
detail: {
name: string;
longitude: number;
latitude: number;
};
}
export interface ControlEvent {
detail: {
controlId: number;
};
}
export interface RegionChangeBeginEvent {
type: 'begin';
causedBy: 'gesture' | 'update';
detail: {
rotate: number;
skew: number;
scale: number;
centerLocation: number;
region: number;
};
}
export interface RegionChangeEndEvent {
type: 'end';
causedBy: 'drag' | 'scale' | 'update';
detail: {
rotate: number;
skew: number;
scale: number;
centerLocation: number;
region: number;
};
}
export type RegionChangeEvent = RegionChangeBeginEvent | RegionChangeEndEvent;
export interface MapElement extends SVGAttributes {
/** 中心经度 */
longitude: number;
/** 中心纬度 */
latitude: number;
/** 缩放级别,取值范围为3-20 */
scale?: number;
/** 最小缩放级别 */
minScale?: number;
/** 最大缩放级别 */
maxScale?: number;
/** 标记点 */
markers?: Marker[];
/** 路线 */
polyline?: Polyline[];
/** 圆 */
circles?: Circle[];
/** 控件(即将废弃,建议使用 cover-view 代替) */
controls?: Control[];
/** 缩放视野以包含所有给定的坐标点 */
includePoints?: Point[];
/** 显示带有方向的当前定位点 */
showLocation?: boolean;
/** 多边形 */
polygons?: Polygon[];
/** 个性化地图使用的key */
subkey?: string;
/** 个性化地图配置的 style,不支持动态修改 */
layerStyle?: number;
/** 旋转角度,范围 0 ~ 360, 地图正北和设备 y 轴角度的夹角 */
rotate?: number;
/** 倾斜角度,范围 0 ~ 40 , 关于 z 轴的倾角 */
skew?: number;
/** 展示3D楼块 */
enable3D?: boolean;
/** 显示指南针 */
showCompass?: boolean;
/** 显示比例尺,工具暂不支持 */
showScale?: boolean;
/** 开启俯视 */
enableOverlooking?: boolean;
/** 开启最大俯视角,俯视角度从 45 度拓展到 75 度 */
enableAutoMaxOverlooking?: boolean;
/** 是否支持缩放 */
enableZoom?: boolean;
/** 是否支持拖动 */
enableScroll?: boolean;
/** 是否支持旋转 */
enableRotate?: boolean;
/** 是否开启卫星图 */
enableSatellite?: boolean;
/** 是否开启实时路况 */
enableTraffic?: boolean;
/** 是否展示 POI 点 */
enablePoi?: boolean;
/** 是否展示建筑物 */
enableBuilding?: boolean;
/** 配置项 */
setting?: object;
/** 点击地图时触发,2.9.0起返回经纬度信息 */
onTap?: (event: TapEvent) => void;
/** 点击标记点时触发,e.detail = {markerId} */
onMarkertap?: (event: MarkerEvent) => void;
/** 点击label时触发,e.detail = {markerId} */
onLabeltap?: (event: MarkerEvent) => void;
/** 点击控件时触发,e.detail = {controlId} */
onControltap?: (event: ControlEvent) => void;
/** 点击标记点对应的气泡时触发e.detail = {markerId} */
onCallouttap?: (event: MarkerEvent) => void;
/** 在地图渲染更新完成时触发 */
onUpdated?: (event: RegionChangeEvent) => void;
/** 视野发生变化时触发, */
onRegionchange?: (event: RegionChangeEvent) => void;
/** 点击地图poi点时触发,e.detail = {name, longitude, latitude} */
onPoitap?: (event: TapEvent) => void;
/** 点击定位标时触发,e.detail = {longitude, latitude} */
onAnchorpointtap?: (event: TapEvent) => void;
}
export type MapContext = ReturnType<typeof uni.createMapContext>;
export type MapContextAddMarkersOptions = Parameters<MapContext['addMarkers']>[0];
export interface MarkerCluster {
clusters: {
center: Marker;
clusterId: number;
markerIds: number[];
}[];
}
扩展 map
组件为小程序的类型定义
要扩展 map
组件就需要先了解它是怎么被定义的,我们可以通过 Ctrl + 左键查看定义它类型的上下文:
interface IntrinsicElementAttributes {
// ...
link: LinkHTMLAttributes;
main: HTMLAttributes;
map: MapHTMLAttributes;
mark: HTMLAttributes;
menu: MenuHTMLAttributes;
meta: MetaHTMLAttributes;
// ...
}
IntrinsicElementAttributes
这里是所有的内部元素标签的定义,可以看到很多熟悉的元素标签。
不过它并没有 export
,那我们得再看看它在那儿使用了:
type ReservedProps = {
key?: string | number | symbol;
ref?: RuntimeCore.VNodeRef;
ref_for?: boolean;
ref_key?: string;
};
type ElementAttrs<T> = T & ReservedProps;
type NativeElements = {
[K in keyof IntrinsicElementAttributes]: ElementAttrs<IntrinsicElementAttributes[K]>;
};
它在 NativeElements
使用并扩展了 ref
等 Vue Component 扩展类型,这里其实就是 Vue 中所有内部元素的类型实现。
并且它在全局环境 JSX
命名空间中覆盖了默认类型定义:
declare global {
namespace JSX {
interface Element extends VNode {}
interface ElementClass {
$props: {};
}
interface ElementAttributesProperty {
$props: {};
}
// 这里覆盖了默认的 IntrinsicElements
interface IntrinsicElements extends NativeElements {
// allow arbitrary elements
// @ts-ignore suppress ts:2374 = Duplicate string index signature.
[name: string]: any;
}
interface IntrinsicAttributes extends ReservedProps {}
}
}
那么我们扩展 map
组件也是同样的思路,在全局环境 JSX
命名空间中覆盖 map
的定义。
-
创建一个
global.d.ts
文件import type { MapElement } from './models/Map/Core'; declare global { namespace JSX { interface IntrinsicElements { // 扩展小程序内置map组件 map: MapElement; } } }
-
将其导入至 tsconfig.json 中:
{ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.nvue"] }
这样我们就可以在 IDE 中获得小程序的 map 组件的类型定义了。
创建 MapContext
实现点聚合只能通过 MapContext
的方式,我们可以通过 wx.createMapContext
获取,由于我用的是 uniApp,对应的 API 为:uni.createMapContext
。
MapContext
通过 id
跟一个 map
组件绑定,操作对应的 map 组件。
<script setup lang="ts">
// createMapContext的第一个参数为对应的 map id,在这里即为 centerMap
const mapCtx = ref<MapContext>();
onMounted(() => {
mapCtx.value = uni.createMapContext('centerMap');
});
</script>
<template>
<map id="centerMap" />
</template>
这里有个小坑,当 map
不是在页面直接使用,而是在某个组件中使用时,uni.createMapContext
将返回 null
!
所以我们需要把它,也只能把它放到页面中来使用,这在封装 hooks 的时候也需要注意。
创建 useMapContext hooks
我们知道 vue3 推出了 组合式 API(Composition API),这让我们很容易拆离部分逻辑代码。
那我们就可以将 uni.createMapContext
相关的逻辑独立出去,让组件内部的代码更加简洁。
lib/hooks/map.ts
/**
* 创建MapContext
* @param mapId Map组件的id
*/
export function useMapContext(mapId: string) {
// MapContext 实例
const mapCtx = ref<MapContext>();
// 在 map 组件渲染后创建 Context
onMounted(() => {
mapCtx.value = uni.createMapContext(mapId);
if (!mapCtx.value) {
console.error('请检查是否为组件内部调用,请将其添加至页面内部');
}
});
return {
mapCtx,
};
}
在组件中使用也很方便:
<script setup lang="ts">
const { mapCtx } = useMapContext('centerMap');
</script>
<template>
<map id="centerMap" />
</template>
这样我们就完成了 MapContext
的创建,并很好的将其作为 hooks 独立在页面代码之外了。
实现点聚合
点聚合功能是基于图层标记 Markers
的,它是 Markers
过多时的一种优化方式。
在用户进行平移、缩放等操作时,动态的计算可视区域的 Markers
图层,其中的算法实现感兴趣的同学可以看看这篇文章 地图兴趣点聚合算法的探索与实践
封装操作 Markers
工具与点聚合初始化
我们在点聚合初始化之前可以做一些预备工作,封装一些与 Markers
相关的工具方法
lib/hooks/map.ts
export function useMapContext() {
// ...
// 用于记录 marker
const markerMap = ref(new Map<number, Marker>());
/**
* 添加多个 marker
* @param options 选项
*/
const addMarkers = (options: Omit<MapContextAddMarkersOptions, 'success' | 'fail'>) => {
return new Promise<{ errMsg: string }>((resolve, reject) => {
if (!mapCtx.value) reject('mapContext 不存在,请检查函数调用情况!');
else {
mapCtx.value.addMarkers({ ...options, success: resolve, fail: reject });
options.markers.forEach(marker => markerMap.value.set(marker.id as number, marker));
}
});
};
/**
* 移除指定的多个 marker
* @param options 选项
*/
const removeMarkers = (options: { markerIds: number[] }) => {
return new Promise<{ errMsg: string }>((resolve, reject) => {
if (!mapCtx.value) reject('mapContext 不存在,请检查函数调用情况!');
else if (options.markerIds.some(id => !markerMap.value.has(id))) reject('markerId 不存在!');
else {
options.markerIds.forEach(id => markerMap.value.delete(id));
mapCtx.value.removeMarkers({ ...options, success: resolve, fail: reject });
}
});
};
/**
* 修改指定的 marker
* @param options 选项
*/
const modifyMarker = async (options: { markerId: number; marker: Partial<Omit<Marker, 'id'>> }) => {
if (!mapCtx.value) throw new Error('mapContext 不存在,请检查函数调用情况!');
const mm = unref(markerMap);
if (!mm.has(options.markerId)) throw new Error('markerId 不存在!');
const modifiedMarker = { ...mm.get(options.markerId), ...options.marker } as Marker;
await addMarkers({ markers: [modifiedMarker], clear: false });
mm.set(options.markerId, modifiedMarker);
return modifiedMarker;
};
/**
* 查找指定的 marker
* @param markerId markerId
*/
const findMarker = (markerId: number) => {
return markerMap.value.get(markerId);
};
// ...
}
有了工具,我们再封装 useMarkerCluster
hooks,独立点聚合方法并实现初始化:
lib/hooks/map.ts
/**
* Map点聚合
* @param mapId Map组件的id
*/
export function useMarkerCluster(mapId: string) {
const { mapCtx, addMarkers, ...othersTools } = useMapContext(mapId);
const addMarkersUseJoin = (markers: Marker[]) => {
return addMarkers({ markers: markers.map(item => ({ ...item, joinCluster: true })), clear: true });
};
onMounted(() => {
// 初始化聚合
mapCtx.value?.initMarkerCluster({
enableDefaultStyle: false,
zoomOnClick: true,
gridSize: 60,
complete: res => {
console.log('initMarkerCluster', res);
},
});
});
return {
mapCtx,
addMarkers: addMarkersUseJoin,
...othersTools,
};
}
注册聚合点创建事件
当用户的操作触发了聚合事件后,会同步触发 markerClusterCreate
事件,在这里我们需要添加新的 Markers
以实现聚合功能。
在 MapContext
上注册 markerClusterCreate
事件:
lib/hooks/map.ts
import MapMarkerClusterPNG from '@/static/map-marker-cluster.png';
// ...
export function useMarkerCluster(mapId: string) {
// ...
onMounted(() => {
// ...
// 注册聚合创建事件
mapCtx.value?.on('markerClusterCreate', async (res: MarkerCluster) => {
const clusters = res.clusters;
if (!clusters || !clusters.length) return;
// 添加聚合点
await addMarkers({
markers: clusters.map(({ center, clusterId, markerIds }) => ({
...center,
// 聚合点大小
width: 33,
height: 38,
clusterId,
// 你的聚合点 icon
iconPath: MapMarkerClusterPNG,
})),
clear: false,
});
});
});
// ...
}
在添加聚合点时有个大坑!重要的事情说三遍!
不能修改原有的 marker id
,否则会在 android
平台出现无法更新 Markers
的情况。
不能修改原有的 marker id
,否则会在 android
平台出现无法更新 Markers
的情况。
不能修改原有的 marker id
,否则会在 android
平台出现无法更新 Markers
的情况。
marker label 样式渲染器
为了方便渲染样式,我们可以在使用 useMarkerCluster
的时候传入一个自定义的 label 样式渲染器,这样可以减少编写样式的样板代码。
lib/hooks/map.ts
// 默认聚合点的样式渲染器
const DEFAULT_CLUSTER_RENDERER = (count: number): MarkerLabel => ({
fontSize: 17,
textAlign: 'center',
color: '#fff',
content: count.toString(),
anchorY: -33,
});
/**
* Map点聚合
* @param mapId Map组件的id
* @param rendererCluster 聚合点的样式渲染器
*/
export function useMarkerCluster(mapId: string, rendererCluster = DEFAULT_CLUSTER_RENDERER) {
const rendererClusterRef = ref(rendererCluster);
// ...
onMounted(() => {
// ...
// 注册聚合创建事件
mapCtx.value?.on('markerClusterCreate', async (res: MarkerCluster) => {
// ...
await addMarkers({
markers: clusters.map(({ center, clusterId, markerIds }) => ({
// ...
// 使用样式渲染器渲染 label
label: rendererClusterRef.value(markerIds.length),
})),
});
});
});
// ...
}
在页面中使用
这里写一个简单的例子给大家参考:
<script setup lang="ts">
// 地图点聚合与标点
const { addMarkers, modifyMarker, mapCtx, findMarker } = useMarkerCluster('centerMap');
// 点击 marker 事件处理
const handleMarkerTap = ({ detail: { markerId } }: MarkerEvent) => {
// 查找 Marker
const marker = findMarker(markerId) as Marker;
console.log('OldMarker ->', marker);
// 修改 Marker
await modifyMarker({ markerId, marker: { width: 46, height: 52 } });
// 移动视图中心至 marker 的位置
mapCtx.value?.moveToLocation({
longitude: marker.longitude,
latitude: marker.latitude,
});
};
// 获取 Markers
const getMarkers = async () => {
const yourMapMarkers = await fetchMarkers();
console.log('GetMockMarkers', await addMarkers(yourMapMarkers));
};
getMarkers();
</script>
<template>
<map id="centerMap" @markertap="handleMarkerTap" />
</template>
Android 平台的适配
小程序的 map
组件在 Android 平台和 ios 的实现有着极大的差异,就如上文所提到的 修改marker id
导致无法更新 Markers
的情况。
还有一些问题也是只在 Android 平台才会出现,我们可以通过 uni.getSystemInfoSync
方法获取平台信息,这里我总结了一些遇到的问题方便各位 debug 。
获取平台信息
const { platform } = uni.getSystemInfoSync();
// Android 平台
const isAndroidPlatform = platform === 'android';
modifyMarker 需要移除原有的 marker
在 Android 平台不能通过 addMarkers
相同 id 的 marker 实现更新,这样会导致存在两个重叠的 marker。
所以我们需要主动移除对应的 marker :
lib/hooks/map.ts
// ...
/**
* 修改指定的 marker
* @param options 选项
*/
const modifyMarker = async (options: { markerId: number; marker: Partial<Omit<Marker, 'id'>> }) => {
if (!mapCtx.value) throw new Error('mapContext 不存在,请检查函数调用情况!');
const mm = unref(markerMap);
if (!mm.has(options.markerId)) throw new Error('markerId 不存在!');
const modifiedMarker = { ...mm.get(options.markerId), ...options.marker } as Marker;
// Android 平台需要主动移除对应的 marker
if (isAndroidPlatform) removeMarkers({ markerIds: [options.markerId] });
await addMarkers({ markers: [modifiedMarker], clear: false });
mm.set(options.markerId, modifiedMarker);
return modifiedMarker;
};
// ...
label 的 anchorX 坐标差异
在 Android 平台上,marker 的坐标与 ios 的实现不同,它们的差异大概是宽度的一半。
如果你需要调整 label anchorX
就需要做一些适配的工作:
lib/hooks/map.ts
// ...
// 默认聚合点的样式渲染器
const DEFAULT_CLUSTER_RENDERER = (count: number, isAndroidPlatform): MarkerLabel => ({
width: 33,
height: 38,
fontSize: 17,
textAlign: 'center',
color: '#fff',
content: count.toString(),
anchorY: -33,
// 差异为宽度的一半,你需要减去这些才能与 ios 保持一直
anchorX: isAndroidPlatform ? -17 : 0,
});
// ...
Map 组件中遇到的问题
顺便记录在使用 Map 组件中遇到的一些其他问题。
scale 的计算与显示
如果你的地图需要展示用户缩放的情况,并且需要让它可以动态的调整大小,你写的代码可能是这样的:
<script setup lang="ts">
// 缩放
const scale = ref(12);
// 修改 Scale
const handleChangeScale = (add = true) => {
// 设置 scale 的最大、最小值
scale.value = Math.max(Math.min(scale.value + (add ? 1 : -1), 20), 3);
};
// 处理RegionChange事件
const handleRegionChange = (env: RegionChangeEvent) => {
if (env.type === 'end' && env.causedBy === 'scale') {
scale.value = Math.round(env.detail.scale);
}
};
</script>
<template>
<map id="centerMap" :scale="scale" @regionchange="handleRegionChange" />
</template>
这里就出现了一个 bug !在监听 map regionChange
事件时,直接修改 scale
会导致用户的视图重新定位到中心位置。
那怎么解决呢?我们可以定义一个只供展示的 showScale
避免这种情况:
<script setup lang="ts">
// 缩放
const scale = ref(12);
const showScale = ref(12);
// 修改 Scale
const handleChangeScale = (add = true) => {
// 设置 scale 的最大、最小值
// scale.value = Math.max(Math.min(scale.value + (add ? 1 : -1), 20), 3);
// 修改时使用 showScale 的值
scale.value = Math.max(Math.min(showScale.value + (add ? 1 : -1), 20), 3);
// 同步 showScale
showScale.value = scale.value;
};
// 处理RegionChange事件
const handleRegionChange = (env: RegionChangeEvent) => {
if (env.type === 'end' && env.causedBy === 'scale') {
// scale.value = Math.round(env.detail.scale);
// 仅修改 showScale
showScale.value = Math.round(env.detail.scale);
}
};
</script>
<template>
<map id="centerMap" :scale="scale" show-scale @regionchange="handleRegionChange" />
</template>
getLocation 的授权检查
如果你需要获取用户的定位信息,直接调用 uni.getLocation
是会提示授权失败的,所以我们需要先检查用户的授权情况。
实现的代码如下:
<script setup lang="ts">
// 获取用户地理位置
const latitude = ref(0);
const longitude = ref(0);
async function getLocation() {
try {
// 请求授权,如果用户未授权则会报错,这时就需要我们提示用户开启授权
await uni.authorize({ scope: 'scope.userLocation' });
const res = await uni.getLocation({});
latitude.value = res.latitude;
longitude.value = res.longitude;
} catch {
const { confirm } = await uni.showModal({
title: '提示',
content: '请授权地理位置',
});
if (!confirm) {
console.log('用户取消了授权');
return;
}
const { authSetting } = await uni.openSetting();
// 当 authSetting 不存在 scope.userLocation 时,代表用户没有同一授权
if (!('scope.userLocation' in authSetting)) console.log('用户取消了授权');
}
}
</script>
<template>
<!-- show-location是展示定位点的必要参数 -->
<map id="centerMap" show-location />
</template>
总结
这篇文章主要是记录了我在使用小程序的 map
组件实现点聚合功能时遇到的一些问题。
可以看出写代码还是不能停留在文档,得多实践,才能感受到双重的折磨~~~
这里给大家提供一个附带工程化的 uniApp vue3 的 vite template:vite-uniapp-template
如果你想在 vscode 中支持 uniApp nvue
文件的开发适配,可以参考我的这篇文章
实现在 VSCode 中无痛开发 nvue:语法高亮、代码提示、eslint 配置及插件 Patch。
输出文章不易,希望大家能多多收藏、评论与点赞!谢谢大家!