<template>
|
<view class="departure-selector">
|
<view class="form-item">
|
<view class="form-label" :class="{ required: required }" v-if="showLabel">{{ label }}</view>
|
<view class="departure-input-container">
|
<view class="address-input-wrapper">
|
<input
|
class="form-input departure-input"
|
:placeholder="placeholder"
|
:value="addressValue"
|
@input="onAddressInput"
|
@focus="onAddressFocus"
|
/>
|
<view class="address-suggestions" v-if="showAddressSuggestions && addressSuggestions.length > 0">
|
<view
|
class="address-suggestion-item"
|
v-for="(item, index) in addressSuggestions"
|
:key="index"
|
@click="selectAddressSuggestion(item)"
|
>
|
<view class="suggestion-name">{{ item.name }}</view>
|
<view class="suggestion-address">{{ item.district }}{{ item.address }}</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="current-location-btn" @click="getCurrentLocation">
|
<uni-icons type="location" size="20" color="#007AFF"></uni-icons>
|
|
</view>
|
</view>
|
|
<view class="form-tip" v-if="showTip && addressValue">
|
<text>{{ tipText }}</text>
|
</view>
|
|
<view class="coordinate-info" v-if="showCoordinates && hasCoordinates">
|
<text class="coordinate-label">GPS坐标:</text>
|
<text class="coordinate-value">{{ longitudeValue }}, {{ latitudeValue }}</text>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script>
|
import { baiduPlaceSuggestion, reverseGeocoder } from "@/api/map"
|
|
export default {
|
name: 'DepartureSelector',
|
props: {
|
// 标签文本
|
label: {
|
type: String,
|
default: '出发地'
|
},
|
// 是否显示标签
|
showLabel: {
|
type: Boolean,
|
default: true
|
},
|
// 是否必填
|
required: {
|
type: Boolean,
|
default: false
|
},
|
// 占位符
|
placeholder: {
|
type: String,
|
default: '请输入出发地地址'
|
},
|
// 当前地址
|
address: {
|
type: String,
|
default: ''
|
},
|
// 经度
|
longitude: {
|
type: [Number, String],
|
default: null
|
},
|
// 纬度
|
latitude: {
|
type: [Number, String],
|
default: null
|
},
|
// 当前区域(用于地址搜索)
|
region: {
|
type: String,
|
default: '广州'
|
},
|
// 是否显示提示信息
|
showTip: {
|
type: Boolean,
|
default: true
|
},
|
// 提示文本
|
tipText: {
|
type: String,
|
default: '提示:可修改默认出发地地址'
|
},
|
// 是否显示坐标信息
|
showCoordinates: {
|
type: Boolean,
|
default: false
|
}
|
},
|
data() {
|
return {
|
// 地址建议相关
|
addressSuggestions: [],
|
showAddressSuggestions: false,
|
addressSearchTimer: null
|
}
|
},
|
computed: {
|
addressValue() {
|
return this.address || ''
|
},
|
longitudeValue() {
|
return this.longitude || null
|
},
|
latitudeValue() {
|
return this.latitude || null
|
},
|
hasCoordinates() {
|
return this.longitudeValue !== null && this.latitudeValue !== null
|
}
|
},
|
methods: {
|
// 地址输入
|
onAddressInput(e) {
|
const address = e.detail.value
|
|
// 触发地址更新事件
|
this.$emit('update:address', address)
|
this.$emit('address-change', address)
|
|
// 防抖处理地址搜索
|
if (this.addressSearchTimer) {
|
clearTimeout(this.addressSearchTimer)
|
}
|
|
if (!address || address.trim() === '') {
|
this.addressSuggestions = []
|
this.showAddressSuggestions = false
|
return
|
}
|
|
this.addressSearchTimer = setTimeout(() => {
|
this.searchAddress(address)
|
}, 300)
|
},
|
|
// 搜索地址建议
|
searchAddress(query) {
|
baiduPlaceSuggestion(query, this.region).then(response => {
|
if (response.code === 200 && response.data) {
|
this.addressSuggestions = response.data
|
this.showAddressSuggestions = true
|
} else {
|
this.addressSuggestions = []
|
this.showAddressSuggestions = false
|
}
|
}).catch(error => {
|
console.error('搜索地址失败:', error)
|
this.addressSuggestions = []
|
this.showAddressSuggestions = false
|
})
|
},
|
|
// 地址输入框获得焦点
|
onAddressFocus() {
|
if (this.addressValue && this.addressSuggestions.length > 0) {
|
this.showAddressSuggestions = true
|
}
|
},
|
|
// 选择地址建议
|
selectAddressSuggestion(item) {
|
const fullAddress = item.district + item.address
|
|
// 更新地址
|
this.$emit('update:address', fullAddress)
|
|
// 更新坐标
|
if (item.location) {
|
this.$emit('update:longitude', item.location.lng)
|
this.$emit('update:latitude', item.location.lat)
|
}
|
|
// 触发选择事件
|
this.$emit('address-selected', {
|
address: fullAddress,
|
longitude: item.location ? item.location.lng : null,
|
latitude: item.location ? item.location.lat : null,
|
location: item.location
|
})
|
|
this.showAddressSuggestions = false
|
this.addressSuggestions = []
|
},
|
geocoder(longitude, latitude){
|
// 调用逆地理编码接口,将坐标转换为地址
|
reverseGeocoder(latitude, longitude)
|
.then(response => {
|
uni.hideLoading()
|
|
console.log('逆地理编码API完整响应:', JSON.stringify(response))
|
|
if (response.code === 200 && response.data) {
|
// 解析后端返回的数据(可能是字符串,需要parse)
|
let responseData = response.data
|
if (typeof responseData === 'string') {
|
try {
|
responseData = JSON.parse(responseData)
|
} catch (e) {
|
console.error('解析响应数据失败:', e)
|
responseData = {}
|
}
|
}
|
|
console.log('解析后的responseData:', responseData)
|
|
// 腾讯地图API返回格式: {status: 0, result: {address: "..."}}
|
let address = ''
|
if (responseData.status === 0 && responseData.result) {
|
address = responseData.result.address || responseData.result.formatted_addresses?.recommend || ''
|
} else if (responseData.address) {
|
// 兼容其他可能的返回格式
|
address = responseData.address
|
} else if (responseData.formattedAddress) {
|
address = responseData.formattedAddress
|
}
|
|
console.log('解析出的地址:', address)
|
|
if (address) {
|
// 更新地址
|
this.$emit('update:address', address)
|
|
// 触发位置获取成功事件
|
this.$emit('location-success', {
|
address: address,
|
longitude: longitude,
|
latitude: latitude
|
})
|
|
console.log('逆地理编码成功,地址已更新:', address)
|
this.$modal.showToast('已获取当前位置')
|
} else {
|
console.error('未能从响应中提取地址,responseData:', responseData)
|
|
// 即使地址解析失败,坐标已保存,触发事件
|
this.$emit('location-success', {
|
address: '',
|
longitude: longitude,
|
latitude: latitude
|
})
|
|
this.$modal.showToast('位置解析失败,请手动输入地址')
|
}
|
} else {
|
console.error('逆地理编码失败,response.code:', response.code, 'msg:', response.msg)
|
|
// 即使地址解析失败,坐标已保存,触发事件
|
this.$emit('location-success', {
|
address: '',
|
longitude: longitude,
|
latitude: latitude
|
})
|
|
this.$modal.showToast('位置解析失败,请手动输入地址')
|
}
|
})
|
.catch(error => {
|
uni.hideLoading()
|
console.error('逆地理编码失败:', error)
|
|
// 即使地址解析失败,坐标已保存,触发事件
|
this.$emit('location-success', {
|
address: '',
|
longitude: longitude,
|
latitude: latitude
|
})
|
|
this.$modal.showToast('位置解析失败,但GPS坐标已保存')
|
})
|
},
|
// 获取当前位置
|
getCurrentLocation() {
|
uni.showLoading({
|
title: '获取位置中...'
|
})
|
|
// 使用uni-app的GPS定位功能
|
uni.getLocation({
|
type: 'gcj02', // 返回国测局坐标,适用于国内地图
|
success: (res) => {
|
console.log('获取到GPS坐标:', res)
|
const latitude = res.latitude
|
const longitude = res.longitude
|
|
// 更新GPS坐标
|
this.$emit('update:longitude', longitude)
|
this.$emit('update:latitude', latitude)
|
this.geocoder(longitude, latitude)
|
|
},
|
fail: (err) => {
|
uni.hideLoading()
|
console.error('获取位置失败:', err)
|
//我们使用默认的坐标来处理
|
//23.20593,113.228998
|
const longitude = 113.228998
|
const latitude = 23.20593
|
this.$emit('location-success', {
|
address: '',
|
longitude: longitude,
|
latitude: latitude
|
})
|
this.geocoder(longitude, latitude)
|
|
// 提示用户可能的原因
|
let errorMsg = '获取位置失败'
|
if (err.errMsg && err.errMsg.includes('auth deny')) {
|
errorMsg = '请在设置中开启位置权限'
|
} else if (err.errMsg && err.errMsg.includes('timeout')) {
|
errorMsg = '定位超时,请稍后重试'
|
}
|
|
// 触发失败事件
|
this.$emit('location-error', err)
|
|
this.$modal.showToast(errorMsg)
|
}
|
})
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.departure-selector {
|
.form-item {
|
margin-bottom: 40rpx;
|
|
.form-label {
|
font-size: 28rpx;
|
margin-bottom: 15rpx;
|
color: #333;
|
|
&.required::before {
|
content: '*';
|
color: #ff0000;
|
margin-right: 5rpx;
|
}
|
}
|
|
.departure-input-container {
|
display: flex;
|
align-items: flex-start;
|
gap: 15rpx;
|
|
.address-input-wrapper {
|
flex: 1;
|
position: relative;
|
|
.departure-input {
|
width: 100%;
|
height: 70rpx;
|
padding: 0 20rpx;
|
border: 1rpx solid #eee;
|
border-radius: 10rpx;
|
font-size: 28rpx;
|
box-sizing: border-box;
|
}
|
|
.address-suggestions {
|
position: absolute;
|
top: 75rpx;
|
left: 0;
|
right: 0;
|
max-height: 400rpx;
|
overflow-y: auto;
|
background-color: white;
|
border: 1rpx solid #eee;
|
border-radius: 10rpx;
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
z-index: 100;
|
|
.address-suggestion-item {
|
padding: 20rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
|
&:last-child {
|
border-bottom: none;
|
}
|
|
&:active {
|
background-color: #f5f5f5;
|
}
|
|
.suggestion-name {
|
font-size: 28rpx;
|
color: #333;
|
margin-bottom: 8rpx;
|
}
|
|
.suggestion-address {
|
font-size: 24rpx;
|
color: #999;
|
}
|
}
|
}
|
}
|
|
.current-location-btn {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
padding: 10rpx 20rpx;
|
background-color: #f0f7ff;
|
border-radius: 10rpx;
|
white-space: nowrap;
|
min-height: 40rpx;
|
|
&:active {
|
background-color: #e0f0ff;
|
}
|
|
text {
|
font-size: 22rpx;
|
color: #007AFF;
|
margin-top: 4rpx;
|
}
|
}
|
}
|
|
.form-tip {
|
margin-top: 10rpx;
|
font-size: 24rpx;
|
color: #999;
|
line-height: 1.5;
|
}
|
|
.coordinate-info {
|
margin-top: 10rpx;
|
padding: 10rpx 15rpx;
|
background-color: #f0f7ff;
|
border-radius: 8rpx;
|
display: flex;
|
align-items: center;
|
|
.coordinate-label {
|
font-size: 24rpx;
|
color: #666;
|
margin-right: 10rpx;
|
}
|
|
.coordinate-value {
|
font-size: 24rpx;
|
color: #007AFF;
|
font-family: monospace;
|
}
|
}
|
}
|
}
|
</style>
|