This commit is contained in:
parent
6cd7bd1dec
commit
bff6833781
146
components/DeliveryInfo.vue
Normal file
146
components/DeliveryInfo.vue
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<template>
|
||||||
|
<ul class="delivery-info">
|
||||||
|
<li v-if="showFullContent" class="delivery-info__item">
|
||||||
|
<Icon class="delivery-info__icon" name="lucide:truck" />
|
||||||
|
<span class="delivery-info__text">Yandex</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="delivery-info__item">
|
||||||
|
<Icon class="delivery-info__icon" name="lucide:map-pin" />
|
||||||
|
<span class="delivery-info__text">{{ checkoutPickupPoint?.address?.full_address }}</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="delivery-info__item">
|
||||||
|
<Icon class="delivery-info__icon" name="lucide:shirt" size="xl" />
|
||||||
|
<span class="delivery-info__text">{{ checkoutPickupPoint?.pickup_services?.is_fitting_allowed ? 'с примеркой' : 'без примерки' }}</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="delivery-info__item">
|
||||||
|
<Icon class="delivery-info__icon" name="lucide:package" />
|
||||||
|
<span class="delivery-info__text">срок хранения — 7 дней</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="delivery-info__item">
|
||||||
|
<UAccordion
|
||||||
|
:items="availableDays"
|
||||||
|
:ui="{
|
||||||
|
body: 'py-0',
|
||||||
|
panel: 'p-0',
|
||||||
|
wrapper: 'space-y-0',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #schedule="{ item }">
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(scheduleItem, index) in item.content"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<Icon class="delivery-info__icon mr-2" name="lucide:clock" />
|
||||||
|
<span class="mr-2">{{ scheduleItem.dayName }}</span>
|
||||||
|
<span>{{ scheduleItem.timeRange }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UAccordion>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<UAccordion :items="details" />
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<UButton
|
||||||
|
v-if="showFullContent"
|
||||||
|
class="justify-content-center"
|
||||||
|
label="Привезти сюда"
|
||||||
|
size="xl"
|
||||||
|
@click="nextStep"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { AccordionItem } from '@nuxt/ui'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { useCheckout } from '~/composables/useCheckout'
|
||||||
|
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
showFullContent?: boolean
|
||||||
|
}>(), {
|
||||||
|
showFullContent: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { checkoutPickupPoint, nextStep } = useCheckout()
|
||||||
|
|
||||||
|
const restrictionDays = {
|
||||||
|
1: 'пн',
|
||||||
|
2: 'вт',
|
||||||
|
3: 'ср',
|
||||||
|
4: 'чт',
|
||||||
|
5: 'пт',
|
||||||
|
6: 'сб',
|
||||||
|
7: 'вс',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для форматирования времени с ведущим нулем
|
||||||
|
const formatTime = (time: { hours: number | string, minutes: number | string }): string => {
|
||||||
|
const hours = String(time.hours).padStart(2, '0')
|
||||||
|
const minutes = String(time.minutes).padStart(2, '0')
|
||||||
|
return `${hours}:${minutes}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для форматирования временного диапазона
|
||||||
|
const formatTimeRange = (timeFrom: { hours: number | string, minutes: number | string }, timeTo: { hours: number | string, minutes: number | string }): string => {
|
||||||
|
return `${formatTime(timeFrom)} - ${formatTime(timeTo)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Аккордеон с расписанием - используем слот вместо HTML строки
|
||||||
|
const availableDays = computed(() => {
|
||||||
|
const scheduleItems = checkoutPickupPoint?.value?.schedule?.restrictions?.map((restriction) => {
|
||||||
|
const dayName = restrictionDays[restriction.days[0]]
|
||||||
|
const timeRange = formatTimeRange(restriction.time_from, restriction.time_to)
|
||||||
|
return { dayName, timeRange }
|
||||||
|
}) || []
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'График работы',
|
||||||
|
icon: 'i-lucide:calendar',
|
||||||
|
slot: 'schedule',
|
||||||
|
content: scheduleItems,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const details = ref<AccordionItem[]>([
|
||||||
|
{
|
||||||
|
label: 'Подробнее',
|
||||||
|
icon: 'i-lucide:info',
|
||||||
|
content: checkoutPickupPoint?.value?.address?.comment || 'Дополнительная информация о пункте выдачи',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.delivery-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
flex: 1;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -20,11 +20,11 @@
|
|||||||
@true-bounds="trueBounds = $event"
|
@true-bounds="trueBounds = $event"
|
||||||
>
|
>
|
||||||
<YandexMapMarker
|
<YandexMapMarker
|
||||||
v-for="marker in pickupPoints"
|
v-for="pickupPoint in pickupPoints"
|
||||||
:key="marker.id"
|
:key="pickupPoint.id"
|
||||||
position="top-center left-center"
|
position="top-center left-center"
|
||||||
:settings="{ coordinates: [marker.position.longitude, marker.position.latitude] }"
|
:settings="{ coordinates: [pickupPoint.position.longitude, pickupPoint.position.latitude] }"
|
||||||
@click="centerMap([marker.position.longitude, marker.position.latitude])"
|
@click="centerMap(pickupPoint)"
|
||||||
>
|
>
|
||||||
<div class="marker">
|
<div class="marker">
|
||||||
<Icon name="i-lucide-map-pin" class="marker__icon" />
|
<Icon name="i-lucide-map-pin" class="marker__icon" />
|
||||||
@ -41,9 +41,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { LngLat, LngLatBounds, YMap } from '@yandex/ymaps3-types'
|
import type { LngLatBounds, YMap } from '@yandex/ymaps3-types'
|
||||||
import type { YMapLocationRequest } from '@yandex/ymaps3-types/imperative/YMap'
|
import type { YMapLocationRequest } from '@yandex/ymaps3-types/imperative/YMap'
|
||||||
import type { YMapClusterer } from '@yandex/ymaps3-types/packages/clusterer'
|
import type { YMapClusterer } from '@yandex/ymaps3-types/packages/clusterer'
|
||||||
|
import type { PickupPoint } from '~/server/shared/types/yandex_pvz'
|
||||||
import { useGeolocation } from '@vueuse/core'
|
import { useGeolocation } from '@vueuse/core'
|
||||||
import { computed, shallowRef } from 'vue'
|
import { computed, shallowRef } from 'vue'
|
||||||
import {
|
import {
|
||||||
@ -53,15 +54,11 @@ import {
|
|||||||
YandexMapDefaultSchemeLayer,
|
YandexMapDefaultSchemeLayer,
|
||||||
YandexMapMarker,
|
YandexMapMarker,
|
||||||
} from 'vue-yandex-maps'
|
} from 'vue-yandex-maps'
|
||||||
|
import { useCheckout } from '~/composables/useCheckout'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{ pickupPoints: PickupPoint[] }>()
|
||||||
pickupPoints: {
|
|
||||||
id: string
|
|
||||||
address: string
|
|
||||||
position: { latitude: number, longitude: number }
|
|
||||||
}[]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
|
const { setCheckoutPickupPoint, isPickupPointSelected } = useCheckout()
|
||||||
const { coords } = useGeolocation()
|
const { coords } = useGeolocation()
|
||||||
const clusterer = shallowRef<YMapClusterer | null>(null)
|
const clusterer = shallowRef<YMapClusterer | null>(null)
|
||||||
const trueBounds = ref<LngLatBounds>([[0, 0], [0, 0]])
|
const trueBounds = ref<LngLatBounds>([[0, 0], [0, 0]])
|
||||||
@ -73,12 +70,15 @@ const location = ref<YMapLocationRequest>({
|
|||||||
zoom: 2,
|
zoom: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
function centerMap(coordinates: LngLat) {
|
function centerMap(pickupPoint: PickupPoint) {
|
||||||
location.value = {
|
location.value = {
|
||||||
center: coordinates,
|
center: [pickupPoint.position.longitude, pickupPoint.position.latitude],
|
||||||
zoom: 18,
|
zoom: 18,
|
||||||
duration: 2500,
|
duration: 500,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCheckoutPickupPoint(pickupPoint)
|
||||||
|
isPickupPointSelected.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(coords, (newCoords) => {
|
watch(coords, (newCoords) => {
|
||||||
|
|||||||
@ -1,13 +1,32 @@
|
|||||||
|
import type { PickupPoint } from '~/server/shared/types/yandex_pvz'
|
||||||
import { createSharedComposable, useStorage } from '@vueuse/core'
|
import { createSharedComposable, useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
export const useCheckout = createSharedComposable(() => {
|
export const useCheckout = createSharedComposable(() => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const contacts = useStorage('checkout-contacts', { name: '', surname: '', phone: '', email: '' })
|
const isPickupPointSelected = ref(false)
|
||||||
|
|
||||||
const setContacts = (data: { name: string, surname: string, phone: string, email: string }) => {
|
const checkoutPickupPoint = useStorage<PickupPoint | undefined>(
|
||||||
contacts.value = data
|
'checkout-pickupPoint',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
serializer: {
|
||||||
|
read: (v: string) => v ? JSON.parse(v) : undefined,
|
||||||
|
write: (v: PickupPoint | undefined) => JSON.stringify(v),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const setCheckoutPickupPoint = (point: PickupPoint | undefined) => {
|
||||||
|
checkoutPickupPoint.value = point
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkoutContacts = useStorage('checkout-contacts', { name: '', surname: '', phone: '', email: '' })
|
||||||
|
|
||||||
|
const setCheckoutContacts = (data: { name: string, surname: string, phone: string, email: string }) => {
|
||||||
|
checkoutContacts.value = data
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkoutSteps = [
|
const checkoutSteps = [
|
||||||
@ -25,9 +44,16 @@ export const useCheckout = createSharedComposable(() => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const currentCheckoutStep = ref(checkoutSteps.find(value => value.title === route.path.split('/').pop()) || checkoutSteps[0])
|
const currentCheckoutStep
|
||||||
|
= ref(checkoutSteps.find(value => value.title === route.path.split('/').pop()) || checkoutSteps[0])
|
||||||
|
|
||||||
function previewStep() {
|
function previewStep() {
|
||||||
|
if (isPickupPointSelected.value) {
|
||||||
|
isPickupPointSelected.value = false
|
||||||
|
setCheckoutPickupPoint(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const findIndex = checkoutSteps.findIndex(value => value.step === currentCheckoutStep.value.step)
|
const findIndex = checkoutSteps.findIndex(value => value.step === currentCheckoutStep.value.step)
|
||||||
if (findIndex !== 0) {
|
if (findIndex !== 0) {
|
||||||
currentCheckoutStep.value = checkoutSteps[findIndex - 1]
|
currentCheckoutStep.value = checkoutSteps[findIndex - 1]
|
||||||
@ -52,8 +78,12 @@ export const useCheckout = createSharedComposable(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contacts,
|
isPickupPointSelected,
|
||||||
setContacts,
|
checkoutPickupPoint,
|
||||||
|
setCheckoutPickupPoint,
|
||||||
|
|
||||||
|
checkoutContacts,
|
||||||
|
setCheckoutContacts,
|
||||||
|
|
||||||
checkoutSteps,
|
checkoutSteps,
|
||||||
currentCheckoutStep,
|
currentCheckoutStep,
|
||||||
|
|||||||
@ -9,8 +9,6 @@
|
|||||||
•
|
•
|
||||||
{{ t(`checkoutSteps.${currentCheckoutStep?.title}`) }}
|
{{ t(`checkoutSteps.${currentCheckoutStep?.title}`) }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<Icon class="cursor-pointer w-6 h-6" name="lucide:arrow-right" @click="nextStep" />
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -25,7 +23,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCheckout } from '../composables/useCheckout'
|
import { useCheckout } from '../composables/useCheckout'
|
||||||
|
|
||||||
const { previewStep, nextStep, currentCheckoutStep, checkoutSteps } = useCheckout()
|
const { previewStep, currentCheckoutStep, checkoutSteps } = useCheckout()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -56,10 +54,13 @@ const { t } = useI18n()
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import PayBlock from '../components/PayBlock.vue'
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { cart } = useCart()
|
const { cart } = useCart()
|
||||||
const { contacts } = useCheckout()
|
const { checkoutContacts, checkoutPickupPoint } = useCheckout()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!route?.query?.ID)
|
if (!route?.query?.ID)
|
||||||
@ -32,12 +32,13 @@ onMounted(async () => {
|
|||||||
payment_method_title: 'Оплата по реквизитам',
|
payment_method_title: 'Оплата по реквизитам',
|
||||||
set_paid: true,
|
set_paid: true,
|
||||||
billing: {
|
billing: {
|
||||||
first_name: contacts?.value?.name,
|
first_name: checkoutContacts?.value?.name,
|
||||||
last_name: contacts?.value?.surname,
|
last_name: checkoutContacts?.value?.surname,
|
||||||
phone: contacts?.value?.phone,
|
phone: checkoutContacts?.value?.phone,
|
||||||
email: contacts?.value?.email,
|
email: checkoutContacts?.value?.email,
|
||||||
address_1: 'ул. Ленина, 1',
|
address_1: checkoutPickupPoint.value?.address?.full_address,
|
||||||
city: 'Москва',
|
postcode: checkoutPickupPoint.value?.address?.postal_code,
|
||||||
|
city: checkoutPickupPoint?.value?.address?.locality,
|
||||||
country: 'RU',
|
country: 'RU',
|
||||||
},
|
},
|
||||||
transaction_id: route?.query?.ID,
|
transaction_id: route?.query?.ID,
|
||||||
|
|||||||
@ -140,14 +140,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useCheckout } from '../../composables/useCheckout'
|
import { useCheckout } from '../../composables/useCheckout'
|
||||||
|
|
||||||
const { contacts, setContacts, nextStep } = useCheckout()
|
const { checkoutContacts, setCheckoutContacts, nextStep } = useCheckout()
|
||||||
|
|
||||||
const { errors, handleSubmit, defineField } = useForm({
|
const { errors, handleSubmit, defineField } = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: contacts.value.name,
|
name: checkoutContacts.value.name,
|
||||||
surname: contacts.value.surname,
|
surname: checkoutContacts.value.surname,
|
||||||
phone: contacts.value.phone,
|
phone: checkoutContacts.value.phone,
|
||||||
email: contacts.value.email,
|
email: checkoutContacts.value.email,
|
||||||
},
|
},
|
||||||
validationSchema: {
|
validationSchema: {
|
||||||
name(value: string) {
|
name(value: string) {
|
||||||
@ -189,7 +189,7 @@ const [phone, phoneAttrs] = defineField('phone')
|
|||||||
const [email, emailAttrs] = defineField('email')
|
const [email, emailAttrs] = defineField('email')
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
setContacts(values)
|
setCheckoutContacts(values)
|
||||||
nextStep()
|
nextStep()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,34 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="coords" class="delivery">
|
<div v-if="coords" class="delivery">
|
||||||
<div class="delivery__sidebar">
|
<div class="delivery__sidebar">
|
||||||
<UInput v-model="searchTerm" size="xl" class="w-full" placeholder="pvz" />
|
<DeliveryInfo v-if="isPickupPointSelected" />
|
||||||
|
|
||||||
|
<div v-else class="delivery__search">
|
||||||
|
<UInput
|
||||||
|
v-model="searchTerm"
|
||||||
|
size="xl"
|
||||||
|
class="w-full"
|
||||||
|
placeholder="Выберите пункт выдачи"
|
||||||
|
:ui="{ trailing: 'pe-1' }"
|
||||||
|
>
|
||||||
|
<template v-if="searchTerm?.length" #trailing>
|
||||||
|
<UButton
|
||||||
|
color="neutral"
|
||||||
|
variant="link"
|
||||||
|
size="sm"
|
||||||
|
icon="i-lucide-circle-x"
|
||||||
|
aria-label="Clear input"
|
||||||
|
@click="searchTerm = ''"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</UInput>
|
||||||
|
|
||||||
<div class="delivery__items">
|
<div class="delivery__items">
|
||||||
<div
|
<div
|
||||||
v-for="point in filteredPvz?.points"
|
v-for="pickupPoint in filteredPoints"
|
||||||
:key="point.id"
|
:key="pickupPoint.id"
|
||||||
class="pickup-point-item"
|
class="pickup-point-card"
|
||||||
@click="centerMap(point)"
|
@click="centerMap(pickupPoint)"
|
||||||
>
|
>
|
||||||
|
<div class="pickup-point-card__content">
|
||||||
<h3>Yandex</h3>
|
<h3>Yandex</h3>
|
||||||
{{ `${point?.address?.street} ${point?.address?.house}` }}
|
<p>{{ `${pickupPoint?.address?.street} ${pickupPoint?.address?.house}` }}</p>
|
||||||
<Icon class="pickup-point-item__action" name="lucide:chevron-right" />
|
<h3>с day month бесплатно</h3>
|
||||||
|
</div>
|
||||||
|
<Icon class="pickup-point-card__action" name="lucide:chevron-right" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PvzMap ref="mapRef" :pickup-points="yandexPvz?.points" />
|
<PvzMap ref="mapRef" :pickup-points="filteredPoints" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { PickupPoint, YandexPvzResponse } from '~/server/shared/types/yandex_pvz'
|
||||||
import { useGeolocation } from '@vueuse/core'
|
import { useGeolocation } from '@vueuse/core'
|
||||||
import { onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import DeliveryInfo from '~/components/DeliveryInfo.vue'
|
||||||
import PvzMap from '~/components/PvzMap.vue'
|
import PvzMap from '~/components/PvzMap.vue'
|
||||||
|
import { useCheckout } from '~/composables/useCheckout'
|
||||||
|
|
||||||
|
const { isPickupPointSelected } = useCheckout()
|
||||||
const { coords } = useGeolocation()
|
const { coords } = useGeolocation()
|
||||||
const mapRef = ref<InstanceType<typeof PvzMap> | null>(null)
|
const mapRef = ref<InstanceType<typeof PvzMap> | null>(null)
|
||||||
const yandexPvz = ref('')
|
const yandexPvz = ref<YandexPvzResponse>()
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
|
|
||||||
const waitForCoords = () =>
|
const waitForCoords = () =>
|
||||||
@ -59,7 +87,7 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// получения пунктов выдачи города из geo_id
|
// получения пунктов выдачи города из geo_id
|
||||||
const { data: yandexPvzApi } = await useFetch('/api/yandex_pvz', {
|
const { data: yandexPvzApi } = await useFetch<YandexPvzResponse>('/api/yandex_pvz', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
geo_id: yandexLocation?.value?.variants[0]?.geo_id,
|
geo_id: yandexLocation?.value?.variants[0]?.geo_id,
|
||||||
@ -70,18 +98,18 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// TODO сделать отдельный компонент UISearch
|
// TODO сделать отдельный компонент UISearch
|
||||||
const filteredPvz = computed(() => {
|
const filteredPoints = computed<PickupPoint[]>(() => {
|
||||||
if (!searchTerm.value && searchTerm.value === '')
|
if (!searchTerm.value || searchTerm.value === '') {
|
||||||
return yandexPvz.value
|
return yandexPvz.value?.points || []
|
||||||
|
|
||||||
const result = yandexPvz?.value?.points?.filter(value => value?.address?.full_address?.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
|
||||||
return {
|
|
||||||
points: [...result],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return yandexPvz.value?.points?.filter(point =>
|
||||||
|
point?.address?.full_address?.toLowerCase().includes(searchTerm.value.toLowerCase()),
|
||||||
|
) || []
|
||||||
})
|
})
|
||||||
|
|
||||||
const centerMap = (point: any) => {
|
const centerMap = (point: any) => {
|
||||||
mapRef.value?.centerMap([point?.position?.longitude, point?.position?.latitude])
|
mapRef.value?.centerMap(point)
|
||||||
}
|
}
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@ -90,36 +118,49 @@ definePageMeta({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@use '~/assets/scss/utils' as *;
|
||||||
|
|
||||||
.delivery {
|
.delivery {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
&__sidebar {
|
&__sidebar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-height: calc(100vh - 40px);
|
||||||
|
overflow-y: auto;
|
||||||
width: 410px;
|
width: 410px;
|
||||||
padding: 24px;
|
padding-inline: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__items {
|
&__items {
|
||||||
|
height: calc(100dvh - 54px - 40px - 24px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: calc(100dvh - 54px - 40px - 24px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pickup-point-item {
|
.pickup-point-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 400px;
|
width: 100%;
|
||||||
height: 100px;
|
padding: 20px;
|
||||||
padding-block: 24px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&__action {
|
&__action {
|
||||||
position: absolute;
|
flex-shrink: 0;
|
||||||
right: 20px;
|
width: 16px;
|
||||||
top: 40px;
|
height: 16px;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -25,30 +25,7 @@
|
|||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<DeliveryInfo :show-full-content="false" />
|
||||||
<UIcon name="i-ph-user" class="text-muted-foreground" />
|
|
||||||
<span>{{ contacts?.name }} {{ contacts?.surname }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<UIcon name="i-ph-user" class="text-muted-foreground" />
|
|
||||||
<span>{{ contacts?.name }} {{ contacts?.surname }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<UIcon name="i-ph-user" class="text-muted-foreground" />
|
|
||||||
<span>{{ contacts?.name }} {{ contacts?.surname }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<UIcon name="i-ph-user" class="text-muted-foreground" />
|
|
||||||
<span>{{ contacts?.name }} {{ contacts?.surname }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<UIcon name="i-ph-user" class="text-muted-foreground" />
|
|
||||||
<span>{{ contacts?.name }} {{ contacts?.surname }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
@ -66,17 +43,17 @@
|
|||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UIcon name="i-ph-user" class="text-muted-foreground" />
|
<UIcon name="i-ph-user" class="text-muted-foreground" />
|
||||||
<span>{{ contacts?.name }} {{ contacts?.surname }}</span>
|
<span>{{ checkoutContacts?.name }} {{ checkoutContacts?.surname }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UIcon name="i-ph-envelope-simple" class="text-muted-foreground" />
|
<UIcon name="i-ph-envelope-simple" class="text-muted-foreground" />
|
||||||
<span>{{ contacts?.email }}</span>
|
<span>{{ checkoutContacts?.email }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UIcon name="i-ph-phone" class="text-muted-foreground" />
|
<UIcon name="i-ph-phone" class="text-muted-foreground" />
|
||||||
<span>{{ contacts?.phone }}</span>
|
<span>{{ checkoutContacts?.phone }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -90,7 +67,7 @@ import SummaryCartItem from '../../components/cart/SummaryCartItem.vue'
|
|||||||
import PayBlock from '../../components/PayBlock.vue'
|
import PayBlock from '../../components/PayBlock.vue'
|
||||||
|
|
||||||
const { cart } = useCart()
|
const { cart } = useCart()
|
||||||
const { contacts, setStep } = useCheckout()
|
const { checkoutContacts, setStep } = useCheckout()
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'checkout',
|
layout: 'checkout',
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
|
import type { YandexPvzResponse } from '~/server/shared/types/yandex_pvz'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { defineEventHandler, readBody } from 'h3'
|
import { defineEventHandler, readBody } from 'h3'
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event): Promise<YandexPvzResponse | { error: string }> => {
|
||||||
try {
|
try {
|
||||||
const data = await readBody(event)
|
const data = await readBody(event)
|
||||||
const apiUrl = process.env.VITE_YANDEX_B2B_BASE_URL!
|
const apiUrl = process.env.VITE_YANDEX_B2B_BASE_URL!
|
||||||
const token = process.env.VITE_YANDEX_B2B_TOKEN!
|
const token = process.env.VITE_YANDEX_B2B_TOKEN!
|
||||||
|
|
||||||
const response = await axios.post(
|
const response = await axios.post<YandexPvzResponse>(
|
||||||
`${apiUrl}/pickup-points/list`,
|
`${apiUrl}/pickup-points/list`,
|
||||||
{
|
{
|
||||||
geo_id: data?.geo_id,
|
geo_id: data?.geo_id,
|
||||||
|
|||||||
75
server/shared/types/yandex_pvz.ts
Normal file
75
server/shared/types/yandex_pvz.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
export interface GeoPosition {
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PvzAddress {
|
||||||
|
apartment: string
|
||||||
|
building: string
|
||||||
|
comment: string
|
||||||
|
country: string
|
||||||
|
full_address: string
|
||||||
|
geoId: number
|
||||||
|
house: string
|
||||||
|
housing: string
|
||||||
|
locality: string
|
||||||
|
postal_code: string
|
||||||
|
region: string
|
||||||
|
street: string
|
||||||
|
subRegion: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Contact {
|
||||||
|
phone: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeObject {
|
||||||
|
hours: number
|
||||||
|
minutes: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScheduleRestriction {
|
||||||
|
days: number[]
|
||||||
|
time_from: TimeObject
|
||||||
|
time_to: TimeObject
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Schedule {
|
||||||
|
time_zone: number
|
||||||
|
restrictions: ScheduleRestriction[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PickupServices {
|
||||||
|
is_fitting_allowed: boolean
|
||||||
|
is_partial_refuse_allowed: boolean
|
||||||
|
is_paperless_pickup_allowed: boolean
|
||||||
|
is_unboxing_allowed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PickupPoint {
|
||||||
|
id: string
|
||||||
|
operator_station_id: string
|
||||||
|
operator_id: string
|
||||||
|
name: string
|
||||||
|
type: 'pickup_point'
|
||||||
|
address: PvzAddress
|
||||||
|
position: GeoPosition
|
||||||
|
instruction: string
|
||||||
|
available_for_dropoff: boolean
|
||||||
|
available_for_c2c_dropoff: boolean
|
||||||
|
contact: Contact
|
||||||
|
schedule: Schedule
|
||||||
|
pickup_services: PickupServices
|
||||||
|
payment_methods: string[]
|
||||||
|
is_dark_store: boolean
|
||||||
|
is_market_partner: boolean
|
||||||
|
is_post_office: boolean
|
||||||
|
is_yandex_branded: boolean
|
||||||
|
dayoffs: any[]
|
||||||
|
deactivation_date: string | null
|
||||||
|
deactivation_date_predicted_debt: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface YandexPvzResponse extends PickupPoint {
|
||||||
|
points: PickupPoint[]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user