微信小程序 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。
输出文章不易,希望大家能多多收藏、评论与点赞!谢谢大家!
Siykt的博客