420 lines
8.3 KiB
Vue
420 lines
8.3 KiB
Vue
<template>
|
|
<view class="map-container">
|
|
<map
|
|
id="mainMap"
|
|
class="map"
|
|
:key="mapKey"
|
|
:longitude="mapCenter.longitude"
|
|
:latitude="mapCenter.latitude"
|
|
:scale="mapZoom"
|
|
:markers="mapMarkers"
|
|
:show-location="false"
|
|
:enable-scroll="true"
|
|
:enable-zoom="true"
|
|
:enable-rotate="false"
|
|
:show-compass="false"
|
|
@markertap="handleMarkerTap"
|
|
@regionchange="handleRegionChange"
|
|
></map>
|
|
|
|
<!-- 地图加载状态 -->
|
|
<view class="map-loading" v-if="!mapCenter.longitude">
|
|
<view class="loading-content">
|
|
<view class="loading-spinner"></view>
|
|
<text>地图加载中...</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 地图上的浮动按钮 -->
|
|
<view class="map-controls">
|
|
<view class="control-btn location-control" @click="handleRelocate">
|
|
<image class="control-icon" src="@/static/scan-icon.png" mode="aspectFit" />
|
|
<text>我的位置</text>
|
|
</view>
|
|
<view class="control-btn scan-control main-btn" @click="handleScan">
|
|
<image class="control-icon" src="@/static/scan-icon.png" mode="aspectFit" />
|
|
<text>扫码使用</text>
|
|
</view>
|
|
<view class="control-btn list-control" @click="handleShowList">
|
|
<image class="control-icon" src="@/static/scan-icon.png" mode="aspectFit" />
|
|
<text>附近场地</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch, nextTick, onUnmounted, onMounted, getCurrentInstance } from 'vue'
|
|
import AmapUtil from '@/utils/amap.js'
|
|
|
|
// Props
|
|
const props = defineProps({
|
|
userLocation: {
|
|
type: Object,
|
|
default: null
|
|
},
|
|
positionList: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
filteredPositions: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
searchKeyword: {
|
|
type: String,
|
|
default: ''
|
|
}
|
|
})
|
|
|
|
// Emits
|
|
const emit = defineEmits([
|
|
'relocate',
|
|
'scan',
|
|
'showList',
|
|
'markerTap',
|
|
'mapCenterChange'
|
|
])
|
|
|
|
// 响应式数据
|
|
const mapKey = ref(0)
|
|
const mapZoom = ref(16) // 地图缩放级别 (16级约等于1:500m比例)
|
|
const mapCenter = ref({
|
|
longitude: 116.397128,
|
|
latitude: 39.916527
|
|
})
|
|
const loadPositionsTimer = ref(null)
|
|
const isMapInitialized = ref(false) // 标记地图是否已初始化
|
|
|
|
// 获取组件实例
|
|
const instance = getCurrentInstance()
|
|
|
|
// 方法定义(需要在watch之前定义)
|
|
const updateMapCenter = (longitude, latitude) => {
|
|
// 检查是否真的需要更新
|
|
if (mapCenter.value.longitude === longitude && mapCenter.value.latitude === latitude) {
|
|
return
|
|
}
|
|
|
|
mapCenter.value = { longitude, latitude }
|
|
mapZoom.value = 16 // 确保缩放级别正确
|
|
|
|
// 延迟调用地图API,确保DOM已更新
|
|
nextTick(() => {
|
|
setTimeout(() => {
|
|
const mapContext = uni.createMapContext('mainMap')
|
|
if (mapContext) {
|
|
mapContext.setCenterOffset({
|
|
longitude,
|
|
latitude,
|
|
success: () => {},
|
|
fail: () => {
|
|
// 备用方案
|
|
mapContext.includePoints({
|
|
points: [{ longitude, latitude }],
|
|
padding: [0, 0, 0, 0]
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}, 200)
|
|
})
|
|
}
|
|
|
|
const handleRelocate = () => {
|
|
emit('relocate')
|
|
}
|
|
|
|
const handleScan = () => {
|
|
emit('scan')
|
|
}
|
|
|
|
const handleShowList = () => {
|
|
emit('showList')
|
|
}
|
|
|
|
const handleMarkerTap = (e) => {
|
|
if (!e.detail || typeof e.detail.markerId === 'undefined') {
|
|
return
|
|
}
|
|
|
|
const markerId = e.detail.markerId
|
|
if (markerId === 9999) {
|
|
uni.showToast({
|
|
title: '这是您的位置',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
|
|
const position = props.filteredPositions[markerId]
|
|
if (position) {
|
|
emit('markerTap', position)
|
|
}
|
|
}
|
|
|
|
const handleRegionChange = (e) => {
|
|
if (e.detail.type === 'end') {
|
|
const { center } = e.detail
|
|
|
|
if (!center || typeof center.longitude === 'undefined' || typeof center.latitude === 'undefined') {
|
|
return
|
|
}
|
|
|
|
mapCenter.value = {
|
|
longitude: center.longitude,
|
|
latitude: center.latitude
|
|
}
|
|
|
|
mapZoom.value = 16 // 确保缩放级别保持正确
|
|
|
|
// 清除之前的定时器
|
|
if (loadPositionsTimer.value) {
|
|
clearTimeout(loadPositionsTimer.value)
|
|
}
|
|
|
|
// 设置防抖定时器,500ms后执行
|
|
loadPositionsTimer.value = setTimeout(() => {
|
|
emit('mapCenterChange', mapCenter.value)
|
|
}, 500)
|
|
}
|
|
}
|
|
|
|
// 计算属性 - 地图标记
|
|
const mapMarkers = computed(() => {
|
|
const markers = []
|
|
|
|
// 添加场地标记
|
|
props.filteredPositions.forEach((item, index) => {
|
|
if (item.longitude && item.latitude) {
|
|
markers.push({
|
|
id: index,
|
|
longitude: parseFloat(item.longitude),
|
|
latitude: parseFloat(item.latitude),
|
|
title: item.name,
|
|
iconPath: '/static/scan-icon.png',
|
|
width: 30,
|
|
height: 30,
|
|
callout: {
|
|
content: item.name,
|
|
fontSize: 14,
|
|
borderRadius: 8,
|
|
bgColor: '#ffffff',
|
|
padding: 10,
|
|
display: 'BYCLICK'
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
// 添加用户位置标记
|
|
if (props.userLocation) {
|
|
markers.push({
|
|
id: 9999, // 特殊ID标识用户位置
|
|
longitude: props.userLocation.longitude,
|
|
latitude: props.userLocation.latitude,
|
|
title: '我的位置',
|
|
iconPath: '/static/scan-icon.png',
|
|
width: 32,
|
|
height: 32,
|
|
callout: {
|
|
content: '我的位置',
|
|
fontSize: 14,
|
|
borderRadius: 8,
|
|
bgColor: '#2196F3',
|
|
color: '#ffffff',
|
|
padding: 10,
|
|
display: 'BYCLICK'
|
|
}
|
|
})
|
|
}
|
|
|
|
return markers
|
|
})
|
|
|
|
// 监听用户位置变化,更新地图中心
|
|
watch(() => props.userLocation, (newLocation) => {
|
|
if (newLocation && newLocation.longitude && newLocation.latitude && !isMapInitialized.value) {
|
|
updateMapCenter(newLocation.longitude, newLocation.latitude)
|
|
isMapInitialized.value = true
|
|
}
|
|
}, { immediate: true, deep: true })
|
|
|
|
// 组件挂载时的初始化
|
|
onMounted(() => {
|
|
// 现在组件只在有用户位置时才会渲染,所以不需要默认位置
|
|
})
|
|
|
|
// 组件卸载时清理
|
|
onUnmounted(() => {
|
|
if (loadPositionsTimer.value) {
|
|
clearTimeout(loadPositionsTimer.value)
|
|
}
|
|
})
|
|
|
|
// 暴露给父组件的方法
|
|
defineExpose({
|
|
mapCenter: computed(() => mapCenter.value)
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
/* 地图容器 */
|
|
.map-container {
|
|
flex: 1;
|
|
position: relative;
|
|
height: 100vh;
|
|
width: 100%;
|
|
|
|
// min-height: 400rpx; /* 确保有最小高度 */
|
|
|
|
.map {
|
|
width: 100%;
|
|
height: 100%;
|
|
// min-height: 400rpx; /* 确保地图有最小高度 */
|
|
}
|
|
|
|
.map-loading {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 10;
|
|
|
|
.loading-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
.loading-spinner {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
border: 8rpx solid #f3f3f3;
|
|
border-top: 8rpx solid #3498db;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
text {
|
|
font-size: 32rpx;
|
|
color: #333;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
|
|
.map-controls {
|
|
position: absolute;
|
|
right: 30rpx;
|
|
bottom: 20rpx;
|
|
// margin-bottom: 30rpx;
|
|
left: 30rpx;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 30rpx;
|
|
|
|
.control-btn {
|
|
min-width: 140rpx;
|
|
height: 80rpx;
|
|
background: #ffffff;
|
|
border-radius: 40rpx;
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
|
transition: all 0.2s ease;
|
|
padding: 0 20rpx;
|
|
|
|
&:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.control-icon {
|
|
width: 32rpx;
|
|
height: 32rpx;
|
|
margin-right: 12rpx;
|
|
}
|
|
|
|
text {
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
white-space: nowrap;
|
|
font-weight: 500;
|
|
}
|
|
|
|
&.main-btn {
|
|
min-width: 160rpx;
|
|
height: 90rpx;
|
|
box-shadow: 0 6rpx 20rpx rgba(33, 150, 243, 0.4);
|
|
transform: translateY(-5rpx);
|
|
|
|
.control-icon {
|
|
width: 36rpx;
|
|
height: 36rpx;
|
|
margin-right: 16rpx;
|
|
}
|
|
|
|
text {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
}
|
|
|
|
&:active {
|
|
transform: translateY(-5rpx) scale(0.95);
|
|
}
|
|
}
|
|
}
|
|
|
|
.scan-control {
|
|
background: #2196F3;
|
|
|
|
.control-icon {
|
|
filter: brightness(0) invert(1);
|
|
}
|
|
|
|
text {
|
|
color: #ffffff;
|
|
}
|
|
}
|
|
|
|
.list-control {
|
|
background: #4CAF50;
|
|
|
|
.control-icon {
|
|
filter: brightness(0) invert(1);
|
|
}
|
|
|
|
text {
|
|
color: #ffffff;
|
|
}
|
|
}
|
|
|
|
.location-control {
|
|
background: #ffffff;
|
|
border: 2rpx solid #e0e0e0;
|
|
|
|
.control-icon {
|
|
filter: none;
|
|
}
|
|
|
|
text {
|
|
color: #333;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
</style> |