---
title: "示例：配送追踪器"
description: "构建一个具有响应式路线进度、地图回退方案和无障碍状态替代方案的生产级配送追踪器。"
canonical_url: "https://nuxt-scripts.zhcndoc.com/scripts/maplibre/guides/delivery-tracker"
last_updated: "2026-08-11T09:33:18.028Z"
---

这个真实场景中的配送追踪器将 GeoJSON 路线与响应式快递员标记结合起来。在生产环境中，可以通过定时器或 [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) 处理程序更新标记；MapLibre 会移动标记，而无需重新创建它。

它演示了响应式路线进度、实时快递员标记、导航控件、错误回退内容，以及 WebGL 画布外的无障碍状态替代方案。

<code-group>
<map-libre-delivery-tracker-demo label="演示">



</map-libre-delivery-tracker-demo>

```vue [源代码]
<script setup lang="ts">
import type { FeatureCollection, LineString } from 'geojson'
import type { LngLatLike } from 'maplibre-gl'

interface Checkpoint {
  label: string
  detail: string
  position: [number, number]
}

const checkpoints: Checkpoint[] = [
  {
    label: 'West Melbourne depot',
    detail: 'Parcel collected',
    position: [144.9495, -37.8101],
  },
  {
    label: 'Docklands checkpoint',
    detail: 'Courier heading east',
    position: [144.9538, -37.8151],
  },
  {
    label: 'Southbank checkpoint',
    detail: 'Final approach',
    position: [144.9632, -37.8227],
  },
  {
    label: 'Flinders Lane delivery',
    detail: 'Customer destination',
    position: [144.9687, -37.8154],
  },
]

const currentIndex = ref(0)
const center = ref<LngLatLike>(checkpoints[0]!.position)
const current = computed(() => checkpoints[currentIndex.value]!)

const route = computed<FeatureCollection<LineString>>(() => {
  const completed = checkpoints
    .slice(0, currentIndex.value + 1)
    .map(checkpoint => checkpoint.position)

  if (completed.length === 1)
    completed.push(completed[0]!)

  return {
    type: 'FeatureCollection',
    features: [
      {
        type: 'Feature',
        properties: { progress: 'remaining' },
        geometry: {
          type: 'LineString',
          coordinates: checkpoints.map(checkpoint => checkpoint.position),
        },
      },
      {
        type: 'Feature',
        properties: { progress: 'completed' },
        geometry: { type: 'LineString', coordinates: completed },
      },
    ],
  }
})

const routeLayers = [
  {
    id: 'delivery-route',
    type: 'line' as const,
    filter: ['==', ['get', 'progress'], 'remaining'],
    paint: {
      'line-color': '#64748b',
      'line-opacity': 0.65,
      'line-width': 5,
    },
  },
  {
    id: 'delivery-progress',
    type: 'line' as const,
    filter: ['==', ['get', 'progress'], 'completed'],
    paint: { 'line-color': '#2563eb', 'line-width': 6 },
  },
]

function advanceCourier() {
  if (currentIndex.value === checkpoints.length - 1)
    return

  currentIndex.value += 1
  center.value = checkpoints[currentIndex.value]!.position
}
</script>

<template>
  <div>
    <p aria-live="polite">
      当前状态：<strong>{{ current.detail }}</strong>
    </p>

    <ScriptMapLibreMap
      v-model:center="center"
      map-style="https://tiles.openfreemap.org/styles/liberty"
      :zoom="14"
      width="100%"
      :height="520"
      aria-label="从西墨尔本到弗林德斯巷的配送路线"
    >
      <template #description>
        从西墨尔本仓库经由码头区和南岸前往弗林德斯巷的配送路线。
        快递员目前位于{{ current.label }}。
      </template>

      <template #error>
        <p role="alert">
          实时地图不可用。请改为查看上方的配送状态。
        </p>
      </template>

      <ScriptMapLibreNavigationControl position="top-right" />

      <ScriptMapLibreGeoJson
        source-id="delivery-route"
        :data="route"
        :layers="routeLayers"
      />

      <ScriptMapLibreMarker
        :position="current.position"
        :aria-label="`Courier at ${current.label}`"
        title="当前快递员位置"
        :options="{ color: '#2563eb' }"
      />
    </ScriptMapLibreMap>

    <button
      type="button"
      :disabled="currentIndex === checkpoints.length - 1"
      @click="advanceCourier"
    >
      快递员前进
    </button>
  </div>
</template>
```

</code-group>

请将订单状态、检查点名称和预计时间放在画布外的普通 HTML 中。`description` 插槽会将路线摘要与地图关联起来，以便辅助技术读取。

MapLibre 坐标使用 `[经度, 纬度]` 顺序。
