Skip to content

Vue 3 组件(@geoverse/vue)

@geoverse/vue 是 GeoVerse 的官方 Vue 3 组件层:把 core 的命令式 API 包装成声明式组件树命令式 composable,让你用模板和响应式数据驱动地图。它是一层薄绑定——不复制任何地图逻辑,坐标 / 聚合 / 绘制等全部留在 core。

React 用户见 React 组件(@geoverse/react),组件清单与事件映射约定与本包 1:1 对齐。

安装

shell
pnpm add @geoverse/vue geoverse ol

geoverseolvue 均为 peerDependencies(与宿主共享实例)。要素变换组件 <GvTransform> 额外依赖可选 peer ol-ext,用到时再装 pnpm add ol-ext

快速上手(声明式)

vue
<script setup lang="ts">
import { ref } from 'vue';
import { GvMap, GvVectorLayer, GvMarker, GvInfoWindow } from '@geoverse/vue';

const center = ref([118.18, 24.49]);
const zoom = ref(12);
const open = ref(false);
</script>

<template>
  <GvMap
    style="height: 480px"
    base="gd-vec"
    v-model:center="center"
    v-model:zoom="zoom"
    scale-line
  >
    <GvVectorLayer>
      <GvMarker
        :options="{ position: [118.18, 24.49], color: 'red', size: 8 }"
        @click="open = true"
      />
    </GvVectorLayer>

    <GvInfoWindow v-model:open="open" :position="[118.18, 24.49]">
      插槽内容,点击标注弹出<!-- 气泡外观由内置默认皮肤提供,如需自定义覆盖 .geoverse-info-window -->
    </GvInfoWindow>
  </GvMap>
</template>
  • <GvMap>onMounted 内创建 GMap(SSR 安全),就绪后才渲染子组件并 provide 给它们;
  • center / zoom 受控,支持 v-model;底图 base 变化走 switchBase
  • 要素组件必须置于 <GvVectorLayer> 内,否则抛出清晰错误。

命令式(composable)

需要完全控制时用 useMap(),在自己的容器上创建 / 销毁地图:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useMap } from '@geoverse/vue';
import { Marker } from '@geoverse/core-ol';

const el = ref<HTMLElement>();
const { map } = useMap(
  { base: 'gd-vec', center: [118.18, 24.49], zoom: 12 },
  el,
);

function addPoint() {
  // map.value 是 markRaw 的 GMap 实例,可直接调用任意 core API
  map.value?.addLayer(/* ... */);
}
</script>

<template>
  <div ref="el" style="height: 480px"></div>
</template>

<GvMap> 子树内则用 injectMap() 取父地图。

全局注册(可选)

ts
import { createApp } from 'vue';
import { GeoVerseVue } from '@geoverse/vue';

createApp(App).use(GeoVerseVue); // 批量注册全部组件

也可只具名导入用到的组件,无需安装插件(利于 tree-shaking)。

组件一览

类别组件
容器GvMap
图层GvVectorLayerGvHeatLayerGvImageLayerGvCustomBaseLayerGvTrafficLayerGvClusterLayerGvSuperClusterLayerGvMassLayerGvNameLayer
要素GvMarkerGvCircleGvPolygonGvPolyline
交互GvDrawGvMeasureGvFeatureEditorGvTransform@experimental
控件 / 弹窗GvOverviewGvInfoWindow
轨迹GvPathSimplifier

约定:图层 / 要素 / 工具组件的 options prop = 对应 core 类的构造选项map / view 由组件自动注入;事件经 emits 转发(如要素的 @click、绘制的 @complete、轨迹的 @move)。每个组件用 defineExpose 暴露底层实例(模板 ref 可取)。

受控属性与响应式更新

  • <GvMap>center / zoom 双向受控,支持 v-model;底图 base 变化触发 switchBase(切投影并重投影既有图层/要素)。
  • 要素几何受控<GvMarker :options>position<GvCircle>center<GvPolygon>/<GvPolyline>path 变化时,组件会按当前底图投影自动重投影并更新几何。坐标统一以 WGS-84 经纬度传入。
    • 几何更新按「值」比较触发(而非对象引用),因此即使用内联 :options="{ ... }"(每次渲染新对象),切底图等无关重渲染也不会误重置几何。
  • 其余「构造即生效」的选项(如样式、聚合 distance)变化时不自动响应,需要时可经组件 ref 取实例命令式更新,或重建组件。

注意事项

  • 响应式陷阱:组件内部所有 ol 派生实例(地图 / 图层 / 要素)一律 markRaw / shallowRef,绝不进入深层响应式——这是正确性前提。你自己持有这些实例时也请遵循。
  • SSR:所有实例化都在 onMounted,Nuxt 3 服务端构建安全;<GvMap> 渲染期只产出容器 DOM。
  • 挂载顺序:图层组件在 setup 阶段即把图层加入地图(早于子要素挂载),确保子要素 addFeature 时图层投影已同步、坐标不错位。
  • 卸载:子组件先移除自身(removeFeature / removeLayer),地图组件最后 destroy();定时器 / 监听 / overlay 由 core 的 destroy() 完整回收。

属性操作(编辑 / 过滤 / 定位)

配合编辑器三包(@geoverse/editor-core + @geoverse/editor-ol),@geoverse/vue 另提供一组headless composable无样式参考组件(RFC-0007)。属性编辑可撤销可同步、过滤高亮、按 id 定位,全部围绕一个 EditSession 展开。

Headless composables(零样式,自己渲染 UI):

composable作用
useFieldSchema字段表(声明 ∪ 推断),随编辑实时重算
useFeatureAttributes单要素草稿副本 + setField/save/reset(save 走可撤销命令)
useAttributeFilter谓词过滤状态 + matchedIds + apply/clear
useFeatureLocatelocate(ids) → core extentOf → 适配器 fit

无样式参考组件(功能完整、可整体替换):GvAttributeForm / GvAttributeFilter / GvFeatureList,默认插槽暴露内部状态以接管渲染。

vue
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { GvMap, GvAttributeForm, GvAttributeFilter, GvFeatureList } from '@geoverse/vue';
import type { GMap } from '@geoverse/core-ol';
import { EditSession } from '@geoverse/editor-core';
import { OlEditorAdapter } from '@geoverse/editor-ol';

const session = shallowRef<EditSession>();
const adapter = shallowRef<OlEditorAdapter>();
const selectedId = ref<string | null>(null);

function onReady(map: GMap) {
  const crs = map.getView().getProjection().getCode();
  const s = new EditSession({ crs, features: /* … */ });
  const a = new OlEditorAdapter(map, {
    crs,
    onSelect: (id, additive) => {
      if (id) s.select([id], additive);
      else if (!additive) s.clearSelection();
      a.setSelection(s.getSelected());
      selectedId.value = id;
    },
  });
  s.attach(a);
  // 过滤命中 → 适配器重着色(命中高亮、其余淡出)
  s.onFilterChange(({ matchedIds, scopedIds }) =>
    a.applyFilter(matchedIds.length ? matchedIds : null, scopedIds),
  );
  session.value = s;
  adapter.value = a;
}
</script>

<template>
  <GvMap base="gd-vec" :center="[118.1, 24.55]" :zoom="11" @ready="onReady" />
  <template v-if="session">
    <GvAttributeFilter :session="session" />
    <GvFeatureList :session="session" :locator="adapter" />
    <GvAttributeForm :session="session" :feature-id="selectedId" />
  </template>
</template>

关于 session 与响应式

EditSession / 适配器等编辑实例同样不进深层响应式(用 shallowRef 持有、markRaw 心智),与 ol 实例一致。详见编辑器 · 属性操作

MapLibre 引擎(@geoverse/vue/libre 子路径)

根入口 @geoverse/vue 绑定 OpenLayers 引擎(geoverse/ol)。若要用 MapLibre GL JS 渲染,改从子路径 @geoverse/vue/libre 导入——它绑定 geoverse/libre,且不会把 OpenLayers 打进 bundle。两套子路径心智一致(同名组件、options = 对应 core 类构造项、WGS-84 坐标),差异见下。

vue
<script setup lang="ts">
import { ref } from 'vue';
import {
  GvMap,
  GvVectorLayer,
  GvMarker,
  GvEditSession,
} from '@geoverse/vue/libre';

const base = ref<'osm' | 'gd-vec' | 'tiandi-vec'>('osm');
const center = ref<[number, number]>([118.18, 24.49]);
const zoom = ref(11);
</script>

<template>
  <GvMap :base="base" v-model:center="center" v-model:zoom="zoom">
    <GvVectorLayer>
      <GvMarker :options="{ position: [118.18, 24.49], color: 'red' }" />
    </GvVectorLayer>
  </GvMap>
</template>

与 OL 版的差异

  • 组件是 core-libre 能力的子集:仅 GvMapGvVectorLayerGvVectorTileLayerGvMarkerGvCircleGvPolygonGvPolylineGvInfoWindowGvEditSession没有 GvHeatLayer / GvMassLayer / GvClusterLayer / GvTrafficLayer / GvImageLayer / GvCustomBaseLayerGvMeasure / GvDraw / GvTransformGvOverview / GvPathSimplifier / GvNameLayer(core-libre 无对应能力)。
  • 就绪门控<GvMap> 在 MapLibre style 加载完成(whenReady)后才渲染子树并触发 ready,子组件挂载时可安全建源 / 加层。
  • 无重投影:libre 要素坐标恒为 WGS-84,直接存储,切底图不漂移(数据边界变换 + 百度瓦片纠偏由 core-libre 处理)。
  • 事件命名随 MapLibre<GvMap> 派发 click / mousemove / moveend(非 OL 的 singleclick / pointermove)。
  • 样式:请引入 maplibre-gl/dist/maplibre-gl.css(弹窗等 UI 依赖)。

编辑器:libre 没有 core 内建交互,<GvEditSession> 走框架无关编辑引擎 + 自绘适配器(EditSession + LibreEditorAdapter),支持撤销/重做/打断/合并/拓扑等命令,并把会话 provide 给子树——同一套无样式属性/历史组合式与参考组件(useFeatureAttributesGvAttributeFormGvHistoryPanel 等)在 libre 子路径同样可用(本就引擎无关)。

vue
<template>
  <GvMap base="osm" :center="[118.15, 24.5]" :zoom="12">
    <GvEditSession :features="seed" :mode="mode" @snapshot="onSnapshot" />
  </GvMap>
</template>

peerDependencies 按引擎可选:只用 libre 时安装 @geoverse/core-libre + maplibre-gl(编辑再加 @geoverse/editor-libre),无需安装 ol / @geoverse/core-ol

在线示例:仓库 examples/vue/pnpm dev:examples/vue/ 基础组件、/vue/attributes.html 属性操作)。