This commit is contained in:
52
components/PayBlock.vue
Normal file
52
components/PayBlock.vue
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user