alsaze 55103d3778
All checks were successful
Deploy / build (push) Successful in 56s
карты пвз
2025-11-11 18:21:20 +03:00

217 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="!hasCoords" class="p-4 text-center">
⏳ Определяем ваше местоположение...
</div>
<YandexMap
v-else
v-model="map"
class="w-full"
:settings="{ location }"
style="height: calc(100dvh - 54px)"
>
<YandexMapDefaultSchemeLayer />
<YandexMapDefaultFeaturesLayer />
<YandexMapClusterer
v-model="clusterer"
:grid-size="64"
zoom-on-cluster-click
@true-bounds="trueBounds = $event"
>
<YandexMapMarker
v-for="pickupPoint in pickupPoints"
:key="pickupPoint.id"
position="top-center left-center"
:settings="{ coordinates: [pickupPoint.position.longitude, pickupPoint.position.latitude] }"
@click="$emit('update:modelValue', pickupPoint)"
>
<div class="marker">
<Icon name="i-lucide-map-pin" class="marker__icon" />
</div>
</YandexMapMarker>
<template #cluster="{ length }">
<div class="cluster fade-in">
{{ length }}
</div>
</template>
</YandexMapClusterer>
<YandexMapControls :settings="{ position: 'bottom left', orientation: 'vertical' }">
<YandexMapControl>
<div
class="control"
>
<UCheckbox
v-model="isFitting"
size="xl"
label="с примеркой"
/>
</div>
</YandexMapControl>
</YandexMapControls>
<YandexMapControls :settings="{ position: 'bottom right', orientation: 'vertical' }">
<YandexMapControl>
<UTabs v-model="activeTab" :content="false" :items="tabs">
<template #map>
{{ activeTab === '0' ? 'Карта' : activeTab === '1' ? 'Список' : 'Другое' }}
</template>
</UTabs>
</YandexMapControl>
</YandexMapControls>
</YandexMap>
</template>
<script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
import type { LngLatBounds, YMap } from '@yandex/ymaps3-types'
import type { YMapLocationRequest } from '@yandex/ymaps3-types/imperative/YMap'
import type { YMapClusterer } from '@yandex/ymaps3-types/packages/clusterer'
import type { PickupPoint } from '~/server/shared/types/yandex_pvz'
import { useGeolocation } from '@vueuse/core'
import { computed, shallowRef } from 'vue'
import {
YandexMap,
YandexMapClusterer,
YandexMapControl,
YandexMapControls,
YandexMapDefaultFeaturesLayer,
YandexMapDefaultSchemeLayer,
YandexMapMarker,
} from 'vue-yandex-maps'
const props = defineProps<{ modelValue: PickupPoint, pickupPoints: PickupPoint[] }>()
const emit = defineEmits<{
(e: 'update:modelValue', value: PickupPoint | undefined): void
(e: 'activeTab', value: string): void
}>()
const { coords } = useGeolocation()
const clusterer = shallowRef<YMapClusterer | null>(null)
const trueBounds = ref<LngLatBounds>([[0, 0], [0, 0]])
const map = shallowRef<null | YMap>(null)
const isFitting = ref(false)
const activeTab = ref('map')
const tabs = computed<TabsItem[]>(() =>
[
{
value: 'map',
label: activeTab.value === 'map' ? 'Карта' : '',
icon: 'lucide:map-pin',
slot: 'map' as const,
},
{
value: 'list',
label: activeTab.value === 'list' ? 'Список' : '',
icon: 'i-lucide-list',
slot: 'list' as const,
},
],
)
const hasCoords = computed(() => coords.value?.latitude !== Infinity && coords.value?.longitude !== Infinity)
const location = ref<YMapLocationRequest>({
zoom: 2,
})
watch(coords, (newCoords) => {
if (newCoords && hasCoords.value) {
location.value = {
center: [newCoords.longitude, newCoords.latitude],
zoom: 9,
duration: 2500,
}
}
}, { once: true })
watch(() => props.modelValue, (newPickupPoint) => {
location.value = {
center: [newPickupPoint.position.longitude, newPickupPoint.position.latitude],
zoom: 18,
duration: 500,
}
})
watch(() => activeTab.value, (newActiveTab) => {
emit('activeTab', newActiveTab)
})
</script>
<style lang="scss" scoped>
.marker {
position: relative;
background-color: #0f172b;
border: 1px solid #0f172b;
border-radius: 12px;
padding: 6px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
transition:
transform 0.2s ease,
background-color 0.3s ease;
&:hover {
transform: scale(1.1);
background-color: #1e293b;
}
&::after {
content: '';
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid #0f172b;
filter: drop-shadow(0 2px 1px rgba(0, 0, 0, 0.15));
}
&__icon {
color: white;
width: 20px;
height: 20px;
}
}
.cluster {
background-color: #0f172b;
color: white;
border-radius: 50%;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
border: 2px solid #fff;
transition:
transform 0.2s ease,
background-color 0.3s ease;
&:hover {
transform: scale(1.1);
background-color: #1e293b;
}
}
.control {
background: var(--ui-bg-elevated);
padding: 8px 18px;
border-radius: 10px;
}
</style>