import Taro from '@tarojs/taro'; import { getWechatLocationDetailed } from '@dukang/weixin-sdk'; import { API_BASE, CLIENT_APP, getToken, request } from './api'; import { DEFAULT_REGION, regionFromGeo, type RegionSelection } from './region-data'; import { FALLBACK_CITY_CODE } from './product-images'; export const GPS_CITY_STORAGE_KEY = 'dukang_gps_city'; export type ResolvedUserCity = { province: string; city: string; district: string; cityCode?: string; cityName?: string; openCity: boolean; region: RegionSelection; displayCity: string; }; type GpsCityCache = ResolvedUserCity & { timestamp: number }; const FALLBACK_CITY: ResolvedUserCity = { province: DEFAULT_REGION.province, city: DEFAULT_REGION.city, district: DEFAULT_REGION.district, cityCode: FALLBACK_CITY_CODE, cityName: '郑州市', openCity: true, region: DEFAULT_REGION, displayCity: '郑州市', }; let locationPrompted = false; function readCache(): GpsCityCache | null { try { const raw = Taro.getStorageSync(GPS_CITY_STORAGE_KEY); if (!raw) return null; const parsed = JSON.parse(String(raw)) as GpsCityCache; if (Date.now() - parsed.timestamp > 30 * 60 * 1000) return null; return parsed; } catch { return null; } } function writeCache(data: ResolvedUserCity) { try { Taro.setStorageSync( GPS_CITY_STORAGE_KEY, JSON.stringify({ ...data, timestamp: Date.now() } satisfies GpsCityCache), ); } catch { /* ignore */ } } async function reportLocationToServer(payload: { latitude?: number; longitude?: number; sdk: 'jssdk' | 'geolocation'; status: 'success' | 'fail'; errMsg?: string; }) { return request<{ province?: string; city?: string; district?: string; cityCode?: string; cityName?: string; openCity?: boolean; }>('/common/wechat/location', { method: 'POST', data: payload, }); } function toResolved(data: { province?: string; city?: string; district?: string; cityCode?: string; cityName?: string; openCity?: boolean; }): ResolvedUserCity | null { if (!data.province || !data.city) return null; const region = regionFromGeo(data.province, data.city, data.district); const displayCity = data.cityName ?? (data.city.endsWith('市') ? data.city : `${data.city}市`); return { province: data.province, city: data.city, district: data.district ?? '', cityCode: data.cityCode, cityName: data.cityName, openCity: !!data.openCity, region, displayCity, }; } async function promptLocationAuth() { if (locationPrompted) return; locationPrompted = true; await Taro.showModal({ title: '位置授权', content: '需要获取您的位置以展示所在城市的商品与门店', confirmText: '去授权', showCancel: true, }).catch(() => {}); } function getMiniLocation(): Promise { return new Promise((resolve, reject) => { Taro.getLocation({ type: 'gcj02', success: resolve, fail: reject, }); }); } async function resolveViaH5Jssdk(): Promise { const outcome = await getWechatLocationDetailed({ apiBase: API_BASE, clientApp: CLIENT_APP, getAccessToken: () => getToken() || null, }); if (!outcome.location) { await reportLocationToServer({ sdk: outcome.sdk, status: 'fail', errMsg: outcome.errMsg, }).catch(() => {}); return null; } try { const data = await reportLocationToServer({ latitude: outcome.location.latitude, longitude: outcome.location.longitude, sdk: outcome.sdk, status: 'success', }); const resolved = toResolved(data); if (resolved) writeCache(resolved); return resolved; } catch { return null; } } /** 获取并解析用户当前城市;失败返回郑州市兜底 */ export async function resolveUserCity(force = false): Promise { if (!force) { const cached = readCache(); if (cached) return cached; } if (process.env.TARO_ENV === 'h5') { const fromJssdk = await resolveViaH5Jssdk(); return fromJssdk ?? FALLBACK_CITY; } if (process.env.TARO_ENV !== 'weapp') { return FALLBACK_CITY; } try { const loc = await getMiniLocation(); const data = await reportLocationToServer({ latitude: loc.latitude, longitude: loc.longitude, sdk: 'jssdk', status: 'success', }); const resolved = toResolved(data); if (resolved) { writeCache(resolved); return resolved; } } catch (err) { const errMsg = err instanceof Error ? err.message : String(err); const denied = /auth deny|authorize|permission|拒绝/i.test(errMsg); if (denied) { await promptLocationAuth(); } await reportLocationToServer({ sdk: 'jssdk', status: 'fail', errMsg: errMsg.slice(0, 200), }).catch(() => {}); } return FALLBACK_CITY; } export function getCityCodeForCatalog(resolved: ResolvedUserCity): string { return resolved.openCity && resolved.cityCode ? resolved.cityCode : FALLBACK_CITY_CODE; }