更新记录

1.0.0(2026-10-30) 下载此版本

  • 从 components/custom-map 同步为独立的 uni_modules 通用组件 jf-map。
  • 保留标记、聚合、告警、配置、事件、插槽和实例方法。
  • 默认图标放入插件 static 目录,使用相对路径引用,支持 easycom 自动引入。

平台兼容性

uni-app(5.01)

Vue2 Vue2插件版本 Vue3 Vue3插件版本 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
√ 1.0.0 √ 1.0.0 - - - - - - -
微信小程序 微信小程序插件版本 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
3.0.2 1.0.0 - - - - - - - - - - -

JfMap

uni-app Vue 3 地图组件。目录包含实现、默认配置、状态图标、聚合与告警逻辑,不依赖首页示例数据。当前验证目标为微信小程序;H5/App 的地图供应商配置、事件与覆盖物支持以各平台为准。本次迁移仅通过 Node 模拟环境回归,尚未完成 HBuilderX 编译和真机验收,package.json 的各运行平台暂标为 -(不确定)。不支持 Vue 2、nvue 和 uni-app x。

需求与官方规范

本次将原 components/custom-map 的能力同步为可整体复制到其他项目的独立插件,插件 ID、标签及组件文件名统一为 jf-map,保留原 Props、事件、插槽和实例方法。通用组件不引入首页数据、页面控件或项目级配置;原组件保留,后续修改不会自动双向同步。

按 uni_modules 官方规范,插件 ID 使用作者前缀加组件名,jf-map 符合命名格式;插件必须有 package.json,图片等静态资源必须放在插件 static 目录,插件内部尽量使用相对路径。

按 easycom 官方规范,组件路径为 uni_modules/插件ID/components/组件名/组件名.vue,即可自动引入。目录如下:

uni_modules/jf-map/
├── components/jf-map/
│   ├── jf-map.vue
│   ├── defaults.js
│   ├── markers.js
│   ├── clusters.js
│   └── alarm.js
├── static/                 # 四种状态 PNG 图标
├── package.json
├── readme.md
├── changelog.md
└── license.md

将整个 jf-map 目录复制到目标项目的 uni_modules 下,直接使用 <jf-map>,无需 import 或 components 注册,也无需额外配置 easycom。若宿主关闭了自动扫描,请恢复 easycom.autoscan: true 或手动引入 @/uni_modules/jf-map/components/jf-map/jf-map.vue。本组件无 npm 或其他 uni_modules 依赖。

组件内默认图标通过 ../../static/*.png 引用,不依赖宿主项目 /static/markers。可调参数继续在 components/jf-map/defaults.js 集中维护,并通过 mapOptions、clusterOptions、alarmOptions、statusStyles、nameOptions、pollIntervalMs 等 Props 覆盖;本次没有新增环境配置、业务阈值或密钥。

从旧组件接入时,将 <custom-map> 改成 <jf-map>,删除旧组件 import 和注册;自定义交互类名相应改为 jf-map-interactive。首页已经按此方式接入。其余数据和事件无需变更。

按 原生 map 官方文档,地图平台能力、地图服务 Key、定位权限及隐私声明由宿主按目标平台配置。插件内不放置 manifest.json、pages.json 或地图 Key;resetCenter() 不申请定位,showLocation 默认关闭。发布插件市场前还应完成目标平台编译与真机测试,并补齐发布者信息和平台兼容性声明。

基本使用

<template>
  <view class="page">
    <jf-map ref="map" :markers="devices" :center="initialCenter" :scale="12"
      :active="pageActive" fit-on-ready
      @marker-click="onMarkerClick" @map-click="onMapClick"
      @cluster-click="onClusterClick" @error="onMapError">
      <template #head>
        <!-- 正常占据高度的头部,父组件自行放置标签页等 -->
        <view>地图头部</view>
      </template>
      <template #top>
        <view class="top-controls">
          <!-- 任意输入、筛选控件 -->
        </view>
      </template>
      <template #bottom>
        <view class="bottom-controls">
          <!-- 整个底部区域由父组件安排左右、上下布局 -->
          <button @tap="$refs.map.resetCenter()">归位</button>
        </view>
      </template>
    </jf-map>
  </view>
</template>

<script>
export default {
  data() {
    return {
      pageActive: true,
      initialCenter: { latitude: 39.909, longitude: 116.39742 },
      devices: [{ id: 'device-1', latitude: 39.909, longitude: 116.39742,
        name: '设备名称', status: 'alarm', showName: false,
        iconPath: '/static/device.png', width: 28, height: 34,
        extra: { deviceId: 'device-1', owner: '业务数据' } }]
    }
  },
  onShow() { this.pageActive = true },
  onHide() { this.pageActive = false },
  methods: {
    onMarkerClick({ marker, originalEvent }) { /* marker.extra 为完整业务记录 */ },
    onMapClick({ latitude, longitude, originalEvent }) { /* 点击地图空白区域 */ },
    onClusterClick({ cluster, memberIds, members, originalEvent }) { /* members 每项均含 extra */ },
    onMapError({ source, error, originalEvent }) { /* 错误展示由父组件处理 */ }
  }
}
</script>

<style scoped>
.page { height: calc(100vh - var(--window-top) - var(--window-bottom)); }
.top-controls { margin: 12px; pointer-events: none; }
.bottom-controls { display: flex; justify-content: flex-end; padding: 12px; pointer-events: none; }
.bottom-controls button { pointer-events: auto; }
</style>

组件必须放在有明确高度的容器中。三个插槽均可省略,不占默认空白,也不内置按钮、输入框、标签页或业务弹窗。

插槽

名称 位置 约定
head 地图上方 独立占据高度,地图填充剩余空间
top 地图内部顶部 全宽区域,父组件自行设置间距和内容
bottom 地图内部底部 完整全宽区域,父组件控制左右上下布局

浮层与插槽区域自身 pointer-events: none,空白区域可继续拖拽、缩放和点击地图。原生 input/button 等常见控件会开启交互;自定义可点击 view/第三方控件请在父组件中显式设置 pointer-events: auto(可添加 jf-map-interactive 类)。不要给整个全屏布局设置交互或拦截 touchmove。

插槽提供 center、scale 和 actions 作用域参数;actions 包含下文操作方法。小程序插槽传递函数受平台编译限制,跨平台代码推荐统一通过 ref 调用方法,不依赖函数型插槽参数。

标记数据

  • id:必填、唯一、稳定的字符串或有限数字。内部原生数字 ID 与业务 ID 隔离,允许业务负数 ID。
  • latitude/longitude:必填数字,范围分别为 ±90、±180;微信国内地图使用 GCJ-02 坐标。
  • name:完整名称;兼容 title,优先使用 name。
  • showName:仅为 true 时显示名称卡片,默认 false。卡片使用单行显示,长名称按实际字体测量后省略为 …,事件中的完整名称不会被截断。nameOptions.maxWidthPx 包括左右 padding。
  • status:默认支持 alarm/normal/scrapped/stopped;可通过 statusStyles 增加状态、调整默认图标和尺寸。
  • iconPath/width/height/anchor/rotate/alpha/zIndex/label/callout/ariaLabel/collisionRelation/level:支持的原生标记绘制参数。外部标记参数优先于状态默认样式;callout 仍受 showName 和名称省略规则管理。customCallout 与原生聚合字段不传给地图,以免绕过名称隐藏及组件聚合机制。
  • extra:任意业务记录,原样保留、不修改;未传时以原始标记的浅拷贝作为 extra。事件的 marker/members 同时保留其他自定义业务字段。

markers 支持响应式新增、删除、换数组和修改内部字段。组件不修改父组件数据。非法/重复 ID 或非法坐标的项被跳过,同时触发 error。数据更新不重置当前视野。

图片限制

推荐透明 PNG/JPG,并显式指定显示 width/height(px);未指定尺寸时使用默认 28×34px。远程图片应使用目标平台可访问的 HTTPS 地址;域名、证书、鉴权和缓存规则按平台配置。SVG、GIF、Base64 不作为跨平台保证格式;动画由原生圆形覆盖物绘制,不依赖 GIF。图片下载、解码等 SDK 错误以原生地图能够上报的内容为准。

聚合图标由组件隐藏 canvas 按实际数量动态生成 PNG,数量不受预生成图片限制,文字按图标宽度自适应;可配置聚合颜色、字体和尺寸。临时图标按样式与数量缓存,每实例缓存数量有上限。canvas 失败时发出 error(source: 'cluster-icon') 并降级显示独立地点,避免无数字聚合点或旧标记残留。不要给组件 canvas 设置 display: none。

Props

参数 默认值/作用
markers [],完整动态标记数据
center { latitude: 0, longitude: 0 },初始中心及归位目标;改变此 prop 会请求移动视野
scale 12,初始缩放;改变此 prop 会请求缩放
fitOnReady false,首次就绪时适配所有有效标记,仅执行一次
active true,是否执行缩放核对和告警动画;页面有显隐生命周期时仍会自动暂停/恢复,小程序之外也建议父页面显式绑定
pollIntervalMs 200,实际视野核对间隔(正数毫秒),兼容事件缺失或结束事件携带旧缩放值
mapOptions 原生地图选项,如 showLocation/enableZoom/enableScroll/minScale/maxScale/polyline/circles;组件管理的坐标、缩放、标记及 includePoints 不会被覆盖,外部 circles 会与告警圆环合并
statusStyles 按状态名合并默认标记样式;未知状态未指定图标时使用默认普通图标
nameOptions 默认 { maxWidthPx: 180, fontSize: 12, padding: 6, borderRadius: 4, color: '#333333', bgColor: '#FFFFFF', display: 'ALWAYS' }
clusterOptions 聚合开关、距离、展开及图标配置,见下文
alarmOptions 动画开关、目标状态、圈数、颜色与速度,见下文

以下均为局部覆盖,未传字段使用 defaults.js:

clusterOptions: {
  enabled: true, gridSize: 60, minimumClusterSize: 2, maxZoom: 18,
  autoExpand: true, expandPadding: [60, 60, 60, 60],
  size: 40, color: '#3B82F6', textColor: '#FFFFFF', fontSize: 14,
  borderColor: '#FFFFFF', borderWidth: 2, textPadding: 5,
  pixelRatio: 2, iconCacheSize: 64
}
alarmOptions: {
  enabled: true, status: 'alarm', color: '#DC2626', ringCount: 3,
  durationMs: 3000, frameIntervalMs: 50, fadeInFraction: 0.15,
  minRadiusPx: 4, maxRadiusPx: 40, strokeWidth: 3,
  strokeOpacity: 0.95, fillOpacity: 0.12
}

配置中的尺寸、周期和间隔必须为合法正数,minimumClusterSize 至少为 2,ringCount 为正整数,fadeInFraction 在 (0,1],透明度在 [0,1],告警 color 使用 #RRGGBB。告警圈以屏幕像素换算实际半径,随缩放保持视觉大小;只在独立告警点显示,聚合后移除,展开后恢复。

无效配置会触发 error(source: 'configuration'),暂停新的绘制/动画启动;修正配置后恢复。不会以零间隔创建动画循环。

聚合按屏幕距离连通合并,支持聚合结果继续合并。当前算法 O(n²),适用于中小规模,不保证无限标记数量的实时性能;千级及以上应结合目标真机压力测试,大量密集点可替换 clusters.js 的空间索引算法。支持任意聚合数量展示不等于无限性能承诺。

事件

所有事件均有 originalEvent:原生回调事件保持原对象;轮询、数据变化或组件内部触发时为 null。这类通知另带 source,不会伪造原生事件。

事件 补充字段
ready source: 'ready', context,可用的原生 MapContext
map-click / map-longpress latitude, longitude
marker-click / callout-click / label-click markerId(业务 ID),marker(完整记录,含 extra)
cluster-click cluster, memberIds, members,cluster 含 id、经纬度、count、memberIds、members
region-change / region-begin / region-end phase, detail, center, scale;center/scale 是最近实际读数,原始 detail 可能尚未完成动画,可靠缩放请监听 scale-change
scale-change scale, previousScale, source,以 SDK 实际缩放值为准
center-change center, previousCenter, source
clusters-change source, groups, clusters, merged, split;成员分组变化时触发,groups 同时包含独立点
cluster-merge / cluster-split 同上;多组并为一组/旧聚合分到多组时分别触发,初始结果只触发 clusters-change
updated 原生渲染更新
error source, error
control-click / poi-click / polyline-click / anchorpoint-click 原生事件在 originalEvent 中,支持情况取决于目标平台

事件监听均可省略,不会替换或停止内部逻辑。若父组件自行处理聚合点击,请设置 clusterOptions.autoExpand: false。完全相同坐标的成员无法通过放大分开,只发出聚合点击事件,由父组件决定展示列表等操作。

实例方法

通过 this.$refs.map 调用:

  • resetCenter():回到 center prop 所指定的中心,保留缩放;不请求用户 GPS 定位权限。
  • setCenter({ latitude, longitude }):移动视野;返回 Promise 表示坐标绑定已提交,不保证原生动画已经结束。
  • setScale(scale):请求缩放;实际完成请监听 scale-change。
  • fitPoints(points?, padding?):适配多个地点,默认使用当前 markers 和 clusterOptions.expandPadding。
  • refresh():手动重新计算标记;一般无需调用,props 的深度变化已监听。
  • getClusters():取得当前聚合点及完整成员。
  • getMapContext():获取原生上下文;不要用 addMarkers/initMarkerCluster 绕过组件管理标记。

示例首页传入 12 个示例地点,默认隐藏名称;通过 head、top、bottom 插槽分别传入标签页、输入框和归位按钮,布局与交互由首页管理。控件文案和标签配置位于 common/home-map.js。验证命令:node tests/jf-map.test.cjs。

异步请求在首次 ready 后才返回标记时,如需自动适配这批地点,请在数据更新后调用一次 fitPoints();后续业务数据刷新不会自动打断用户正在查看的视野。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

默认不请求定位权限;启用 mapOptions.showLocation 时,宿主应用需按目标平台配置定位权限及隐私声明。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

组件本身不主动采集或上传业务数据;原生地图服务的数据处理以目标平台及地图供应商的隐私政策为准。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

许可协议

MIT License

Copyright (c) 2018 DCloud

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

暂无用户评论。