---
title: "示例：取货定位器"
description: "构建一个符合生产环境需求的商店定位器，支持无障碍选择、地图回退、弹出窗口和配送区域。"
canonical_url: "https://nuxt-scripts.zhcndoc.com/scripts/leaflet/guides/pickup-locator"
last_updated: "2026-08-11T09:33:17.515Z"
---

这个真实场景中的取货定位器将位置列表与地图配对使用。当地图图块加载失败时，列表仍会提供地址和营业时间，同时为键盘用户提供直接选择位置的方式。

它演示了响应式选择、无障碍位置按钮、明确的 OpenStreetMap 署名、标记弹出窗口、错误回退内容以及 GeoJSON 配送区域。

<code-group>
<leaflet-pickup-locator-demo label="演示">



</leaflet-pickup-locator-demo>

```vue [Source]
<script setup lang="ts">
import type { Feature, Polygon } from 'geojson'
import type { LatLngExpression, LatLngTuple } from 'leaflet'

interface PickupLocation {
  id: string
  name: string
  address: string
  hours: string
  position: LatLngTuple
}

const locations: PickupLocation[] = [
  {
    id: 'flinders-lane',
    name: '弗林德斯巷',
    address: '墨尔本 VIC 州 Centre Place',
    hours: '今天营业至下午 6 点',
    position: [-37.8164, 144.9656],
  },
  {
    id: 'queen-victoria-market',
    name: '维多利亚女王市场',
    address: '墨尔本 VIC 州 Queen Street',
    hours: '今天营业至下午 3 点',
    position: [-37.8076, 144.9568],
  },
  {
    id: 'southbank',
    name: '南岸',
    address: '墨尔本 VIC 州 Southbank Promenade',
    hours: '今天营业至晚上 8 点',
    position: [-37.8202, 144.9655],
  },
]

const selectedId = ref(locations[0]!.id)
const center = ref<LatLngExpression>(locations[0]!.position)
const zoom = ref(14)

const deliveryZone: Feature<Polygon> = {
  type: 'Feature',
  properties: { name: '当日配送区域' },
  geometry: {
    type: 'Polygon',
    coordinates: [[
      [144.946, -37.802],
      [144.982, -37.802],
      [144.986, -37.828],
      [144.951, -37.832],
      [144.946, -37.802],
    ]],
  },
}

function selectLocation(location: PickupLocation) {
  selectedId.value = location.id
  center.value = location.position
  zoom.value = 16
}
</script>

<template>
  <div class="pickup-locator">
    <aside aria-labelledby="pickup-title">
      <h2 id="pickup-title">
        在墨尔本取货
      </h2>

      <ol>
        <li v-for="location in locations" :key="location.id">
          <button
            type="button"
            :aria-pressed="selectedId === location.id"
            @click="selectLocation(location)"
          >
            <strong>{{ location.name }}</strong>
            <span>{{ location.address }}</span>
            <small>{{ location.hours }}</small>
          </button>
        </li>
      </ol>
    </aside>

    <ScriptLeafletMap
      v-model:center="center"
      v-model:zoom="zoom"
      width="100%"
      :height="520"
      aria-label="墨尔本市中心的取货地点和配送区域"
    >
      <template #error>
        <p role="alert">
          地图不可用。请从列表中选择一个取货地点。
        </p>
      </template>

      <ScriptLeafletTileLayer
        url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
        :options="{
          maxZoom: 19,
          attribution: '&copy; <a href=&quot;https://www.openstreetmap.org/copyright&quot;>OpenStreetMap 贡献者</a>',
        }"
      />

      <ScriptLeafletGeoJson
        :data="deliveryZone"
        :options="{
          style: {
            color: '#15803d',
            fillColor: '#86efac',
            fillOpacity: 0.18,
            weight: 2,
          },
        }"
      />

      <ScriptLeafletMarker
        v-for="location in locations"
        :key="location.id"
        :position="location.position"
        :alt="`${location.name}取货地点`"
        :title="location.name"
        @click="selectLocation(location)"
      >
        <ScriptLeafletPopup
          :open="selectedId === location.id"
          :options="{ minWidth: 180 }"
        >
          <strong>{{ location.name }}</strong><br>
          {{ location.address }}<br>
          {{ location.hours }}
        </ScriptLeafletPopup>
      </ScriptLeafletMarker>
    </ScriptLeafletMap>
  </div>
</template>
```

</code-group>

使用 CSS Grid，在宽屏上将列表放置在地图旁边，在小屏上将列表放置在地图上方。即使地图是主要视觉内容，也要将列表保留在文档中。

GeoJSON 坐标使用 `[经度, 纬度]`；Leaflet 标记位置使用 `[纬度, 经度]`。
