定位获取城市功能,需要微信地图的key?

This commit is contained in:
2026-07-06 14:02:07 +08:00
parent 67af6d7d53
commit c87d79109a
18 changed files with 792 additions and 70 deletions
+25
View File
@@ -70,6 +70,31 @@ export const DEFAULT_REGION: RegionSelection = {
district: '金水区',
};
export const FALLBACK_CITY_REGION: RegionSelection = {
province: '河南省',
city: '郑州市',
district: REGION_ALL,
};
export function regionFromGeo(province: string, city: string, district?: string): RegionSelection {
const cityName = city.endsWith('市') ? city : `${city}`;
const provinceInTree = PROVINCES.includes(province) ? province : DEFAULT_REGION.province;
const cities = getCities(provinceInTree);
const matchedCity = cities.includes(cityName)
? cityName
: cities.find((c) => c.replace(/市$/, '') === city.replace(/市$/, '')) ?? cityName;
const districts = getDistricts(provinceInTree, matchedCity);
const districtName =
district && districts.includes(district)
? district
: REGION_ALL;
return normalizeRegionSelection({
province: provinceInTree,
city: cities.includes(matchedCity) ? matchedCity : matchedCity,
district: districtName,
});
}
/** 校验已选地区是否仍存在于数据源中 */
export function normalizeRegionSelection(selection: RegionSelection): RegionSelection {
if (selection.province === REGION_ALL) {
+144
View File
@@ -0,0 +1,144 @@
import { getWechatLocationDetailed } from '@dukang/weixin-sdk';
import { apiBase } from './api';
import { weixinSdk } from './weixin';
import { regionFromGeo, type RegionSelection } from './region-data';
export const GPS_CITY_STORAGE_KEY = 'dukang_gps_city';
export const CITY_STORAGE_KEY = 'dukang_selected_city';
export const FALLBACK_CITY_CODE = '410100';
export type ResolvedUserCity = {
province: string;
city: string;
district: string;
cityCode?: string;
cityName?: string;
openCity: boolean;
region: RegionSelection;
displayCity: string;
};
type GpsCityCache = ResolvedUserCity & { timestamp: number };
function readCache(): GpsCityCache | null {
try {
const raw = sessionStorage.getItem(GPS_CITY_STORAGE_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw) as GpsCityCache;
if (Date.now() - parsed.timestamp > 30 * 60 * 1000) return null;
return parsed;
} catch {
return null;
}
}
function writeCache(data: ResolvedUserCity) {
sessionStorage.setItem(
GPS_CITY_STORAGE_KEY,
JSON.stringify({ ...data, timestamp: Date.now() } satisfies GpsCityCache),
);
}
async function reportLocationToServer(payload: {
latitude?: number;
longitude?: number;
sdk: 'jssdk' | 'geolocation';
status: 'success' | 'fail';
errMsg?: string;
}) {
const token = localStorage.getItem('accessToken');
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Client-App': 'USER_H5',
};
if (token) headers.Authorization = `Bearer ${token}`;
const res = await fetch(`${apiBase}/common/wechat/location`, {
method: 'POST',
headers,
body: JSON.stringify(payload),
});
const json = await res.json();
if (json.code !== 0) {
throw new Error(json.message || '定位上报失败');
}
return json.data as {
province?: string;
city?: string;
district?: string;
cityCode?: string;
cityName?: string;
openCity?: boolean;
};
}
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,
};
}
/** 获取并解析用户当前城市(微信 JSSDK 优先),失败返回 null */
export async function resolveUserCity(force = false): Promise<ResolvedUserCity | null> {
if (!force) {
const cached = readCache();
if (cached) return cached;
}
const outcome = await getWechatLocationDetailed({
apiBase,
clientApp: 'USER_H5',
getAccessToken: () => localStorage.getItem('accessToken'),
});
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);
if (resolved.openCity && resolved.cityCode) {
localStorage.setItem(CITY_STORAGE_KEY, resolved.cityCode);
}
}
return resolved;
} catch {
return null;
}
}
export function syncCityCodeFromGps(resolved: ResolvedUserCity) {
if (resolved.openCity && resolved.cityCode) {
localStorage.setItem(CITY_STORAGE_KEY, resolved.cityCode);
}
}