карта ПВЗ
All checks were successful
Deploy / build (push) Successful in 12m45s

This commit is contained in:
alsaze
2025-10-22 22:57:29 +03:00
parent 3119ecc2fa
commit 8e68d5b162
14 changed files with 314 additions and 115 deletions

52
components/PayBlock.vue Normal file
View File

@@ -0,0 +1,52 @@
<template>
<div>
<UPageCard>
<template #body>
<div>товары {{ `(${cart?.line_items?.length} шт)` }}</div>
<div v-if="cartSum">
<ProductPrice :is-headline="false" text="итого" :price="cartSum" />
</div>
</template>
<template #footer>
<UButton
class="justify-center text-center w-2xs"
size="xl"
:label="pay ? 'оформить заказ' : 'перейти к оформлению'"
@click="pay ? createOrder() : router.push(`/checkout/delivery`)"
/>
</template>
</UPageCard>
</div>
</template>
<script setup lang="ts">
import type { IBspb } from '~/server/shared/types/bspb'
defineProps({
pay: {
type: Boolean,
default: false,
},
})
const router = useRouter()
const { cart, cartSum } = useCart()
const createOrder = async () => {
const { data } = await useFetch<IBspb>('/api/bspb', {
method: 'POST',
body: {
order: {
typeRid: 'Purchase',
amount: cartSum,
currency: 'RUB',
hppRedirectUrl: `${import.meta.env.VITE_BASE_URL}/cart`,
},
},
})
const redirectUrl = `${data?.value?.order?.hppUrl}?orderId=${data?.value?.order?.id}&password=${data.value?.order?.password}`
window.open(redirectUrl, '_blank')
}
</script>

View File

@@ -1,6 +1,12 @@
<template>
<div class="product-price">
<h2>{{ price }} <Icon name="ph:currency-rub" /></h2>
<h2 v-if="isHeadline" class="product-price__text">
{{ text }} {{ price }} <Icon name="ph:currency-rub" />
</h2>
<div v-else class="product-price__text">
{{ text }} {{ price }} <Icon name="ph:currency-rub" />
</div>
</div>
</template>
@@ -10,12 +16,20 @@ defineProps({
type: [String, Number],
required: true,
},
isHeadline: {
type: Boolean,
default: true,
},
text: {
type: String,
default: '',
},
})
</script>
<style lang="scss">
.product-price {
h2 {
&__text {
display: flex;
flex-direction: row;
gap: 4px;

View File

@@ -1,13 +1,61 @@
<template>
<div ref="mapContainer" class="w-full" style="height: calc(100dvh - 54px)" />
<div v-if="!coordsReady" class="p-4 text-center">
<div v-if="!hasCoords" class="p-4 text-center">
Определяем ваше местоположение...
</div>
<YandexMap
v-else
v-model="map"
:settings="{
location: {
center: [coords?.longitude, coords?.latitude],
zoom: 9,
},
}"
class="w-full"
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="marker in pickupPoints"
:key="marker.id"
position="top-center left-center"
:settings="{ coordinates: [marker.position.longitude, marker.position.latitude] }"
>
<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>
</YandexMap>
</template>
<script setup lang="ts">
import type { LngLatBounds, YMap } from '@yandex/ymaps3-types'
import type { YMapClusterer } from '@yandex/ymaps3-types/packages/clusterer'
import { useGeolocation } from '@vueuse/core'
import { ref, watch } from 'vue'
import { computed, shallowRef } from 'vue'
import {
YandexMap,
YandexMapClusterer,
YandexMapDefaultFeaturesLayer,
YandexMapDefaultSchemeLayer,
YandexMapMarker,
} from 'vue-yandex-maps'
const props = defineProps<{
pickupPoints: {
@@ -17,61 +65,79 @@ const props = defineProps<{
}[]
}>()
const token = '13f4c06b-cb7e-4eeb-81f1-af52f12587b2'
const mapContainer = ref<HTMLDivElement | null>(null)
const { coords } = useGeolocation()
const coordsReady = ref(false)
const mapInstance = ref<any | null>(null)
const initMap = async (lat: number, lon: number) => {
if (!window.ymaps) {
await new Promise<void>((resolve) => {
const script = document.createElement('script')
script.src = `https://api-maps.yandex.ru/2.1/?apikey=${token}&lang=ru_RU`
script.onload = () => resolve()
document.head.appendChild(script)
})
const hasCoords = computed(() => coords.value?.latitude !== Infinity && coords.value?.longitude !== Infinity)
const map = shallowRef<null | YMap>(null)
const clusterer = shallowRef<YMapClusterer | null>(null)
const trueBounds = ref<LngLatBounds>([[0, 0], [0, 0]])
</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;
}
window.ymaps.ready(() => {
const map = new window.ymaps.Map(mapContainer.value!, {
center: [lat, lon],
zoom: 10,
controls: ['zoomControl'],
})
mapInstance.value = map
&::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));
}
props?.pickupPoints?.forEach((point) => {
const placemark = new window.ymaps.Placemark(
[point?.position?.latitude, point?.position?.longitude],
{
balloonContent: `<strong>${Object.values(point?.position)}</strong>`,
hintContent: Object.values(point?.position),
},
{ preset: 'islands#redIcon' },
)
map.geoObjects.add(placemark)
})
})
&__icon {
color: white;
width: 20px;
height: 20px;
}
}
const centerMap = (lat: number, lon: number) => {
if (!mapInstance.value)
return
.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;
mapInstance.value.setCenter([lat, lon], 18)
&:hover {
transform: scale(1.1);
background-color: #1e293b;
}
}
defineExpose({ centerMap })
watch(
() => coords.value,
async (val) => {
if (val.latitude && val.longitude && !coordsReady.value) {
coordsReady.value = true
await initMap(val.latitude, val.longitude)
}
},
{ deep: true },
)
</script>
</style>