forked from zhouruizhe/gmTouringMiniApp
微信主包上限 2 MB,30 张 800×600 照片单独就 2.6 MB, 整包 3.2 MB,上传被拒(错误码 80051)。 按使用场景把照片拆成两层: - 主包缩略图 400×300 q75(678 KB),供地图、行程、打卡卡片使用, 这些场景最大只显示 192rpx(≈288 px @DPR3),800×600 是过剩 - 分包大图 800×600 q66(1832 KB),供详情页 hero 使用, 随 pages-poi 分包按需下载 两层都保持 4:3,PoiImage 的 aspectFill 行为不变,视觉表现与之前一致。 详情页从 pages/poi/detail 移到 pages-poi/detail:主包页面无法引用 分包资源,hero 要用大图,页面就必须在分包内。新增 coverPhotoFullUrlFor() 供分包内页面取大图,占位图仍指向主包。 verify-weixin-output.mjs 增加防回归检查:分包注册、两层照片数量 一致、主包与各分包实际体积(留 64 KB 余量)。包体再超限会在 verify 阶段失败,不必等上传才发现。 process-poi-photos.mjs 重写为双层产出,幂等,末尾报告包体占用并 在超限时退出非零。原图改从 src/photo-intake/ 读取(不提交仓库)。 改造后:主包 1522 KB、分包 pages-poi 1847 KB,均在 2048 KB 内。 mp-weixin 与 H5 均编译通过,97 个测试通过,vue-tsc 与 ESLint 无错。
745 lines
20 KiB
Vue
745 lines
20 KiB
Vue
<script setup lang="ts">
|
|
import type { PoiCategory, PoiDatasetMeta, PoiSummary } from '@/domain/poi'
|
|
import type { PlanResponse } from '@/domain/travel'
|
|
import type { PoiMapMarker } from '@/services/map'
|
|
import MapFilterHeader from '@/components/map/MapFilterHeader.vue'
|
|
import PoiSummaryCard from '@/components/map/PoiSummaryCard.vue'
|
|
import PageState from '@/components/poi/PageState.vue'
|
|
import { getPoiRepository } from '@/data/poi'
|
|
import { buildMarkerIdMap, createPoiMarkers } from '@/services/map'
|
|
import { getSessionPlanningOrigin, loadPlan } from '@/services/travel-assistant'
|
|
import { useLocationStore, useMapStore } from '@/stores'
|
|
|
|
const MAP_ID = 'guangming-cultural-map'
|
|
const mapStore = useMapStore()
|
|
const locationStore = useLocationStore()
|
|
|
|
const pageState = ref<'loading' | 'ready' | 'data_error' | 'dataset_empty'>('loading')
|
|
const mapReady = ref(false)
|
|
const mapError = ref(false)
|
|
const categories = ref<PoiCategory[]>([])
|
|
const allPoiSummaries = ref<PoiSummary[]>([])
|
|
const datasetMeta = ref<PoiDatasetMeta | null>(null)
|
|
const activePlan = shallowRef<PlanResponse | null>(null)
|
|
const mapContext = shallowRef<ReturnType<typeof uni.createMapContext> | null>(null)
|
|
const markerIdToPoiId = shallowRef(new Map<number, string>())
|
|
const lastMarkerTapAt = ref(0)
|
|
const navigating = ref(false)
|
|
let includePointsRequestVersion = 0
|
|
let locationListenerAttached = false
|
|
let locationUpdatesStarted = false
|
|
let locationUpdatesStarting = false
|
|
let locationUpdatesRequested = false
|
|
let locationRequestVersion = 0
|
|
|
|
const activeCategoryCode = computed(() => mapStore.categoryCode)
|
|
const selectedPoiId = computed(() => mapStore.selectedPoiId)
|
|
const viewport = computed(() => mapStore.viewport)
|
|
const locationReady = computed(() => locationStore.status === 'ready' && Boolean(locationStore.snapshot))
|
|
const locationButtonText = computed(() => {
|
|
if (locationStore.status === 'locating')
|
|
return '定位中…'
|
|
if (locationReady.value)
|
|
return '回到我的位置'
|
|
if (locationStore.status === 'denied')
|
|
return '开启位置权限'
|
|
return '定位到我'
|
|
})
|
|
const filteredPois = computed(() => {
|
|
if (!activeCategoryCode.value)
|
|
return allPoiSummaries.value
|
|
return allPoiSummaries.value.filter(poi => poi.categoryCode === activeCategoryCode.value)
|
|
})
|
|
const selectedPoi = computed(() => {
|
|
if (!selectedPoiId.value)
|
|
return null
|
|
return allPoiSummaries.value.find(poi => poi.id === selectedPoiId.value) ?? null
|
|
})
|
|
const markers = computed<PoiMapMarker[]>(() => {
|
|
const baseMarkers = createPoiMarkers(
|
|
filteredPois.value,
|
|
categories.value,
|
|
markerIdToPoiId.value,
|
|
selectedPoiId.value,
|
|
)
|
|
if (!activePlan.value)
|
|
return baseMarkers
|
|
|
|
const routeOrder = new Map(activePlan.value.itinerary.items.map((item, index) => [item.placeId, index + 1]))
|
|
return baseMarkers.map((marker) => {
|
|
const poiId = markerIdToPoiId.value.get(marker.id)
|
|
const order = poiId ? routeOrder.get(poiId) : undefined
|
|
if (!order)
|
|
return marker
|
|
return {
|
|
...marker,
|
|
callout: {
|
|
...marker.callout,
|
|
content: selectedPoiId.value === poiId ? `${order} · ${marker.callout.content}` : `路线 ${order}`,
|
|
color: '#ffffff',
|
|
bgColor: '#167a5b',
|
|
display: 'ALWAYS',
|
|
},
|
|
zIndex: Math.max(marker.zIndex, 8),
|
|
}
|
|
})
|
|
})
|
|
const mapPoints = computed<Array<{ latitude: number, longitude: number }>>(() => filteredPois.value.map(poi => ({
|
|
latitude: poi.latitude,
|
|
longitude: poi.longitude,
|
|
})))
|
|
const plannedPois = computed(() => {
|
|
if (!activePlan.value)
|
|
return []
|
|
const repository = getPoiRepository()
|
|
return activePlan.value.itinerary.items.flatMap((item) => {
|
|
const poi = repository.getPoiById(item.placeId)
|
|
return poi ? [poi] : []
|
|
})
|
|
})
|
|
const plannedOrigin = computed(() => activePlan.value
|
|
? getSessionPlanningOrigin(activePlan.value.conversationId)
|
|
: null)
|
|
const plannedRoutePoints = computed(() => [
|
|
...(activePlan.value?.itinerary.startsFromCurrentLocation && plannedOrigin.value
|
|
? [{ latitude: plannedOrigin.value.latitude, longitude: plannedOrigin.value.longitude }]
|
|
: []),
|
|
...plannedPois.value.map(poi => ({ latitude: poi.latitude, longitude: poi.longitude })),
|
|
])
|
|
const plannedRoutePolyline = computed(() => plannedRoutePoints.value.length > 1
|
|
? [{
|
|
points: plannedRoutePoints.value,
|
|
color: '#167a5bcc',
|
|
width: 6,
|
|
dottedLine: false,
|
|
arrowLine: true,
|
|
borderColor: '#ffffffcc',
|
|
borderWidth: 2,
|
|
}]
|
|
: [])
|
|
|
|
function loadActivePlan() {
|
|
activePlan.value = mapStore.plannedRouteVisible ? loadPlan() : null
|
|
}
|
|
|
|
function includePlannedRoute() {
|
|
const context = mapContext.value
|
|
if (!context || !mapReady.value || plannedRoutePoints.value.length === 0)
|
|
return
|
|
nextTick(() => {
|
|
context.includePoints({
|
|
points: plannedRoutePoints.value,
|
|
padding: [uni.upx2px(70), uni.upx2px(70), uni.upx2px(150), uni.upx2px(70)],
|
|
})
|
|
})
|
|
}
|
|
|
|
function loadDataset() {
|
|
pageState.value = 'loading'
|
|
mapError.value = false
|
|
|
|
try {
|
|
const repository = getPoiRepository()
|
|
const nextMeta = repository.getDatasetMeta()
|
|
const nextCategories = repository.getCategories()
|
|
const nextPois = repository.getPoiSummaries()
|
|
|
|
datasetMeta.value = nextMeta
|
|
categories.value = nextCategories
|
|
allPoiSummaries.value = nextPois
|
|
markerIdToPoiId.value = buildMarkerIdMap(nextPois.map(poi => poi.id))
|
|
|
|
const untouchedDefaultViewport = mapStore.viewport.longitude === 113.935
|
|
&& mapStore.viewport.latitude === 22.748
|
|
&& mapStore.viewport.scale === 11
|
|
if (untouchedDefaultViewport)
|
|
mapStore.resetViewport(nextMeta.defaultViewport)
|
|
if (mapStore.categoryCode && !nextCategories.some(category => category.code === mapStore.categoryCode))
|
|
mapStore.setCategory(null)
|
|
if (mapStore.selectedPoiId && !nextPois.some(poi => poi.id === mapStore.selectedPoiId))
|
|
mapStore.selectPoi(null)
|
|
|
|
pageState.value = nextPois.length === 0 ? 'dataset_empty' : 'ready'
|
|
}
|
|
catch (error) {
|
|
console.error('Failed to load POI dataset', error)
|
|
categories.value = []
|
|
allPoiSummaries.value = []
|
|
markerIdToPoiId.value = new Map()
|
|
pageState.value = 'data_error'
|
|
}
|
|
}
|
|
|
|
function includeFilteredPoints() {
|
|
const requestVersion = ++includePointsRequestVersion
|
|
const context = mapContext.value
|
|
const points = mapPoints.value.map(point => ({ ...point }))
|
|
const bottomPadding = selectedPoi.value ? 360 : 80
|
|
|
|
if (!mapReady.value || !context || points.length === 0)
|
|
return
|
|
|
|
nextTick(() => {
|
|
if (requestVersion !== includePointsRequestVersion
|
|
|| !mapReady.value
|
|
|| mapContext.value !== context) {
|
|
return
|
|
}
|
|
|
|
if (points.length === 1) {
|
|
mapStore.updateViewport({
|
|
...points[0],
|
|
scale: 15,
|
|
})
|
|
return
|
|
}
|
|
|
|
try {
|
|
context.includePoints({
|
|
points,
|
|
padding: [
|
|
uni.upx2px(48),
|
|
uni.upx2px(48),
|
|
uni.upx2px(bottomPadding),
|
|
uni.upx2px(48),
|
|
],
|
|
fail: (error) => {
|
|
handleIncludePointsFailure(requestVersion, context, error)
|
|
},
|
|
})
|
|
}
|
|
catch (error) {
|
|
handleIncludePointsFailure(requestVersion, context, error)
|
|
}
|
|
})
|
|
}
|
|
|
|
function handleIncludePointsFailure(
|
|
requestVersion: number,
|
|
context: ReturnType<typeof uni.createMapContext>,
|
|
error: unknown,
|
|
) {
|
|
if (requestVersion !== includePointsRequestVersion || mapContext.value !== context)
|
|
return
|
|
|
|
console.error('Failed to include POI points', error)
|
|
includePointsRequestVersion += 1
|
|
mapReady.value = false
|
|
mapError.value = true
|
|
mapContext.value = null
|
|
}
|
|
|
|
function selectCategory(categoryCode: string | null) {
|
|
if (mapStore.categoryCode === categoryCode)
|
|
return
|
|
|
|
mapStore.setCategory(categoryCode)
|
|
mapStore.hidePlannedRoute()
|
|
activePlan.value = null
|
|
if (selectedPoi.value && categoryCode && selectedPoi.value.categoryCode !== categoryCode)
|
|
mapStore.selectPoi(null)
|
|
|
|
includeFilteredPoints()
|
|
}
|
|
|
|
function selectMarker(event: { detail: { markerId: number } }) {
|
|
lastMarkerTapAt.value = Date.now()
|
|
const poiId = markerIdToPoiId.value.get(Number(event.detail.markerId))
|
|
if (!poiId)
|
|
return
|
|
|
|
// A marker tap is more recent than any pending category fit operation.
|
|
includePointsRequestVersion += 1
|
|
mapStore.selectPoi(poiId)
|
|
const poi = allPoiSummaries.value.find(item => item.id === poiId)
|
|
if (poi) {
|
|
mapStore.updateViewport({
|
|
longitude: poi.longitude,
|
|
latitude: poi.latitude,
|
|
scale: Math.max(viewport.value.scale, 13),
|
|
})
|
|
}
|
|
}
|
|
|
|
function clearSelection() {
|
|
if (Date.now() - lastMarkerTapAt.value < 250)
|
|
return
|
|
mapStore.selectPoi(null)
|
|
}
|
|
|
|
function updateViewport(event: { type: string, detail: Record<string, unknown> }) {
|
|
const changeType = String(event.detail.type ?? event.type ?? '')
|
|
if (changeType !== 'end')
|
|
return
|
|
|
|
const longitude = Number(event.detail.longitude)
|
|
const latitude = Number(event.detail.latitude)
|
|
const eventScale = Number(event.detail.scale)
|
|
const fallbackScale = Number.isFinite(eventScale) ? eventScale : viewport.value.scale
|
|
if (Number.isFinite(longitude) && Number.isFinite(latitude)) {
|
|
updateViewportWithScale(longitude, latitude, fallbackScale)
|
|
return
|
|
}
|
|
|
|
mapContext.value?.getCenterLocation({
|
|
success: (location: { longitude: number, latitude: number }) => {
|
|
updateViewportWithScale(location.longitude, location.latitude, fallbackScale)
|
|
},
|
|
})
|
|
}
|
|
|
|
function updateViewportWithScale(longitude: number, latitude: number, fallbackScale: number) {
|
|
const context = mapContext.value as (ReturnType<typeof uni.createMapContext> & {
|
|
getScale?: (options: {
|
|
success: (result: { scale: number }) => void
|
|
fail: () => void
|
|
}) => void
|
|
}) | null
|
|
|
|
if (!context?.getScale) {
|
|
mapStore.updateViewport({ longitude, latitude, scale: fallbackScale })
|
|
return
|
|
}
|
|
|
|
context.getScale({
|
|
success: (result) => {
|
|
const scale = Number(result.scale)
|
|
mapStore.updateViewport({
|
|
longitude,
|
|
latitude,
|
|
scale: Number.isFinite(scale) ? scale : fallbackScale,
|
|
})
|
|
},
|
|
fail: () => {
|
|
mapStore.updateViewport({ longitude, latitude, scale: fallbackScale })
|
|
},
|
|
})
|
|
}
|
|
|
|
function resetToAll() {
|
|
mapStore.setCategory(null)
|
|
mapStore.selectPoi(null)
|
|
mapStore.hidePlannedRoute()
|
|
activePlan.value = null
|
|
const target = datasetMeta.value?.defaultViewport
|
|
if (target)
|
|
mapStore.resetViewport(target)
|
|
includeFilteredPoints()
|
|
}
|
|
|
|
function isPermissionDenied(error: unknown): boolean {
|
|
if (!error || typeof error !== 'object')
|
|
return false
|
|
const message = String((error as { errMsg?: unknown }).errMsg ?? '')
|
|
return /auth deny|auth denied|authorize:fail|permission/i.test(message)
|
|
}
|
|
|
|
function locationChangeListener(result: UniNamespace.OnLocationChangeCallbackResult) {
|
|
const longitude = Number(result.longitude)
|
|
const latitude = Number(result.latitude)
|
|
const accuracy = Number(result.accuracy ?? result.horizontalAccuracy)
|
|
locationStore.updateLocation(longitude, latitude, accuracy)
|
|
}
|
|
|
|
function attachLocationListener() {
|
|
if (locationListenerAttached)
|
|
return
|
|
uni.onLocationChange(locationChangeListener)
|
|
locationListenerAttached = true
|
|
}
|
|
|
|
function detachLocationListener() {
|
|
if (!locationListenerAttached)
|
|
return
|
|
uni.offLocationChange(locationChangeListener)
|
|
locationListenerAttached = false
|
|
}
|
|
|
|
function startForegroundLocationUpdates() {
|
|
if (locationUpdatesStarted || locationUpdatesStarting || !locationUpdatesRequested)
|
|
return
|
|
|
|
locationUpdatesStarting = true
|
|
attachLocationListener()
|
|
uni.startLocationUpdate({
|
|
type: 'gcj02',
|
|
success: () => {
|
|
locationUpdatesStarting = false
|
|
if (!locationUpdatesRequested) {
|
|
uni.stopLocationUpdate()
|
|
detachLocationListener()
|
|
return
|
|
}
|
|
locationUpdatesStarted = true
|
|
},
|
|
fail: (error) => {
|
|
locationUpdatesStarting = false
|
|
detachLocationListener()
|
|
console.error('Failed to start foreground location updates', error)
|
|
},
|
|
})
|
|
}
|
|
|
|
function stopForegroundLocationUpdates() {
|
|
locationUpdatesRequested = false
|
|
locationRequestVersion += 1
|
|
detachLocationListener()
|
|
if (locationUpdatesStarted) {
|
|
uni.stopLocationUpdate()
|
|
locationUpdatesStarted = false
|
|
}
|
|
}
|
|
|
|
function centerOnUserLocation() {
|
|
const snapshot = locationStore.snapshot
|
|
if (!snapshot)
|
|
return
|
|
includePointsRequestVersion += 1
|
|
mapStore.selectPoi(null)
|
|
mapStore.updateViewport({
|
|
longitude: snapshot.longitude,
|
|
latitude: snapshot.latitude,
|
|
scale: 15,
|
|
})
|
|
}
|
|
|
|
function requestUserLocation() {
|
|
if (locationStore.status === 'locating')
|
|
return
|
|
if (locationStore.status === 'denied') {
|
|
uni.openSetting({
|
|
success: (settings) => {
|
|
if (settings.authSetting['scope.userLocation']) {
|
|
locationUpdatesRequested = true
|
|
locationStore.startLocating()
|
|
requestCurrentLocation()
|
|
}
|
|
},
|
|
})
|
|
return
|
|
}
|
|
if (locationReady.value) {
|
|
locationUpdatesRequested = true
|
|
centerOnUserLocation()
|
|
startForegroundLocationUpdates()
|
|
return
|
|
}
|
|
|
|
locationUpdatesRequested = true
|
|
locationStore.startLocating()
|
|
requestCurrentLocation()
|
|
}
|
|
|
|
function requestCurrentLocation() {
|
|
const requestVersion = ++locationRequestVersion
|
|
uni.getLocation({
|
|
type: 'gcj02',
|
|
isHighAccuracy: true,
|
|
highAccuracyExpireTime: 5000,
|
|
success: (result) => {
|
|
if (requestVersion !== locationRequestVersion)
|
|
return
|
|
const updated = locationStore.updateLocation(
|
|
Number(result.longitude),
|
|
Number(result.latitude),
|
|
Number(result.accuracy ?? result.horizontalAccuracy),
|
|
)
|
|
if (!updated) {
|
|
locationStore.failLocation('error', '定位结果无效,请稍后重试')
|
|
return
|
|
}
|
|
centerOnUserLocation()
|
|
startForegroundLocationUpdates()
|
|
},
|
|
fail: (error) => {
|
|
if (requestVersion !== locationRequestVersion)
|
|
return
|
|
if (isPermissionDenied(error)) {
|
|
locationStore.failLocation('denied', '位置权限未开启')
|
|
uni.showModal({
|
|
title: '需要位置权限',
|
|
content: '开启位置权限后,可在地图显示实时方位,并按当前位置优化推荐路线。',
|
|
confirmText: '去设置',
|
|
success: result => result.confirm && requestUserLocation(),
|
|
})
|
|
return
|
|
}
|
|
locationStore.failLocation('unavailable', '暂时无法获取位置')
|
|
uni.showToast({ title: '定位失败,请检查系统定位服务', icon: 'none' })
|
|
},
|
|
})
|
|
}
|
|
|
|
function openDetail(poiId: string) {
|
|
if (navigating.value)
|
|
return
|
|
navigating.value = true
|
|
uni.navigateTo({
|
|
url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}`,
|
|
complete: () => {
|
|
setTimeout(() => {
|
|
navigating.value = false
|
|
}, 500)
|
|
},
|
|
})
|
|
}
|
|
|
|
function openCheckInRecords() {
|
|
uni.navigateTo({ url: '/pages/check-in/records' })
|
|
}
|
|
|
|
function initializeMap() {
|
|
includePointsRequestVersion += 1
|
|
mapReady.value = false
|
|
mapError.value = false
|
|
|
|
nextTick(() => {
|
|
try {
|
|
mapContext.value = uni.createMapContext(MAP_ID)
|
|
mapReady.value = true
|
|
if (activePlan.value)
|
|
includePlannedRoute()
|
|
else
|
|
includeFilteredPoints()
|
|
}
|
|
catch (error) {
|
|
console.error('Failed to initialize map', error)
|
|
mapReady.value = false
|
|
mapError.value = true
|
|
}
|
|
})
|
|
}
|
|
|
|
function handleMapError(event: unknown) {
|
|
console.error('Map component error', event)
|
|
includePointsRequestVersion += 1
|
|
mapReady.value = false
|
|
mapError.value = true
|
|
mapContext.value = null
|
|
}
|
|
|
|
onLoad(() => {
|
|
loadDataset()
|
|
})
|
|
|
|
onReady(() => {
|
|
initializeMap()
|
|
})
|
|
|
|
onShow(() => {
|
|
loadActivePlan()
|
|
if (activePlan.value)
|
|
includePlannedRoute()
|
|
if (locationReady.value && locationUpdatesRequested)
|
|
startForegroundLocationUpdates()
|
|
})
|
|
|
|
onHide(stopForegroundLocationUpdates)
|
|
onUnload(stopForegroundLocationUpdates)
|
|
</script>
|
|
|
|
<template>
|
|
<view class="map-page">
|
|
<MapFilterHeader
|
|
:categories="categories"
|
|
:active-category-code="activeCategoryCode"
|
|
:result-count="filteredPois.length"
|
|
:coverage-label="datasetMeta?.coverageLabel ?? 'POC 示例数据'"
|
|
:loading="pageState === 'loading'"
|
|
@check-ins="openCheckInRecords"
|
|
@select="selectCategory"
|
|
/>
|
|
|
|
<view v-if="pageState === 'loading'" class="map-page__state">
|
|
<PageState state="loading" title="正在加载光明区文旅资源…" />
|
|
</view>
|
|
|
|
<view v-else-if="pageState === 'data_error'" class="map-page__state">
|
|
<PageState
|
|
state="error"
|
|
title="点位数据加载失败"
|
|
description="暂时无法读取点位信息,请稍后重试"
|
|
action-label="重新加载"
|
|
@action="loadDataset"
|
|
/>
|
|
</view>
|
|
|
|
<view v-else-if="pageState === 'dataset_empty'" class="map-page__state">
|
|
<PageState state="empty" title="暂无可展示点位" description="本期审核数据中暂时没有已发布点位" />
|
|
</view>
|
|
|
|
<template v-else>
|
|
<view v-if="filteredPois.length === 0" class="map-page__empty-strip">
|
|
<text>当前分类暂无点位</text>
|
|
<button @click="resetToAll">查看全部</button>
|
|
</view>
|
|
|
|
<view class="map-page__viewport">
|
|
<view v-if="mapError" class="map-page__map-error">
|
|
<PageState
|
|
state="error"
|
|
title="地图服务暂不可用"
|
|
description="请检查网络后重试地图;位置功能仅在您主动点击定位按钮后启用"
|
|
action-label="重试地图"
|
|
@action="initializeMap"
|
|
/>
|
|
</view>
|
|
<map
|
|
v-else
|
|
:id="MAP_ID"
|
|
class="map-page__map"
|
|
:longitude="viewport.longitude"
|
|
:latitude="viewport.latitude"
|
|
:scale="viewport.scale"
|
|
:markers="markers"
|
|
:polyline="plannedRoutePolyline"
|
|
:show-location="locationReady"
|
|
:enable-rotate="false"
|
|
:enable-overlooking="false"
|
|
:show-compass="false"
|
|
@markertap="selectMarker"
|
|
@tap="clearSelection"
|
|
@regionchange="updateViewport"
|
|
@error="handleMapError"
|
|
>
|
|
<cover-view class="map-page__reset" @tap="resetToAll">
|
|
回到全域
|
|
</cover-view>
|
|
<cover-view
|
|
class="map-page__locate"
|
|
:class="{ 'map-page__locate--active': locationReady }"
|
|
@tap="requestUserLocation"
|
|
>
|
|
{{ locationButtonText }}
|
|
</cover-view>
|
|
<cover-view v-if="activePlan" class="map-page__route" @tap="includePlannedRoute">
|
|
完整路线 · {{ plannedPois.length }} 站
|
|
</cover-view>
|
|
</map>
|
|
</view>
|
|
|
|
<PoiSummaryCard
|
|
v-if="selectedPoi"
|
|
:poi="selectedPoi"
|
|
@close="mapStore.selectPoi(null)"
|
|
@detail="openDetail"
|
|
/>
|
|
</template>
|
|
</view>
|
|
</template>
|
|
|
|
<route lang="yaml" type="home">
|
|
layout: map
|
|
style:
|
|
navigationBarTitleText: 光明文旅地图
|
|
navigationBarBackgroundColor: '#ffffff'
|
|
navigationBarTextStyle: black
|
|
disableScroll: true
|
|
</route>
|
|
|
|
<style scoped lang="scss">
|
|
.map-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
background: #f5f7f6;
|
|
}
|
|
|
|
.map-page__state,
|
|
.map-page__map-error {
|
|
display: flex;
|
|
flex: 1;
|
|
align-items: stretch;
|
|
justify-content: stretch;
|
|
min-height: 0;
|
|
}
|
|
|
|
.map-page__empty-strip {
|
|
display: flex;
|
|
flex: none;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
min-height: 72rpx;
|
|
padding: 0 32rpx;
|
|
font-size: 24rpx;
|
|
color: #934b00;
|
|
background: #fff3e0;
|
|
}
|
|
|
|
.map-page__empty-strip button {
|
|
width: auto;
|
|
padding: 0 24rpx;
|
|
margin: 0;
|
|
font-size: 24rpx;
|
|
font-weight: 600;
|
|
line-height: 72rpx;
|
|
color: #0e6247;
|
|
background: transparent;
|
|
border: 0;
|
|
}
|
|
|
|
.map-page__empty-strip button::after {
|
|
border: 0;
|
|
}
|
|
|
|
.map-page__viewport {
|
|
position: relative;
|
|
flex: 1;
|
|
min-height: 480rpx;
|
|
}
|
|
|
|
.map-page__map {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.map-page__reset {
|
|
position: absolute;
|
|
top: 24rpx;
|
|
right: 24rpx;
|
|
padding: 14rpx 22rpx;
|
|
font-size: 24rpx;
|
|
font-weight: 600;
|
|
line-height: 36rpx;
|
|
color: #0e6247;
|
|
background: #fff;
|
|
border-radius: 28rpx;
|
|
box-shadow: 0 8rpx 24rpx rgb(24 32 29 / 16%);
|
|
}
|
|
|
|
.map-page__locate {
|
|
position: absolute;
|
|
right: 24rpx;
|
|
bottom: 28rpx;
|
|
padding: 14rpx 22rpx;
|
|
font-size: 24rpx;
|
|
font-weight: 600;
|
|
line-height: 36rpx;
|
|
color: #34473d;
|
|
background: #fff;
|
|
border-radius: 28rpx;
|
|
box-shadow: 0 8rpx 24rpx rgb(24 32 29 / 16%);
|
|
}
|
|
|
|
.map-page__locate--active {
|
|
color: #fff;
|
|
background: #167a5b;
|
|
}
|
|
|
|
.map-page__route {
|
|
position: absolute;
|
|
bottom: 28rpx;
|
|
left: 24rpx;
|
|
padding: 14rpx 22rpx;
|
|
font-size: 24rpx;
|
|
font-weight: 700;
|
|
line-height: 36rpx;
|
|
color: #fff;
|
|
background: #167a5b;
|
|
border-radius: 28rpx;
|
|
box-shadow: 0 8rpx 24rpx rgb(24 32 29 / 16%);
|
|
}
|
|
</style>
|