mapv-sdk 使用教程(宿主接入入门)
面向宿主工程开发者的入门教程:介绍如何在自己的 Vue 3 项目里安装、配置并使用
mapv-sdk,给出最小可运行示例与宿主扩展示例。所有代码与仓库内example/最小示例工程一一对应。
目录
- SDK 是什么
- 使用环境(前置条件)
- 安装
- 快速开始(最小示例)
- 配置方法
- 组件 API:
MapvLayers - 对外导出清单
- 宿主扩展面(不改 SDK 源码能做什么)
- 完整示例工程结构
- 更新 SDK 依赖(宿主侧)
- 常见问题与踩坑
- 已知限制(未完成口子)
1. SDK 是什么
mapv-sdk 是一个 Vue 3 npm 包,封装了「地图编辑器」的图层可视化主页面。宿主工程安装后,只需挂一个组件即可复用整张可视化地图,并借助它暴露的 props / 事件 / store / 组合式函数做个性化扩展。
SDK 已覆盖的核心可视化能力:
- 底图切换(高德 / 天地图 / OSM)
- 图层树与图例(连续渐变 / 离散 / 尺寸 / 内联图例)
- 专题图(5 类)
- 空间查询(画圆 / 多边形 / 手绘面 / 行政区 / 等时圈 / 测距测面积)
- 时间轴、属性表、格网栅格、迁徙图、点线面文字标注、全景图、POI 搜索、城市选择
核心组件是 MapvLayers(props 化,不依赖 vue-router):宿主用 props 传 eId/mapId 即可渲染整张地图,无需自己写路由和地图初始化。
2. 使用环境(前置条件)
2.1 运行时技术栈
SDK 是 Vue 3 + Ant Design Vue 4 的组件包,宿主工程必须满足:
| 项 | 要求 |
|---|---|
| 前端框架 | Vue 3.5+(Composition API) |
| UI 组件库 | Ant Design Vue 4.x(宿主自己 app.use(Antd) 提供样式,见下) |
| 状态管理 | Pinia 2.x + pinia-plugin-persistedstate |
| 构建工具 | Vite(示例用 5.4;SDK 产物是 ES Module,需宿主支持 ESM) |
| 语言 | TypeScript(SDK 附带 index.d.ts 类型声明) |
| Node | 建议 ≥ 18(示例工程按 Vite 5 要求) |
2.2 后端依赖
SDK 只负责前端渲染,数据来自后端 datam 服务。示例工程里:
- 后端默认地址:
http://localhost:3001 - 前端开发时经 Vite 代理
/datam → localhost:3001(见示例vite.config.ts) - 后端需能响应
auth/maptree(拉地图/图层配置)、data/query(查图层数据)、字典、快照、附件等接口
2.3 浏览器
- 现代浏览器即可(Chrome / Edge / Safari / Firefox 近期版本)
- 渲染使用 Leaflet 1.9 + 海量点走 canvas,无需 WebGL 硬性要求(3D Tiles 除外)
3. 安装
SDK 以 npm 包形式提供(当前为本地 tarball,file: 引用):
# 宿主工程安装 tarball
npm install /path/to/mapv-sdk-1.0.0.tgz
宿主 package.json 里的引用方式(等价于上面的手动安装):
{
"dependencies": {
"mapv-sdk": "file:./mapv-sdk-1.0.0.tgz"
}
}
3.1 peer 依赖(宿主必须同时安装)
SDK 的 peerDependencies 不随包打入,宿主要自行安装:
vue pinia pinia-plugin-persistedstate ant-design-vue @ant-design/icons-vue
leaflet leaflet.markercluster @turf/turf jsts(=2.7.1) chroma-js crypto-js
dayjs lodash-es mitt @vueuse/core echarts vue-echarts axios qs js-cookie
注意
jsts必须固定2.7.1:^2.7.0会解析到 2.12.1,其 package.json 无main/exports入口,Vite 会报Failed to resolve entry for package "jsts"。
3.2 vendored 脚本托管(重要)
SDK 包内含 dist-sdk/lib/(rbush.js / canvasRender / d3 / pano / ih-leaflet-canvaslayer-field / UEditorPlus 等),这些是 IIFE/UMD 的动态 <script> 加载脚本,运行时按 assetsBase + '/lib/...' 路径动态加载。宿主必须把这些文件托管到可访问的静态路径:
- 开发期:把
node_modules/mapv-sdk/dist-sdk/lib拷到public/lib(示例工程用postinstall脚本自动完成) - 生产部署:把该目录托管到
assetsBase(默认/)可访问路径下,例如 Nginx 的/lib/
4. 快速开始(最小示例)
以下是最小可运行的宿主接入三步:main.ts 配置 → 全局高度链 → App.vue 挂载。
4.1 main.ts:注册插件
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
import './style.css' // 宿主全局高度链(见 4.3)
import MapvPlugin from 'mapv-sdk'
import 'mapv-sdk/style.css' // SDK 全局样式(popup/图例/测量标签/antd 覆盖等)
import App from './App.vue'
// 自建 pinia 并传给 SDK:宿主组件里 useLayersStore() 才能读到 SDK 内部同一个 store
// 注意:宿主自建 pinia 时 SDK 不会自动挂 persist 插件,必须自己 use 一次,
// 否则 SDK 内部 store(app/layers/layout)的 persist 静默失效(如「切城市刷新后回退默认北京」)。
const pinia = createPinia().use(piniaPluginPersistedstate)
createApp(App)
.use(Antd) // ① 宿主必须提供 antd 样式(SDK 包内不注入 antd 样式)
.use(MapvPlugin, {
apiBaseUrl: '/datam/dm/v1/', // ② 后端 API 基础路径(dev 走代理,生产 /dm/v1/)
assetsBase: '/', // ③ vendored 脚本基础路径(需把 dist-sdk/lib 托管到 /lib)
pinia, // ④ 复用宿主 pinia,让宿主与 SDK 共享同一 store
})
.mount('#app')
4.2 App.vue:挂载组件
<template>
<!-- 显式 import 后直接使用;也可经 app.use(MapvPlugin) 全局注册为 <MapvLayers /> -->
<MapvLayers
e-id="siptea"
map-id="114fcae0-7402-11ea-881a-072941e6c675"
/>
</template>
<script setup lang="ts">
import { MapvLayers } from 'mapv-sdk'
</script>
4.3 style.css:全局高度链(必做)
SDK 的 .layers-page 用 height: 100%,依赖 html → body → #app 都有确定高度;否则地图容器高度为 0、页面空白。
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body,
#app {
width: 100%;
height: 100%;
}
4.4 运行与验证
npm run dev # 示例默认端口 5174
浏览器打开后,SDK 会自动请求 auth/maptree 拉取地图配置:
- 能渲染地图 = 后端 + 代理 + vendored 脚本托管都通了
- 地图数据走
apiBaseUrl(dev=/datam/dm/v1/,经代理到localhost:3001) - vendored 脚本(canvasRender / panolens / 风场)走
assetsBase
5. 配置方法
SDK 有两层配置:环境配置(install() 的 options / setDatamContext())和地图参数(<MapvLayers> 的 props)。分工:环境配置给「装在哪、后端在哪」;props 给「这次渲染哪张地图、显示行为」。
5.1 环境配置 DatamOptions
install(app, options) 或运行时 setDatamContext(options) 接受的字段(都是可选的,缺省走 URL / import.meta.env / window.olmap 兜底):
| 字段 | 类型 | 说明 | 默认 |
|---|---|---|---|
eId |
string |
企业 ID(也可用组件 prop 传) | URL ?eId |
mapId |
string |
地图 ID(也可用组件 prop 传) | URL ?mapId |
ckey |
string |
菜单 ckey | URL ?ckey |
password |
string |
地图密码 | URL ?password |
baseLayerName |
string |
默认底图名称 | '高德地图' |
screenshotsId |
string |
快照还原 ID | URL ?screenshotsId |
_command |
string |
快照命令 | URL ?_command |
apiBaseUrl |
string |
后端 API 基础路径(数据中台基地址;editor/api 子前缀由 SDK 写死) |
import.meta.env.VITE_API_BASE_URL 或 /dm/v1/ |
assetsBase |
string |
vendored 脚本基础路径(canvasRender/panolens/windField 动态脚本) | import.meta.env.BASE_URL 或 / |
runtimeConfig |
object |
外部运行时配置(替代 window.olmap.config) |
window.olmap?.config |
persistKeyPrefix |
string |
pinia persist key 前缀(预留,见「已知限制」) | 'mapv' |
pinia |
Pinia |
复用宿主 pinia(见 4.1,推荐显式传入) | 自建(含 persistedstate) |
assetsBase尾部斜杠约定:setDatamContext会自动replace(/\/+$/, ''),避免${assetsBase}/lib/...拼成//lib/...(协议相对 URL → 502)。
5.2 组件 props(地图参数)
见 第 6 节 的 props 全表。宿主既可在 install 里传 eId/mapId,也可在组件上按 prop 传;props 优先级高于环境配置。
6. 组件 API:MapvLayers
6.1 Props 全表
| Prop | 类型 | 默认 | 说明 |
|---|---|---|---|
eId |
string |
— | 企业 ID |
mapId |
string |
— | 地图 ID |
ckey |
string |
— | 菜单 ckey |
password |
string |
— | 地图密码 |
baseLayerName |
string |
— | 默认底图名称 |
screenshotsId |
string |
— | 快照还原 ID |
_command |
string |
— | 快照命令 |
defaultLogo |
string |
— | 后台地图未配 map.tag.logoUrl 时,图层面板头部的兜底图标路径 |
layersPanelTitle |
string |
— | 图层面板标题覆盖(默认显示后台地图名 map.tag.name / map.name / 「图层管理」) |
hideAllCount |
boolean |
false |
隐藏图层面板头部右侧的图层总数徽标 |
allowMultipleCircles |
boolean |
true |
画圆是否允许连续画多个;false=只保留最新一个圆 |
spatialShapeMenuItems |
(info) => SpatialShapeMenuItem[] \| null |
— | 给绘制成品(画圆/多边形/手绘面/行政区)追加自定义右键菜单项 |
layerContextMenuItems |
(ctx) => LayerContextMenuItem[] |
— | 给图层树节点右键菜单追加宿主自定义项 |
disableFeaturePopup |
boolean |
false |
关闭「勾选图层要素」点击时的内置信息弹窗 |
手绘圆最大半径(后台
map.tag.maxRadius,米)、绘面最大面积(后台map.tag.maxArea,平方米)由后台地图配置控制,不走 prop。
6.2 Slots
| 插槽 | 位置 | 说明 |
|---|---|---|
#toolbar |
图层面板头部下方工具栏 | 宿主注入工具栏内容;不传内容时工具栏自动隐藏 |
#layers-panel-actions-more |
图层面板头部「更多」下拉按钮右侧 | 宿主注入额外图标按钮(与开关/下拉同一行) |
6.3 Events
| 事件 | 载荷 | 说明 |
|---|---|---|
feature-click |
DataFeatureClickEvent |
勾选图层要素(点/线/面/拉线标注)被点击。同载荷另广播到 bus layers:featureClick |
DataFeatureClickEvent 结构:
{
layerId: string // 所属图层 id
geometryType?: string // Point / LineString / Polygon / MultiPolygon…(点与拉线标注为 Point)
data: any // 被点击要素的原始记录(含 tag / geom)
latlng?: [number, number] // 点击坐标 [lat, lng](canvas 海量点由记录 geom 推导)
layer?: any // Leaflet 图层(GeoJSON 的 Path / 拉线标注的 Marker;canvas 海量点无)
originalEvent?: any // 原始 Leaflet 鼠标事件(canvas 海量点无)
}
6.4 扩展回调示例
<MapvLayers
e-id="siptea"
map-id="..."
default-logo="/img/map-default-logo.svg"
layers-panel-title="演示地图标题"
:allow-multiple-circles="false"
:hide-all-count="false"
:spatial-shape-menu-items="spatialShapeMenuItems"
:layer-context-menu-items="layerContextMenuItems"
@feature-click="onFeatureClick"
>
<template #toolbar>
<a-button size="small" @click="onToolbarClick('按钮A')">按钮A</a-button>
</template>
<template #layers-panel-actions-more>
<a-button type="text" size="small" @click="onPanelHelp">
<QuestionCircleOutlined />
</a-button>
</template>
</MapvLayers>
spatialShapeMenuItems:SDK 每「画好一个图形」时调用,返回的菜单项排在最上面(默认「计算面积」之前)。回调参数:
{
type: 'Circle' | 'Polygon' | 'Freehand' | 'GetAreaFromPoint'
layer: any // 成品 Leaflet 图层;右键菜单回调 e.relatedTarget 就是它
center?: [number, number] // Circle:圆心 [lng, lat]
radius?: number // Circle:半径(米)
latlngs?: Array<[number, number]> // Polygon / Freehand:顶点 [lng, lat](手绘是凸包)
}
菜单项 { text: string; callback: (e) => void; icon?/iconCls?/disabled? }。callback 拿被右键图形坐标用 e.relatedTarget.toGeoJSON()(底图切换坐标系会变,不要在菜单项里缓存 info 坐标做几何,右键当下取才准)。
layerContextMenuItems:右键图层树节点时,SDK 先按图层配置生成基础项(筛选/权重/统计/时间轴/导出/数据列表),再调用本函数,返回项排在基础项之后。点击自定义项发 bus 事件 layers:contextMenuCustom({key, layerId, layer, dataSet})。
function layerContextMenuItems(ctx: LayerContextMenuContext): LayerContextMenuItem[] {
return [{ key: 'copy-info', label: `复制图层信息(${ctx.layer?.tag?.name ?? ctx.layerId})` }]
}
7. 对外导出清单
import ... from 'mapv-sdk' 可用的全部导出:
组件 / 插件
MapvLayers(组件)、默认导出MapvPlugin(install插件)
运行时上下文
useDatamContext/getDatamContext/setDatamContext/provideDatamContext- 类型
DatamContext/DatamOptions/RuntimeConfig
Pinia store
useLayersStore(图层/勾选/数据/图例/空间查询状态)useAppStore(当前城市等应用级状态)useLayoutStore(布局状态)
组合式函数
useLeafletMap(地图初始化/渲染)useMainMap/mainMapInstance(获取当前 Leaflet map 实例)useSpatialQuery(空间查询:画圆/多边形/手绘面/行政区/等时圈)useThematic(专题图)useLayersData(图层数据加载)useEventBus(事件总线)
类型
- 图层类型:
LayerInfo/LayerStyle/TreeNode/MapInfo/DataSet/DataSetField/LayerType/StyleType - 地图类型:
SpatialQueryParams/ThematicParams/MapViewParams/ThematicActiveButtons/LegendState/BaseLayerType ApiResponse- 回调类型:
DataFeatureClickEvent、SpatialShapeType/SpatialShapeMenuItem/SpatialShapeMenuInfo、LayerContextMenuItem/LayerContextMenuContext/LayerContextMenuProvider
8. 宿主扩展面(不改 SDK 源码能做什么)
核心思路:共享 pinia 读状态 + useEventBus 收/发事件 + 自拼 UI 叠在地图上,不碰 SDK 源码。example/ 就是完整演示。
8.1 可读的状态(useLayersStore / useAppStore)
| store 字段 | 含义 |
|---|---|
layerIdArray |
已勾选图层 ID(勾选变化的信号源) |
layersMap[layerId] |
图层配置(layer.tag.dataSetId 是数据集 ID) |
dataMap[layerId] |
已勾选图层的过滤后数据(异步填充) |
dataSetMap / modelMap |
数据集 / 字段模型元数据 |
layerCountMap[layerId] |
图层数据量 |
legendIndexMap[layerId] / legendMap[layerId] |
图例勾选状态 / 图例定义 |
params.polygon / params.polygonFrom |
当前空间查询多边形 + 类型(Circle/Polygon/Freehand/GetAreaFromPoint/Isochrone) |
appStore.currentCityInfo |
当前城市(默认北京市,快照还原会覆盖) |
8.2 事件总线(useEventBus().on/off/emit)
可订阅:
| 事件 | 载荷 |
|---|---|
map:ready |
Leaflet map 实例 |
map:cityChanged |
{ name, adcode?, center? } |
thematic:gridLoaded |
格网加载完成 |
layers:featureClick |
DataFeatureClickEvent(与 @feature-click 同构) |
layers:contextMenuCustom |
{ key, layerId, layer, dataSet }(右键菜单宿主注入项被点击) |
可发布:
| 事件 | 作用 |
|---|---|
layers:checkedChanged |
触发 SDK 重查已勾选图层 |
map:locateCity |
定位城市 |
thematic:toggle |
切换专题图 |
layers:showDataTable |
打开属性表 |
坑:普通勾选/取消图层树不会发
layers:checkedChanged(它只在筛选/权重表单提交、缓冲区/空间聚合变更时发)。监听「勾选变化」要用watch(() => store.layerIdArray)。
8.3 关键坑(务必读)
- 共享 pinia:
MapvPlugin({ pinia })传自建 pinia,否则宿主拿不到 SDK 内部 store。 - 数据异步:勾选后
dataMap不是同步就绪,要watch(() => store.dataMap, cb, { deep: true })或 computed 响应式读。 useLayersStore()只能在组件setup()里调(组件外用需先setActivePinia(pinia));主动查数据用useLayersData().loadLayerData([layerId])。- 注入查询条件:
layer.params['tag.city'] = '北京'走getQueryFromLayer,字符串字段会变成{ 'tag.city': { $regex: '北京' } }(子串匹配);要精确相等用layer.programParams = { 'tag.city': '北京' }(原始 mongo 条件直接$and)。
9. 完整示例工程结构
example/ 目录:
example/
├── index.html
├── package.json # mapv-sdk 引用 file:./mapv-sdk-1.0.0.tgz + peer deps
├── vite.config.ts # 端口 5174 + 代理 /datam -> localhost:3001
├── public/
│ ├── img/ # 宿主静态图片(如 default-logo)
│ └── lib/ # postinstall 从 dist-sdk/lib 拷来的 vendored 脚本
└── src/
├── main.ts # 插件注册(antd + MapvPlugin + pinia 共享)
├── style.css # 全局高度链 + 宿主侧扩展样式
├── App.vue # <MapvLayers> 挂载 + 各扩展组件
└── demo/
├── LayerWatch.vue # 勾选/取消提示 + 右侧可收起侧窗
├── SpatialQueryInfo.vue# 空间查询信息卡(面积 + 数据量)
├── CityFilter.vue # 城市条件注入(监听 map:cityChanged)
└── LegendWatch.vue # 图例勾选变化提示(diff legendIndexMap)
四个已落地扩展(详见源码注释):
LayerWatch.vue:watch(layerIdArray)diff 出增删 → 提示;computed读dataMap[id].length展示数据量。侧窗展开时在body打mapv-side-panel-open标记,用全局 CSS 把 SDK 右侧浮动按钮左推。SpatialQueryInfo.vue:computed读params.polygon(显隐)+turfArea算面积 +dataMap求和算数据量。CityFilter.vue:监听map:cityChanged→ 给所有layersMap的layer.params['tag.city'] = 城市名→emit('layers:checkedChanged')触发重查。LegendWatch.vue:watch(store.legendIndexMap)diff 找出本次 toggle 的(layerId, index),配合layerCountMap异步更新后统一弹「勾选/取消图例」提示。
10. 更新 SDK 依赖(宿主侧)
# 日常开发
npm run dev # 示例默认端口 5174(与仓库父工程 5173 错开)
# 仅验证「装包 + 导入 + 打包」链路
npm run build
当 SDK 发布新 tarball 后,宿主更新依赖(以 example/ 为例):
cd example
npm run update-mapv-sdk
# 等价:rm -rf node_modules/mapv-sdk node_modules/.package-lock.json node_modules/.vite package-lock.json && npm install
file:tarball 版本号不变时(仍是 1.0.0),package-lock.json里的旧integrity会让 npm 跳过重新解包,所以必须删掉package-lock.json(连同node_modules/mapv-sdk)再npm install;postinstall也会随之重新把dist-sdk/lib拷到public/lib。
11. 常见问题与踩坑
| # | 现象 | 原因 / 处理 |
|---|---|---|
| 1 | 页面空白、地图高度为 0 | 缺全局高度链:html,body,#app{height:100%} 必须提供(见 4.3) |
| 2 | Failed to resolve entry for package "jsts" |
jsts 被解析到 2.12.1(无 main/exports)。宿主依赖固定 "jsts": "2.7.1" |
| 3 | 动态脚本加载 404 / 502 | vendored 脚本走 assetsBase + '/lib/...';assetsBase 不要以 / 结尾(会自动剥掉,否则拼成 //lib 协议相对 URL),并把 dist-sdk/lib 托管到可访问路径 |
| 4 | 城市切换后刷新回退默认北京 | 宿主自建 pinia 时没 use(piniaPluginPersistedstate),SDK store 的 persist 静默失效;自建 pinia 必须自己挂插件(见 4.1) |
| 5 | 更新 tarball 后不生效 | 版本号不变时 npm 复用旧 integrity;删 package-lock.json + node_modules/mapv-sdk 再装 |
| 6 | 宿主拿不到 SDK 内部 store | 没传 pinia;MapvPlugin({ pinia }) 显式传自建 pinia |
| 7 | 勾选图层后读不到数据 | dataMap 异步填充;用 watch/computed 响应式读,别同步取 |
| 8 | useLayersStore() 在组件外报错 |
组件外用需先 setActivePinia(pinia) |
| 9 | antd 组件无样式 | SDK 不注入 antd 样式;宿主必须 app.use(Antd) + import 'ant-design-vue/dist/reset.css' |
| 10 | 下拉框空白「空值项」 | 历史遗留,部分数据清洗改动已回退;宿主侧确保空值用 undefined/[] 而非 '' |
12. 已知限制(未完成口子)
以下「预留但未生效」的口子,宿主接入时需知悉:
persistKeyPrefix未生效:已进上下文,但三个 store 的 persist key 仍写死mapv_*,字段目前没被真正使用(多实例场景才需要改)。HtmlEditor的 UEditor 路径仍写死/mapv/lib/UEditorPlus/,未改成读assetsBase。- 完整
apiClient注入未做:目前只做到apiBaseUrl+ cookie token 兜底;若宿主后端鉴权方式完全不同,需再扩展自定义接口。