Map
Lightweight vector map container with marker, polygon overlay, route, zoom, and interaction events.
Basic usage
Map is a zero-dependency vector map canvas. Coordinates use a normalized 0–100 space — handy for data-center floor plans, campus topology, regional monitoring, or network node diagrams in admin systems. Markers, overlays, and routes are all interactive: clicking an overlay fires overlay-click, clicking a marker fires marker-click, and clicking empty space fires map-click.
点击地图、标记或区域后,这里会显示事件 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: true,
points: [
{ x: 18, y: 20 },
{ x: 56, y: 18 },
{ x: 60, y: 48 },
{ x: 28, y: 55 },
],
},
{
id: 'zone-risk',
label: '风险区域',
tone: 'warning',
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',
points: [
{ x: 24, y: 34 },
{ x: 52, y: 46 },
{ x: 72, y: 38 },
],
},
{
id: 'backup-link',
tone: 'warning',
dashed: true,
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> import { CfMap } from '@chufix-design/react';
export default function Demo() {
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 },
],
},
];
return (
<>
<CfMap
markers={markers}
overlays={overlays}
routes={routes}
onMarkerClick={onMarkerClick}
onOverlayClick={onOverlayClick}
onMapClick={onMapClick}
/>
</>
);
} import { CfMap } from '@chufix-design/react';
export default function Demo() {
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: true,
points: [
{ x: 18, y: 20 },
{ x: 56, y: 18 },
{ x: 60, y: 48 },
{ x: 28, y: 55 },
],
},
{
id: 'zone-risk',
label: '风险区域',
tone: 'warning',
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',
points: [
{ x: 24, y: 34 },
{ x: 52, y: 46 },
{ x: 72, y: 38 },
],
},
{
id: 'backup-link',
tone: 'warning',
dashed: true,
points: [
{ x: 24, y: 34 },
{ x: 42, y: 74 },
{ x: 72, y: 38 },
],
},
];
return (
<>
<CfMap
markers={markers}
overlays={overlays}
routes={routes}
onMarkerClick={onMarkerClick}
onOverlayClick={onOverlayClick}
onMapClick={onMapClick}
/>
</>
);
} API
| Prop | Type | Default | Description |
|---|---|---|---|
markers | MapMarker[] | [] | Marker points; fields are id / label / x / y / tone? / value? / disabled? |
overlays | MapOverlay[] | [] | Polygon overlays. With interactive: true they respond to click and keyboard activation |
routes | MapRoute[] | [] | Polyline routes; supports tone and dashed |
height | number | string | 360 | Map height |
center | { x: number; y: number } | { x: 50, y: 50 } | Viewport center |
zoom | number | 1 | Initial zoom level |
minZoom / maxZoom | number | 0.7 / 2.4 | Zoom bounds |
showGrid | boolean | true | Show the background grid |
showLabels | boolean | true | Show marker labels |
controls | boolean | true | Show zoom / reset controls |
activeId | string | — | Highlight a marker or overlay |
Events
| Vue event | React callback | payload | Description |
|---|---|---|---|
marker-click | onMarkerClick | { marker, nativeEvent } | Click or keyboard activation on a marker |
marker-enter / marker-leave | onMarkerEnter / onMarkerLeave | { marker, nativeEvent } | Marker hover state |
overlay-click | onOverlayClick | { overlay, nativeEvent } | Click or keyboard activation on an interactive overlay |
map-click | onMapClick | { point, nativeEvent } | Click on empty space; payload is normalized coordinates |
viewport-change | onViewportChange | { center, zoom } | Fired after using zoom / reset controls |
Design notes
This release intentionally avoids binding to any specific mapping service — the focus is on getting the UI-library essentials right (overlays, events, state, accessibility). When integrating Leaflet or Mapbox later, the same markers / overlays / events surface can stay; only the rendering layer needs to swap to real tiles or a WebGL map.
反馈与讨论
Map · Discussion