Files
cheflinkuser/src/pages-user/pages/search-address/index.vue
T
2026-03-17 12:03:54 +08:00

459 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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>
<z-paging>
<template #top>
<navbar/>
<view class="bg-white px-30rpx py-26rpx">
<view class="flex items-center h-86rpx px-28rpx bg-#F6F7F9 rounded-20rpx">
<wd-input v-model="mapSearchKeyword" :no-border="true" :placeholder="t('common.placeholder.pleaseEnter')"
custom-class="!w-full !bg-transparent" placeholder-class="text-#9B9CA0 text-28rpx">
<template #prefix>
<view class="w-12rpx h-12rpx rounded-50% bg-#F97C34"></view>
</template>
</wd-input>
</view>
<view class="" @click="handleUseLocation">
<view class="text-32rpx text-#333 font-500 my-30rpx">{{ t('common.useMyCurrentLocation') }}</view>
<view class="text-28rpx text-#333 font-500 flex items-center pb-20rpx pl-24rpx">
<view class="i-carbon:location-current text-32rpx mr-14rpx mt-4rpx"></view>
{{ t('common.useCurrentLocation') }}
</view>
</view>
</view>
</template>
<view :change:prop="mapRenderjs.searchPlace" :prop="querySearch" class="bg-#f5f5f5">
<view class="px-22rpx py-20rpx">
<template v-if="placesLength === 0">
<view class="center py-100rpx">
<image class="w-250rpx h-250rpx" src="@img/chef/100.png"></image>
</view>
</template>
<template v-else>
<view class="rounded-36rpx bg-white">
<template v-for="(item,index) in logicStore.placesList" :key="index">
<view :class="[index === logicStore.placesList.length - 1 ? '' : 'border-bottom']"
class="px-22rpx pb-30rpx pt-34rpx"
@click="handleClickLocation(item)">
<!-- <view >{{ item }}</view> -->
<view class="text-#000 text-26rpx font-bold">{{ item.displayName }}</view>
<view class="text-#9B9CA0 text-24rpx flex-center-sb">
<view>{{ item.formattedAddress }}</view>
</view>
</view>
</template>
</view>
</template>
</view>
<view id="map" :change:prop="mapRenderjs.initMap" :prop="mapDataComputed" :user-location="userLocation"
class="absolute left-0 bottom-0 w-0 h-0"></view>
</view>
</z-paging>
</template>
<script lang="ts" setup>
import Config from "@/config";
import {useLogicStore} from "@/pages-user/store/logic";
import {useUserStore} from "@/store";
import { getCurrentInstance } from "vue";
const {t, locale} = useI18n();
const userStore = useUserStore()
const logicStore = useLogicStore()
let openerEventChannel: any = null
onLoad(() => {
// 统一通过 getOpenerEventChannel 获取导航传入的 eventChannel
const pages = getCurrentPages() as any[]
const page = pages[pages.length - 1]
openerEventChannel = page?.getOpenerEventChannel?.() || null
})
// 生命周期:清空地址列表
onMounted(() => {
logicStore.clearPlacesList()
})
const placesLength = computed(() => {
return logicStore.placesList.length;
});
const userLocation = computed(() => ({
longitude: userStore.location.longitude,
latitude: userStore.location.latitude
}));
watch(
() => logicStore.searchLoading,
(newValue) => {
if (newValue) {
uni.showLoading({
title: 'Loading...',
mask: true,
});
} else {
uni.hideLoading();
}
},
{immediate: true}
);
// 地图搜索关键词
const mapSearchKeyword = ref<string>('');
const querySearch = computed(() => {
return {
keyword: mapSearchKeyword.value,
}
})
function handleClickLocation(item: any) {
console.log('item', item)
openerEventChannel?.emit?.('chooseAddress', item)
uni.navigateBack()
}
// 使用当前位置
function handleUseLocation() {
// 检查是否获取到了当前定位
if (!userStore.location.longitude || !userStore.location.latitude) {
// 尝试再次获取定位
uni.getLocation({
isHighAccuracy: true,
type: 'gcj02',
geocode: true,
success: async (res) => {
getCityName(res.latitude, res.longitude)
},
fail: (err) => {
const settings = uni.getAppAuthorizeSetting()
console.log(settings)
if (settings.locationAuthorized === 'denied') {
uni.showToast({
title: t('common.prompt.please-authorize-location-information'),
icon: 'none'
})
setTimeout(()=> {
uni.openAppAuthorizeSetting()
})
}
},
})
} else {
openerEventChannel?.emit?.('chooseAddress', {
displayName: userStore.location.location,
formattedAddress: userStore.location.formattedAddress || '',
location: {
lng: userStore.location.longitude,
lat: userStore.location.latitude
}
})
uni.navigateBack()
}
}
function getCityName(latitude: number, longitude: number) {
const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${Config.googleMapKey}&language=${locale.value === 'zh-Hans' ? 'zh-CN' : locale.value}`;
uni.request({
url,
method: 'GET',
timeout: 10000,
success: (res: any) => {
const results = res.data?.results || [];
console.log('geocode results:', results);
if (!results.length) {
return handleFail();
}
const addr = results[0]; // 最高匹配度
const components = addr.address_components || [];
// 提取城市名的工具函数
const pickAddress = (types: string[]) => {
return components.find((item) => item.types.some((t) => types.includes(t)));
};
// 寻找城市名(多层兜底)
let cityObj =
pickAddress(["locality"]) ||
pickAddress(["administrative_area_level_2"]) ||
pickAddress(["administrative_area_level_1"]) ||
pickAddress(["political"]);
const cityName = cityObj?.long_name || null;
// 从 Google 获取完整地址
const formattedAddress = addr.formatted_address || cityName || "";
console.log("city:", cityObj);
console.log("formattedAddress:", formattedAddress);
if (!cityName) {
return handleFail();
}
// 存入 store
userStore.location = {
location: cityName,
formattedAddress,
longitude,
latitude
};
// 回传上一页(eventChannel
openerEventChannel?.emit?.('chooseAddress', {
displayName: cityName,
formattedAddress,
location: { lng: longitude, lat: latitude }
});
uni.navigateBack();
},
fail: () => handleFail()
});
function handleFail() {
uni.showToast({
title: t('common.prompt.getLocationFailed'),
icon: 'none'
});
}
}
const mapDataComputed = computed(() => {
return {
language: locale.value,
lng: userStore.location.longitude || 174.7633,
lat: userStore.location.latitude || -36.8485,
}
})
</script>
<script lang="ts">
import { defineComponent } from "vue";
import { useLogicStore } from "@/pages-user/store/logic";
export default defineComponent({
methods: {
onMapSearchResult(places: any) {
const logicStore = useLogicStore()
console.log(places, '接收到的搜索结果')
if (places && places.length > 0) {
// 保持与原先一致的解析逻辑
const parsedPlaces = places
.map((placeArray: any) => {
const placeData = placeArray[0]?.[0]
if (!placeData) return null
const placeId = placeData[1]
const formattedAddress = placeData[8] || ''
const locationArray = placeData[11]
const displayNameArray = placeData[27]
return {
id: placeId,
displayName: displayNameArray?.[0] || formattedAddress,
formattedAddress: formattedAddress,
location: locationArray
? {
lat: locationArray[0],
lng: locationArray[1],
}
: null,
}
})
.filter(Boolean)
console.log('解析后的地址列表', parsedPlaces)
logicStore.setPlacesList(parsedPlaces)
} else {
logicStore.setPlacesList([])
}
},
onMapNotLoaded() {
uni.showToast({
title: 'Map is not loaded yet, please wait',
icon: 'none',
})
},
onMapSearchTimeout() {
uni.showToast({
title: 'Search timeout, please try again',
icon: 'none',
})
},
onMapSearchLoading(_isLoading: boolean) {
// 如需联动 loading,可在此处驱动 store
},
},
})
</script>
<script lang="renderjs" module="mapRenderjs">
import {Loader} from "@googlemaps/js-api-loader"
import * as R from 'ramda'
import Config from '@/config'
import {debounce} from "throttle-debounce";
let map = null
let mapLoaded = false; // 地图加载完成标志
// @ts-ignore
export default {
data() {
return {
region: "US",
lan: 'en',
google: null,
canvas: null,
center: null,
dotLottie: null,
marker: null,
markerViewList: [],
AdvancedMarkerElement: null,
lng: -77.0365,
lat: 38.8977,
userLocation: {
longitude: 0,
latitude: 0
}
}
},
props: {
userLocation: Object
},
mounted() {
console.log('mounted mapRenderjs')
mapLoaded = false; // 重置地图加载完成标志
// !map&&this.initMap()
this.searchPlace = debounce(200, this.searchPlace)
},
methods: {
initMap({lng, lat, language}) {
console.log('initMap', lng, lat)
console.log('当前系统语言', language)
this.lan = language === 'zh-Hans' ? 'zh-CN' : 'en'
this.region = language === 'zh-Hans' ? 'CN' : 'US'
this.lng = language === 'zh-Hans' ? 116.4074 : -77.0365
this.lat = language === 'zh-Hans' ? 39.9042 : 38.8977
console.log('当前系统语言', this.lan)
console.log('当前系统区域', this.region)
this.$nextTick(() => {
const loader = new Loader({
apiKey: Config.googleMapKey,
version: "weekly",
region: this.region, // 设置为美国
language: this.lan,
});
loader.load().then(async (google) => {
const {Map} = await google.maps.importLibrary("maps")
this.google = google
console.log('google', google.maps)
// 地图相关配置
// 指定自定义地图样式的 ID。
// center: 地图初始中心点的经纬度(lat, lng)。
// zoom: 地图初始缩放级别。
// fullscreenControl: 是否显示全屏按钮(false 表示不显示)。
// cameraControl: 是否显示相机控制(false 表示不显示)。
// disableDefaultUI: 是否禁用所有默认 UI 控件(true 表示全部禁用)。
// gestureHandling: 设置手势操作方式("greedy" 表示允许所有手势操作)
const mapOptions = {
mapId: 'ff2268c265ce7a40',
center: {
lat: this.lat,
lng: this.lng,
},
zoom: 12,
fullscreenControl: false,
cameraControl: false,
disableDefaultUI: true,
gestureHandling: "greedy",
};
map = new Map(document.getElementById("map"), mapOptions);
mapLoaded = true; // 地图加载完成
});
})
},
async searchPlace({keyword}) {
console.log('搜索关键词', keyword);
if (!keyword) {
this.$ownerInstance.callMethod('onMapSearchResult', [])
return;
}
if (!mapLoaded) {
console.log('地图未加载完成,无法搜索');
this.$ownerInstance.callMethod('onMapNotLoaded');
return;
}
if (!map || !this.google) {
return
}
this.$ownerInstance.callMethod('onMapSearchLoading', true);
let timeoutId;
try {
// 设置搜索超时
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
this.$ownerInstance.callMethod('onMapSearchLoading', false);
reject(new Error('Search timeout'));
}, 10000); // 10 秒超时
});
const {Place, SearchByTextRankPreference} = await this.google.maps.importLibrary("places");
const request = {
textQuery: keyword,
fields: ['id', 'displayName', 'location', 'formattedAddress', 'addressComponents'],
locationBias: {lat: this.lat, lng: this.lng},
language: this.lan,
maxResultCount: 20,
region: this.region,
rankPreference: SearchByTextRankPreference.RELEVANCE
};
const searchPromise = Place.searchByText(request);
const {places} = await Promise.race([searchPromise, timeoutPromise]);
console.log('地图搜索结果原始数据', places);
// 将 Google Places API 返回的对象转换为可序列化的数组格式
const serializedPlaces = places.map(place => {
// 提取需要的字段并转换为普通对象
return [[{
1: place.id,
8: place.formattedAddress || '',
9: place.addressComponents || [],
11: place.location ? [place.location.lat(), place.location.lng()] : null,
27: place.displayName ? [
typeof place.displayName === 'string' ? place.displayName : place.displayName.text,
place.displayName.languageCode || this.lan
] : null
}]];
});
console.log('序列化后的搜索结果', serializedPlaces);
this.$ownerInstance.callMethod('onMapSearchResult', serializedPlaces);
} catch (e) {
console.log('搜索错误原因', e);
if (e.message === 'Search timeout') {
this.$ownerInstance.callMethod('onMapSearchTimeout');
}
this.$ownerInstance.callMethod('onMapSearchResult', []);
} finally {
clearTimeout(timeoutId);
this.$ownerInstance.callMethod('onMapSearchLoading', false);
}
}
},
}
</script>
<style>
page {
background-color: #f5f5f5;
}
</style>
<style lang="scss" scoped>
</style>