mrr.sj.front/pages/address/search.vue

2292 lines
83 KiB
Vue
Raw Normal View History

<template>
<view class="search-address-page">
<!-- 自定义导航栏 -->
<custom-navbar title="搜索地址" :showBack="true" backgroundColor="#FFFFFF"></custom-navbar>
<!-- 城市选择-->
<view class="search-container" v-if="isSelectCity">
<text class="search-container-text" @click="goSelectCity">{{ city.name }}</text>
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/Fill.jpg" mode="aspectFit" class="text-img"></image>
</view>
<!-- 地图容器 -->
<view class="map-container" :style="{ height: mapHeight + 'rpx' }">
<map
v-if="mapVisible"
:key="mapRenderKey"
id="map"
class="map"
:latitude="latitude"
:longitude="longitude"
scale="19"
@tap="onMapTap"
@regionchange="changeMap"
></map>
<image
v-if="showMapSnapshot"
class="map-snapshot"
:src="mapSnapshotUrl"
mode="aspectFill"
@load="onSnapshotLoad"
@error="onSnapshotError"
></image>
<cover-view v-if="mapVisible" class="center-marker">
<cover-image
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/04007cab-3047-42b0-baab-44df344a92d2"
class="center-marker-img"
></cover-image>
</cover-view>
</view>
<!-- 底部编辑区域 -->
<view class="bottom-edit-section">
<!-- 地址识别输入行 -->
<view class="address-input-row">
<textarea class="raw-address-input" v-model="rawAddress" placeholder="粘贴文本到此处,将自动识别地址信息。" placeholder-style="color: #C6C9CC; font-size: 14px;" :maxlength="200" :adjust-position="true" />
<view class="action-buttons">
<view class="action-btn paste-btn" @click="hasRawContent ? clearRawAddress() : onPaste()">
<text class="btn-text" :style="{ color: '#333333' }">{{ hasRawContent ? '一键清空' : '粘贴' }}</text>
</view>
<view class="action-btn smart-btn" :class="{ 'smart-btn-active': hasRawContent }" @click="onSmartRecognize">
<text class="btn-text" :style="{ color: hasRawContent ? '#E8101E' : '#999999' }">{{ smartBtnText }}</text>
</view>
</view>
</view>
<!-- 地址信息栏 -->
<view class="address-info-panel">
<view class="info-row" @click="selectRegion">
<text class="row-label">所在地区</text>
<view class="row-content">
<text :class="currentRegion ? 'content-text' : 'placeholder'">{{ currentRegion || "请选择"}}</text>
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/71c63001-a104-49ad-a90d-30d2ade73f4d.png" mode="aspectFit" class="arrow-icon"></image>
</view>
</view>
<view class="line"></view>
<view class="info-row">
<text class="row-label">详细地址</text>
<input type="text" class="detail-input" v-model="detailAddress" @input="onDetailInput" placeholder="请填写详细地址" placeholder-style="color: #999999; font-size: 14px;" :adjust-position="true" />
</view>
<view class="line"></view>
</view>
<!-- 确认按钮 -->
<view class="confirm-btn" @click="onConfirm"><text class="con-btn-text">确认</text></view>
</view>
<!-- 地区选择弹窗用户端 -->
<view class="popup" :class="{ show: showRegionPicker }">
<view class="popup-mask" @click="closeRegionPicker"></view>
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">选择所在地区</text>
<text class="popup-close" @click="closeRegionPicker">×</text>
</view>
<view class="region-body">
<picker-view
@change="onRegionChange"
class="picker-scroll"
:indicator-style="indicatorStyle"
:value="regionPickerValue"
>
<picker-view-column>
<view
class="picker-item"
v-for="(item, index) in regionProvinceList"
:key="index"
>{{ item.province }}</view>
</picker-view-column>
<picker-view-column>
<view
class="picker-item"
v-for="(item, index) in regionCityList"
:key="index"
>{{ item.city }}</view>
</picker-view-column>
<picker-view-column>
<view
class="picker-item"
v-for="(item, index) in regionDistrictList"
:key="index"
>{{ item.area }}</view>
</picker-view-column>
</picker-view>
</view>
<view class="popup-footer">
<view class="popup-btn cancel" @click="closeRegionPicker">
<text class="btn-text">取消</text>
</view>
<view class="popup-btn confirm" @click="confirmRegion">
<text class="btn-text">确定</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import request from '@/utils/request'
import ChinaCitys from './data/ChinaCitys.json'
import store from '@/store'
import { setVersion } from '@/utils/version.js'
export default {
data() {
return {
mapVisible: true,
showMapSnapshot: false,
mapSnapshotUrl: '',
mapRenderKey: 0,
mapMoveTimer: null,
mapProgrammaticUntil: 0,
mapGuardTimer: null,
mapReverseTimer: null,
popupRestoreTimer: null,
reverseGeocodeRequestId: 0,
dependencyInitLock: false,
dependencyInitTimer: null,
blankPageInitLock: false,
// 地图与定位相关
systemInfo: {},
latitude: 0,
longitude: 0,
newLatitude: 0,
newLongitude: 0,
mapHeight: 670,
city: {
center: '',
name: ''
},
isSelectCity: false,
searchCity: uni.getStorageSync('searchCity'),
source: '',
recognizeMapMoving: false,
recognizeMapMoveTimer: null,
// 编辑区数据
manualDetail: '',
isManualDetail: false,
currentProvince: '',
currentCity: '',
currentDistrict: '',
currentDetail: '',
selectedProvinceCode: '',
selectedCityCode: '',
selectedDistrictCode: '',
rawAddress: '',
hasRawContent: false,
smartBtnText: '智能识别',
hasDetailContent: false,
currentRegion: '',
detailAddress: '',
// 首次进入且上页没有详细地址时,禁止自动回填详细地址
suppressInitDetailFill: false,
ignoreRegionChangeOnce: false, // 忽略一次程序触发的地图变化
// 地区选择弹窗数据
showRegionPicker: false,
regionProvinceList: [],
regionCityList: [],
regionDistrictList: [],
regionPickerValue: [0, 0, 0],
indicatorStyle: 'height: 68rpx;',
userSelectedRegion: false,
// 高德地图 key
amapKey: '30b7eb1a1b2f88edc085b9b3ee9a2188'
}
},
watch: {
rawAddress(newVal) {
this.hasRawContent = (newVal || '').trim().length > 0
this.smartBtnText = '智能识别'
},
detailAddress(newVal) {
this.hasDetailContent = (newVal || '').trim().length > 0
}
},
onLoad(options) {
console.log('ChinaCitys 数据:', ChinaCitys)
console.log('ChinaCitys 长度:', ChinaCitys?.length)
this.systemInfo = uni.getSystemInfoSync()
this.source = options.source || ''
this.mapHeight = 670
const incomingAddress = options.address ? decodeURIComponent(options.address) : ''
const hasDependency = !!options.dependency
const hasLocation = !!(options.longitude && options.latitude)
const skipAutoLocate = String(options.skip_auto_locate || '') === '1'
const initCurrentLocation = String(options.init_current_location || '') === '1'
const silentLocate = String(options.silent_locate || '') === '1'
// 1. 有传入地区:优先按传入地区初始化,禁止首屏地图自动反写
if (hasDependency) {
const regions = decodeURIComponent(options.dependency).split('-')
console.log('传入地区信息:', regions)
this.currentProvince = regions[0] || ''
this.currentCity = regions[1] || ''
this.currentDistrict = regions[2] || ''
let regionText = ''
if (this.currentProvince) regionText += this.currentProvince
if (this.currentCity && this.currentCity !== this.currentProvince) regionText += this.currentCity
if (this.currentDistrict) regionText += this.currentDistrict
this.currentRegion = regionText
if (incomingAddress) {
this.detailAddress = incomingAddress
this.manualDetail = incomingAddress
this.currentDetail = incomingAddress
this.isManualDetail = true
this.suppressInitDetailFill = false
} else {
this.detailAddress = ''
this.manualDetail = ''
this.currentDetail = ''
this.isManualDetail = false
this.suppressInitDetailFill = true
}
// 关键:锁住首次地图初始化阶段,避免首屏被旧地图中心点反写
this.startDependencyInitLock()
// 有经纬度:只用于地图定位,不再 reverseGeocode 覆盖所在地区
if (hasLocation) {
this.applyMapLocation(options.longitude, options.latitude, true)
return
}
// 没经纬度:用地区中心定位地图
const dependencyCode = options.dependency_code
;(async () => {
let center = null
if (dependencyCode && regions[2]) {
center = await this.getDistrictCenter(regions[2], dependencyCode)
}
if (!center && regions[2]) {
center = await this.getDistrictCenter(regions[2])
}
if (!center && regions[1] && regions[1] !== regions[0]) {
center = await this.getDistrictCenter(regions[1])
}
if (!center && regions[0]) {
center = await this.getDistrictCenter(regions[0])
}
if (center) {
const locationRes = center.split(',')
if (locationRes.length >= 2) {
this.updateMapCenter(
parseFloat(locationRes[0]),
parseFloat(locationRes[1]),
true
)
}
} else if (!skipAutoLocate) {
// 走当前位置时,不再锁
this.clearDependencyInitLock()
this.getCurrentLocation()
} else {
this.clearDependencyInitLock()
}
})()
return
}
// 2. 只有经纬度,没有地区
if (hasLocation) {
this.applyMapLocation(options.longitude, options.latitude, true)
if (incomingAddress) {
this.detailAddress = incomingAddress
this.manualDetail = incomingAddress
this.currentDetail = incomingAddress
this.isManualDetail = true
this.suppressInitDetailFill = false
} else {
this.detailAddress = ''
this.manualDetail = ''
this.currentDetail = ''
this.isManualDetail = false
this.suppressInitDetailFill = true
}
this.reverseGeocode(parseFloat(options.longitude), parseFloat(options.latitude))
return
}
// 3. 从城市选择页进入
if (options.city) {
const city = JSON.parse(decodeURIComponent(options.city))
this.city = city
this.ignoreRegionChangeOnce = true
this.getNowLocation(city.center)
this.isSelectCity = true
return
}
// 4. 仅标记城市选择模式
if (options.isSelectCity) {
this.isSelectCity = true
this.getCurrentLocation()
return
}
// 5. 手艺人信息页首次进入且详细地址为空:按当前定位初始化
if (initCurrentLocation) {
this.blankPageInitLock = false
this.getCurrentLocation(silentLocate, true)
return
}
// 6. 空白进入且明确要求不自动定位:
// 不仅不调用当前位置还要锁住首屏地图默认中心点触发的regionchange反写
if (skipAutoLocate && !hasDependency && !hasLocation) {
this.blankPageInitLock = true
this.currentProvince = ''
this.currentCity = ''
this.currentDistrict = ''
this.currentRegion = ''
this.detailAddress = ''
this.manualDetail = ''
this.currentDetail = ''
this.isManualDetail = false
this.suppressInitDetailFill = true
return
}
// 7. 默认当前位置
if (!skipAutoLocate) {
this.blankPageInitLock = false
this.getCurrentLocation(silentLocate, false)
}
},
onShow() {
// 只有城市选择模式下,才允许 searchCity 缓存更新地图
if (!this.isSelectCity) return
const searchCity = uni.getStorageSync('searchCity')
if (searchCity && this.city.name !== searchCity.name) {
this.city = searchCity
this.searchCity = searchCity
this.ignoreRegionChangeOnce = true
this.getNowLocation(searchCity.center)
}
},
methods: {
startDependencyInitLock(duration = 800) {
this.dependencyInitLock = true
if (this.dependencyInitTimer) {
clearTimeout(this.dependencyInitTimer)
}
this.dependencyInitTimer = setTimeout(() => {
this.dependencyInitLock = false
this.dependencyInitTimer = null
}, duration)
},
clearDependencyInitLock() {
this.dependencyInitLock = false
if (this.dependencyInitTimer) {
clearTimeout(this.dependencyInitTimer)
this.dependencyInitTimer = null
}
},
clearBlankPageInitLock() {
this.blankPageInitLock = false
},
isUserGestureMapEvent(causedBy) {
return ['gesture', 'drag', 'scale'].includes(String(causedBy || '').toLowerCase())
},
applyMapLocation(longitude, latitude, noReverse = true) {
const lng = Number(longitude)
const lat = Number(latitude)
if (!Number.isFinite(lng) || !Number.isFinite(lat)) return
// 程序主动改中心时,给一个更短的保护时间
// 避免真机上用户刚进页面第一次拖动也被吞掉
this.ignoreRegionChangeOnce = true
this.mapProgrammaticUntil = Date.now() + 280
this.longitude = lng
this.latitude = lat
this.newLongitude = lng
this.newLatitude = lat
if (this.mapGuardTimer) {
clearTimeout(this.mapGuardTimer)
}
this.mapGuardTimer = setTimeout(() => {
this.ignoreRegionChangeOnce = false
this.mapGuardTimer = null
}, 320)
if (this.mapReverseTimer) {
clearTimeout(this.mapReverseTimer)
this.mapReverseTimer = null
}
if (!noReverse) {
this.mapReverseTimer = setTimeout(() => {
this.reverseGeocode(lng, lat)
this.mapReverseTimer = null
}, 220)
}
},
// 直辖市处理
normalizeCityName(province, city) {
let cityName = ''
if (Array.isArray(city)) {
cityName = city[0] || ''
} else {
cityName = city || ''
}
const fallbackCities = [
'北京市',
'上海市',
'天津市',
'重庆市',
'香港特别行政区',
'澳门特别行政区'
]
if (!cityName && fallbackCities.includes(province)) {
cityName = province
}
return cityName
},
buildMapSnapshotUrl() {
const lng = Number(this.longitude || this.newLongitude || 0).toFixed(6)
const lat = Number(this.latitude || this.newLatitude || 0).toFixed(6)
return `https://restapi.amap.com/v3/staticmap?location=${lng},${lat}&zoom=17&size=750*500&scale=2&markers=mid,0xE8101E,A:${lng},${lat}&key=${this.amapKey}`
},
onSnapshotLoad() {
console.log('地图加载成功:', this.mapSnapshotUrl)
},
onSnapshotError(e) {
console.log('地图加载失败:', e, this.mapSnapshotUrl)
uni.showToast({
title: '地图加载失败',
icon: 'none'
})
},
findRegionIndexByCode(baseData, provinceCode, cityCode, areaCode) {
const result = { provinceIndex: -1, cityIndex: -1, areaIndex: -1 }
const targetProvinceCode = (provinceCode || '').slice(0, 6)
const targetCityCode = (cityCode || '').slice(0, 6)
const targetAreaCode = (areaCode || '').slice(0, 6)
if (!targetProvinceCode || !targetCityCode || !targetAreaCode) return result
for (let pIdx = 0; pIdx < baseData.length; pIdx++) {
const provinceItem = baseData[pIdx]
const currentProvinceCode = (provinceItem.code || '').slice(0, 6)
if (currentProvinceCode === targetProvinceCode) {
result.provinceIndex = pIdx
const citys = provinceItem.citys || []
for (let cIdx = 0; cIdx < citys.length; cIdx++) {
const cityItem = citys[cIdx]
const currentCityCode = (cityItem.code || '').slice(0, 6)
if (currentCityCode === targetCityCode) {
result.cityIndex = cIdx
const areas = cityItem.areas || []
for (let aIdx = 0; aIdx < areas.length; aIdx++) {
const areaItem = areas[aIdx]
const currentAreaCode = (areaItem.code || '').slice(0, 6)
if (currentAreaCode === targetAreaCode) {
result.areaIndex = aIdx
return result
}
}
return result
}
}
return result
}
}
return result
},
convert(item) {
const data = {
key: this.amapKey,
locations: `${item.longitude},${item.latitude}`,
coordsys: 'gps'
}
return new Promise((resolve) => {
uni.request({
url: 'https://restapi.amap.com/v3/assistant/coordinate/convert',
method: 'GET',
data,
success(res) {
resolve(res.data)
}
})
})
},
getLocation(silent = false) {
return new Promise((resolve, reject) => {
const handleFail = (err) => {
if (!silent) {
uni.showToast({ title: '获取位置失败', icon: 'none' })
}
reject(err)
}
if (this.$store?.state?.version?.DeviceBrand === 'ios') {
uni.getLocation({
type: 'wgs84',
isHighAccuracy: true,
success: async (res) => {
try {
const convert = await this.convert(res)
const locations = String(convert?.locations || '').split(',')
if (locations.length >= 2) {
res.longitude = Number(locations[0])
res.latitude = Number(locations[1])
}
resolve(res)
} catch (error) {
handleFail(error)
}
},
fail: handleFail
})
} else {
uni.getLocation({
type: 'gcj02',
geocode: true,
success: (res) => resolve(res),
fail: handleFail
})
}
})
},
async getCurrentLocation(silent = false, lockInit = false) {
this.blankPageInitLock = false
try {
const locationRes = await this.getLocation(silent)
if (lockInit) {
// 首次进入搜索地址页、且需要按当前定位初始化时,锁住地图初始化阶段
this.startDependencyInitLock(1200)
this.userSelectedRegion = false
this.isManualDetail = false
this.suppressInitDetailFill = false
}
this.updateMapCenter(locationRes.longitude, locationRes.latitude, false)
} catch (error) {
// 静默定位失败时,保持页面为空,等用户自己操作
if (silent) {
this.latitude = 0
this.longitude = 0
this.newLatitude = 0
this.newLongitude = 0
this.currentProvince = ''
this.currentCity = ''
this.currentDistrict = ''
this.currentRegion = ''
this.detailAddress = ''
this.manualDetail = ''
this.currentDetail = ''
this.isManualDetail = false
// 关键补丁:
// 未授权/静默定位失败后,立刻进入空白锁定模式,
// 防止地图默认中心点再把“北京市西城区”反写回来
this.blankPageInitLock = true
this.suppressInitDetailFill = true
}
console.log('定位失败', error)
}
},
getNowLocation(center) {
if (!center) return
const locationRes = center.split(',')
if (locationRes.length < 2) return
this.updateMapCenter(
parseFloat(locationRes[0]),
parseFloat(locationRes[1]),
false
)
},
normalizeAdcode6(code) {
return String(code || '').slice(0, 6)
},
validateLocationWithDependency(longitude, latitude, dependencyCode) {
return new Promise((resolve) => {
const targetCode = this.normalizeAdcode6(dependencyCode)
if (!targetCode || !longitude || !latitude) {
resolve(true)
return
}
uni.request({
url: 'https://restapi.amap.com/v3/geocode/regeo',
method: 'GET',
data: {
key: this.amapKey,
location: `${longitude},${latitude}`,
extensions: 'base'
},
success: (res) => {
const adcode = this.normalizeAdcode6(
res?.data?.regeocode?.addressComponent?.adcode || ''
)
// 取不到adcode时不拦避免误杀正常流程
if (!adcode) {
resolve(true)
return
}
resolve(adcode === targetCode)
},
fail: () => {
// 校验失败时不拦,避免把正常功能带坏
resolve(true)
}
})
})
},
async locateByDependencyCenter(regions, dependencyCode, skipAutoLocate) {
let center = null
if (dependencyCode && regions[2]) {
center = await this.getDistrictCenter(regions[2], dependencyCode)
}
if (!center && regions[2]) {
center = await this.getDistrictCenter(regions[2])
}
if (!center && regions[1] && regions[1] !== regions[0]) {
center = await this.getDistrictCenter(regions[1])
}
if (!center && regions[0]) {
center = await this.getDistrictCenter(regions[0])
}
if (center) {
const locationRes = center.split(',')
if (locationRes.length >= 2) {
this.updateMapCenter(
parseFloat(locationRes[0]),
parseFloat(locationRes[1]),
true
)
}
} else if (!skipAutoLocate) {
this.getCurrentLocation()
}
},
// 逆地理编码
reverseGeocode(lng, lat) {
const requestId = ++this.reverseGeocodeRequestId
const targetLng = parseFloat(lng)
const targetLat = parseFloat(lat)
if (!Number.isFinite(targetLng) || !Number.isFinite(targetLat)) return
uni.request({
url: 'https://restapi.amap.com/v3/geocode/regeo',
data: {
key: this.amapKey,
location: `${targetLng},${targetLat}`,
extensions: 'base'
},
success: (res) => {
// 只认最后一次逆地理编码,避免旧请求把新位置覆盖掉
if (requestId !== this.reverseGeocodeRequestId) return
if (res.data.status === '1' && res.data.regeocode) {
if (this.recognizeMapMoving) return
const regeo = res.data.regeocode
const formatted = regeo.formatted_address || ''
const addrComp = regeo.addressComponent || {}
const provinceName = addrComp.province || ''
const cityName = this.normalizeCityName(provinceName, addrComp.city)
const districtName = addrComp.district || ''
let region = ''
if (provinceName) region += provinceName
if (cityName && cityName !== provinceName) region += cityName
if (districtName) region += districtName
if (!this.userSelectedRegion) {
this.currentRegion = region || formatted
this.currentProvince = provinceName
this.currentCity = cityName
this.currentDistrict = districtName
const adcode = addrComp.adcode || ''
if (adcode.length >= 6) {
this.selectedDistrictCode = adcode
this.selectedCityCode = adcode.substring(0, 4) + '00'
this.selectedProvinceCode = adcode.substring(0, 2) + '0000'
}
}
let detail = ''
if (region && formatted.startsWith(region)) {
detail = formatted.substring(region.length).trim()
} else {
detail = formatted.trim()
}
// 首次进入且上页没传详细地址:保持空
if (this.suppressInitDetailFill) {
this.suppressInitDetailFill = false
return
}
if (!this.isManualDetail) {
this.detailAddress = detail || formatted
this.manualDetail = this.detailAddress
this.currentDetail = this.detailAddress
}
}
},
fail: (err) => {
if (requestId !== this.reverseGeocodeRequestId) return
console.error('逆地理编码失败:', err)
}
})
},
// 地图点击:点击后立即更新地区和详细地址
onMapTap(e) {
this.clearDependencyInitLock()
this.clearBlankPageInitLock()
this.userSelectedRegion = false
this.isManualDetail = false
this.suppressInitDetailFill = false
const tapLat = Number(e?.detail?.latitude)
const tapLng = Number(e?.detail?.longitude)
if (Number.isFinite(tapLat) && Number.isFinite(tapLng)) {
this.latitude = tapLat
this.longitude = tapLng
this.newLatitude = tapLat
this.newLongitude = tapLng
this.reverseGeocode(tapLng, tapLat)
return
}
const mapCtx = uni.createMapContext('map', this)
mapCtx.getCenterLocation({
success: (res) => {
this.latitude = res.latitude
this.longitude = res.longitude
this.newLatitude = res.latitude
this.newLongitude = res.longitude
this.reverseGeocode(res.longitude, res.latitude)
},
fail: (err) => {
console.error('点击地图获取中心点失败', err)
}
})
},
syncCenterFromMap() {
if (this.dependencyInitLock) return
if (this.blankPageInitLock) return
const mapCtx = uni.createMapContext('map', this)
mapCtx.getCenterLocation({
success: (res) => {
if (
!res ||
!Number.isFinite(Number(res.longitude)) ||
!Number.isFinite(Number(res.latitude))
) {
return
}
this.userSelectedRegion = false
this.isManualDetail = false
this.suppressInitDetailFill = false
this.latitude = Number(res.latitude)
this.longitude = Number(res.longitude)
this.newLatitude = Number(res.latitude)
this.newLongitude = Number(res.longitude)
this.reverseGeocode(this.longitude, this.latitude)
},
fail: (err) => {
console.error('syncCenterFromMap失败', err)
}
})
},
// 地图拖动结束用getCenterLocation拿中心点然后更新地区和详细地址
changeMap(e) {
if (this.showRegionPicker || !this.mapVisible) return
if (this.recognizeMapMoving) return
if (this.dependencyInitLock) return
const eventType = e?.detail?.type || e?.type || ''
const causedBy = e?.detail?.causedBy || e?.causedBy || ''
if (this.blankPageInitLock) {
if (this.isUserGestureMapEvent(causedBy)) {
this.clearBlankPageInitLock()
} else {
return
}
}
// 只跳过明确的 begin
if (eventType === 'begin') return
if (Date.now() < this.mapProgrammaticUntil) {
return
}
const isProgrammaticEvent = causedBy && causedBy !== 'gesture'
if (isProgrammaticEvent && this.ignoreRegionChangeOnce) {
this.ignoreRegionChangeOnce = false
return
}
if (this.mapMoveTimer) {
clearTimeout(this.mapMoveTimer)
}
const delay = eventType === 'end' ? 80 : 260
this.mapMoveTimer = setTimeout(() => {
this.syncCenterFromMap()
setTimeout(() => {
this.syncCenterFromMap()
}, 180)
this.mapMoveTimer = null
}, delay)
},
// 城市选择
goSelectCity() {
uni.navigateTo({
url: '/subPackages/address/selectCity'
})
},
// 底部编辑区方法
clearRawAddress() {
this.rawAddress = ''
uni.showToast({ title: '已清空', icon: 'none' })
},
onDetailInput(e) {
const val = e.detail.value || ''
this.detailAddress = val
this.manualDetail = val
this.currentDetail = val
this.isManualDetail = val.trim().length > 0
},
onPaste() {
uni.getClipboardData({
success: (res) => {
if (res.data) {
this.rawAddress = res.data
uni.showToast({ title: '已粘贴', icon: 'none' })
}
}
})
},
sanitizeRawAddress(raw) {
let text = (raw || '')
.replace(/[\r\n\t]+/g, ' ')
.replace(/ /g, ' ')
.replace(/\s+/g, ' ')
.trim()
const addressMatch = text.match(/地址[:]\s*(.*)/)
if (addressMatch && addressMatch[1]) {
text = addressMatch[1].trim()
}
text = text
.replace(/(收货人|联系人|姓名)[:]\s*[\u4e00-\u9fa5A-Za-z·]{1,20}/g, '')
.replace(/(电话|手机)[:]?\s*1[3-9]\d{9}/g, '')
.replace(/1[3-9]\d{9}/g, '')
.replace(/[,;]+/g, ' ')
.replace(/\s+/g, ' ')
.trim()
return text
},
escapeRegExp(str) {
return (str || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
},
getNetworkTypeSafe() {
return new Promise((resolve) => {
uni.getNetworkType({
success: (res) => resolve(res.networkType || 'unknown'),
fail: () => resolve('unknown')
})
})
},
async ensureRecognizeNetworkAvailable() {
const networkType = await this.getNetworkTypeSafe()
if (networkType === 'none') {
this.showRecognizeNetworkError()
return false
}
return true
},
showRecognizeNetworkError() {
uni.showToast({
title: '网络异常,无法进行地址识别与地图定位,请检查网络后重试',
icon: 'none',
duration: 3500
})
},
isLikelyNetworkError(error) {
const msg = String(
error?.errMsg ||
error?.message ||
error ||
''
).toLowerCase()
return (
msg.includes('network') ||
msg.includes('timeout') ||
msg.includes('request:fail') ||
msg.includes('fail')
)
},
hasStrongAddressFeature(text) {
return /(\d|号|栋|幢|单元|室|楼|层|座|路|街|巷|道|村|镇|乡|里|弄|园|苑|小区|大厦|广场|市场|学校|医院|公寓|酒店|中心|胡同)/.test(text || '')
},
containsObviousInvalidNoise(text) {
return /(价格|价钱|多少钱|举报|投诉|建议|咨询|测试|示例|比如|比方|假设|无效|乱填)/i.test(text || '')
},
isLikelyInvalidRecognizedDetail(text) {
const value = String(text || '').trim()
if (!value) return true
const hasChinese = /[\u4e00-\u9fa5]/.test(value)
const hasDigit = /\d/.test(value)
const hasAddressUnit = this.hasStrongAddressFeature(value)
const longPureLetters = /\b[A-Za-z]{4,}\b/.test(value) && !/[A-Za-z]\d|\d[A-Za-z]/.test(value)
if (this.containsObviousInvalidNoise(value) && !hasAddressUnit && !hasDigit) {
return true
}
if (longPureLetters && !hasAddressUnit && !hasDigit) {
return true
}
if (!hasChinese && !hasDigit && !hasAddressUnit) {
return true
}
return false
},
isPreciseGeocodeLevel(level) {
return [
'门牌号',
'兴趣点',
'道路',
'道路交叉口',
'乡镇',
'村庄',
'村级地名'
].includes(String(level || '').trim())
},
validateRecognizedDetail(detailText, geo) {
const cleaned = String(detailText || '').trim()
if (!cleaned) {
return {
valid: false,
cleaned: '',
reason: 'empty'
}
}
if (this.isLikelyInvalidRecognizedDetail(cleaned)) {
return {
valid: false,
cleaned: '',
reason: 'invalid_text'
}
}
if (!geo) {
return {
valid: false,
cleaned: '',
reason: 'geocode_failed'
}
}
if (!this.isPreciseGeocodeLevel(geo.level) && !this.hasStrongAddressFeature(cleaned)) {
return {
valid: false,
cleaned: '',
reason: 'low_precision'
}
}
return {
valid: true,
cleaned,
reason: ''
}
},
cleanRecognizedDetail(detail, province, city, district, town) {
let text = (detail || '').trim()
const parts = [province, city, district, town]
.filter(Boolean)
.map(item => this.escapeRegExp(item))
if (parts.length) {
const reg = new RegExp(`^(${parts.join('|')})+`)
text = text.replace(reg, '')
}
text = text
.replace(/(收货人|联系人|姓名)[:].*$/g, '')
.replace(/(电话|手机)[:]?\s*1[3-9]\d{9}.*$/g, '')
.replace(/备注[:].*$/g, '')
.replace(/1[3-9]\d{9}/g, '')
.replace(/[(][^)]*[)]/g, '')
.replace(/^[,;、\s]+/, '')
.replace(/\s+/g, ' ')
.trim()
return text
},
buildRecognizedDetail(townName, detail) {
const town = (townName || '').trim()
const detailText = (detail || '').trim()
if (town && detailText) {
return detailText.startsWith(town) ? detailText : `${town}${detailText}`
}
return detailText || town
},
buildExactRecognizedDetail(hit, sanitizedText, province, city, district) {
const directDetail = (
hit?.detail ||
hit?.address ||
hit?.addr ||
hit?.full_address_detail ||
hit?.address_detail ||
''
).trim()
// 接口已经给了明确详细地址,就直接用,不再清洗、不再删镇街
if (directDetail) {
return directDetail
}
// 接口没单独给详细地址时,再从原始识别文本里扣掉省市区,保留后面的完整内容
let text = (sanitizedText || '').trim()
const prefix = [
province || '',
city && city !== province ? city : '',
district || ''
].join('')
if (prefix && text.startsWith(prefix)) {
text = text.slice(prefix.length).trim()
}
return text
},
getRecognizeLevel(hit, detailText, rawText) {
const province = (hit?.province_name || '').trim()
const city = (hit?.city_name || '').trim()
const district = (hit?.county_name || '').trim()
if (/火星市|月球|银河|测试地址/.test(rawText || '')) {
return 'low'
}
if (!province || (!city && !district)) {
return 'low'
}
if (!detailText || detailText.length < 4) {
return 'medium'
}
return 'high'
},
// 垃圾文本拦截
shouldOnlyToastForGarbageRecognize(rawText, hit, detailText, detailRejected = false) {
const input = String(rawText || '').trim()
if (!input) return true
const province = (hit?.province_name || '').trim()
const city = (hit?.city_name || '').trim()
const district = (hit?.county_name || '').trim()
const hasFullRegion = !!(province && (city || district))
if (!hasFullRegion) return false
const hasAddressFeature = this.hasStrongAddressFeature(input)
const hasRegionKeyword = /(省|市|区|县|旗|镇|乡|村|街道)/.test(input)
const hasLongLetterChunk = /[A-Za-z]{2,}/.test(input)
const hasInvalidNoise = this.containsObviousInvalidNoise(input)
// 像“付i啊不符合可不vi了哈女本办法本办法v就看不上”这种
// 既不像正常地址,又夹杂明显乱码/字母串,同时还没有有效详细地址时,只提示不回填
if (!hasAddressFeature && !hasRegionKeyword && !detailText) {
if (hasLongLetterChunk || hasInvalidNoise || detailRejected) {
return true
}
}
return false
},
getRecognizeToast(level, removedNoise, geocodeOk, hit, detailText, detailRejected = false) {
const province = (hit?.province_name || '').trim()
const city = (hit?.city_name || '').trim()
const district = (hit?.county_name || '').trim()
const hasRegion = !!(province || city || district)
const hasFullRegion = !!(province && (city || district))
const hasDetail = !!(detailText && detailText.trim())
if (detailRejected && hasFullRegion && !hasDetail) {
return {
type: 'modal',
title: '已识别到地区,详细地址未自动填入',
content: '已识别到省市区,但检测到详细地址中包含无效或不完整内容,系统未自动填入详细地址,请手动补充。'
}
}
if (level === 'low') {
if (!hasRegion) {
return {
type: 'modal',
title: '已识别,但未匹配到有效地区',
content: '未查找到明确的省市区信息,请手动选择所在地区,并检查输入内容是否包含错别字、备注或无效地址。'
}
}
if (hasRegion && !hasFullRegion) {
return {
type: 'modal',
title: '已识别到部分地区信息',
content: '当前仅识别到部分地区,未能完整匹配到市区县,请手动补全所在地区并核对详细地址。'
}
}
if (hasFullRegion && !hasDetail) {
return {
type: 'modal',
title: '已识别到地区,但详细地址不完整',
content: '已识别到省市区,但未能准确提取详细地址,请补充门牌号、楼栋号等信息。'
}
}
return {
type: 'modal',
title: '已识别,但结果不够准确',
content: '当前地址已部分识别,请核对所在地区和详细地址,必要时请手动修正。'
}
}
if (level === 'medium') {
if (!hasDetail) {
return {
type: 'toast',
title: removedNoise ? '已识别到地区,请补充详细地址' : '已识别到地区,请补充详细地址'
}
}
if (!geocodeOk) {
return {
type: 'toast',
title: removedNoise ? '已识别,但未查到地图位置' : '已识别,但未查到对应地图位置'
}
}
return {
type: 'toast',
title: removedNoise ? '已识别到部分地址' : '已识别到部分地址,请核对'
}
}
if (!geocodeOk) {
return {
type: 'toast',
title: removedNoise ? '已识别,但未查到地图位置' : '已识别,请核对地址和地图位置'
}
}
return {
type: 'toast',
title: removedNoise ? '识别成功' : '识别成功,请核对'
}
},
parseStreetStd(streetStd = '') {
const parsed = {}
String(streetStd)
.split(/\t+/)
.forEach(item => {
const index = item.indexOf('=')
if (index === -1) return
const key = item.slice(0, index).trim().toLowerCase()
const value = item.slice(index + 1).trim()
if (key) {
parsed[key] = value
}
})
return {
province_name: (parsed.prov || parsed.province || '').trim(),
city_name: (parsed.city || '').trim(),
county_name: (parsed.district || parsed.county || parsed.area || '').trim(),
town_name: (parsed.town || parsed.street || '').trim(),
detail: (parsed.detail || parsed.address || parsed.addr || '').trim(),
address: (parsed.address || parsed.detail || parsed.addr || '').trim(),
province_code: (parsed.province_code || parsed.prov_code || '').trim(),
city_code: (parsed.city_code || '').trim(),
county_code: (parsed.county_code || parsed.district_code || parsed.area_code || '').trim()
}
},
normalizeRecognizeHit(res) {
if (!res || (res.code !== 200 && res.code !== 0)) return null
// 旧格式1data 是数组
if (Array.isArray(res.data) && res.data.length) {
return res.data[0]
}
// 旧格式2data.data 是数组
if (Array.isArray(res?.data?.data) && res.data.data.length) {
return res.data.data[0]
}
// 旧格式3result 是数组
if (Array.isArray(res.result) && res.result.length) {
return res.result[0]
}
// 新格式data 本身就是对象,且直接带这些字段
if (
res.data &&
typeof res.data === 'object' &&
(
res.data.province_name ||
res.data.city_name ||
res.data.county_name ||
res.data.detail ||
res.data.address
)
) {
return res.data
}
// 新格式data.street.street_std
if (res?.data?.street && typeof res.data.street === 'object') {
const street = res.data.street
const stdParsed = this.parseStreetStd(street.street_std || '')
return {
province_name: (street.province_name || street.province || stdParsed.province_name || '').trim(),
city_name: (street.city_name || street.city || stdParsed.city_name || '').trim(),
county_name: (street.county_name || street.district || street.county || stdParsed.county_name || '').trim(),
town_name: (street.town_name || street.town || street.street_name || stdParsed.town_name || '').trim(),
detail: (street.detail || street.address || stdParsed.detail || stdParsed.address || '').trim(),
address: (street.address || street.detail || stdParsed.address || stdParsed.detail || '').trim(),
province_code: (street.province_code || stdParsed.province_code || '').trim(),
city_code: (street.city_code || stdParsed.city_code || '').trim(),
county_code: (street.county_code || street.district_code || stdParsed.county_code || '').trim()
}
}
return null
},
getRecognizeApiUrl() {
const app = typeof getApp === 'function' ? getApp() : null
const baseUrl =
app?.globalData?.url ||
app?.globalData?.baseUrl ||
app?.globalData?.apiBaseUrl ||
'https://api.mrrwlkj.top'
return `${String(baseUrl).replace(/\/+$/, '')}/api/recognize/address`
},
buildMultipartFormData(fields = {}) {
const boundary = '----UniFormBoundary' + Date.now()
let body = ''
Object.keys(fields).forEach((key) => {
const value = fields[key] == null ? '' : String(fields[key])
body += `--${boundary}\r\n`
body += `Content-Disposition: form-data; name="${key}"\r\n\r\n`
body += `${value}\r\n`
})
body += `--${boundary}--\r\n`
return {
boundary,
body
}
},
requestRecognizeAddress(rawText) {
return request.post('/api/recognize/address', {
text: rawText
})
},
geocodeRecognizedAddress(address) {
return new Promise((resolve) => {
if (!address) {
resolve({
ok: true,
data: null,
error: null
})
return
}
uni.request({
url: 'https://restapi.amap.com/v3/geocode/geo',
method: 'GET',
data: {
key: this.amapKey,
address,
output: 'JSON'
},
success: (res) => {
if (res.data.status === '1' && res.data.geocodes?.length > 0) {
resolve({
ok: true,
data: res.data.geocodes[0],
error: null
})
} else {
resolve({
ok: true,
data: null,
error: null
})
}
},
fail: (error) => resolve({
ok: false,
data: null,
error
})
})
})
},
async onSmartRecognize() {
this.clearBlankPageInitLock()
this.userSelectedRegion = false
this.isManualDetail = false
this.suppressInitDetailFill = false
if (!this.hasRawContent) {
uni.showToast({ title: '请先粘贴或输入地址', icon: 'none' })
return
}
const originalRawText = (this.rawAddress || '').trim()
if (!originalRawText) {
uni.showToast({ title: '请先粘贴或输入地址', icon: 'none' })
return
}
const hasNetwork = await this.ensureRecognizeNetworkAvailable()
if (!hasNetwork) {
return
}
const sanitizedText = this.sanitizeRawAddress(originalRawText)
const removedNoise = sanitizedText !== originalRawText
if (!sanitizedText) {
uni.showToast({ title: '未提取到有效地址', icon: 'none' })
return
}
uni.showLoading({ title: '识别中...' })
try {
const res = await this.requestRecognizeAddress(sanitizedText)
console.log('智能识别接口返回:', res)
const hit = this.normalizeRecognizeHit(res)
if (!res || (res.code !== 0 && res.code !== 200) || !hit) {
uni.hideLoading()
uni.showToast({
title: res?.msg || '地址解析失败',
icon: 'none'
})
return
}
console.log('智能识别标准化结果:', hit)
const province = (hit.province_name || '').trim()
const city = (hit.city_name || '').trim()
const district = (hit.county_name || '').trim()
const town = (hit.town_name || '').trim()
let regionText = ''
if (province) regionText += province
if (city && city !== province) regionText += city
if (district) regionText += district
if (!regionText) {
uni.hideLoading()
const tip = this.getRecognizeToast('low', removedNoise, false, hit, '', false)
if (tip.type === 'modal') {
uni.showModal({
title: tip.title,
content: tip.content,
showCancel: false
})
} else {
uni.showToast({
title: tip.title,
icon: 'none'
})
}
return
}
const detailCandidate = this.cleanRecognizedDetail(
this.buildExactRecognizedDetail(
hit,
sanitizedText,
province,
city,
district
),
province,
city,
district,
town
)
const fullAddress = `${regionText}${detailCandidate}`.trim()
console.log('用于定位的完整地址:', fullAddress)
let geocodeOk = false
let geo = null
let detailRejected = false
let detailText = ''
const fullGeoResult = await this.geocodeRecognizedAddress(fullAddress)
if (!fullGeoResult.ok) {
uni.hideLoading()
this.showRecognizeNetworkError()
return
}
geo = fullGeoResult.data
const detailDecision = this.validateRecognizedDetail(detailCandidate, geo)
detailRejected = !detailDecision.valid && !!detailCandidate
detailText = detailDecision.valid ? detailDecision.cleaned : ''
// ====== 回填到页面下方的位置 ======
this.currentProvince = province
this.currentCity = city
this.currentDistrict = district
this.currentRegion = regionText
this.selectedProvinceCode = hit.province_code || ''
this.selectedCityCode = hit.city_code || ''
this.selectedDistrictCode = hit.county_code || ''
this.detailAddress = detailText
this.manualDetail = detailText
this.currentDetail = detailText
this.isManualDetail = !!detailText
// =================================
// 如果详细地址被判定为无效,则退回到仅用省市区定位,避免把脏文本带进详情页
if (!detailText) {
const regionGeoResult = await this.geocodeRecognizedAddress(regionText)
if (!regionGeoResult.ok) {
uni.hideLoading()
this.showRecognizeNetworkError()
return
}
geo = regionGeoResult.data
}
if (geo) {
const location = geo.location
const adcode = geo.adcode || ''
if (adcode.length >= 6) {
this.selectedDistrictCode = adcode
this.selectedCityCode = adcode.substring(0, 4) + '00'
this.selectedProvinceCode = adcode.substring(0, 2) + '0000'
}
if (location) {
const [lng, lat] = location.split(',')
this.longitude = parseFloat(lng)
this.latitude = parseFloat(lat)
this.newLongitude = this.longitude
this.newLatitude = this.latitude
// 智能识别后这次地图自动移动,只允许移动地图,不允许回头覆盖识别结果
this.recognizeMapMoving = true
if (this.recognizeMapMoveTimer) {
clearTimeout(this.recognizeMapMoveTimer)
}
this.updateMapCenter(this.longitude, this.latitude, true)
geocodeOk = true
this.recognizeMapMoveTimer = setTimeout(() => {
this.recognizeMapMoving = false
this.recognizeMapMoveTimer = null
}, 1500)
}
}
const level = this.getRecognizeLevel(hit, detailText, sanitizedText)
this.smartBtnText = '已识别'
uni.hideLoading()
const tip = this.getRecognizeToast(level, removedNoise, geocodeOk, hit, detailText, detailRejected)
if (tip.type === 'modal') {
uni.showModal({
title: tip.title,
content: tip.content,
showCancel: false
})
} else {
uni.showToast({
title: tip.title,
icon: 'none'
})
}
} catch (error) {
console.error('智能识别失败:', error)
uni.hideLoading()
if (this.isLikelyNetworkError(error)) {
this.showRecognizeNetworkError()
return
}
uni.showToast({
title: '识别失败,请稍后重试',
icon: 'none'
})
}
},
// 更新地图中心
updateMapCenter(longitude, latitude, noReverse = false) {
const lng = parseFloat(longitude)
const lat = parseFloat(latitude)
if (!Number.isFinite(lng) || !Number.isFinite(lat)) return
this.applyMapLocation(lng, lat, noReverse)
},
// 地区选择弹窗
selectRegion() {
this.openRegionPicker()
},
openRegionPicker() {
this.regionProvinceList = ChinaCitys || []
let provinceIndex = 0
let cityIndex = 0
let areaIndex = 0
if (this.selectedProvinceCode && this.selectedCityCode && this.selectedDistrictCode) {
const idx = this.findRegionIndexByCode(
ChinaCitys,
this.selectedProvinceCode,
this.selectedCityCode,
this.selectedDistrictCode
)
provinceIndex = idx.provinceIndex >= 0 ? idx.provinceIndex : 0
cityIndex = idx.cityIndex >= 0 ? idx.cityIndex : 0
areaIndex = idx.areaIndex >= 0 ? idx.areaIndex : 0
}
this.regionCityList = this.regionProvinceList[provinceIndex]?.citys || []
this.regionDistrictList = this.regionCityList[cityIndex]?.areas || []
this.regionPickerValue = [provinceIndex, cityIndex, areaIndex]
if (this.mapMoveTimer) {
clearTimeout(this.mapMoveTimer)
this.mapMoveTimer = null
}
this.mapSnapshotUrl = this.buildMapSnapshotUrl()
this.showMapSnapshot = true
this.mapVisible = false
this.showRegionPicker = true
},
closeRegionPicker() {
this.showRegionPicker = false
if (this.popupRestoreTimer) {
clearTimeout(this.popupRestoreTimer)
}
this.popupRestoreTimer = setTimeout(() => {
this.showMapSnapshot = false
this.mapSnapshotUrl = ''
// 恢复地图时做一次保护,避免重建后的 regionchange 抢状态
this.ignoreRegionChangeOnce = true
this.mapProgrammaticUntil = Date.now() + 500
if (this.mapGuardTimer) {
clearTimeout(this.mapGuardTimer)
}
this.mapGuardTimer = setTimeout(() => {
this.ignoreRegionChangeOnce = false
this.mapGuardTimer = null
}, 550)
this.mapVisible = true
this.mapRenderKey += 1
this.popupRestoreTimer = null
}, 300)
},
onRegionChange(e) {
const val = e.detail.value
this.regionPickerValue = val
const provinceIdx = val[0]
const cityIdx = val[1] || 0
if (this.regionProvinceList.length > provinceIdx) {
this.regionCityList = this.regionProvinceList[provinceIdx].citys || []
const maxCityIdx = this.regionCityList.length - 1
const adjustedCityIdx = Math.min(cityIdx, maxCityIdx)
if (adjustedCityIdx !== cityIdx) {
this.regionPickerValue = [provinceIdx, adjustedCityIdx, 0]
}
if (this.regionCityList.length > adjustedCityIdx) {
this.regionDistrictList = this.regionCityList[adjustedCityIdx].areas || []
const areaIdx = val[2] || 0
const maxAreaIdx = this.regionDistrictList.length - 1
if (areaIdx > maxAreaIdx) {
this.regionPickerValue = [provinceIdx, adjustedCityIdx, 0]
}
} else {
this.regionDistrictList = []
this.regionPickerValue = [provinceIdx, 0, 0]
}
} else {
this.regionCityList = []
this.regionDistrictList = []
this.regionPickerValue = [0, 0, 0]
}
},
async confirmRegion() {
this.clearBlankPageInitLock()
const [provinceIdx, cityIdx, districtIdx] = this.regionPickerValue
const provinceItem = this.regionProvinceList[provinceIdx]
const cityItem = this.regionCityList[cityIdx]
const districtItem = this.regionDistrictList[districtIdx]
if (!provinceItem || !cityItem || !districtItem) {
uni.showToast({ title: '选择无效', icon: 'none' })
return
}
this.userSelectedRegion = true
const regionStr = provinceItem.province + cityItem.city + districtItem.area
this.currentProvince = provinceItem.province
this.currentCity = cityItem.city
this.currentDistrict = districtItem.area
this.currentRegion = regionStr
this.selectedProvinceCode = provinceItem.code
this.selectedCityCode = cityItem.code
this.selectedDistrictCode = districtItem.code
// 选地区后只定位到地区中心,详细地址保持空
this.isManualDetail = false
this.suppressInitDetailFill = true
this.detailAddress = ''
this.manualDetail = ''
this.currentDetail = ''
const shortCode = districtItem.code ? districtItem.code.toString().slice(0, 6) : ''
this.startDependencyInitLock()
const center = await this.getDistrictCenter(districtItem.area, shortCode)
if (center) {
const [lng, lat] = center.split(',')
this.updateMapCenter(parseFloat(lng), parseFloat(lat), true)
} else {
uni.showToast({ title: '无法定位该地区,请手动拖动地图', icon: 'none' })
}
this.closeRegionPicker()
},
// 获取地区中心点
getDistrictCenter(keyword, adcode = '', subdistrict = 0) {
return new Promise((resolve) => {
const params = {
key: this.amapKey,
subdistrict,
extensions: 'all'
}
const queryAdcode = () => {
if (!adcode) return Promise.resolve(null)
return new Promise((resolveInner) => {
uni.request({
url: 'https://restapi.amap.com/v3/config/district',
method: 'GET',
data: {
...params,
keywords: adcode.slice(0, 6)
},
success: (res) => {
if (res.data.status === '1' && res.data.districts && res.data.districts.length > 0) {
const district = res.data.districts[0]
if (district.center) {
resolveInner(district.center)
return
}
}
resolveInner(null)
},
fail: () => resolveInner(null)
})
})
}
const queryKeyword = () => {
return new Promise((resolveInner) => {
uni.request({
url: 'https://restapi.amap.com/v3/config/district',
method: 'GET',
data: {
...params,
keywords: keyword
},
success: (res) => {
if (res.data.status === '1' && res.data.districts && res.data.districts.length > 0) {
const district = res.data.districts[0]
if (district.center) {
resolveInner(district.center)
return
}
}
resolveInner(null)
},
fail: () => resolveInner(null)
})
})
}
queryAdcode().then(center => {
if (center) {
resolve(center)
} else {
queryKeyword().then(resolve)
}
})
})
},
// 确认返回
onConfirm() {
const fullAddress = (this.currentRegion + (this.detailAddress ? ' ' + this.detailAddress : '')).trim()
const addressItem = {
name: fullAddress,
address: this.detailAddress,
location: `${this.longitude},${this.latitude}`,
pname: this.currentProvince,
cityname: this.currentCity,
adname: this.currentDistrict,
province: this.currentProvince,
city: this.currentCity,
district: this.currentDistrict,
provinceCode: this.selectedProvinceCode,
cityCode: this.selectedCityCode,
districtCode: this.selectedDistrictCode,
source: this.source
}
const pages = getCurrentPages()
const prevPage = pages[pages.length - 2]
const currentPage = pages[pages.length - 1]
const source = this.source || currentPage.options.source
if (source === 'syr_info') {
uni.$emit('address-selected-for-syr', addressItem)
uni.setStorageSync('syr_selected_address', addressItem)
} else if (source === 'sj_info') {
uni.$emit('address-selected-for-sj', addressItem)
uni.setStorageSync('sj_selected_address', addressItem)
if (getApp().globalData) getApp().globalData.selectedAddress = addressItem
} else if (prevPage) {
if (prevPage.$vm && typeof prevPage.$vm.editAddress === 'function') {
prevPage.$vm.editAddress(addressItem)
} else {
uni.$emit('address-selected', addressItem)
}
} else {
uni.$emit('address-selected', addressItem)
}
uni.navigateBack()
}
}
}
</script>
<style scoped lang="less">
.search-address-page {
flex: 1;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: #FFFFFF;
}
/* 城市选择 */
.search-container {
background-color: #FFFFFF;
display: flex;
flex-direction: row;
align-items: center;
padding: 32rpx 30rpx 20rpx 30rpx;
.search-container-text {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 32rpx;
color: #333333;
line-height: 45rpx;
}
.text-img {
width: 20rpx;
height: 12rpx;
margin-left: 8rpx;
margin-right: 26rpx;
}
}
/* 地图区域 */
.map-container {
width: 750rpx;
position: relative;
overflow: hidden;
}
.map {
width: 750rpx;
height: 100%;
}
.map-snapshot {
position: absolute;
left: 0;
top: 0;
width: 750rpx;
height: 100%;
display: block;
z-index: 2;
background: #f5f5f5;
}
.center-marker {
position: absolute;
left: 375rpx;
transform: translateX(-50%);
top: 250rpx;
width: 42rpx;
height: 69rpx;
z-index: 3;
.center-marker-img {
width: 42rpx;
}
}
.map-snapshot {
width: 750rpx;
height: 100%;
display: block;
}
/* 底部编辑区域(用户端样式) */
.bottom-edit-section {
background-color: #FFFFFF;
padding: 20rpx 20rpx 0 20rpx;
flex: 1;
display: flex;
flex-direction: column;
}
.address-input-row {
// width: 702rpx;
display: flex;
flex-direction: column;
background-color: #F5F5F5;
border-radius: 20rpx;
padding: 16rpx 24rpx;
margin-bottom: 30rpx;
}
.raw-address-input {
width: 100%;
height: 120rpx;
font-size: 14px;
color: #333333;
background-color: transparent;
border-width: 0;
padding: 0;
margin: 0;
}
.action-buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 0rpx;
}
.action-btn {
margin-left: 16rpx;
padding: 7rpx 20rpx;
font-size: 24rpx;
border-radius: 11rpx;
border-width: 1rpx;
border-style: solid;
background-color: transparent;
align-items: center;
justify-content: center;
}
.action-btn:first-child {
margin-left: 0;
}
.btn-text {
font-size: 24rpx;
font-weight: 400;
line-height: 33rpx;
}
.paste-btn {
border-color: #C6C9CC;
color: #333333;
}
.smart-btn {
border-color: #C6C9CC;
color: #999999;
}
.smart-btn.smart-btn-active {
border-color: #E8101E;
color: #E8101E;
}
.address-info-panel {
background-color: #FFFFFF;
border-radius: 16rpx;
margin-bottom: 40rpx;
}
.info-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 30rpx 0;
}
.line {
border-bottom: 1rpx solid #F0F0F0;
width: 540rpx;
margin-left: 150rpx;
}
.row-label {
font-size: 28rpx;
color: #333333;
font-weight: 500;
width: 160rpx;
}
.row-content {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
}
.content-text {
font-size: 28rpx;
color: #333333;
margin-right: 12rpx;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.placeholder {
color: #999999;
font-size: 28rpx;
}
.arrow-icon {
width: 14rpx;
height: 26rpx;
position: absolute;
right: 60rpx;
}
.detail-input {
flex: 1;
font-size: 28rpx;
color: #333333;
padding: 0;
border-width: 0;
background-color: transparent;
height: 60rpx;
}
.confirm-btn {
background-color: #E8101E;
border-radius: 49rpx;
height: 98rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #FFFFFF;
margin-top: 40rpx;
}
.con-btn-text {
font-size: 32rpx;
color: #FFFFFF;
}
/* 弹窗基础 */
.popup {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
visibility: hidden;
opacity: 0;
transition: all 0.3s ease;
}
.popup.show {
visibility: visible !important;
opacity: 1 !important;
}
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.popup-content {
position: relative;
background-color: #ffffff;
border-radius: 24rpx 24rpx 0 0;
padding: 32rpx;
transform: translateY(100%);
transition: transform 0.3s ease;
max-height: 70vh;
}
.popup.show .popup-content {
transform: translateY(0);
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
padding: 0 0 20rpx 0;
border-bottom: 1rpx solid #F0F0F0;
}
.popup-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.popup-close {
font-size: 40rpx;
color: #999;
padding: 10rpx;
}
.region-body {
height: 400rpx;
overflow: hidden;
}
.picker-scroll {
height: 400rpx;
}
.picker-item {
height: 68rpx;
line-height: 68rpx;
text-align: center;
font-size: 28rpx;
color: #333;
}
.popup-footer {
display: flex;
margin-top: 30rpx;
padding-top: 20rpx;
border-top: 1rpx solid #F0F0F0;
}
.popup-btn {
flex: 1;
height: 88rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0 16rpx;
}
.popup-btn.cancel {
background-color: #f5f5f5;
}
.popup-btn.confirm {
background: #e8101e;
}
.popup-btn .btn-text {
font-size: 32rpx;
font-weight: 500;
}
.popup-btn.cancel .btn-text {
color: #666666;
}
.popup-btn.confirm .btn-text {
color: #ffffff;
}
/* 指示器样式(沿用原有) */
.indicatorStyle {
height: 68rpx;
}
</style>