карты пвз

This commit is contained in:
alsaze 2025-11-11 20:10:11 +03:00
parent 55103d3778
commit e5420edc64
6 changed files with 133 additions and 53 deletions

View File

@ -48,6 +48,8 @@ const searchTerm = ref('')
</script> </script>
<style lang="scss"> <style lang="scss">
@use '~/assets/scss/utils' as *;
.pickup-point-card { .pickup-point-card {
position: relative; position: relative;
width: 100%; width: 100%;
@ -62,6 +64,10 @@ const searchTerm = ref('')
height: calc(100dvh - 54px - 40px - 24px); height: calc(100dvh - 54px - 40px - 24px);
overflow-y: auto; overflow-y: auto;
flex-shrink: 0; flex-shrink: 0;
@include mobile {
height: calc(100dvh - 72px - 40px - 32px);
}
} }
&__content { &__content {

View File

@ -0,0 +1,21 @@
<template>
<div class="control">
<UCheckbox
v-model="modelValue"
size="xl"
label="с примеркой"
/>
</div>
</template>
<script setup lang="ts">
const modelValue = defineModel<boolean>({ required: true })
</script>
<style scoped lang="scss">
.control {
background: var(--ui-bg-elevated);
padding: 8px 18px;
border-radius: 10px;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<UTabs v-model="activeTab" :content="false" :items="tabs">
<template #map>
{{ activeTab === '0' ? 'Карта' : activeTab === '1' ? 'Список' : 'Другое' }}
</template>
</UTabs>
</template>
<script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
import { IPvzMapTabs } from '#shared/types'
import { computed } from 'vue'
const activeTab = defineModel('activeTab', { type: String, default: IPvzMapTabs.MAP })
const tabs = computed<TabsItem[]>(() =>
[
{
value: IPvzMapTabs.MAP,
label: activeTab.value === 'map' ? 'Карта' : '',
icon: 'lucide:map-pin',
slot: 'map' as const,
},
{
value: IPvzMapTabs.LIST,
label: activeTab.value === 'list' ? 'Список' : '',
icon: 'i-lucide-list',
slot: 'list' as const,
},
],
)
</script>
<style scoped lang="scss">
</style>

View File

@ -40,36 +40,24 @@
<YandexMapControls :settings="{ position: 'bottom left', orientation: 'vertical' }"> <YandexMapControls :settings="{ position: 'bottom left', orientation: 'vertical' }">
<YandexMapControl> <YandexMapControl>
<div <MapControlFitting v-model="isFitting" />
class="control"
>
<UCheckbox
v-model="isFitting"
size="xl"
label="с примеркой"
/>
</div>
</YandexMapControl> </YandexMapControl>
</YandexMapControls> </YandexMapControls>
<YandexMapControls :settings="{ position: 'bottom right', orientation: 'vertical' }"> <YandexMapControls :settings="{ position: 'bottom right', orientation: 'vertical' }">
<YandexMapControl> <YandexMapControl>
<UTabs v-model="activeTab" :content="false" :items="tabs"> <MapControlTabs v-model="activeTab" />
<template #map>
{{ activeTab === '0' ? 'Карта' : activeTab === '1' ? 'Список' : 'Другое' }}
</template>
</UTabs>
</YandexMapControl> </YandexMapControl>
</YandexMapControls> </YandexMapControls>
</YandexMap> </YandexMap>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
import type { 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 type { PickupPoint } from '~/server/shared/types/yandex_pvz'
import { IPvzMapTabs } from '#shared/types'
import { useGeolocation } from '@vueuse/core' import { useGeolocation } from '@vueuse/core'
import { computed, shallowRef } from 'vue' import { computed, shallowRef } from 'vue'
import { import {
@ -81,38 +69,23 @@ import {
YandexMapDefaultSchemeLayer, YandexMapDefaultSchemeLayer,
YandexMapMarker, YandexMapMarker,
} from 'vue-yandex-maps' } from 'vue-yandex-maps'
import MapControlFitting from '~/components/MapControlFitting.vue'
import MapControlTabs from '~/components/MapControlTabs.vue'
const props = defineProps<{ modelValue: PickupPoint, pickupPoints: PickupPoint[] }>() const props = defineProps<{ modelValue: PickupPoint, pickupPoints: PickupPoint[] }>()
const emit = defineEmits<{ defineEmits<{
(e: 'update:modelValue', value: PickupPoint | undefined): void (e: 'update:modelValue', value: PickupPoint | undefined): void
(e: 'activeTab', value: string): void
}>() }>()
const activeTab = defineModel('activeTab', { type: String, default: IPvzMapTabs.MAP })
const isFitting = defineModel('isFitting', { type: Boolean, default: false })
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]])
const map = shallowRef<null | YMap>(null) 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 hasCoords = computed(() => coords.value?.latitude !== Infinity && coords.value?.longitude !== Infinity)
const location = ref<YMapLocationRequest>({ const location = ref<YMapLocationRequest>({
@ -136,10 +109,6 @@ watch(() => props.modelValue, (newPickupPoint) => {
duration: 500, duration: 500,
} }
}) })
watch(() => activeTab.value, (newActiveTab) => {
emit('activeTab', newActiveTab)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -207,10 +176,4 @@ watch(() => activeTab.value, (newActiveTab) => {
background-color: #1e293b; background-color: #1e293b;
} }
} }
.control {
background: var(--ui-bg-elevated);
padding: 8px 18px;
border-radius: 10px;
}
</style> </style>

View File

@ -25,20 +25,65 @@
</template> </template>
</UDrawer> </UDrawer>
activeTab <UModal
{{ activeTab }} v-model:open="openDeliverySearch"
fullscreen
:ui="{
body: 'overflow-hidden',
container: 'p-0',
overlay: 'bg-black/50',
base: 'rounded-none shadow-none',
header: 'hidden',
}"
>
<template #body>
<DeliverySearch
v-if="activeTab === IPvzMapTabs.LIST"
:filtered-points="filteredPoints"
/>
<UDrawer
v-if="isMobile"
v-model:open="open"
fixed
:ui="{
content: 'fixed bg-default ring ring-default flex focus:outline-none overflow-hidden',
container: 'w-full flex flex-col gap-4 p-4 overflow-hidden',
body: 'flex-1 overflow-y-auto',
}"
>
<template #content>
<div class="px-4 pb-4">
<DeliveryInfo v-if="isPickupPointSelected" />
</div>
</template>
</UDrawer>
</template>
<template #footer>
<div class="d-flex flex-row w-full justify-between">
<MapControlFitting v-model="isFitting" />
<MapControlTabs v-model:active-tab="activeTab" />
</div>
</template>
</UModal>
<!-- Desktop Mobile --> <!-- Desktop Mobile -->
<PvzMap <PvzMap
v-model="checkoutPickupPoint" v-model="checkoutPickupPoint"
v-model:active-tab="activeTab"
v-model:is-fitting="isFitting"
:pickup-points="filteredPoints" :pickup-points="filteredPoints"
@active-tab="value => activeTab = value"
@update:model-value="updatePoint()" @update:model-value="updatePoint()"
@update:is-fitting="value => isFitting = value"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { PickupPoint, YandexPvzResponse } from '~/server/shared/types/yandex_pvz' import type { PickupPoint, YandexPvzResponse } from '~/server/shared/types/yandex_pvz'
import { IPvzMapTabs } from '#shared/types'
import { useGeolocation, useMediaQuery } from '@vueuse/core' import { useGeolocation, useMediaQuery } from '@vueuse/core'
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import DeliveryInfo from '~/components/DeliveryInfo.vue' import DeliveryInfo from '~/components/DeliveryInfo.vue'
@ -52,7 +97,9 @@ const open = ref(false)
const isMobile = useMediaQuery('(max-width: 1280px)') const isMobile = useMediaQuery('(max-width: 1280px)')
const yandexPvz = ref<YandexPvzResponse>() const yandexPvz = ref<YandexPvzResponse>()
const searchTerm = ref('') const searchTerm = ref('')
const activeTab = ref() const activeTab = ref(IPvzMapTabs.MAP)
const isFitting = ref()
const openDeliverySearch = ref(false)
const waitForCoords = () => const waitForCoords = () =>
new Promise<void>((resolve) => { new Promise<void>((resolve) => {
@ -98,9 +145,8 @@ const filteredPoints = computed<PickupPoint[]>(() => {
return yandexPvz.value?.points || [] return yandexPvz.value?.points || []
} }
return yandexPvz.value?.points?.filter(point => return yandexPvz.value?.points?.filter(point => point?.address?.full_address?.toLowerCase().includes(searchTerm.value.toLowerCase())
point?.address?.full_address?.toLowerCase().includes(searchTerm.value.toLowerCase()), || isFitting.value === point.pickup_services.is_fitting_allowed) || []
) || []
}) })
function updatePoint() { function updatePoint() {
@ -108,6 +154,10 @@ function updatePoint() {
open.value = true open.value = true
} }
watch(() => activeTab.value, () => {
openDeliverySearch.value = activeTab.value === IPvzMapTabs.LIST
})
definePageMeta({ definePageMeta({
layout: 'checkout', layout: 'checkout',
}) })

4
shared/types.ts Normal file
View File

@ -0,0 +1,4 @@
export enum IPvzMapTabs {
MAP = 'map',
LIST = 'list',
}