fix(desktop): карта ПВЗ на Яндекс.Картах вместо нерабочего OpenLayers
На странице пунктов выдачи (стол администратора) диалог карты падал с «Failed to resolve component: ol-map»: boot/map.ts с регистрацией vue3-openlayers никогда не был подключён в quasar.config (массив boot), поэтому ol-* не резолвились и карта не рисовалась. OpenLayers в проекте больше нигде не используется — переводим shared/ui/Map на Yandex Maps JS API (как рабочий виджет KUMapWithList) и сносим OpenLayers: - shared/ui/Map/Map.vue: single-point карта на ymaps (метка — pin.svg), тот же публичный API long/lat; деградированный баннер без ключа. - shared/lib/yandexMaps: загрузчик SDK вынесен из widgets/KUMapWithList в shared (DRY) — общий для виджета списка ПВЗ и single-point карты. - удалён boot/map.ts (мёртвый, не в boot) и зависимость vue3-openlayers. Lockfile подровняется при следующей установке (vue3-openlayers пока остаётся orphan-записью). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -86,7 +86,6 @@
|
||||
"vue-router": "^4.0.12",
|
||||
"vue-window-size": "^2.0.0",
|
||||
"vue3-dropzone": "^2.2.1",
|
||||
"vue3-openlayers": "^10.0.1",
|
||||
"windows-1251": "^3.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import OpenLayersMap from 'vue3-openlayers';
|
||||
import 'vue3-openlayers/dist/main.css';
|
||||
|
||||
export default boot(({ app }) => {
|
||||
app.use(OpenLayersMap);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { loadYandexMaps } from './loadYandexMaps'
|
||||
+6
-3
@@ -1,9 +1,12 @@
|
||||
/**
|
||||
* Динамический загрузчик Yandex Maps JS API (Story 2.3).
|
||||
* Динамический загрузчик Yandex Maps JS API.
|
||||
*
|
||||
* Идемпотентен: повторный вызов возвращает закэшированный `Promise<typeof ymaps>`.
|
||||
* Если `apiKey` пуст — возвращает Promise.reject; компонент должен показать
|
||||
* деградированный UI (список без карты).
|
||||
* Если `apiKey` пуст — возвращает Promise.reject; компонент-потребитель должен
|
||||
* показать деградированный UI (без карты).
|
||||
*
|
||||
* Единая точка загрузки SDK для всех карт desktop (виджет списка ПВЗ,
|
||||
* single-point карта в диалогах и т.д.) — поэтому живёт в shared/lib.
|
||||
*/
|
||||
let cached: Promise<unknown> | null = null
|
||||
|
||||
@@ -1,52 +1,114 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue'
|
||||
import { env } from 'src/shared/config'
|
||||
import { loadYandexMaps } from 'src/shared/lib/yandexMaps'
|
||||
import PinIcon from 'src/assets/pin.svg'
|
||||
|
||||
const props = defineProps({
|
||||
long: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
lat: {
|
||||
type: Number,
|
||||
required: true,
|
||||
/**
|
||||
* Single-point карта на Yandex Maps JS API: показывает одну метку по
|
||||
* координатам. Используется в диалогах просмотра адреса (например, карточка
|
||||
* ПВЗ на столе администратора).
|
||||
*
|
||||
* SDK грузится лениво через общий {@link loadYandexMaps}; если ключ
|
||||
* YANDEX_MAPS_API_KEY не задан — показывается деградированный баннер.
|
||||
*/
|
||||
|
||||
const props = defineProps<{
|
||||
long: number
|
||||
lat: number
|
||||
}>()
|
||||
|
||||
const apiKey = env.YANDEX_MAPS_API_KEY
|
||||
|
||||
const mapContainer = ref<HTMLElement | null>(null)
|
||||
let mapInstance: any = null
|
||||
let ymapsRef: any = null
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
function center(): [number, number] {
|
||||
// Yandex принимает координаты в порядке [широта, долгота].
|
||||
return [Number(props.lat), Number(props.long)]
|
||||
}
|
||||
|
||||
function renderPlacemark(): void {
|
||||
if (!mapInstance || !ymapsRef) return
|
||||
mapInstance.geoObjects.removeAll()
|
||||
const placemark = new ymapsRef.Placemark(
|
||||
center(),
|
||||
{},
|
||||
{
|
||||
iconLayout: 'default#image',
|
||||
iconImageHref: PinIcon,
|
||||
iconImageSize: [24, 32],
|
||||
iconImageOffset: [-12, -32],
|
||||
},
|
||||
)
|
||||
mapInstance.geoObjects.add(placemark)
|
||||
}
|
||||
|
||||
async function initMap(): Promise<void> {
|
||||
if (!apiKey || !mapContainer.value) return
|
||||
try {
|
||||
ymapsRef = await loadYandexMaps(apiKey)
|
||||
await nextTick()
|
||||
if (!mapContainer.value) return
|
||||
mapInstance = new ymapsRef.Map(mapContainer.value, {
|
||||
center: center(),
|
||||
zoom: 17,
|
||||
controls: ['zoomControl', 'fullscreenControl'],
|
||||
})
|
||||
renderPlacemark()
|
||||
|
||||
// Внутри диалога контейнер на момент монтирования может иметь нулевой
|
||||
// размер (анимация раскрытия) — подгоняем карту под вьюпорт по ресайзу.
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
mapInstance?.container.fitToViewport()
|
||||
})
|
||||
resizeObserver.observe(mapContainer.value)
|
||||
} catch (err) {
|
||||
console.warn('[Map] Yandex Maps init упал:', err)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.lat, props.long],
|
||||
() => {
|
||||
if (mapInstance) {
|
||||
mapInstance.setCenter(center(), 17)
|
||||
renderPlacemark()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
void initMap()
|
||||
})
|
||||
|
||||
const projection = ref('EPSG:4326')
|
||||
const zoom = ref(18)
|
||||
const rotation = ref(0)
|
||||
|
||||
const pinCenter = computed(() => {
|
||||
return [Number(props.long), Number(props.lat)]
|
||||
onBeforeUnmount(() => {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
resizeObserver = null
|
||||
}
|
||||
if (mapInstance) {
|
||||
mapInstance.destroy()
|
||||
mapInstance = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.q-mt-lg
|
||||
ol-map(
|
||||
:load-tiles-while-animating="true"
|
||||
:load-tiles-while-interacting="true"
|
||||
style="height: 300px; border: 1px solid grey;"
|
||||
)
|
||||
ol-view(
|
||||
ref="view"
|
||||
:center="pinCenter"
|
||||
:enable-rotation="false"
|
||||
:zoom="zoom"
|
||||
:projection="projection"
|
||||
:rotation="rotation"
|
||||
)
|
||||
|
||||
ol-tile-layer
|
||||
ol-source-osm
|
||||
ol-vector-layer
|
||||
ol-source-vector
|
||||
ol-feature
|
||||
ol-geom-point(v-if="pinCenter" :coordinates="pinCenter")
|
||||
ol-style
|
||||
ol-style-stroke(color="red" :width="2")
|
||||
ol-style-fill(color="rgba(255,255,255,0.1)")
|
||||
ol-style-icon(:src="PinIcon" :scale="1.5")
|
||||
|
||||
.ya-map(v-if="apiKey" ref="mapContainer")
|
||||
.banner.banner--warn(v-else)
|
||||
q-icon.banner__icon(name="warning" size="18px")
|
||||
.banner__body Карта недоступна: не задан ключ Яндекс.Карт.
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ya-map {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border: 1px solid var(--p-line);
|
||||
border-radius: var(--p-r-md, 12px);
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { env } from 'src/shared/config'
|
||||
import { loadYandexMaps } from '../lib/loadYandexMaps'
|
||||
import { loadYandexMaps } from 'src/shared/lib/yandexMaps'
|
||||
import type { IMarketplaceKUDetails } from 'src/entities/MarketplaceKUDetails'
|
||||
|
||||
const props = withDefaults(
|
||||
|
||||
Reference in New Issue
Block a user