开发预览 更新于 2026-05-10

Map 地图

轻量矢量地图容器,支持 marker、polygon overlay、route、缩放与交互事件。

基础用法

Map 先提供零依赖的矢量地图底座:坐标使用 0–100 的归一化空间,适合机房平面图、园区拓扑、区域监控、网络节点图等后台系统场景。 标记、区域覆盖物、路线都可以交互,点击覆盖物时会触发 overlay-click,点击标记会触发 marker-click,点击空白地图会触发 map-click

背景
Edge A99%Core12msEdge B3 alertsBackupidle
edge-a点击地图、标记或区域后,这里会显示事件 payload。
<script setup lang="ts">
import { ref } from 'vue';
import {
  CfBadge,
  CfMap,
  type MapCanvasEvent,
  type MapMarkerEvent,
  type MapOverlayEvent,
  type MapViewport,
} from '@chufix-design/vue';

const activeId = ref('edge-a');
const lastEvent = ref('点击地图、标记或区域后,这里会显示事件 payload。');

const markers = [
  { id: 'edge-a', label: 'Edge A', x: 24, y: 34, tone: 'success' as const, value: '99%' },
  { id: 'dc-core', label: 'Core', x: 52, y: 46, tone: 'info' as const, value: '12ms' },
  { id: 'edge-b', label: 'Edge B', x: 72, y: 38, tone: 'warning' as const, value: '3 alerts' },
  { id: 'backup', label: 'Backup', x: 42, y: 74, tone: 'default' as const, value: 'idle' },
];

const overlays = [
  {
    id: 'zone-north',
    label: '北区覆盖层',
    tone: 'info' as const,
    interactive: true,
    points: [
      { x: 18, y: 20 },
      { x: 56, y: 18 },
      { x: 60, y: 48 },
      { x: 28, y: 55 },
    ],
  },
  {
    id: 'zone-risk',
    label: '风险区域',
    tone: 'warning' as const,
    interactive: true,
    points: [
      { x: 61, y: 30 },
      { x: 84, y: 38 },
      { x: 78, y: 62 },
      { x: 58, y: 56 },
    ],
  },
];

const routes = [
  {
    id: 'primary-link',
    tone: 'success' as const,
    points: [
      { x: 24, y: 34 },
      { x: 52, y: 46 },
      { x: 72, y: 38 },
    ],
  },
  {
    id: 'backup-link',
    tone: 'warning' as const,
    dashed: true,
    points: [
      { x: 24, y: 34 },
      { x: 42, y: 74 },
      { x: 72, y: 38 },
    ],
  },
];

function onMarkerClick(event: MapMarkerEvent) {
  activeId.value = event.marker.id;
  lastEvent.value = `marker-click: ${event.marker.label} / ${event.marker.value ?? '-'}`;
}

function onOverlayClick(event: MapOverlayEvent) {
  activeId.value = event.overlay.id;
  lastEvent.value = `overlay-click: ${event.overlay.label} / ${event.overlay.points.length} points`;
}

function onMapClick(event: MapCanvasEvent) {
  lastEvent.value = `map-click: x=${event.point.x.toFixed(1)}, y=${event.point.y.toFixed(1)}`;
}

function onViewportChange(viewport: MapViewport) {
  lastEvent.value = `viewport-change: zoom=${viewport.zoom.toFixed(1)}, center=${viewport.center.x},${viewport.center.y}`;
}
</script>

<template>
  <div class="map-demo">
    <CfMap
      :markers="markers"
      :overlays="overlays"
      :routes="routes"
      :active-id="activeId"
      :height="420"
      @marker-click="onMarkerClick"
      @overlay-click="onOverlayClick"
      @map-click="onMapClick"
      @viewport-change="onViewportChange"
    />
    <div class="map-demo__status">
      <CfBadge tone="info" :content="activeId" />
      <code>{{ lastEvent }}</code>
    </div>
  </div>
</template>

<style scoped>
.map-demo {
  display: grid;
  gap: 12px;
  width: 100%;
}
.map-demo__status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.map-demo__status code {
  white-space: normal;
}
</style>
<script setup>
import { ref } from 'vue';
import {
  CfBadge,
  CfMap,
} from '@chufix-design/vue';

const activeId = ref('edge-a');
const lastEvent = ref('点击地图、标记或区域后,这里会显示事件 payload。');

const markers = [
  { id: 'edge-a', label: 'Edge A', x: 24, y: 34, tone: 'success', value: '99%' },
  { id: 'dc-core', label: 'Core', x: 52, y: 46, tone: 'info', value: '12ms' },
  { id: 'edge-b', label: 'Edge B', x: 72, y: 38, tone: 'warning', value: '3 alerts' },
  { id: 'backup', label: 'Backup', x: 42, y: 74, tone: 'default', value: 'idle' },
];

const overlays = [
  {
    id: 'zone-north',
    label: '北区覆盖层',
    tone: 'info',
    interactive,
    points: [
      { x: 18, y: 20 },
      { x: 56, y: 18 },
      { x: 60, y: 48 },
      { x: 28, y: 55 },
    ],
  },
  {
    id: 'zone-risk',
    label: '风险区域',
    tone: 'warning',
    interactive,
    points: [
      { x: 61, y: 30 },
      { x: 84, y: 38 },
      { x: 78, y: 62 },
      { x: 58, y: 56 },
    ],
  },
];

const routes = [
  {
    id: 'primary-link',
    tone: 'success',
    points: [
      { x: 24, y: 34 },
      { x: 52, y: 46 },
      { x: 72, y: 38 },
    ],
  },
  {
    id: 'backup-link',
    tone: 'warning',
    dashed,
    points: [
      { x: 24, y: 34 },
      { x: 42, y: 74 },
      { x: 72, y: 38 },
    ],
  },
];

function onMarkerClick(event) {
  activeId.value = event.marker.id;
  lastEvent.value = `marker-click: ${event.marker.label} / ${event.marker.value ?? '-'}`;
}

function onOverlayClick(event) {
  activeId.value = event.overlay.id;
  lastEvent.value = `overlay-click: ${event.overlay.label} / ${event.overlay.points.length} points`;
}

function onMapClick(event) {
  lastEvent.value = `map-click: x=${event.point.x.toFixed(1)}, y=${event.point.y.toFixed(1)}`;
}

function onViewportChange(viewport) {
  lastEvent.value = `viewport-change: zoom=${viewport.zoom.toFixed(1)}, center=${viewport.center.x},${viewport.center.y}`;
}
</script>

<template>
  <div class="map-demo">
    <CfMap
      :markers="markers"
      :overlays="overlays"
      :routes="routes"
      :active-id="activeId"
      :height="420"
      @marker-click="onMarkerClick"
      @overlay-click="onOverlayClick"
      @map-click="onMapClick"
      @viewport-change="onViewportChange"
    />
    <div class="map-demo__status">
      <CfBadge tone="info" :content="activeId" />
      <code>{{ lastEvent }}</code>
    </div>
  </div>
</template>

<style scoped>
.map-demo {
  display: grid;
  gap: 12px;
  width: 100%;
}
.map-demo__status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.map-demo__status code {
  white-space: normal;
}
</style>
<CfMap
markers={markers}
overlays={overlays}
routes={routes}
onMarkerClick={onMarkerClick}
onOverlayClick={onOverlayClick}
onMapClick={onMapClick}
/>
<CfMap
markers={markers}
overlays={overlays}
routes={routes}
onMarkerClick={onMarkerClick}
onOverlayClick={onOverlayClick}
onMapClick={onMapClick}
/>

API

属性类型默认值说明
markersMapMarker[][]标记点,字段为 id / label / x / y / tone? / value? / disabled?
overlaysMapOverlay[][]多边形覆盖物,interactivetrue 时支持点击与键盘触发
routesMapRoute[][]折线路线,支持 tonedashed
heightnumber | string360地图高度
center{ x: number; y: number }{ x: 50, y: 50 }视口中心点
zoomnumber1初始缩放级别
minZoom / maxZoomnumber0.7 / 2.4缩放边界
showGridbooleantrue是否显示地图网格
showLabelsbooleantrue是否显示 marker label
controlsbooleantrue是否显示缩放 / 重置控件
activeIdstring高亮 marker 或 overlay

Events

Vue 事件React 回调payload说明
marker-clickonMarkerClick{ marker, nativeEvent }点击或键盘激活 marker
marker-enter / marker-leaveonMarkerEnter / onMarkerLeave{ marker, nativeEvent }marker hover 状态
overlay-clickonOverlayClick{ overlay, nativeEvent }点击或键盘激活交互覆盖物
map-clickonMapClick{ point, nativeEvent }点击地图空白区域,返回归一化坐标
viewport-changeonViewportChange{ center, zoom }点击缩放 / 重置控件后触发

设计说明

这个版本故意不绑定具体地图服务,优先把 UI 库应该有的“覆盖物、事件、状态、可访问性”能力打实。后续如果接入 Leaflet / Mapbox,可以保留同一组 markers / overlays / events API,只把渲染适配到真实瓦片或 WebGL 地图。

反馈与讨论

Map 地图 的讨论

0
0 / 600
一键发送
正在加载评论...