<template>
|
<view class="map-selector-container">
|
<view class="search-bar">
|
<input
|
class="search-input"
|
placeholder="请输入地址"
|
v-model="searchKeyword"
|
@focus="onSearchFocus"
|
/>
|
<button class="search-btn" @click="searchAddress">搜索</button>
|
<button class="location-btn" @click="getCurrentLocation" :disabled="isGettingLocation">
|
<uni-icons v-if="!isGettingLocation" type="location" size="18" color="white"></uni-icons>
|
<uni-icons v-else type="spinner-cycle" size="18" color="white"></uni-icons>
|
</button>
|
</view>
|
|
<!-- 选中地址界面 - 显示在列表上方 -->
|
<view class="selected-address" v-if="selectedAddress">
|
<view class="selected-header">
|
<uni-icons type="checkmarkempty" size="20" color="#007AFF"></uni-icons>
|
<text class="address-title">已选中地址</text>
|
<view class="cancel-btn" @click="cancelSelection">
|
<uni-icons type="closeempty" size="16" color="#999"></uni-icons>
|
</view>
|
</view>
|
<view class="address-info">
|
<view class="address-name">{{ selectedAddress.title }}</view>
|
<view class="address-detail">{{ selectedAddress.address }}</view>
|
</view>
|
<button class="confirm-btn" @click="confirmAddress">
|
<uni-icons type="checkmarkempty" size="18" color="white"></uni-icons>
|
<text>确认选择此地址</text>
|
</button>
|
</view>
|
|
<!-- 地址搜索结果列表 -->
|
<view class="address-list" v-if="showSearchResults && searchResults.length > 0">
|
<view
|
class="address-item"
|
v-for="(item, index) in searchResults"
|
:key="index"
|
@click="selectAddress(item)"
|
>
|
<!-- 确保使用插值语法 -->
|
<view class="address-title">{{ item.title }}</view>
|
<view class="address-detail">{{ item.address }}</view>
|
</view>
|
</view>
|
|
<!-- 搜索无结果提示 -->
|
<view class="no-results" v-if="showSearchResults && searchResults.length === 0 && searchCompleted">
|
<text>未找到相关地址</text>
|
</view>
|
|
<!-- UniApp原生地图组件,支持多平台 -->
|
<view class="map-container" id="mapContainer">
|
<!-- 所有平台统一使用uniapp原生map组件 -->
|
<map
|
id="map"
|
:longitude="longitude"
|
:latitude="latitude"
|
:markers="markers"
|
:polyline="polyline"
|
:circles="circles"
|
:controls="controls"
|
:include-points="includePoints"
|
:show-location="true"
|
:enable-3D="false"
|
:show-compass="false"
|
:enable-overlooking="false"
|
:enable-zoom="true"
|
:enable-scroll="true"
|
:enable-rotate="false"
|
:enable-satellite="false"
|
:enable-traffic="false"
|
@markertap="onMarkerTap"
|
@callouttap="onCalloutTap"
|
@controltap="onControlTap"
|
@regionchange="onRegionChange"
|
@tap="onMapTap"
|
class="map-webview"
|
></map>
|
|
<view class="map-placeholder" v-if="!isMapLoaded">
|
<text>地图加载中...</text>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script>
|
import { searchAddress, reverseGeocoder } from '@/api/map'
|
|
export default {
|
name: 'MapSelector',
|
props: {
|
// 初始地址
|
initialAddress: {
|
type: String,
|
default: ''
|
}
|
},
|
data() {
|
return {
|
searchKeyword: '',
|
searchResults: [], // 确保这是响应式数据
|
selectedAddress: null,
|
isMapLoaded: false,
|
showSearchResults: false, // 控制是否显示搜索结果
|
searchCompleted: false, // 标记搜索是否完成
|
showDebug: true, // 调试开关
|
// 地图相关
|
longitude: 113.324520,
|
latitude: 23.099994,
|
markers: [],
|
polyline: [],
|
circles: [],
|
controls: [],
|
includePoints: [],
|
isGettingLocation: false, // 是否正在获取位置
|
// 防止自动聚焦的标志
|
shouldPreventAutoFocus: true
|
}
|
},
|
mounted() {
|
console.log('地图选择器组件已挂载');
|
|
// 检查API Key是否已配置
|
if (!this.isTencentMapKeyConfigured()) {
|
console.error('[system] Map key not configured.')
|
this.$modal.showToast('地图API Key未配置,请联系管理员')
|
return
|
}
|
|
// 初始化地图
|
this.initMap()
|
|
// 延迟处理初始地址,避免跨域问题
|
if (this.initialAddress) {
|
this.searchKeyword = this.initialAddress
|
// 不再尝试自动聚焦,避免跨域问题
|
// 用户需要手动点击输入框才能聚焦
|
}
|
|
// 延迟一段时间后允许自动操作(如果需要的话)
|
setTimeout(() => {
|
this.shouldPreventAutoFocus = false
|
}, 1000)
|
},
|
methods: {
|
// 检查腾讯地图API Key是否已配置
|
isTencentMapKeyConfigured() {
|
// 对于后端代理方式,前端不再需要检查Key配置
|
return true
|
},
|
|
// 搜索框获得焦点事件
|
onSearchFocus() {
|
// 用户主动聚焦,不会触发跨域问题
|
console.log('搜索框获得焦点')
|
// 重置防聚焦标志
|
this.shouldPreventAutoFocus = false
|
},
|
|
// 获取当前位置
|
getCurrentLocation() {
|
if (this.isGettingLocation) {
|
return
|
}
|
|
this.isGettingLocation = true
|
this.$modal.showToast('正在获取当前位置...')
|
|
uni.getLocation({
|
type: 'gcj02',
|
success: (res) => {
|
console.log('获取位置成功:', res)
|
this.handleLocationSuccess(res.longitude, res.latitude)
|
this.isGettingLocation = false
|
},
|
fail: (error) => {
|
console.error('获取位置失败:', error)
|
this.$modal.showToast('获取位置失败,请检查定位权限')
|
this.isGettingLocation = false
|
}
|
})
|
},
|
|
// 处理位置获取成功
|
handleLocationSuccess(longitude, latitude) {
|
// 参数验证
|
if (longitude === undefined || longitude === null || latitude === undefined || latitude === null) {
|
console.error('位置获取失败,参数不完整', { longitude, latitude });
|
this.$modal.showToast('位置获取失败');
|
return;
|
}
|
|
// 检查参数有效性
|
if (isNaN(longitude) || isNaN(latitude)) {
|
console.error('位置获取失败,坐标包含非法值', { longitude, latitude });
|
this.$modal.showToast('位置获取失败,坐标无效');
|
return;
|
}
|
|
this.longitude = longitude
|
this.latitude = latitude
|
|
// 设置标记
|
this.markers = [{
|
id: Date.now(),
|
longitude: longitude,
|
latitude: latitude,
|
title: '当前位置',
|
iconPath: '/static/icons/location.png',
|
width: 30,
|
height: 30
|
}]
|
|
// 逆地址解析
|
this.reverseGeocode(latitude, longitude, true)
|
|
this.$modal.showToast('已定位到当前位置')
|
this.isMapLoaded = true
|
},
|
|
// 初始化地图
|
initMap() {
|
// 获取用户位置
|
uni.getLocation({
|
type: 'gcj02',
|
success: (res) => {
|
// 参数验证
|
if (res.longitude === undefined || res.longitude === null ||
|
res.latitude === undefined || res.latitude === null) {
|
console.error('初始化地图时位置获取失败,参数不完整', res);
|
this.$modal.showToast('位置获取失败');
|
this.isMapLoaded = true;
|
return;
|
}
|
|
// 检查参数有效性
|
if (isNaN(res.longitude) || isNaN(res.latitude)) {
|
console.error('初始化地图时位置获取失败,坐标包含非法值', res);
|
this.$modal.showToast('位置获取失败,坐标无效');
|
this.isMapLoaded = true;
|
return;
|
}
|
|
this.longitude = res.longitude
|
this.latitude = res.latitude
|
|
// 设置默认标记
|
this.markers = [{
|
id: 0,
|
longitude: this.longitude,
|
latitude: this.latitude,
|
title: '当前位置',
|
iconPath: '/static/icons/location.png',
|
width: 30,
|
height: 30
|
}]
|
|
// 设置加载状态
|
this.isMapLoaded = true
|
},
|
fail: () => {
|
// 设置加载状态
|
this.isMapLoaded = true
|
this.$modal.showToast('获取位置失败')
|
}
|
})
|
},
|
|
// 地图点击事件
|
onMapTap(e) {
|
// 在点击位置添加标记
|
const { longitude, latitude } = e.detail
|
|
// 参数验证
|
if (longitude === undefined || longitude === null || latitude === undefined || latitude === null) {
|
console.error('地图点击事件参数不完整', { longitude, latitude });
|
this.$modal.showToast('位置信息获取失败');
|
return;
|
}
|
|
this.markers = [{
|
id: Date.now(),
|
longitude,
|
latitude,
|
title: '选中位置',
|
iconPath: '/static/icons/location-selected.png',
|
width: 30,
|
height: 30
|
}]
|
|
// 逆地址解析获取地址信息
|
this.reverseGeocode(latitude, longitude)
|
},
|
|
// 逆地址解析
|
reverseGeocode(lat, lng, isCurrentLocation = false) {
|
// 参数验证
|
if (lat === undefined || lat === null || lng === undefined || lng === null) {
|
console.error('逆地址解析参数不完整,缺少经纬度坐标', { lat, lng });
|
this.$modal.showToast('参数错误:缺少经纬度坐标');
|
return;
|
}
|
|
// 检查参数有效性
|
if (isNaN(lat) || isNaN(lng)) {
|
console.error('逆地址解析参数无效,坐标包含非法值', { lat, lng });
|
this.$modal.showToast('参数错误:坐标格式无效');
|
return;
|
}
|
|
// 使用后端代理接口进行逆地址解析
|
reverseGeocoder(lat, lng).then(res => {
|
if (res.code === 200) {
|
// 解析后端返回的腾讯地图API响应
|
const responseData = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
|
if (responseData && responseData.status === 0 && responseData.result) {
|
const result = responseData.result
|
this.selectedAddress = {
|
title: isCurrentLocation ? '当前位置' : (result.address || '选中位置'),
|
address: result.address || `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
lat: lat,
|
lng: lng
|
}
|
} else {
|
this.selectedAddress = {
|
title: isCurrentLocation ? '当前位置' : '选中位置',
|
address: `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
lat: lat,
|
lng: lng
|
}
|
}
|
} else {
|
this.selectedAddress = {
|
title: isCurrentLocation ? '当前位置' : '选中位置',
|
address: `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
lat: lat,
|
lng: lng
|
}
|
}
|
}).catch(error => {
|
console.error('逆地址解析失败:', error)
|
this.$modal.showToast('地址解析失败:' + (error.message || '请稍后重试'))
|
this.selectedAddress = {
|
title: isCurrentLocation ? '当前位置' : '选中位置',
|
address: `经纬度: ${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
lat: lat,
|
lng: lng
|
}
|
})
|
},
|
|
// 搜索地址 - 只在点击搜索按钮时调用
|
searchAddress() {
|
console.log('执行搜索,关键词:', this.searchKeyword);
|
if (!this.searchKeyword) {
|
this.$modal.showToast('请输入地址')
|
return
|
}
|
|
// 使用后端代理接口进行地址搜索
|
this.searchAddressViaBackend()
|
},
|
|
// 通过后端代理搜索地址
|
searchAddressViaBackend() {
|
// 显示搜索结果区域
|
this.showSearchResults = true;
|
this.searchCompleted = false;
|
|
console.log('开始搜索地址:', this.searchKeyword);
|
|
searchAddress(this.searchKeyword, '广州').then(res => {
|
console.log('地址搜索返回结果:', res);
|
// 确保正确更新搜索结果
|
this.searchResults = [];
|
|
if (res.code === 200) {
|
// 解析后端返回的腾讯地图API响应
|
const responseData = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
|
console.log('解析后的响应数据:', responseData);
|
|
if (responseData && responseData.status === 0 && responseData.data && responseData.data.length > 0) {
|
// 直接赋值数组,确保响应式更新
|
this.searchResults = responseData.data.map(item => ({
|
title: item.title,
|
address: item.address,
|
lat: item.location.lat,
|
lng: item.location.lng
|
}));
|
console.log('处理后的搜索结果:', this.searchResults);
|
|
// 添加调试信息
|
console.log('搜索结果数量:', this.searchResults.length);
|
console.log('showSearchResults:', this.showSearchResults);
|
console.log('v-if条件结果:', this.showSearchResults && this.searchResults.length > 0);
|
} else {
|
console.log('未找到相关地址');
|
// 即使没有数据也要显示"未找到相关地址"
|
this.searchCompleted = true;
|
}
|
} else {
|
console.log('搜索失败,错误代码:', res.code);
|
this.$modal.showToast('搜索失败,请重试')
|
}
|
|
// 标记搜索完成
|
this.searchCompleted = true;
|
}).catch(error => {
|
console.error('搜索失败:', error)
|
this.$modal.showToast('搜索失败,请重试')
|
// 标记搜索完成
|
this.searchCompleted = true;
|
})
|
},
|
|
// 选择地址
|
selectAddress(item) {
|
this.selectedAddress = item
|
this.markLocation(item.lat, item.lng)
|
|
// 选中地址后,保持搜索结果列表显示
|
console.log('地址已选中:', item);
|
},
|
|
// 在地图上标记位置
|
markLocation(lat, lng) {
|
this.longitude = lng
|
this.latitude = lat
|
this.markers = [{
|
id: Date.now(),
|
longitude: lng,
|
latitude: lat,
|
title: this.selectedAddress?.title || '选中位置',
|
iconPath: '/static/icons/location-selected.png',
|
width: 30,
|
height: 30
|
}]
|
},
|
|
// 确认选择地址
|
confirmAddress() {
|
if (!this.selectedAddress) {
|
this.$modal.showToast('请先选择地址')
|
return
|
}
|
|
// 显示确认提示
|
this.$modal.showToast('地址选择成功')
|
|
// 触发事件,将选中的地址传递给父组件
|
this.$emit('addressSelected', {
|
title: this.selectedAddress.title,
|
address: this.selectedAddress.address,
|
lat: this.selectedAddress.lat,
|
lng: this.selectedAddress.lng
|
})
|
|
// 确认后隐藏搜索结果和选中地址界面
|
setTimeout(() => {
|
this.selectedAddress = null
|
this.showSearchResults = false
|
this.searchResults = []
|
}, 1000)
|
},
|
|
// 取消选择地址
|
cancelSelection() {
|
this.selectedAddress = null
|
// 只清空选中地址,不清空搜索结果
|
// this.showSearchResults = false
|
// this.searchResults = []
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss">
|
.map-selector-container {
|
width: 100%;
|
height: 100vh;
|
display: flex;
|
flex-direction: column;
|
|
.search-bar {
|
display: flex;
|
padding: 15rpx 20rpx;
|
background-color: white;
|
flex-shrink: 0;
|
position: relative;
|
z-index: 100; /* 提高层级 */
|
|
.search-input {
|
flex: 1;
|
height: 70rpx;
|
padding: 0 20rpx;
|
border: 1rpx solid #eee;
|
border-radius: 10rpx;
|
font-size: 28rpx;
|
}
|
|
.search-btn {
|
width: 120rpx;
|
height: 70rpx;
|
margin-left: 20rpx;
|
background-color: #007AFF;
|
color: white;
|
border-radius: 10rpx;
|
font-size: 28rpx;
|
}
|
|
.location-btn {
|
width: 70rpx;
|
height: 70rpx;
|
margin-left: 10rpx;
|
background-color: #1AAD19;
|
color: white;
|
border-radius: 10rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
|
&:active {
|
background-color: #179B16;
|
}
|
|
&[disabled] {
|
background-color: #ccc;
|
opacity: 0.6;
|
}
|
}
|
}
|
|
.debug-panel {
|
padding: 15rpx 20rpx;
|
background-color: #fff3cd;
|
border: 1rpx solid #ffeaa7;
|
z-index: 100; /* 提高层级 */
|
position: relative;
|
|
.debug-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 10rpx;
|
|
.debug-title {
|
font-weight: bold;
|
color: #856404;
|
}
|
|
.debug-toggle {
|
font-size: 24rpx;
|
padding: 5rpx 10rpx;
|
background-color: #ffc107;
|
border: none;
|
border-radius: 5rpx;
|
}
|
}
|
|
.debug-content {
|
font-size: 24rpx;
|
color: #856404;
|
|
text {
|
display: block;
|
margin-bottom: 5rpx;
|
word-break: break-all;
|
}
|
}
|
}
|
|
/* 地址搜索结果列表 - 调整位置 */
|
.address-list {
|
max-height: 40vh;
|
min-height: 200rpx;
|
overflow-y: auto;
|
background-color: white;
|
position: absolute;
|
top: 300rpx; /* 在选中地址界面下方显示 */
|
left: 0;
|
right: 0;
|
z-index: 90; /* 确保在地图上方 */
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
|
|
.address-item {
|
padding: 25rpx 30rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
min-height: 100rpx;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
|
.address-title {
|
font-size: 32rpx;
|
font-weight: bold;
|
margin-bottom: 8rpx;
|
color: #333;
|
line-height: 1.3;
|
}
|
|
.address-detail {
|
font-size: 28rpx;
|
color: #666;
|
line-height: 1.4;
|
}
|
}
|
}
|
|
/* 无结果提示 - 调整位置 */
|
.no-results {
|
padding: 30rpx;
|
text-align: center;
|
background-color: white;
|
color: #999;
|
font-size: 28rpx;
|
position: absolute;
|
top: 150rpx; /* 调整位置 */
|
left: 0;
|
right: 0;
|
z-index: 90; /* 确保在地图上方 */
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
|
min-height: 200rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.map-container {
|
flex: 1;
|
min-height: 50vh;
|
max-height: 70vh;
|
position: relative;
|
|
.map-webview {
|
width: 100%;
|
height: 100%;
|
}
|
|
// 地图样式
|
map {
|
width: 100%;
|
height: 100%;
|
}
|
|
.map-placeholder {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
height: 100%;
|
background-color: #f5f5f5;
|
font-size: 28rpx;
|
color: #999;
|
}
|
}
|
|
.selected-address {
|
padding: 20rpx 30rpx;
|
background-color: white;
|
border-top: 1rpx solid #f0f0f0;
|
flex-shrink: 0;
|
position: absolute;
|
top: 150rpx; /* 在搜索栏下方显示 */
|
left: 0;
|
right: 0;
|
z-index: 95; /* 确保在地图上方 */
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
|
|
.selected-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 15rpx;
|
|
.address-title {
|
font-size: 28rpx;
|
font-weight: bold;
|
color: #007AFF;
|
margin-left: 10rpx;
|
}
|
|
.cancel-btn {
|
width: 40rpx;
|
height: 40rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 50%;
|
background-color: #f5f5f5;
|
|
&:active {
|
background-color: #e0e0e0;
|
}
|
}
|
}
|
|
.address-info {
|
background-color: #f8f9fa;
|
padding: 25rpx;
|
border-radius: 12rpx;
|
margin-bottom: 20rpx;
|
|
.address-name {
|
font-size: 32rpx;
|
font-weight: bold;
|
color: #333;
|
margin-bottom: 10rpx;
|
line-height: 1.3;
|
}
|
|
.address-detail {
|
font-size: 28rpx;
|
color: #666;
|
line-height: 1.5;
|
}
|
}
|
|
.confirm-btn {
|
width: 100%;
|
height: 80rpx;
|
background-color: #007AFF;
|
color: white;
|
border-radius: 10rpx;
|
font-size: 32rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
|
text {
|
margin-left: 10rpx;
|
}
|
|
&:active {
|
background-color: #0056CC;
|
}
|
}
|
}
|
}
|
</style>
|