@bonncloud/tianditu
@bonncloud/tianditu
天地图(国家地理信息公共服务平台)全功能 TypeScript SDK —— JS API 4.0 地图组件/控件 + Web 服务 HTTP API,同一套类型定义同时服务 Node.js 与 Vue 3。
- 浏览器端:
loadTianditu()脚本加载器、TiandituMap面向对象封装、覆盖物/图层/控件/量算工厂、Vue 3 组件与组合式函数 - Node.js 端:地名搜索 / 地理编码 / 逆地理编码 / 驾车 / 公交 / 行政区划 / 静态地图全量 HTTP 客户端
- 坐标系工具:WGS84 / GCJ02 / BD09 互转(纯 TS,前后端通用)
- 零运行时依赖,只依赖标准
fetch;ESM + CJS +.d.ts+ sourcemap - 类型与封装严格按实测 API 边界定义,不存在的能力给出明确错误而不是静默失败
上图为
examples/vue3-demo的真实运行截图(天地图矢量底图 + 控件 + 覆盖物 + 自定义控件)。
目录
- 1. 包介绍
- 2. 包功能
- 3. 接口与组件调用示例
- 3.1 脚本加载器 · 3.2 地图实例 · 3.3 覆盖物 · 3.4 图层 · 3.5 控件 · 3.6 量算/定位/绘制 · 3.7 几何工具
- 3.8 Web 服务客户端 · 3.9 无状态函数式 API · 3.10
jsApi便捷方法 · 3.11 坐标工具 - 3.12 Vue 3 组件 · 3.13 Vue 3 组合式函数 · 3.14 类型导出 · 3.15 完整可运行示例
- 4. 注意事项
- 5. 版权
1. 包介绍
1.1 这是什么
天地图官方提供两条能力线,这个包把两条线都封装成了带完整类型的 TypeScript SDK:
| 能力线 | 运行位置 | 本包对应入口 |
|---|---|---|
| JS API 4.0(在网页里嵌地图、画覆盖物、加控件) | 浏览器 | loadTianditu()、TiandituMap、覆盖物/图层/控件工厂、Vue 组件 |
| Web 服务 HTTP API(搜索、地理编码、路径规划、静态图) | 服务端(Node.js) | TiandituClient、jsApi、无状态函数 |
1.2 为什么要多一层封装
天地图官方文档与实际运行时有几处已知不一致,直接照文档写代码会在运行时才炸。本包在实测基础上做了三件事:
- 只声明真实存在的 API。官方文档站有页面但主脚本里没有的类(
T.Label、T.Geocoder、T.DrawTool等)一律不封装,调用等价能力时给出明确错误或替代实现(详见 4.1)。 - 抹平已知坑。容器选择器归一化、
setCenter缺失、DivIcon只接受 HTML 字符串、自定义控件不回调onAdd、驾车规划返回 XML 且单位混用 —— 都在包内处理掉。 - Key 类型分离。浏览器端 Key 与服务端 Key 权限完全隔离,包内两套 API 各自明确要求对应的 Key(详见 4.2)。
1.3 包信息
| 项 | 值 |
|---|---|
| 包名 | @bonncloud/tianditu |
| 当前版本 | 0.1.2 |
| 许可 | MIT |
| 类型 | type: module,ESM + CJS 双产物,附带 .d.ts 与 sourcemap |
| 运行时依赖 | 无(只用标准 fetch 与 DOM/BOM API) |
| Node 版本 | >=18(低版本可注入 fetch 实现) |
| Vue 支持 | vue@^3.3,作为可选 peer 依赖,仅 vue 子路径需要 |
| 浏览器支持 | 支持 fetch 与 Promise 的现代浏览器 |
1.4 安装
npm i @bonncloud/tianditu
# 或
pnpm add @bonncloud/tianditu
yarn add @bonncloud/tianditu
Vue 3 项目无需额外安装 vue(已在项目中),SDK 不会强制引入它。
1.5 子路径导出
按需引入可以避免把整个 SDK 打进产物:
| 导入路径 | 内容 |
|---|---|
@bonncloud/tianditu |
全部能力:加载器、地图封装、Web 服务客户端、坐标与量算工具、jsApi、类型 |
@bonncloud/tianditu/services |
只要 Web 服务 HTTP API(Node.js 后端推荐) |
@bonncloud/tianditu/browser |
jsApi 的具名导出形式(服务端 API 的 Promise 化便捷方法) |
@bonncloud/tianditu/vue |
Vue 3 组件与组合式函数(会引入 vue) |
@bonncloud/tianditu/types |
纯类型定义(零运行时代码) |
import { TiandituClient } from '@bonncloud/tianditu/services' // Node.js 后端
import { jsApi } from '@bonncloud/tianditu/browser' // 便捷方法
import { TdtMap, TdtMarker } from '@bonncloud/tianditu/vue' // Vue 3
import type { SearchResponse } from '@bonncloud/tianditu/types' // 仅类型
2. 包功能
2.1 功能总览
| 模块 | 功能 | 入口 |
|---|---|---|
| 脚本加载 | 按需注入 JS API、单例去重、超时、扩展库与第三方依赖按序加载、SSR 环境明确报错 | loadTianditu loadScripts loadPlugin getTianditu isTiandituLoaded setTiandituNamespace |
| 地图实例 | 创建/包装地图、中心与层级、范围自适应、坐标与像素互转、覆盖物/控件/图层增删、事件、销毁 | TiandituMap |
| 覆盖物 | 图标、标注、文字标注、折线、多边形、圆、矩形、信息窗口 | createIcon createMarker createTextMarker updateTextMarker createPolyline createPolygon createCircle createRectangle createInfoWindow |
| 图层 | 官方 WMTS 底图(8 种图层 × 2 种投影)、自定义瓦片图层 | createBaseLayer createTileLayer |
| 控件 | 缩放、比例尺、底图切换、版权、自定义 DOM 控件 | createZoomControl createScaleControl createMapTypeControl createCopyrightControl createCustomControl |
| 底图类型 | 矢量 / 影像 / 影像混合 / 地形 / 地形混合 切换 | getMapType setMapType createDefaultMapTypeItems |
| 量算与定位 | 折线总长、逐段距离、距离格式化、交互式采点、浏览器定位 | measureDistance measureSegments formatDistance drawShape locate moveTo |
| 几何工具 | 坐标/像素/范围对象的归一化与构造 | toLngLat toLngLatArray toPoint toLngLatBounds boundsFromString boundsOf lngLatToTuple resolveContainer resolvePoints |
| Web 服务 | 地名搜索 V2.0(8 种 queryType)、地理编码、逆地理编码、驾车规划、公交规划、行政区划 V2.0、静态地图 | TiandituClient 及同名的无状态函数 |
| 便捷方法 | 服务端 API 的 Promise 化 + 结果归一化(坐标转数字、坐标串转数组、XML 解析) | jsApi(geocode reverseGeocode reverseGeocodeDetail search searchPois drivingRoute transitRoute busLineSearch parseLngLatPath) |
| 坐标工具 | WGS84 / GCJ02 / BD09 三者互转、球面距离、坐标串解析与格式化 | wgs84ToGcj02 gcj02ToWgs84 gcj02ToBd09 bd09ToGcj02 wgs84ToBd09 bd09ToWgs84 outOfChina distance parseLngLat formatLngLat round |
| Vue 3 | 10 个组件 + 4 个组合式函数 | @bonncloud/tianditu/vue |
| 错误处理 | 统一异常类型,携带 HTTP 状态码、天地图业务码、原始响应、请求地址;请求超时、失败重试、自定义 fetch |
TiandituServiceError |
| 工程能力 | 完整 TS 类型、ESM/CJS 双格式、sourcemap、41 项单元测试、Vue 3 示例工程、真实 Key 联调脚本 | pnpm typecheck pnpm test pnpm build scripts/live-check.mjs |
2.2 能力明细
浏览器端(JS API 4.0)
- 底图:
normalsatellitehybridterrainterrainHybrid - 官方 WMTS 图层:
vec(矢量底图)cva(矢量注记)eva(矢量英文注记)img(影像底图)cia(影像注记)ter(地形晕渲)cta(地形注记)ibo(境界),投影支持c(经纬度 / EPSG:4326)与w(球面墨卡托 / EPSG:900913) - 控件:
Control.ZoomControl.ScaleControl.MapTypeControl.Copyright+ 任意 DOM 自定义控件(自动按topleft/topright/bottomleft/bottomright停靠) - 官方开源扩展库:热力图
HeatmapOverlay、车辆轨迹CarTrack、D3 覆盖物D3SvgOverlay、缓冲区BufferTool、图片覆盖物ImageOverlay
服务端(Web 服务 HTTP API)
| 服务 | 接口 | SDK 方法 |
|---|---|---|
| 地名搜索 V2.0 | GET /v2/search |
search() |
| 地理编码 | GET /geocoder(ds=) |
geocode() |
| 逆地理编码 | GET /geocoder(postStr=) |
reverseGeocode() |
| 驾车规划 | GET /drive |
drive() |
| 公交规划 | GET /transit |
transit() |
| 行政区划 V2.0 | GET /v2/administrative |
administrative() |
| 静态地图 | GET /staticimage |
staticMapUrl() / staticMap() |
搜索服务的 queryType 覆盖全部 8 种取值:普通 1、视野内 2、周边 3、地名 7、多边形 10、行政区划 12、数据分类 13、统计 14。
2.3 明确不提供的能力
以下类在官方文档站有页面,但在 api?v=4.0 主脚本(138 KB)中检索出现 0 次,运行时为 undefined,因此本包不提供:
| 缺失的官方类 | 本包的替代方案 |
|---|---|
T.Label |
createTextMarker()(= T.Marker + T.DivIcon) |
T.Geocoder / T.LocalSearch / T.DrivingRoute / T.WalkingRoute / T.TransitRoute / T.BusLineSearch |
调 Web 服务 API:TiandituClient / jsApi(注意 Key 类型) |
T.DrawTool / T.DistanceTool / T.AreaTool / T.MeasureTool |
drawShape() 采点 + measureDistance() / measureSegments() |
T.Geolocation |
locate()(基于浏览器原生 navigator.geolocation) |
T.Control.OverviewMap(鹰眼) |
createOverviewMapControl() 会直接抛错;可自行用第二个 T.Map 实例实现 |
T.MarkerClusterer(点聚合) |
官方 opensource/openlibrary/MarkerClusterer.js 为 404,4.0 不可用;需自行实现或换用其他聚合方案 |
3. 接口与组件调用示例
3.1 脚本加载器
loadTianditu(options)
加载 JS API 并在全局注入 T 命名空间,返回带完整类型的 T。同一 URL 只会真正加载一次。
import { loadTianditu } from '@bonncloud/tianditu'
const T = await loadTianditu({
tk: 'YOUR_BROWSER_TK', // 【必填】浏览器端 Key
version: '4.0', // 可选,默认 '4.0'
protocol: 'https', // 可选,默认 'https'
timeout: 15_000, // 可选,默认 15000ms
// apiUrl: 'https://your-cdn/tianditu/api?v=4.0&tk=xxx', // 私有化部署时覆盖脚本地址
// plugins: ['HeatmapOverlay'], // 需要一并加载的官方扩展库
// dependencies: ['https://cdn.jsdelivr.net/npm/d3@7'], // 扩展库依赖的第三方脚本(先加载)
})
const map = new T.Map('map')
| 参数 | 类型 | 说明 |
|---|---|---|
tk |
string |
必填,浏览器端 Key |
version |
string |
默认 4.0 |
protocol |
'http' | 'https' |
默认 https |
apiUrl |
string |
自定义脚本地址,优先级最高 |
plugins |
(TiandituPluginName | string)[] |
扩展库名称或 URL |
dependencies |
string[] |
第三方依赖脚本 URL,先于扩展库加载 |
timeout |
number |
默认 15000 |
loadPlugin(name, options?) / loadScripts(urls, timeout?)
import { loadTianditu, loadPlugin, loadScripts } from '@bonncloud/tianditu'
// 主脚本加载完成后,单独加载官方扩展库
await loadTianditu({ tk: 'YOUR_BROWSER_TK' })
const T = await loadPlugin('HeatmapOverlay')
// 热力图使用前需先准备数据
const overlay = new T.HeatmapOverlay({ radius: 20 })
overlay.setDataSet({
max: 100,
data: [{ lng: 116.4, lat: 39.9, count: 60 }],
})
overlay.addDataPoint(116.42, 39.93, 30)
map.addOverLay(overlay)
// 需要第三方依赖的扩展库,例如 D3SvgOverlay 依赖 d3
await loadPlugin('D3SvgOverlay', {
dependencies: ['https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'],
})
// 加载任意脚本(按序串行)
await loadScripts(['https://cdn.example.com/a.js', 'https://cdn.example.com/b.js'])
可用的扩展库名称(也可直接传 URL):
| 名称 | 用途 |
|---|---|
HeatmapOverlay |
热力图 |
CarTrack |
车辆轨迹动画 |
D3SvgOverlay |
D3 图形覆盖物 |
BufferTool |
缓冲区分析 |
ImageOverlay |
图片覆盖物 |
getTianditu() / isTiandituLoaded() / setTiandituNamespace(ns)
import { getTianditu, isTiandituLoaded, setTiandituNamespace } from '@bonncloud/tianditu'
if (!isTiandituLoaded()) {
await loadTianditu({ tk: 'YOUR_BROWSER_TK' })
}
const T = getTianditu() // 未加载时抛出明确错误
// SSR 预渲染、单元测试,或已用 <script> 手动引入时,手动注入命名空间
setTiandituNamespace(globalThis.T ?? null)
3.2 地图实例 TiandituMap
创建
import { TiandituMap } from '@bonncloud/tianditu'
// 方式一(推荐):自动加载脚本 + 创建地图
const map = await TiandituMap.create({
tk: 'YOUR_BROWSER_TK',
container: '#map', // 支持 '#map' / 'map' / '#app .map' / HTMLElement
center: [116.40769, 39.89945],
zoom: 12,
minZoom: 3,
maxZoom: 18,
scrollWheelZoom: true, // 其余 MapOptions 原样透传
})
// 方式二:脚本已加载(如 <script> 引入)时同步创建
const map2 = TiandituMap.createSync('#map')
// 方式三:包装一个已存在的原生 T.Map 实例
const raw = new T.Map('map')
const map3 = TiandituMap.from(T, raw)
container 写法归一化:天地图原生 new T.Map() 只认不带 # 的元素 id,传 '#map' 会在内部抛 Cannot read properties of null (reading '_tdt_events')。resolveContainer() 已统一处理,找不到容器时给出明确错误。
状态与方法
// 中心点与层级
map.getCenter() // T.LngLat
map.getCenterTuple() // [lng, lat]
map.setCenter([116.4, 39.9]) // 4.0 无 setCenter,内部用 panTo 实现
map.getZoom() // number
map.setZoom(14)
map.setView([116.4, 39.9], 14) // 同时设中心点与层级
// 范围
const bounds = map.getBounds() // T.LngLatBounds
map.fitBounds([[116.3, 39.85], [116.5, 39.95]]) // 注意:坐标顺序是 [lng, lat]
map.setMaxBounds(bounds)
map.setMinZoom(4)
map.setMaxZoom(18)
// 距离与换算
map.getDistance([116.4, 39.9], [116.5, 39.9]) // 两点球面距离(米)
const p = map.lngLatToContainerPoint([116.4, 39.9]) // T.Point 容器像素坐标
const ll = map.containerPointToLngLat([100, 200]) // T.LngLat
// 底图与样式
map.setBaseMap('satellite') // normal / satellite / hybrid / terrain / terrainHybrid
map.setStyle('indigo') // 官方地图样式
// 容器尺寸变化后通知地图重算(侧边栏收起、弹窗打开等场景必调)
map.resize()
// 生命周期
map.getContainer() // HTMLElement
map.destroy() // 解绑全部事件、清空覆盖物与图层、清空容器
所有写操作(setCenter setZoom addOverlay addControl …)均返回 this,可链式调用:
map.setBaseMap('hybrid').setZoom(13).setCenter([116.4, 39.9])
事件
// on() 返回取消监听的函数
const off = map.on('click', (e) => {
console.log(e.lnglat?.getLng(), e.lnglat?.getLat())
console.log(e.containerPoint?.x, e.containerPoint?.y)
})
off()
// 只监听一次
map.once('load', () => console.log('地图加载完成'))
// 手动解绑
const handler = (e: unknown) => console.log(e)
map.on('moveend', handler)
map.off('moveend', handler)
支持的事件:click dblclick contextmenu mousemove mouseover mouseout movestart move moveend zoomstart zoomend addoverlay removeoverlay addcontrol removecontrol clearoverlays dragstart drag dragend layeradd layerremove load resize levels touchstart touchmove touchend longpress。
覆盖物 / 控件 / 图层 / 信息窗口
map.addOverlay(marker) // 也可写 map.addOverLay(原生别名)
map.removeOverlay(marker)
map.getOverlays() // Overlay[]
map.clearOverlays()
map.addControl(control)
map.removeControl(control)
map.addLayer(layer)
map.removeLayer(layer)
map.getLayers() // TileLayer[]
map.clearLayers()
map.openInfoWindow(infoWindow, [116.4, 39.9])
map.closeInfoWindow()
原生 T.Map 实例始终保留在 map.raw 上,本类未覆盖的能力可直接调用:
map.raw.setMapType(TMAP_SATELLITE_MAP)
3.3 覆盖物工厂
所有工厂函数的第一个参数都是 T 命名空间。
import {
createIcon, createMarker, createTextMarker, updateTextMarker,
createPolyline, createPolygon, createCircle, createRectangle,
createInfoWindow, resolvePoints,
} from '@bonncloud/tianditu'
const T = await loadTianditu({ tk: TK })
createIcon(T, options) —— 自定义图标
const icon = createIcon(T, {
iconUrl: '/marker.png',
iconSize: new T.Point(32, 40),
iconAnchor: new T.Point(16, 40), // 图标尖角相对左上角的偏移
})
createMarker(T, lnglat, options?) —— 点标注
const marker = createMarker(T, [116.4, 39.9], {
icon, // 可选,T.Icon 或 T.DivIcon
draggable: true,
title: '北京',
opacity: 1,
zIndexOffset: 100,
rotation: 0,
})
map.addOverlay(marker)
marker.addEventListener('click', (e) => console.log('点击标注', e))
marker.addEventListener('dragend', (e) => {
const ll = marker.getLngLat()
console.log('拖拽结束', ll.getLng(), ll.getLat())
})
createTextMarker(T, lnglat, options) —— 文字标注
天地图 4.0 没有 T.Label,官方做法是 Marker + DivIcon。本函数已封装,并对文本做了 HTML 转义。
const label = createTextMarker(T, [116.42, 39.93], {
text: '文字标注', // 必填,内部已做 HTML 转义
style: {
color: '#d03050',
fontSize: '14px',
backgroundColor: 'rgba(255,255,255,0.9)',
padding: '2px 6px',
borderRadius: '4px',
},
className: 'my-label', // 附加 class
draggable: false,
zIndexOffset: 10,
})
map.addOverlay(label)
// 更新文本(内部改写 .tdt-text-marker 的 textContent)
updateTextMarker(label, '新的文字')
style 的键名用 camelCase,内部会转成 kebab-case 后写入 style 属性(fontSize → font-size)。
createPolyline(T, points, options?) —— 折线
const line = createPolyline(T, [[116.3, 39.9], [116.5, 39.9]], {
color: '#165dff',
weight: 4,
opacity: 0.9,
lineStyle: 'solid', // solid / dashed / dotted 或自定义字符串
dashArray: '6,4',
showLabel: true, // 显示距离标签
showArrow: false,
clickable: true,
})
map.addOverlay(line)
createPolygon(T, points, options?) —— 多边形
const polygon = createPolygon(
T,
[[116.3, 39.85], [116.45, 39.85], [116.45, 39.95]],
{ color: '#00b42a', weight: 2, fillColor: '#00b42a', fillOpacity: 0.2 },
)
map.addOverlay(polygon)
createCircle(T, center, radius, options?) —— 圆
const circle = createCircle(T, [116.35, 39.93], 1500, { // radius 单位:米
color: '#ff7d00', weight: 2, fillColor: '#ff7d00', fillOpacity: 0.15,
})
map.addOverlay(circle)
createRectangle(T, sw, ne, options?) —— 矩形
const rect = createRectangle(T, [116.3, 39.85], [116.45, 39.95], {
color: '#722ed1', weight: 2, fillColor: '#722ed1', fillOpacity: 0.15,
})
map.addOverlay(rect)
createInfoWindow(T, content?, options?) —— 信息窗口
const iw = createInfoWindow(T, '<strong>标题</strong><br/>内容支持 HTML', {
minWidth: 120,
maxWidth: 320,
offset: new T.Point(0, -30),
autoPan: true,
closeButton: true,
closeOnClick: true,
})
map.openInfoWindow(iw, [116.4, 39.9]) // 由地图打开(推荐)
map.closeInfoWindow()
// 也可以自行控制生命周期
iw.setLngLat(toLngLat(T, [116.4, 39.9]))
iw.open()
iw.close()
iw.isOpen() // boolean
iw.setContent('<b>新内容</b>')
resolvePoints(T, points) —— 批量拿到 T.LngLat[]
const lnglats = resolvePoints(T, [[116.4, 39.9], { lng: 116.5, lat: 39.9 }])
3.4 图层工厂
createBaseLayer(T, tk, layer?, options?) —— 官方 WMTS 底图
import { createBaseLayer, type BaseLayerName } from '@bonncloud/tianditu'
// 叠加影像注记
map.addLayer(createBaseLayer(T, TK, 'cia', { projection: 'w' }))
// 经纬度投影的矢量底图 + 注记
map.addLayer(createBaseLayer(T, TK, 'vec', { projection: 'c' }))
map.addLayer(createBaseLayer(T, TK, 'cva', { projection: 'c' }))
// 自定义瓦片域名序号与格式
map.addLayer(createBaseLayer(T, TK, 'img', { projection: 'w', subdomain: 3, format: 'tiles' }))
// 完全自定义 URL 模板(优先级最高)
map.addLayer(
createBaseLayer(T, TK, 'vec', {
urlTemplate: 'https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&...&tk=' + TK,
}),
)
layer |
含义 | projection |
含义 |
|---|---|---|---|
vec |
矢量底图 | w |
球面墨卡托 EPSG:900913(默认) |
cva |
矢量注记 | c |
经纬度 EPSG:4326 |
eva |
矢量英文注记 | ||
img |
影像底图 | ||
cia |
影像注记 | ||
ter |
地形晕渲 | ||
cta |
地形注记 | ||
ibo |
境界 |
options 额外支持 subdomain(默认 0)、format(默认 tiles)、urlTemplate,以及全部 TileLayerOptions。
createTileLayer(T, url, options?) —— 自定义瓦片图层
import { createTileLayer } from '@bonncloud/tianditu'
const layer = createTileLayer(T, 'https://tile.example.com/{z}/{x}/{y}.png', {
minZoom: 3,
maxZoom: 18,
opacity: 0.8,
zIndex: 10,
tileSize: 256,
subdomains: '0123',
detectRetina: true,
attribution: '© 我的瓦片服务',
crossOrigin: true,
})
map.addLayer(layer)
map.removeLayer(layer)
map.clearLayers()
3.5 控件工厂
import {
createZoomControl, createScaleControl, createMapTypeControl,
createCopyrightControl, createCustomControl, createOverviewMapControl,
createDefaultMapTypeItems, getMapType, setMapType,
} from '@bonncloud/tianditu'
内置控件
map.addControl(createZoomControl(T, { position: 'topleft' }))
map.addControl(createScaleControl(T, { position: 'bottomleft' }))
// 底图切换控件:默认 5 项(地图 / 卫星 / 卫星混合 / 地形 / 地形混合)
map.addControl(createMapTypeControl(T, undefined, { position: 'topright' }))
// 自定义底图切换项
map.addControl(
createMapTypeControl(
T,
[
{ title: '地图', icon: 'https://.../vector.png', layer: getMapType('normal') },
{ title: '卫星', icon: 'https://.../satellite.png', layer: getMapType('satellite') },
],
{ position: 'topright' },
),
)
// 版权控件
map.addControl(createCopyrightControl(T, { position: 'bottomright', content: '© 天地图' }))
position 取值:'topleft' 'topright' 'bottomleft' 'bottomright';offset 支持 [x, y] 或 { x, y }。
createCustomControl(element, options?) —— 任意 DOM 控件
天地图对非 Control 子类的对象不会回调 onAdd,导致控件停在页面左上角。本函数在创建时就应用定位,并把地图容器设为定位上下文。
const el = document.createElement('div')
el.className = 'my-control'
el.innerHTML = '<button>定位</button>'
el.querySelector('button')!.addEventListener('click', () => locate())
const control = createCustomControl(el, { position: 'bottomright', offset: [0, -10] })
map.addControl(control)
// 显示控制
control.hide()
control.show()
control.isVisible()
map.removeControl(control)
底图类型切换
map.setBaseMap('satellite') // 封装层提供
setMapType(map.raw, 'terrainHybrid') // 底层函数式调用
const type = getMapType('normal') // 拿到官方 TMAP_NORMAL_MAP 常量
createDefaultMapTypeItems() // 生成默认的底图切换项数组
| 名称 | 对应全局常量 |
|---|---|
normal |
TMAP_NORMAL_MAP |
satellite |
TMAP_SATELLITE_MAP |
hybrid |
TMAP_HYBRID_MAP |
terrain |
TMAP_TERRAIN_MAP |
terrainHybrid |
TMAP_TERRAIN_HYBRID_MAP |
createOverviewMapControl() —— 鹰眼
// 天地图 JS API 4.0 不存在 T.Control.OverviewMap,调用会直接抛出明确错误
createOverviewMapControl(T) // ✗ Error: 天地图 JS API 4.0 不存在 T.Control.OverviewMap
如需缩略图,请用第二个 T.Map 实例自行实现。
3.6 量算、定位与绘制
天地图 4.0 没有 DrawTool / DistanceTool / AreaTool / MeasureTool / Geolocation,本包基于确实存在的 API 提供等价能力。
import {
measureDistance, measureSegments, formatDistance,
drawShape, locate, moveTo,
} from '@bonncloud/tianditu'
测距
const points: [number, number][] = [[116.35, 39.92], [116.45, 39.90], [116.55, 39.95]]
measureDistance(T, map.raw, points) // 折线总长(米),逐段累加官方 getDistance
measureSegments(T, map.raw, points) // [段1, 段2](米),长度比 points 少 1
formatDistance(1234.567) // '1.23 km'
formatDistance(850) // '850 m'
formatDistance(Number.NaN) // '-'
drawShape(T, map, options?) —— 交互式采点
替代官方 DrawTool:单击加点、双击结束,返回 Promise<T.LngLat[]>。
const lnglats = await drawShape(T, map.raw, {
onAdd: (lnglat, all) => console.log(`已采集 ${all.length} 个点`, lnglat.getLng()),
})
// 采完接量算
if (lnglats.length > 1) {
console.log(formatDistance(measureDistance(T, map.raw, lnglats)))
}
// 也可以用来画多边形
if (lnglats.length > 2) {
map.addOverlay(createPolygon(T, lnglats, { fillColor: '#00b42a' }))
}
locate(options?) —— 浏览器定位
基于原生 navigator.geolocation,返回 WGS84 坐标(天地图底图为 CGCS2000,差异厘米级可直接使用)。
const { lng, lat, accuracy, lnglat } = await locate({
enableHighAccuracy: true,
timeout: 10_000,
maximumAge: 30_000,
})
map.setCenter([lng, lat])
map.addOverlay(createMarker(T, [lng, lat], { title: `精度 ${Math.round(accuracy)} 米` }))
moveTo(map, lnglat, zoom?)
moveTo(map.raw, new T.LngLat(116.4, 39.9)) // 只移动中心
moveTo(map.raw, new T.LngLat(116.4, 39.9), 15) // 同时改层级
3.7 几何工具
把各种「像坐标」的输入统一成天地图原生对象,省掉手写 new T.LngLat()。
import {
toLngLat, toLngLatArray, toPoint, toLngLatBounds,
boundsFromString, boundsOf, lngLatToTuple, resolveContainer,
} from '@bonncloud/tianditu'
// 输入支持:[lng, lat] 数组、{ lng, lat }、{ lon, lat }、已有 T.LngLat
toLngLat(T, [116.4, 39.9])
toLngLat(T, { lng: 116.4, lat: 39.9 })
toLngLat(T, { lon: 116.4, lat: 39.9 })
toLngLat(T, existingLngLat)
toLngLatArray(T, [[116.4, 39.9], [116.5, 39.9]])
// 像素坐标:[x, y] 或 { x, y }
toPoint(T, [100, 200])
toPoint(T, { x: 100, y: 200 })
// 范围
toLngLatBounds(T, [116.3, 39.85], [116.5, 39.95]) // 西南角 → 东北角
boundsFromString(T, '116.3,39.85,116.5,39.95') // "minx,miny,maxx,maxy"
boundsOf(T, [[116.3, 39.85], [116.5, 39.95]]) // 由点集自动计算外包矩形
lngLatToTuple(new T.LngLat(116.4, 39.9)) // [116.4, 39.9]
// 容器归一化
const el = resolveContainer('#map') // 支持 '#map' / 'map' / '#app .map' / HTMLElement
boundsOf 在点集为空时会抛出 TypeError。
3.8 Web 服务客户端 TiandituClient
⚠️ 必须使用「服务端」类型的 Key,且必须在服务端调用(浏览器直连会被拒,见 4.3)。
import { TiandituClient } from '@bonncloud/tianditu/services'
const client = new TiandituClient({
tk: process.env.TIANDITU_SERVER_TK!,
baseURL: 'https://api.tianditu.gov.cn', // 可选,默认值即此
timeout: 8000, // 可选,默认 10000ms
retry: 1, // 可选,默认 0
headers: { 'User-Agent': 'my-app/1.0' }, // 可选
// fetch: myFetch, // 可选,Node < 18 时注入实现
})
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
tk |
string |
— | 必填,服务端 Key |
baseURL |
string |
https://api.tianditu.gov.cn |
服务基地址,可指向自有转发端点 |
timeout |
number |
10000 |
单次请求超时(毫秒) |
retry |
number |
0 |
失败重试次数,重试间隔按次数递增 |
fetch |
typeof fetch |
全局 fetch |
自定义实现 |
headers |
Record<string, string> |
— | 额外请求头 |
所有方法都接受可选的第二个参数 extra:{ signal?: AbortSignal; timeout?: number }。
client.search(params, extra?) —— 地名搜索 V2.0
import { SearchQueryType } from '@bonncloud/tianditu/services'
// 周边搜索(queryType = 3)
const nearby = await client.search({
queryType: SearchQueryType.Nearby,
keyWord: '公园',
pointLonlat: '116.48016,39.93136',
queryRadius: 3000, // 米,10 公里内
start: 0,
count: 20,
show: 2, // 1 基本信息 / 2 详细信息
})
console.log(nearby.pois?.[0]?.name, nearby.pois?.[0]?.lonlat, nearby.pois?.[0]?.distance)
// 普通搜索(queryType = 1)需要 mapBound + level
const normal = await client.search({
queryType: SearchQueryType.Normal,
keyWord: '北京大学',
mapBound: '116.02,39.83,116.65,39.99',
level: 12,
start: 0,
count: 10,
})
// 视野内搜索(queryType = 2)
await client.search({ queryType: SearchQueryType.InView, keyWord: '医院', mapBound: '-180,-90,180,90', level: 12 })
// 地名搜索(queryType = 7)
await client.search({
queryType: SearchQueryType.PlaceName, keyWord: '西湖',
mapBound: '-180,-90,180,90', level: 12, specify: '156330000',
})
// 多边形搜索(queryType = 10),首尾坐标相同
await client.search({
queryType: SearchQueryType.Polygon, keyWord: '学校',
polygon: '116.3,39.9,116.5,39.9,116.5,39.95,116.3,39.95,116.3,39.9',
})
// 行政区划区域搜索(queryType = 12)
await client.search({ queryType: SearchQueryType.Region, keyWord: '银行', specify: '156110000' })
// 数据分类搜索(queryType = 13)
await client.search({ queryType: SearchQueryType.DataType, keyWord: '餐饮', specify: '156110000' })
// 统计搜索(queryType = 14)
const stat = await client.search({ queryType: SearchQueryType.Statistics, keyWord: '加油站', specify: '156110000' })
console.log(stat.statistics?.[0]?.count)
// 超时 / 取消
const ac = new AbortController()
await client.search({ queryType: SearchQueryType.Normal, keyWord: '公园', mapBound: '-180,-90,180,90', level: 12 }, { signal: ac.signal, timeout: 5000 })
ac.abort()
响应字段:resultType(1 POI / 2 统计 / 3 行政区 / 4 建议词 / 5 线路)、count、pois、statistics、area、lineData、prompt、status。
client.geocode(params, extra?) / client.reverseGeocode(params, extra?)
// 地址 → 坐标
const geo = await client.geocode({ keyWord: '北京市海淀区莲花池西路28号' })
console.log(geo.location?.lon, geo.location?.lat, geo.location?.level, geo.location?.score)
// 坐标 → 地址
const rev = await client.reverseGeocode({ lon: 116.37304, lat: 39.92594, ver: 1 })
console.log(rev.result?.formatted_address)
console.log(rev.result?.addressComponent.poi, rev.result?.addressComponent.road)
client.drive(params, extra?) —— 驾车规划
官方该接口返回的是 XML(不是 JSON),SDK 已解析成
DriveResponse,原始文本保留在raw。
const drive = await client.drive({
orig: '116.35,39.92',
dest: '116.39,39.90',
mid: '116.36,39.91;116.38,39.91', // 可选,途经点用分号分隔
style: 0, // 0 最快 / 1 最短 / 2 避开高速 / 3 步行
})
console.log(drive.format) // 'xml'
console.log(drive.distance) // '7.0' —— ⚠️ 单位是「公里」
console.log(drive.duration) // '482.0' —— 单位是「秒」
drive.route?.steps?.forEach((s, i) => {
console.log(i, s.instruction, s.distanceText, s.distance) // 例:'1.2公里' 1200(⚠️ 单位是「米」)
})
单位不一致是官方响应自带的坑:顶层 <distance> 是公里,而按段的里程是米。DriveStep.duration 官方文档中的 streetDistance 字段在 <routes>/<item> 里其实不存在,SDK 会从指引文本 strguide 解析(解析不到时为 NaN / 空串)。
client.transit(params, extra?) —— 公交规划
import { BusLineType } from '@bonncloud/tianditu/services'
const bus = await client.transit({
startPosition: '116.42,39.93',
endPosition: '116.34,39.93',
lineType: BusLineType.Fast, // 按位可组合:Fast 1 / LessTransfer 2 / LessWalk 4 / NoSubway 8
})
console.log(bus.resultCode) // 0 正常;1 找不到起点;2 找不到终点;3 规划失败;4/5 建议步行;6 参数错误
console.log(bus.hasSubway) // 0 不含地铁 / 1 含地铁
bus.results[0]?.lines?.forEach((line) => {
console.log(line.lineName) // 如 '3路—4路—5路'
line.segments.forEach((seg) => console.log(seg.segmentType, seg.segmentLine.segmentDistance))
})
resultCode 为 3(规划失败)或 6(参数错误)时抛错;1 2 4 5 属于「有提示但可用」的返回,不抛错。
client.administrative(params, extra?) —— 行政区划 V2.0
官方文档:https://lbs.tianditu.gov.cn/server/administrative2.html
import { AdminChildLevel } from '@bonncloud/tianditu/services'
const admin = await client.administrative({
keyword: '156110000', // 行政区划名称或 9 位行政区划编码
childLevel: AdminChildLevel.One, // '0' 不下级 / '1' 下一级 / '2' 下两级 / '3' 下三级
extensions: true, // 需要轮廓数据
})
console.log(admin.status) // 200 正常,其他请看 admin.message
admin.data.district.forEach((item) => {
console.log(item.name, item.gb, item.pgb, item.level)
console.log(item.center.lng, item.center.lat)
console.log(item.boundary) // WKT MULTIPOLYGON 串(extensions: true 时才有)
console.log(item.children?.length) // 下级行政区(childLevel 非 0 时才有)
})
status === 200只代表请求成功,不代表一定查到数据:查无结果时data.district为空数组, SDK 不抛错,请自行判断data.district.length(官方响应里没有 V1.0 的returncode)。- 参数:
keyword必填,支持行政区划名称或编码,由官方自动识别,名称支持模糊查询; 只传一个字符时官方只返回suggestion,不返回district。childLevel默认'0'、extensions默认false。 - 条目字段:
gb(行政区划编码)、pgb(上级行政区划编码,官方参数表未列出但实测稳定存在)、center: { lng, lat }、level: number(2 区县 / 3 市 / 4 省 / 5 国家)、boundary(WKT 串,extensions: false时该字段不存在而非空串)。 - 持续拿到空结果要先排查限流:实测官方在触发限流时可能不返回错误码,而是返回
status: 200且district为空数组(显式形式是{"code":302010,"msg":"该tk已限流"})。 所以不要把空数组当成「该名称不存在」——先确认配额与调用频率。 名称查询(keyword: '北京')与编码查询(keyword: '156110000')都可以正常返回数据。 - 官方 V1.0 已于 2024-06-30 下线:旧的
GET /administrative(postStr传参, 含searchWord/searchType/needSubInfo/needAll/needPolygon/needPre) 现已恒返回 HTTP 418,0.2.0 起已彻底移除,相关能力映射见CHANGELOG.md。
client.staticMapUrl(params?) / client.staticMap(params?, extra?)
// 生成可直接用于 <img src> 的地址
const url = client.staticMapUrl({
center: '116.40,39.93',
zoom: 12,
width: 800, // 最大 1024
height: 600, // 最大 1024
layers: 'vec_c,cva_c', // vec_c / cva_c / img_c / cia_c / ter_c / cta_c / ibo_c(_c 经纬度 / _w 墨卡托)
markers: '116.40,39.93,北京',
labels: '116.40,39.93',
bbox: '116.3,39.85,116.5,39.95',
})
// 拉取图片二进制
const buffer = await client.staticMap({ center: '116.40,39.93', zoom: 10, width: 600, height: 400 })
fs.writeFileSync('map.png', Buffer.from(buffer))
client.raw(path, params?, extra?) —— 逃生舱
调用 SDK 未封装的任意天地图 Web 服务接口:
const res = await client.raw('/staticimage', { center: '116.4,39.93', width: 400, height: 300 })
client.tk / client.baseURL
client.tk // readonly string
client.baseURL // readonly string(已去掉结尾斜杠)
3.9 无状态函数式 API
不持有客户端实例,适合一次性调用或配合 tree-shaking。第二个参数传 Key 字符串或 TiandituClientOptions。
import { search, geocode, reverseGeocode, drive, transit, administrative, staticMapUrl } from '@bonncloud/tianditu/services'
const tk = process.env.TIANDITU_SERVER_TK!
const res = await search({ queryType: 1, keyWord: '公园', mapBound: '-180,-90,180,90', level: 12 }, tk)
const geo = await geocode({ keyWord: '北京市海淀区莲花池西路28号' }, { tk, timeout: 8000 })
const rev = await reverseGeocode({ lon: 116.37304, lat: 39.92594 }, tk)
const dri = await drive({ orig: '116.35,39.92', dest: '116.39,39.90' }, tk)
const bus = await transit({ startPosition: '116.42,39.93', endPosition: '116.34,39.93', lineType: 1 }, tk)
const adm = await administrative({ keyword: '156110000', childLevel: '1' }, tk)
const img = staticMapUrl(tk, { center: '116.40,39.93', zoom: 10, width: 400, height: 300 })
其他工具函数
import { DEFAULT_BASE_URL, parseDriveResponse, parseDistanceFromGuide, createClient } from '@bonncloud/tianditu/services'
import { TiandituServiceError, getJSON, toPostStr } from '@bonncloud/tianditu/services'
DEFAULT_BASE_URL // 'https://api.tianditu.gov.cn'
parseDistanceFromGuide('沿朝阳北路走300米并向北直行') // { meters: 300, text: '300米' }
parseDistanceFromGuide('沿崇文门东大街走1.2公里并..') // { meters: 1200, text: '1.2公里' }
parseDistanceFromGuide('沿朝阳北路行驶到目的地。') // null
parseDriveResponse(xmlText) // DriveResponse
createClient('YOUR_TK') // TiandituClient
toPostStr({ keyWord: '公园', start: 0 }) // '{"keyWord":"公园","start":0}'
错误处理
import { TiandituServiceError } from '@bonncloud/tianditu'
try {
await client.search({ queryType: 1, keyWord: '公园', mapBound: '-180,-90,180,90', level: 12 })
} catch (err) {
if (err instanceof TiandituServiceError) {
err.message // '搜索失败:xxx'
err.status // HTTP 状态码,网络层失败为 0
err.code // 天地图业务码,如 infocode / status / resultCode
err.payload // 原始响应文本
err.url // 请求地址
}
}
3.10 jsApi —— 服务端 API 的便捷方法
在 TiandituClient 之上做了结果归一化(坐标转数字、坐标串转数组、XML 解析),一行拿到能直接用的结果。底层同样是 Web 服务 HTTP API,需要服务端 Key。
import { jsApi, TiandituClient, SearchQueryType } from '@bonncloud/tianditu'
// 或具名引入:import { geocode, searchPois } from '@bonncloud/tianditu/browser'
鉴权参数 auth 支持两种写法:
const auth = process.env.TIANDITU_SERVER_TK! // ① 传 Key 字符串(临时创建客户端)
const auth2 = new TiandituClient({ tk, timeout: 8000, retry: 1 }) // ② 传客户端实例(推荐,复用配置)
jsApi.geocode(auth, address)
const loc = await jsApi.geocode(auth, '北京市海淀区莲花池西路28号')
// → { lng: 116.290158, lat: 39.894696, level: '门址', score: 100, raw }
console.log(loc?.lng, loc?.lat, loc?.level, loc?.score)
// 未匹配到时返回 null
jsApi.reverseGeocode(auth, lnglat) / jsApi.reverseGeocodeDetail(auth, lnglat)
// 只取格式化地址文本
const addr = await jsApi.reverseGeocode(auth, [116.37304, 39.92594])
// → '北京市西城区西什库大街31号院23'
const addr2 = await jsApi.reverseGeocode(auth, { lon: 116.37304, lat: 39.92594 })
// 完整结构化结果(省市区、道路、周边 POI 等)
const detail = await jsApi.reverseGeocodeDetail(auth, [116.37304, 39.92594])
console.log(detail?.addressComponent.poi, detail?.addressComponent.road)
jsApi.searchPois(auth, params) / jsApi.search(auth, params)
// 归一化 POI 列表
const pois = await jsApi.searchPois(auth, {
queryType: SearchQueryType.Nearby,
keyWord: '公园',
pointLonlat: '116.48016,39.93136',
queryRadius: 3000,
})
// → [{ name, address, lng, lat, phone, province, city, county, typeName, distance, raw }]
pois.forEach((p) => console.log(p.name, p.lng, p.lat, p.distance))
// 需要 statistics / area / lineData 时取原始响应
const raw = await jsApi.search(auth, { queryType: SearchQueryType.Normal, keyWord: '北京大学', mapBound: '-180,-90,180,90', level: 12 })
console.log(raw.count, raw.pois?.length)
jsApi.drivingRoute(auth, params)
const route = await jsApi.drivingRoute(auth, { orig: '116.35,39.92', dest: '116.39,39.90' })
// → { distance: 5.82, duration: 720, path: [[lng, lat], ...], raw }
// ↑ 全程距离单位是「公里」
console.log(route.distance, route.duration, route.path.length)
map.addOverlay(createPolyline(T, route.path, { color: '#165dff' }))
// 按段距离单位是「米」(从指引文本解析)
route.raw.route?.steps?.forEach((s) => console.log(s.distanceText, s.distance))
// → '400米' 400
// → '1.2公里' 1200
jsApi.transitRoute(auth, params) / jsApi.busLineSearch(auth, keyword, options?)
const bus = await jsApi.transitRoute(auth, {
startPosition: '116.42,39.93',
endPosition: '116.34,39.93',
lineType: 1,
})
// 天地图没有独立的「公交线路查询」接口,这里用搜索服务检索线路名并取 lineData
const lines = await jsApi.busLineSearch(auth, '1路', {
specify: '156110000', // 限定行政区国标码(9 位)
mapBound: '116.02,39.83,116.65,39.99',
level: 12,
start: 0,
count: 10,
})
// → [{ stationNum, poiType, name, uuid }]
jsApi.parseLngLatPath(coordinateString)
jsApi.parseLngLatPath('116.4,39.9;116.5,39.9') // [[116.4, 39.9], [116.5, 39.9]]
jsApi.parseLngLatPath('116.4,39.9 116.5,39.9') // 分号、空格、换行均可
jsApi.parseLngLatPath('') // []
jsApi 全部方法:geocode reverseGeocode reverseGeocodeDetail search searchPois drivingRoute transitRoute busLineSearch parseLngLatPath。
3.11 坐标工具
纯 TypeScript 实现,Node.js 与浏览器通用,无任何地图依赖。
import {
wgs84ToGcj02, gcj02ToWgs84, gcj02ToBd09, bd09ToGcj02,
wgs84ToBd09, bd09ToWgs84, outOfChina,
distance, parseLngLat, formatLngLat, round,
} from '@bonncloud/tianditu'
// WGS84(GPS)↔ GCJ02(高德 / 腾讯)
wgs84ToGcj02(116.404, 39.915) // [116.4102445, 39.9164043]
gcj02ToWgs84(116.4102445, 39.9164043) // [116.404, 39.915],迭代逼近,往返误差约 1e-14
// GCJ02 ↔ BD09(百度)
gcj02ToBd09(116.404, 39.915)
bd09ToGcj02(116.404, 39.915)
// 一步到位
wgs84ToBd09(116.404, 39.915)
bd09ToWgs84(116.404, 39.915)
// 境外坐标不做偏移
outOfChina(116.404, 39.915) // false
// 球面距离(米,保留 2 位小数)
distance([116.4, 39.9], [121.47, 31.23]) // 1068272.85
// 坐标串解析与格式化
parseLngLat('116.404, 39.915') // [116.404, 39.915],解析失败抛 TypeError
formatLngLat([116.404, 39.915]) // '116.404,39.915'
formatLngLat(116.404, 39.915) // '116.404,39.915'
// 保留小数位
round(116.404123456, 6) // 116.404123
gcj02ToWgs84 因官方偏移算法不可逆,采用 3 次迭代逼近(单次反向误差约 1~2 米,3 次后为厘米级)。
3.12 Vue 3 组件
import {
TdtMap, TdtMarker, TdtPolyline, TdtPolygon, TdtCircle, TdtRectangle,
TdtInfoWindow, TdtLabel, TdtTileLayer, TdtControl,
} from '@bonncloud/tianditu/vue'
除 TdtMap 外,所有覆盖物/图层/控件组件都必须放在 <TdtMap> 内部(通过注入获取地图实例)。
TdtMap
| Prop | 类型 | 默认 | 说明 |
|---|---|---|---|
tk |
string |
— | 必填,浏览器端 Key |
center |
LngLatLike |
— | 中心点 [lng, lat] |
zoom |
number |
12 |
缩放级别 |
minZoom / maxZoom |
number |
— | 缩放范围 |
projection |
string |
EPSG:900913 |
地图投影 |
plugins / dependencies |
string[] |
— | 一并加载的扩展库 / 其依赖 |
mapOptions |
MapOptions |
— | 原生 T.Map 的其它配置项 |
controls |
TdtBuiltinControls |
— | 内置控件开关,见下 |
width / height |
string |
100% / 100% |
容器尺寸 |
| 事件 | 载荷 | 说明 |
|---|---|---|
@ready |
TiandituMap |
地图初始化完成(此时控件已挂载) |
@error |
Error |
脚本加载或初始化失败 |
默认插槽在 ready 后才渲染,并透出 map 与 T:
<TdtMap :tk="TK" :center="[116.4, 39.9]" :zoom="12" @ready="onReady">
<template #default="{ map, T }">
<span v-if="map">当前层级 {{ map.getZoom() }}</span>
</template>
</TdtMap>
controls 内置开关(传 true 用默认位置,传对象自定义):
<TdtMap
:tk="TK"
:controls="{
zoom: true,
scale: { position: 'bottomleft' },
mapType: { position: 'topright' },
copyright: { position: 'bottomright', content: '© 天地图' },
}"
/>
通过 ref 可拿到组件暴露的实例:
<script setup lang="ts">
import { ref } from 'vue'
import { TdtMap } from '@bonncloud/tianditu/vue'
const mapRef = ref<InstanceType<typeof TdtMap> | null>(null)
// mapRef.value?.map / .T / .ready / .init / .container
</script>
TdtMarker
| Prop | 类型 | 默认 | 说明 |
|---|---|---|---|
position |
LngLatLike |
— | 必填,[lng, lat] |
iconUrl |
string |
— | 自定义图标地址 |
iconSize |
[number, number] |
[25, 41] |
图标尺寸 |
iconAnchor |
[number, number] |
— | 图标锚点 |
draggable |
boolean |
false |
是否可拖拽 |
title |
string |
— | 悬浮提示 |
zIndexOffset |
number |
— | 层级偏移 |
opacity |
number |
1 |
透明度 |
options |
MarkerOptions |
— | 原生配置透传 |
事件:@click @dblclick @dragend @mouseover @mouseout(载荷为事件对象)。暴露 marker。
<TdtMarker :position="[116.4, 39.9]" title="北京" draggable @dragend="onDragEnd" />
<TdtMarker :position="p" icon-url="/pin.png" :icon-size="[32, 40]" :icon-anchor="[16, 40]" @click="onClick" />
TdtPolyline
| Prop | 类型 | 默认 |
|---|---|---|
points |
LngLatLike[] |
— (必填) |
color |
string |
#3388ff |
weight |
number |
3 |
opacity |
number |
0.9 |
lineStyle |
LineStyle |
— |
showLabel |
boolean |
— |
options |
PolylineOptions |
— |
事件:@click。暴露 polyline。
<TdtPolyline :points="[[116.3, 39.9], [116.5, 39.9]]" color="#165dff" :weight="5" show-label />
TdtPolygon
| Prop | 类型 | 默认 |
|---|---|---|
points |
LngLatLike[] |
— (必填) |
color |
string |
#3388ff |
weight |
number |
2 |
opacity |
number |
0.9 |
fillColor |
string |
#3388ff |
fillOpacity |
number |
0.2 |
lineStyle |
LineStyle |
— |
options |
PolygonOptions |
— |
事件:@click。暴露 polygon。
<TdtPolygon :points="area" fill-color="#00b42a" :fill-opacity="0.25" />
TdtCircle
| Prop | 类型 | 默认 |
|---|---|---|
center |
LngLatLike |
— (必填) |
radius |
number |
— (必填,单位米) |
color / weight / opacity |
string / number / number |
#3388ff / 2 / 0.9 |
fillColor / fillOpacity |
string / number |
#3388ff / 0.2 |
options |
CircleOptions |
— |
事件:@click。暴露 circle。
<TdtCircle :center="[116.35, 39.93]" :radius="1500" fill-color="#ff7d00" />
TdtRectangle
| Prop | 类型 | 默认 |
|---|---|---|
sw |
LngLatLike |
— (必填,西南角) |
ne |
LngLatLike |
— (必填,东北角) |
color / weight |
string / number |
#3388ff / 2 |
fillColor / fillOpacity |
string / number |
#3388ff / 0.2 |
options |
RectangleOptions |
— |
事件:@click。暴露 rectangle。
<TdtRectangle :sw="[116.3, 39.85]" :ne="[116.45, 39.95]" fill-color="#722ed1" />
TdtInfoWindow
| Prop | 类型 | 默认 | 说明 |
|---|---|---|---|
content |
string |
— | 内容,支持 HTML 片段 |
position |
LngLatLike |
— | 必填,显示位置 |
visible |
boolean |
true |
是否显示,切 false 会关闭 |
offset |
[number, number] |
— | 偏移像素 |
options |
InfoWindowOptions |
— | 原生配置透传 |
暴露 infoWindow。
<TdtInfoWindow v-if="show" :position="pos" :content="`<strong>${title}</strong><br/>${address}`" :offset="[0, -30]" />
TdtLabel
天地图 4.0 无 T.Label,本组件基于 Marker + DivIcon 实现。
| Prop | 类型 | 默认 |
|---|---|---|
text |
string |
— (必填) |
position |
LngLatLike |
— (必填) |
color |
string |
#1f2329 |
fontSize |
number |
12 |
backgroundColor |
string |
— |
padding |
string |
— |
borderColor |
string |
— |
draggable |
boolean |
— |
className |
string |
— |
事件:@click。暴露 marker。
<TdtLabel :position="[116.42, 39.93]" text="天地图 4.0" :font-size="14" color="#d03050" background-color="rgba(255,255,255,.9)" padding="2px 6px" />
TdtTileLayer
| Prop | 类型 | 默认 | 说明 |
|---|---|---|---|
url |
string |
— | 自定义瓦片地址(与 baseLayer 二选一) |
baseLayer |
BaseLayerName |
vec |
官方底图图层名 |
projection |
'c' | 'w' |
w |
官方底图投影 |
baseOptions |
BaseLayerOptions |
— | 底图构造参数 |
options |
TileLayerOptions |
— | 图层构造参数 |
visible |
boolean |
true |
是否显示 |
opacity |
number |
— | 透明度(响应式更新) |
暴露 layer。tk 自动从 <TdtMap> 上下文获取。
<TdtMap :tk="TK">
<!-- 叠加影像注记 -->
<TdtTileLayer base-layer="cia" projection="w" :opacity="0.9" />
<!-- 自定义瓦片服务 -->
<TdtTileLayer url="https://tile.example.com/{z}/{x}/{y}.png" :options="{ maxZoom: 18 }" />
</TdtMap>
TdtControl
把任意 DOM 放进一个自动按 position 停靠的控件里。
| Prop | 类型 | 默认 |
|---|---|---|
position |
ControlPosition |
topright |
offset |
[number, number] |
— |
visible |
boolean |
true |
暴露 control。
<TdtControl position="bottomright" :offset="[0, -10]">
<div class="card">
<button @click="locate">定位</button>
</div>
</TdtControl>
3.13 Vue 3 组合式函数
useTianditu(options) —— 加载脚本
import { useTianditu } from '@bonncloud/tianditu/vue'
const { T, isLoading, error, load } = useTianditu({
tk: 'YOUR_BROWSER_TK',
immediate: true, // 默认 true,在 onMounted 中自动加载
})
// 手动加载(幂等)
await load()
返回:T、isLoading、error、load。
useTdtMap(containerRef, options, provideToDescendants?) —— 创建地图
import { ref } from 'vue'
import { useTdtMap } from '@bonncloud/tianditu/vue'
const container = ref<HTMLElement | null>(null)
const { map, T, ready, isLoading, error, init, provideContext } = useTdtMap(container, {
tk: 'YOUR_BROWSER_TK',
center: [116.4, 39.9],
zoom: 12,
immediate: true, // 默认 true,onMounted 自动初始化
})
center 与 zoom 是响应式的,变化会自动同步到地图。组件卸载(onScopeDispose)时自动 destroy()。
<template>
<div ref="container" style="height: 400px" />
</template>
useTiandituOverlay(create, deps, hooks) —— 自定义覆盖物生命周期
把任意覆盖物绑定到父级 <TdtMap> 提供的地图实例上:地图就绪后自动创建、依赖变化自动重建、组件卸载自动移除。
import { useTiandituOverlay } from '@bonncloud/tianditu/vue'
import { createPolyline } from '@bonncloud/tianditu'
const props = defineProps<{ points: [number, number][] }>()
const { overlay, refresh, remove } = useTiandituOverlay(
({ T }) => createPolyline(T, props.points, { color: '#165dff', weight: 4 }),
[() => props.points], // 依赖变化时重建
{
events: { click: (e) => console.log('点击折线', e) }, // 自动绑定事件
onCreated: (instance, { map }) => console.log('已添加到地图', map.getZoom()),
},
)
返回:overlay(Ref<O | null>)、refresh()、remove()。
useTiandituServices(options) —— Web 服务响应式封装
import { useTiandituServices } from '@bonncloud/tianditu/vue'
import { SearchQueryType } from '@bonncloud/tianditu'
const { client, loading, error, search, geocode, reverseGeocode, drive, transit, administrative, run } =
useTiandituServices({
tk: 'injected-by-server', // 经转发端点时传占位符
baseURL: '/api/tianditu',
timeout: 10_000,
})
// 方法出错时不抛出,而是返回 undefined 并把错误放进 error
const res = await search({
queryType: SearchQueryType.Nearby,
keyWord: '公园',
pointLonlat: '116.48016,39.93136',
queryRadius: 2000,
})
if (!res) console.log(error.value)
// run() 用于调用 client 上的任意方法(如 raw)
const custom = await run((c) => c.raw('/staticimage', { width: 400 }))
返回:client(ComputedRef<TiandituClient>)、loading、error、search、geocode、reverseGeocode、drive、transit、administrative、run。
TIANDITU_MAP_CONTEXT —— 自定义组件的注入键
import { inject } from 'vue'
import { TIANDITU_MAP_CONTEXT } from '@bonncloud/tianditu/vue'
const ctx = inject(TIANDITU_MAP_CONTEXT, null)
// ctx.T / ctx.map / ctx.ready / ctx.tk
ctx.map.value 为 TiandituMap,ctx.T.value 为 T 命名空间。用它可以写出自己的 <TdtXxx> 组件。
3.14 类型导出
// 服务端与客户端配置、请求/响应结构
import type {
TiandituClientOptions, SearchParams, SearchResponse, SearchPoi,
GeocodeParams, GeocodeResponse, ReverseGeocodeParams, ReverseGeocodeResponse,
DriveParams, DriveResponse, DriveRoute, DriveStep,
TransitParams, TransitResponse, TransitLine, TransitSegment,
AdministrativeParams, AdministrativeResponse, AdministrativeItem,
StaticMapParams, CoordSystem, ServiceStatus,
} from '@bonncloud/tianditu/types'
// 枚举
import { SearchQueryType, DriveStyle, BusLineType, AdminChildLevel } from '@bonncloud/tianditu/types'
// 浏览器端类型
import type {
TiandituNamespace, MapOptions, MarkerOptions, PolylineOptions, PolygonOptions,
CircleOptions, RectangleOptions, InfoWindowOptions, TileLayerOptions,
IconOptions, DivIconOptions, TextMarkerOptions, MapTypeItem,
LngLat, Point, LngLatBounds, Map, Overlay, Control,
LngLatLike, PointLike, LngLatTuple, LineStyle,
TiandituEvent, MapMouseEvent, MapEventMap, OverlayEventMap,
} from '@bonncloud/tianditu/types'
LngLatLike 覆盖四种输入:[lng, lat]、{ lng, lat }、{ lon, lat }、已有 T.LngLat。PointLike 支持 [x, y] 与 { x, y }。
3.15 完整可运行示例
Node.js 后端:搜索 + 地理编码 + 路径规划
import { TiandituClient, SearchQueryType } from '@bonncloud/tianditu/services'
const client = new TiandituClient({ tk: process.env.TIANDITU_SERVER_TK!, timeout: 8000, retry: 1 })
const pois = await client.search({
queryType: SearchQueryType.Nearby,
keyWord: '公园',
pointLonlat: '116.48016,39.93136',
queryRadius: 3000,
start: 0,
count: 20,
})
console.log(`找到 ${pois.count} 个结果,首个:${pois.pois?.[0]?.name}`)
const geo = await client.geocode({ keyWord: '北京市海淀区莲花池西路28号' })
const addr = await client.reverseGeocode({ lon: 116.37304, lat: 39.92594 })
console.log(geo.location?.lon, geo.location?.lat, '→', addr.result?.formatted_address)
const drive = await client.drive({ orig: '116.35,39.92', dest: '116.39,39.90' })
console.log(`全程 ${drive.distance} 公里 / ${drive.duration} 秒`)
const imgUrl = client.staticMapUrl({ center: '116.40,39.93', zoom: 12, width: 800, height: 600 })
console.log(imgUrl)
浏览器:命令式 API
import {
loadTianditu, TiandituMap,
createMarker, createTextMarker, createPolyline, createPolygon,
createZoomControl, createScaleControl, createBaseLayer,
drawShape, measureDistance, formatDistance,
} from '@bonncloud/tianditu'
const TK = import.meta.env.VITE_TIANDITU_TK
const T = await loadTianditu({ tk: TK })
const map = await TiandituMap.create({
tk: TK,
container: '#map',
center: [116.40769, 39.89945],
zoom: 12,
})
map.addControl(createZoomControl(T, { position: 'topleft' }))
map.addControl(createScaleControl(T, { position: 'bottomleft' }))
map.addLayer(createBaseLayer(T, TK, 'cia', { projection: 'w' })) // 叠加影像注记
map.addOverlay(createMarker(T, [116.4, 39.9], { title: '北京' }))
map.addOverlay(createTextMarker(T, [116.42, 39.93], { text: '文字标注', style: { color: '#d03050' } }))
map.addOverlay(createPolyline(T, [[116.3, 39.9], [116.5, 39.9]], { color: '#165dff', weight: 4 }))
map.addOverlay(createPolygon(T, [[116.3, 39.85], [116.45, 39.85], [116.45, 39.95]], { fillColor: '#00b42a' }))
const off = map.on('click', (e) => console.log(e.lnglat?.getLng(), e.lnglat?.getLat()))
// 交互式测距
const pts = await drawShape(T, map.raw)
if (pts.length > 1) console.log(`总长 ${formatDistance(measureDistance(T, map.raw, pts))}`)
map.setBaseMap('satellite')
off()
map.destroy()
Vue 3:组件 + 组合式函数 + 转发调用 Web 服务
<script setup lang="ts">
import { ref } from 'vue'
import {
TdtMap, TdtMarker, TdtPolyline, TdtPolygon, TdtCircle, TdtLabel, TdtInfoWindow, TdtControl,
useTiandituServices,
} from '@bonncloud/tianditu/vue'
import { SearchQueryType, type TiandituMap } from '@bonncloud/tianditu'
const TK = import.meta.env.VITE_TIANDITU_TK // 浏览器端 Key:可供地图渲染
// Web 服务不能由浏览器直连,baseURL 指向自有转发端点,前端不持有服务端 Key
const { search, reverseGeocode, loading, error } = useTiandituServices({
tk: 'injected-by-server',
baseURL: '/api/tianditu',
})
const center = ref<[number, number]>([116.40769, 39.89945])
const map = ref<TiandituMap | null>(null)
const show = ref(true)
const pois = ref<{ name: string; position: [number, number] }[]>([])
async function doSearch() {
const res = await search({
queryType: SearchQueryType.Nearby,
keyWord: '公园',
pointLonlat: center.value.join(','),
queryRadius: 3000,
})
pois.value = (res?.pois ?? []).map((p) => {
const [lng, lat] = p.lonlat.split(',').map(Number)
return { name: p.name, position: [lng, lat] as [number, number] }
})
}
</script>
<template>
<TdtMap
:tk="TK"
:center="center"
:zoom="12"
:controls="{ zoom: true, scale: { position: 'bottomleft' }, mapType: true }"
@ready="map = $event"
>
<TdtMarker v-for="poi in pois" :key="poi.name" :position="poi.position" :title="poi.name" />
<TdtPolyline :points="[[116.3, 39.9], [116.5, 39.9]]" color="#165dff" :weight="4" />
<TdtPolygon :points="[[116.3, 39.85], [116.45, 39.85], [116.45, 39.95]]" fill-color="#00b42a" />
<TdtCircle :center="center" :radius="1500" fill-color="#ff7d00" />
<TdtLabel :position="[116.42, 39.93]" text="标注文字" />
<TdtInfoWindow v-if="show" :position="center" content="<b>标题</b><br/>内容" />
<TdtControl position="bottomright">
<button @click="doSearch">搜索周边</button>
</TdtControl>
</TdtMap>
</template>
浏览器接入 Web 服务:后端转发
前端不直连天地图(原因见 4.3),把 baseURL 指向自己的转发端点即可:
浏览器 ──/api/tianditu/**──▶ 你的后端 ──▶ https://api.tianditu.gov.cn/**
(覆盖 tk、不转发 Referer/Origin)
// 前端:tk 传占位符,转发层会覆盖成真实 Key
const client = new TiandituClient({ tk: 'injected-by-server', baseURL: '/api/tianditu', timeout: 10_000 })
const { search } = useTiandituServices({ tk: 'injected-by-server', baseURL: '/api/tianditu' })
const loc = await jsApi.geocode(client, '北京市海淀区莲花池西路28号') // jsApi 直接接受客户端实例
// 后端:三件事 —— 覆盖 tk、不转发 Referer/Origin、原样回传响应体
// 可直接复用的实现见 examples/vue3-demo/server/tiandituProxy.ts
// (开发期以 Vite 插件形态提供,其中的 handleProxy() 可原样挂到 Express / Koa)
开发与联调
pnpm install
pnpm typecheck # vue-tsc 类型检查
pnpm test # vitest,41 项用例
pnpm build # 产出 dist(ESM + CJS + d.ts + sourcemap)
node scripts/live-check.mjs # 逐个打真实接口(需在 examples/vue3-demo/.env 配置服务端 Key)
# 运行 Vue 3 示例
cd examples/vue3-demo
pnpm install
pnpm dev # http://localhost:5177,同时启用 /api/tianditu 转发
示例工程的 .env 需要两个 Key(类型不同,权限不通用):
| 变量 | 前缀 | 用途 |
|---|---|---|
VITE_TIANDITU_TK |
必须带 VITE_ |
浏览器端 Key,给 <TdtMap> 渲染底图 |
TIANDITU_SERVER_TK |
不要带 VITE_ |
服务端 Key,只在 Node 侧读取,由转发中间件注入 |
带 VITE_ 前缀的变量会被 Vite 内联进前端产物、随页面公开 —— 服务端 Key 绝不能加这个前缀。
4. 注意事项
4.1 JS API 4.0 的能力边界(最重要)
实测加载 https://api.tianditu.gov.cn/api?v=4.0(返回版本 4.1.6)后,全局 T 上可用的公开类为:
Map LngLat Point Bounds LngLatBounds Icon DivIcon Marker Polyline
Polygon Circle Rectangle InfoWindow TileLayer MapType Overlay
LayerGroup GeoJSON Version
T.Control: Zoom Scale MapType Copyright
此外主脚本上还存在 Protocol ScriptLoader extend setOptions 与 T.Control.DataSources,但本包未为其提供类型封装,需要时走 (T as any).xxx。
不存在的类(文档站有页面但运行时 undefined)及其替代方案见 2.3。官方文档站 api/js4.0/pages-class/ 下的 Label.html 与 MarkerClusterer.html 属遗留页面:在主脚本(138 KB)中检索这两个名字均出现 0 次,聚合库 opensource/openlibrary/MarkerClusterer.js 也是 404。
4.2 两种 Key 权限不通用
在天地图控制台创建应用时要选应用类型,两类 Key 权限完全隔离:
| Key 类型 | 可用于 | 用在 Web 服务 API 会得到 |
|---|---|---|
| 浏览器端 | JS API 4.0(网页里嵌地图) | 403 + {"code":301012,"msg":"权限类型错误"} |
| 服务端 | Web 服务 API(搜索/地理编码/路径规划…) | 可用 |
做完整功能请建两个应用、拿两个 Key:一个浏览器端给 <TdtMap :tk>,一个服务端给 TiandituClient / useTiandituServices / jsApi。
4.3 浏览器不能直连 Web 服务,必须走转发
服务端 Key 会校验请求来源:从 Node 调用不带 Referer 时正常,而带浏览器 Referer / Origin 的请求会被拒:
403 {"code":301013,"resolve":"Key权限类型为:服务器端,请使用服务器端访问!"}
这不是 CORS 配置缺失(响应头里有 Access-Control-Allow-Origin: *),而是业务层的来源校验,因此前端无法直连。请在自有后端用 TiandituClient 包一层接口给前端,或把 baseURL 指向转发端点(见 3.15)。
4.4 坐标系
- 天地图底图基于 CGCS2000,与 WGS84 差异在厘米级,日常开发可直接等同。
- 与高德 / 腾讯(GCJ02)、百度(BD09)的数据混用前,先用内置坐标工具互转,否则会有几百米级的偏移。
- 天地图服务端接口的坐标一律是
"lng,lat"顺序(经度在前),范围是"minx,miny,maxx,maxy"。
4.5 单位与返回值
- 驾车规划全程距离是「公里」(
drive.distance/jsApi.drivingRoute().distance),而按段距离是「米」(route.steps[].distance)。同一份响应混用两种单位。 - 官方
<routes>/<item>里其实没有streetDistance字段,段里程只写在指引文本里,SDK 会解析出来(解析不到时为NaN/ 空串)。 - 公交规划的
resultCode中1245属于「有提示但可用」的返回,3(规划失败)与6(参数错误)才抛错。 - 行政区划 V2.0 的
status为200只代表请求成功,查无结果时data.district为空数组且不抛错; 条目字段是gb/pgb/center: { lng, lat }/boundary,不存在 V1.0 的lnt/cityCode/returncode。
4.6 容器与初始化
- 原生
new T.Map()只认不带#的元素 id或 DOM 元素,传'#map'会抛Cannot read properties of null (reading '_tdt_events')。本包已归一化,但请确保容器在创建地图时已存在且有尺寸。 - 容器尺寸变化后(侧边栏开合、标签页切换、弹窗打开)务必调用
map.resize(),否则地图会显示不全。 - 使用 Vue 组件时,
<TdtMap>的父容器需要有明确高度(组件默认height: 100%)。
4.7 框架与环境
- SSR / Node:
loadTianditu()在无document的环境会立即抛出明确错误,请在onMounted/useEffect等客户端生命周期中调用;Web 服务客户端前后端通用,可放心在 Node 中使用。 fetch:Node.js< 18无内置fetch,通过new TiandituClient({ fetch })注入实现(如node-fetch)。- Vue:
vue@^3.3是可选 peer 依赖,只有引入@bonncloud/tianditu/vue时才需要。
4.8 配额与合规
- 天地图按日限制调用量(例如个人开发者地图搜索 3000 次/日、地理编码 7000 次/日等,以官方最新公布为准),超限会返回对应错误码,生产环境请做好缓存与降级。
- 静态地图单边最大
1024;周边搜索半径最大 10 公里;搜索结果start/count上限 300。 - 请勿把服务端 Key 写进前端代码或前端环境变量(
VITE_/NEXT_PUBLIC_等前缀会自动内联进产物)。
4.9 已知小问题
TiandituMap.createSync(container)的第二个参数options类型为CreateMapOptions(其中container是必填项),实际上只读取center与zoom。传入其它字段不会生效;如需完整配置请使用TiandituMap.create()。<TdtInfoWindow>声明了@close事件,但当前版本尚未触发(天地图信息窗口关闭由地图侧closeInfoWindow()触发),如需感知关闭请自行监听map.raw上的事件。- 文字标注的
style会把 camelCase 键转成 kebab-case,webkit前缀属性会缺失开头的-:传webkitTextStrokeColor生成的是webkit-text-stroke-color:#xxx(浏览器会忽略该声明)。因此<TdtLabel>的borderColor描边目前不生效;如需描边,请在渲染后用 CSS 自行覆盖.tdt-text-marker。 - 本 README 中的截图位于
docs/screenshot.png,docs/目录不在package.json的files白名单内,因此在 npm 包页面上该图不会显示(仓库内正常)。如需在 npm 上展示,请在files中补上docs/screenshot.png。 - 实测服务端结论:用真实服务端 Key 跑
node scripts/live-check.mjs的结果为 13/13 通过。 0.1.x 时行政区划步骤恒失败(CloudWAF 返回418),当时判断为「服务端侧拦截,需检查该服务是否已开通」, 实际原因是 SDK 打的是已于 2024-06-30 下线的 V1.0 端点GET /administrative—— 该路径现在即便不带任何参数也返回418,属路径级失效,而非权限未开通。0.2.0 改用GET /v2/administrative后已恢复正常。
5. 版权
5.1 本包许可
本包基于 MIT License 发布,版权归 Bonncloud 所有:
MIT License
Copyright (c) 2026 Bonncloud
完整条款见 LICENSE。你可以在保留版权声明与许可声明的前提下,自由使用、修改、合并、发布、分发、再许可及销售本软件。
本包按「原样」提供,不附带任何形式的担保。
5.2 天地图服务与数据
本包只是天地图官方 API 的客户端封装,不包含、不代理、不转售任何地图数据或服务:
- 天地图平台及其地图数据、瓦片、检索与规划服务的权利归国家基础地理信息中心(天地图)所有,使用前请自行申请服务许可 Key。
- 使用天地图服务须遵守《天地图服务条款》《天地图 API 使用许可》及国家测绘地理信息相关法律法规,请以 天地图官网 公布的最新条款为准。
- 请勿滥用接口、绕过配额限制,或把天地图数据用于条款禁止的用途。
- 基于天地图成果制作并公开发布的地图,应按天地图要求标注数据来源与地图审图号(具体标注格式与审图号以天地图官方公布为准);SDK 提供了
createCopyrightControl()便于在地图上添加版权标注。 - 服务可用性、配额、接口响应与字段定义均由天地图官方决定,可能随版本变更,本包不对此作出承诺。
5.3 第三方
- 本包无运行时第三方依赖。
- 开发期依赖(Vite、TypeScript、Vitest、Vue 等)各自遵循其开源许可。
- 官方开源扩展库(
HeatmapOverlay、CarTrack、D3SvgOverlay、BufferTool、ImageOverlay)由天地图以 CDN 形式提供,需通过loadPlugin()单独加载,其版权归原作者所有,不在本包许可范围内。
相关文件
| 文件 | 说明 |
|---|---|
| CHANGELOG.md | 版本变更记录,含实测结论与破坏性变更说明 |
| LICENSE | MIT 许可全文 |
| examples/vue3-demo | Vue 3 + Vite 全功能示例工程(含 Web 服务转发中间件) |
| scripts/live-check.mjs | 真实接口联调自检脚本 |
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.0 | ^3.3.0 | MIT | |
| 0.1.3 | ^3.3.0 | MIT | |
| 0.1.2 | ^3.3.0 | MIT |