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

2292 lines
83 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>