diff --git a/pages/address/search.vue b/pages/address/search.vue index 0bd244e..164c05e 100644 --- a/pages/address/search.vue +++ b/pages/address/search.vue @@ -140,6 +140,7 @@ export default { data() { return { + useApiRawRecognize: false, // API直出:true mapVisible: true, showMapSnapshot: false, mapSnapshotUrl: '', @@ -394,6 +395,64 @@ }, methods: { + // 直辖市处理 + normalizeMunicipalityName(name) { + const value = String(name || '').trim() + + const map = { + '北京': '北京市', + '北京市': '北京市', + '上海': '上海市', + '上海市': '上海市', + '天津': '天津市', + '天津市': '天津市', + '重庆': '重庆市', + '重庆市': '重庆市', + '香港': '香港特别行政区', + '香港特别行政区': '香港特别行政区', + '澳门': '澳门特别行政区', + '澳门特别行政区': '澳门特别行政区' + } + + return map[value] || value + }, + + normalizeRecognizedRegionParts(province, city, district) { + let p = this.normalizeMunicipalityName(province) + let c = this.normalizeMunicipalityName(city) + const d = String(district || '').trim() + + const municipalities = [ + '北京市', + '上海市', + '天津市', + '重庆市', + '香港特别行政区', + '澳门特别行政区' + ] + + if (!p && municipalities.includes(c)) { + p = c + } + + if (!c && municipalities.includes(p)) { + c = p + } + + return { + province: p, + city: c, + district: d + } + }, + + buildRegionText(province, city, district) { + let text = '' + if (province) text += province + if (city && city !== province) text += city + if (district) text += district + return text + }, startDependencyInitLock(duration = 800) { this.dependencyInitLock = true @@ -1036,28 +1095,46 @@ const value = String(text || '').trim() if (!value) return true - // 1. 检查是否包含常见的地址特征词 - const addressKeywords = [ - '路', '街', '巷', '道', '号', '栋', '幢', '楼', '层', '室', '单元', - '门', '里', '弄', '小区', '花园', '广场', '大厦', '中心', '苑', '园', - '村', '镇', '乡', '街', '大道', '胡同', '巷子', '店', '场', '城' - ] - const hasKeyword = addressKeywords.some(kw => value.includes(kw)) - // 2. 检查是否有中文 + 数字/字母的乱码模式(例如“165160ugf8oe”) - const hasChinese = /[\u4e00-\u9fa5]/.test(value) - const hasDigitOrLetter = /[a-zA-Z0-9]/.test(value) - const isLikelyGarbage = hasChinese && hasDigitOrLetter && !hasKeyword + // 明显无效词 + if (/(价格|价钱|多少钱|举报|投诉|建议|咨询|测试|示例|比如|比方|假设|无效|乱填)/i.test(value)) { + return true + } - // 3. 检查纯英文长串 - const longPureLetters = /\b[A-Za-z]{4,}\b/.test(value) && !/[A-Za-z]\d|\d[A-Za-z]/.test(value) + // 纯数字或纯字母长串 + if (/^\d{6,}$/.test(value) || /^[A-Za-z]{6,}$/.test(value)) { + return true + } - // 4. 检查明显的无效词(原有逻辑) - const hasInvalidNoise = /(价格|价钱|多少钱|举报|投诉|建议|咨询|测试|示例|比如|比方|假设|无效|乱填)/i.test(value) + // 特殊符号过多 + const specialChars = (value.match(/[^\u4e00-\u9fa5a-zA-Z0-9]/g) || []).length + if (specialChars / value.length > 0.3) { + return true + } - if (hasInvalidNoise && !hasKeyword && !hasDigitOrLetter) return true - if (longPureLetters && !hasKeyword && !hasDigitOrLetter) return true - if (isLikelyGarbage) return true - if (!hasChinese && !hasDigitOrLetter && !hasKeyword) return true + // 长度过短 + if (value.length < 2) { + return true + } + + // 新增:长英文乱码片段 + const hasLongLetterChunk = /[A-Za-z]{6,}/.test(value) + + // 新增:英文字母占比过高 + const letterCount = (value.match(/[A-Za-z]/g) || []).length + const letterRatio = value.length ? (letterCount / value.length) : 0 + + // 是否包含正常地址特征 + const hasAddressFeature = this.hasStrongAddressFeature(value) + + // 既有长英文串,又没有门牌/楼栋/道路等地址特征,判为无效 + if (hasLongLetterChunk && !hasAddressFeature) { + return true + } + + // 英文占比过高,且没有地址特征,也判为无效 + if (letterRatio > 0.35 && !hasAddressFeature) { + return true + } return false }, @@ -1076,31 +1153,32 @@ validateRecognizedDetail(detailText, geo, requireKeyword = true) { const cleaned = String(detailText || '').trim() - if (!cleaned) { return { valid: false, cleaned: '', reason: 'empty' } } - // 要求必须包含地址特征词(除非地理编码级别极高) - const hasKeyword = /(路|街|巷|道|号|栋|幢|楼|层|室|单元|门|里|弄|小区|花园|广场|大厦|中心|苑|园|村|镇|乡)/.test(cleaned) - if (requireKeyword && !hasKeyword) { - return { valid: false, cleaned: '', reason: 'no_address_keyword' } - } - + // 先做详细地址本身的有效性判断 if (this.isLikelyInvalidRecognizedDetail(cleaned)) { - return { valid: false, cleaned: '', reason: 'invalid_text' } + return { valid: false, cleaned: '', reason: 'invalid_detail' } } + // 有些地址虽然没有“号栋室”,但高德返回到了较精确层级,也可以放行 + const geoLevel = String(geo?.level || '').trim() + const preciseLevel = this.isPreciseGeocodeLevel(geoLevel) + const hasAddressFeature = this.hasStrongAddressFeature(cleaned) + + // 没有地理编码结果,直接无效 if (!geo) { return { valid: false, cleaned: '', reason: 'geocode_failed' } } - // 允许低精度级别但带关键词的地址(比如“某某村”) - if (!this.isPreciseGeocodeLevel(geo.level) && !hasKeyword) { - return { valid: false, cleaned: '', reason: 'low_precision' } + // 需要至少满足一项:有明确地址特征,或高德定位层级较精确 + if (hasAddressFeature || preciseLevel) { + return { valid: true, cleaned, reason: '' } } - return { valid: true, cleaned, reason: '' } + // 否则认为地区虽然能定位,但详细地址本身不够可信 + return { valid: false, cleaned: '', reason: 'detail_not_precise' } }, cleanRecognizedDetail(detail, province, city, district, town) { @@ -1136,29 +1214,23 @@ }, buildExactRecognizedDetail(hit, sanitizedText, province, city, district) { - const directDetail = ( - hit?.detail || - hit?.address || - hit?.addr || - hit?.full_address_detail || - hit?.address_detail || - '' - ).trim() + const directDetail = (hit?.detail || hit?.address || hit?.addr || hit?.full_address_detail || hit?.address_detail || '').trim(); - // 接口已经给了明确详细地址,就直接返回 - if (directDetail) { - return directDetail - } - - // 接口没给详细地址时,再从原始文本里扣掉省市区 - return this.stripLeadingRegionParts( + // 计算从原始文本中扣除省市区后的详细地址 + const stripped = this.stripLeadingRegionParts( sanitizedText, - [ - province || '', - city && city !== province ? city : '', - district || '' - ] - ) + [province, city, district].filter(Boolean) + ).trim(); + + // 如果 directDetail 存在且长度合理,优先使用;否则使用 stripped + if (directDetail && directDetail.length >= 4) { + // 如果 stripped 更长,且 directDetail 是 stripped 的前缀,说明接口返回的不完整,使用 stripped + if (stripped.length > directDetail.length && stripped.startsWith(directDetail)) { + return stripped; + } + return directDetail; + } + return stripped; }, getRecognizeLevel(hit, detailText, rawText) { @@ -1457,9 +1529,12 @@ stripLeadingRegionParts(text, parts = []) { let result = String(text || '').trim() - const validParts = parts - .map(item => String(item || '').trim()) - .filter(Boolean) + + const validParts = [...new Set( + parts + .map(item => String(item || '').trim()) + .filter(Boolean) + )].sort((a, b) => b.length - a.length) if (!result || !validParts.length) return result @@ -1500,9 +1575,7 @@ } const hasNetwork = await this.ensureRecognizeNetworkAvailable() - if (!hasNetwork) { - return - } + if (!hasNetwork) return const sanitizedText = this.sanitizeRawAddress(originalRawText) const removedNoise = sanitizedText !== originalRawText @@ -1531,15 +1604,18 @@ console.log('智能识别标准化结果:', hit) - const province = (hit.province_name || '').trim() - const city = (hit.city_name || '').trim() - const district = (hit.county_name || '').trim() + const normalizedRegion = this.normalizeRecognizedRegionParts( + hit.province_name, + hit.city_name, + hit.county_name + ) + + const province = normalizedRegion.province + const city = normalizedRegion.city + const district = normalizedRegion.district const town = (hit.town_name || '').trim() - let regionText = '' - if (province) regionText += province - if (city && city !== province) regionText += city - if (district) regionText += district + const regionText = this.buildRegionText(province, city, district) if (!regionText) { uni.hideLoading() @@ -1591,8 +1667,8 @@ geo = fullGeoResult.data - // 使用增强版验证,要求详细地址必须包含地址特征词(除非地理编码为精确级别且由接口直接返回) - // 这里先尝试用候选详细地址验证 + // ========== 改进1:使用增强的 validateRecognizedDetail ========== + // 该方法内部会结合地理编码级别和强/弱关键词进行判断 const detailDecision = this.validateRecognizedDetail(detailCandidate, geo, true) detailRejected = !detailDecision.valid && !!detailCandidate detailText = detailDecision.valid ? detailDecision.cleaned : '' @@ -1606,8 +1682,7 @@ return } geo = regionGeoResult.data - // 地区定位成功,但详细地址无效,标记详细地址留空 - detailText = '' + detailText = '' // 详细地址留空 } if (geo) { @@ -1650,6 +1725,7 @@ detailRejected ) + // 低可信度或明显无效 → 只提示,不回填 if (level === 'low' || shouldOnlyToast) { uni.hideLoading() @@ -1678,39 +1754,8 @@ return } - // ====== 回填到页面下方的位置 ====== - 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 - // ================================= - - 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' - }) - } + // 正常回填 + this.commitRecognizedResult(province, city, district, regionText, detailText, geo, level, removedNoise, geocodeOk, hit, detailRejected) } catch (error) { console.error('智能识别失败:', error) uni.hideLoading() @@ -1727,6 +1772,39 @@ } }, + commitRecognizedResult(province, city, district, regionText, detailText, geo, level, removedNoise, geocodeOk, hit, detailRejected) { + 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 + + 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' + }) + } + }, + // 更新地图中心 updateMapCenter(longitude, latitude, noReverse = false) { const lng = parseFloat(longitude)