import { NearbyUser, AppGlobalData } from '../../types/index' import { api } from '../../utils/api' import { formatDistance, formatLastSeen, getAvatarGradient, getPetEmoji } from '../../utils/format' import { requireLocation } from '../../utils/auth' import { NEARBY_RADIUS_KM } from '../../utils/constants' const DEFAULT_LAT = 31.2304 const DEFAULT_LNG = 121.4737 const app = getApp<{ globalData: AppGlobalData }>() Page({ data: { statusBarHeight: 0, navbarHeight: 44, navbarPaddingRight: 0, tabBarHeight: 56, viewMode: 'map' as 'map' | 'list', mapCenter: { latitude: DEFAULT_LAT, longitude: DEFAULT_LNG }, mapScale: 15, markers: [] as any[], circles: [] as any[], nearbyList: [] as any[], onlineCount: 0, activePet: null as any, loading: false, refreshing: false, hasMore: true, myLocation: null as any, }, onLoad() { const info = wx.getSystemInfoSync() const { statusBarHeight, navbarHeight, navbarPaddingRight } = app.globalData.navBarInfo this.setData({ statusBarHeight, navbarHeight, navbarPaddingRight, tabBarHeight: info.windowHeight - (info.safeArea?.bottom ?? info.windowHeight) + 56, }) this.initLocation() }, onShow() { const tabBar = this.getTabBar() as any tabBar?.setSelected?.(1) }, async initLocation() { try { const loc = await requireLocation() this.setData({ mapCenter: loc, myLocation: loc, circles: [{ latitude: loc.latitude, longitude: loc.longitude, radius: NEARBY_RADIUS_KM * 1000, strokeWidth: 2, strokeColor: 'rgba(255, 79, 145, 0.3)', fillColor: 'rgba(255, 79, 145, 0.05)', }], }) app.globalData.currentLocation = loc this.loadNearby(loc.latitude, loc.longitude) } catch { this.loadNearby(DEFAULT_LAT, DEFAULT_LNG) } }, async loadNearby(lat: number, lng: number) { this.setData({ loading: true }) try { const list = await api.getNearbyPets(lat, lng, NEARBY_RADIUS_KM) const nearbyList = list.map(item => this.enrichItem(item, lat, lng)) const onlineCount = nearbyList.filter(i => i.isOnline).length this.setData({ nearbyList, onlineCount, markers: this.buildMarkers(nearbyList, lat, lng), }) } catch { wx.showToast({ title: '加载附近失败', icon: 'none' }) } finally { this.setData({ loading: false, refreshing: false }) } }, enrichItem(item: NearbyUser, myLat: number, myLng: number): any { const pet = item.pet || item.user?.pets?.[0] return { userId: item.userId, petId: pet?._id, petName: pet?.name || '未知', breed: pet?.breed || '宠物', emoji: pet ? getPetEmoji(pet.species, pet.breed) : '🐾', gradient: getAvatarGradient(item.userId), distanceText: formatDistance(item.distance), isOnline: item.isOnline, lastSeenText: formatLastSeen(item.user?.lastSeen || ''), locationName: item.user?.location || '', latitude: item.location?.latitude || myLat, longitude: item.location?.longitude || myLng, } }, buildMarkers(list: any[], myLat: number, myLng: number): any[] { const markers: any[] = [ { id: 0, latitude: myLat, longitude: myLng, width: 40, height: 40, callout: { content: '我在这', color: '#ffffff', fontSize: 11, borderRadius: 12, bgColor: '#ff4f91', padding: 6, display: 'ALWAYS', }, }, ] list.forEach((item, idx) => { markers.push({ id: idx + 1, latitude: item.latitude, longitude: item.longitude, width: 36, height: 36, callout: { content: item.petName, color: '#272235', fontSize: 11, borderRadius: 12, bgColor: 'rgba(255,255,255,0.92)', padding: 6, display: 'BYCLICK', }, }) }) return markers }, onMarkerTap(e: WechatMiniprogram.CustomEvent) { const id = e.markerId if (id === 0) return const item = this.data.nearbyList[id - 1] if (!item) return this.setData({ activePet: item }) }, onPopupTap() { const pet = this.data.activePet if (!pet) return wx.navigateTo({ url: `/pages/pet-detail/pet-detail?userId=${pet.userId}` }) }, async onGreeting(e: WechatMiniprogram.CustomEvent) { const { uid, pid } = e.currentTarget.dataset if (!uid) return try { await api.sendGreeting(uid, pid) wx.showToast({ title: '打招呼成功!', icon: 'success' }) this.setData({ activePet: null }) } catch { wx.showToast({ title: '发送失败', icon: 'none' }) } }, onPreviewTap(e: WechatMiniprogram.CustomEvent) { const { uid, lat, lng } = e.currentTarget.dataset const item = this.data.nearbyList.find((i: any) => i.userId === uid) if (!item) return this.setData({ mapCenter: { latitude: Number(lat), longitude: Number(lng) }, activePet: item, }) }, onItemTap(e: WechatMiniprogram.CustomEvent) { const uid = e.currentTarget.dataset.uid wx.navigateTo({ url: `/pages/pet-detail/pet-detail?userId=${uid}` }) }, setViewMode(e: WechatMiniprogram.CustomEvent) { const mode = e.currentTarget.dataset.mode as 'map' | 'list' this.setData({ viewMode: mode, activePet: null }) }, onLocateMe() { const loc = this.data.myLocation if (loc) { this.setData({ mapCenter: { ...loc } }) } else { this.initLocation() } }, onZoomIn() { const scale = Math.min(20, this.data.mapScale + 1) this.setData({ mapScale: scale }) }, onZoomOut() { const scale = Math.max(10, this.data.mapScale - 1) this.setData({ mapScale: scale }) }, onRegionChange() {}, onFilter() { wx.showActionSheet({ itemList: ['500m 内', '1km 内', '3km 内', '5km 内'], success: res => { const radii = [0.5, 1, 3, 5] const r = radii[res.tapIndex] const loc = this.data.myLocation if (loc) this.loadNearby(loc.latitude, loc.longitude) }, }) }, async onRefresh() { this.setData({ refreshing: true }) const loc = this.data.myLocation if (loc) await this.loadNearby(loc.latitude, loc.longitude) else this.setData({ refreshing: false }) }, onLoadMore() {}, })