新增:IP定位服务抽取和请求去重优化
This commit is contained in:
@@ -6,10 +6,12 @@
|
|||||||
import { CityPicker, Modal } from '../ui/index.js';
|
import { CityPicker, Modal } from '../ui/index.js';
|
||||||
import { Validator, Formatter } from '../utils/index.js';
|
import { Validator, Formatter } from '../utils/index.js';
|
||||||
import { DraftManager, FormIdGenerator, UserCache } from '../core/index.js';
|
import { DraftManager, FormIdGenerator, UserCache } from '../core/index.js';
|
||||||
import { ASSET_CONFIG, BASIC_INFO_CONFIG, PROVINCE_CITY_DATA, CACHE_CONFIG } from '../config/index.js';
|
import { ASSET_CONFIG, BASIC_INFO_CONFIG, PROVINCE_CITY_DATA, CACHE_CONFIG, API_CONFIG } from '../config/index.js';
|
||||||
import { showToast } from '../ui/toast.js';
|
import { showToast } from '../ui/toast.js';
|
||||||
import { AreaService } from '../services/area.service.js';
|
import { AreaService } from '../services/area.service.js';
|
||||||
|
import { getLocationService } from '../services/location.service.js';
|
||||||
import { AuthFlowService, AUTH_STATUS } from '../services/index.js';
|
import { AuthFlowService, AUTH_STATUS } from '../services/index.js';
|
||||||
|
import { ApiClient } from '../core/api.js';
|
||||||
|
|
||||||
export class BasicInfoPage {
|
export class BasicInfoPage {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -71,6 +73,9 @@ export class BasicInfoPage {
|
|||||||
this.renderForm();
|
this.renderForm();
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.updateProgress();
|
this.updateProgress();
|
||||||
|
|
||||||
|
// IP定位自动填充城市
|
||||||
|
this.autoFillCityByLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -687,29 +692,27 @@ export class BasicInfoPage {
|
|||||||
|
|
||||||
// 如果没有 h5Urls,显示成功提示
|
// 如果没有 h5Urls,显示成功提示
|
||||||
if (!h5Urls || h5Urls.length === 0) {
|
if (!h5Urls || h5Urls.length === 0) {
|
||||||
|
// 有 redirectUrl 跳转到指定地址,否则返回首页
|
||||||
|
const finalUrl = redirectUrl || window.location.href.split('?')[0];
|
||||||
|
const btnText = redirectUrl ? '立即跳转' : '返回首页';
|
||||||
|
|
||||||
authContainer.innerHTML = `
|
authContainer.innerHTML = `
|
||||||
<div class="auth-complete-section">
|
<div class="auth-complete-section">
|
||||||
<div class="auth-complete-icon">✓</div>
|
<div class="auth-complete-icon">✓</div>
|
||||||
<div class="auth-complete-title">信息提交成功</div>
|
<div class="auth-complete-title">信息提交成功</div>
|
||||||
<div class="auth-complete-desc">您的申请已提交成功!</div>
|
<div class="auth-complete-desc">您的申请已提交成功!</div>
|
||||||
${redirectUrl ? `
|
|
||||||
<div class="auth-countdown">
|
<div class="auth-countdown">
|
||||||
<span id="countdownSeconds">5</span> 秒后自动跳转...
|
<span id="countdownSeconds">5</span> 秒后自动跳转...
|
||||||
</div>
|
</div>
|
||||||
<button class="auth-redirect-btn" id="redirectNowBtn">立即跳转</button>
|
<button class="auth-redirect-btn" id="redirectNowBtn">${btnText}</button>
|
||||||
` : `
|
|
||||||
<button class="auth-redirect-btn" onclick="window.location.reload()">返回首页</button>
|
|
||||||
`}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const mainContainer = document.querySelector('.container') || document.body;
|
const mainContainer = document.querySelector('.container') || document.body;
|
||||||
mainContainer.insertBefore(authContainer, mainContainer.firstChild);
|
mainContainer.insertBefore(authContainer, mainContainer.firstChild);
|
||||||
|
|
||||||
// 如果有 redirectUrl,启动倒计时
|
// 启动 5 秒倒计时
|
||||||
if (redirectUrl) {
|
this.startFinalCountdown(finalUrl);
|
||||||
this.startFinalCountdown(redirectUrl);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -919,6 +922,82 @@ export class BasicInfoPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过IP定位自动填充城市
|
||||||
|
*/
|
||||||
|
async autoFillCityByLocation() {
|
||||||
|
try {
|
||||||
|
// 如果已经有城市值,跳过
|
||||||
|
if (this.basicInfoValues.city) {
|
||||||
|
console.log('[BasicInfoPage] 城市已存在,跳过IP定位填充');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[BasicInfoPage] 开始IP定位...');
|
||||||
|
const locationService = getLocationService();
|
||||||
|
const location = await locationService.getLocation();
|
||||||
|
|
||||||
|
if (location) {
|
||||||
|
const { province, city } = location;
|
||||||
|
console.log('[BasicInfoPage] IP定位成功:', province, city);
|
||||||
|
|
||||||
|
// 查找城市代码
|
||||||
|
const cityCode = await this.findCityCode(province, city);
|
||||||
|
|
||||||
|
if (cityCode) {
|
||||||
|
// 自动填充城市
|
||||||
|
this.handleCityConfirm({
|
||||||
|
value: city,
|
||||||
|
province: province,
|
||||||
|
city: city,
|
||||||
|
provinceCode: cityCode.provinceCode,
|
||||||
|
cityCode: cityCode.cityCode
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[BasicInfoPage] 城市自动填充成功:', city);
|
||||||
|
} else {
|
||||||
|
console.warn('[BasicInfoPage] 未找到城市代码:', province, city);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[BasicInfoPage] IP定位失败: 未获取到位置信息');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[BasicInfoPage] IP定位异常:', error);
|
||||||
|
// 静默失败,不影响用户正常使用
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找城市代码
|
||||||
|
*/
|
||||||
|
async findCityCode(provinceName, cityName) {
|
||||||
|
try {
|
||||||
|
// 获取省份数据
|
||||||
|
const provinces = await AreaService.getProvinces();
|
||||||
|
const province = provinces.find(p => p.name === provinceName);
|
||||||
|
|
||||||
|
if (!province) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取城市数据
|
||||||
|
const cities = await AreaService.getCities(province.code);
|
||||||
|
const city = cities.find(c => c.name === cityName && c.code.length === 4);
|
||||||
|
|
||||||
|
if (city) {
|
||||||
|
return {
|
||||||
|
provinceCode: province.code,
|
||||||
|
cityCode: city.code
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[BasicInfoPage] 查找城市代码失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 销毁页面
|
* 销毁页面
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,23 +13,50 @@ const CACHE_DURATION = 10 * 60 * 1000; // 10分钟
|
|||||||
* 区域服务
|
* 区域服务
|
||||||
*/
|
*/
|
||||||
export class AreaService {
|
export class AreaService {
|
||||||
|
// 正在进行的请求缓存
|
||||||
|
static pendingRequests = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取区域列表
|
* 获取区域列表
|
||||||
* @param {string} provincecode - 省份代码(可选)
|
* @param {string} provincecode - 省份代码(可选)
|
||||||
* @returns {Promise<Array>} 区域列表 [{code, name}]
|
* @returns {Promise<Array>} 区域列表 [{code, name}]
|
||||||
*/
|
*/
|
||||||
static async getAreaList(provincecode) {
|
static async getAreaList(provincecode) {
|
||||||
// 检查缓存
|
|
||||||
const cacheKey = `${CACHE_KEY_PREFIX}${provincecode || 'all'}`;
|
const cacheKey = `${CACHE_KEY_PREFIX}${provincecode || 'all'}`;
|
||||||
const cached = localStorage.getItem(cacheKey);
|
|
||||||
|
|
||||||
|
// 检查 localStorage 缓存
|
||||||
|
const cached = localStorage.getItem(cacheKey);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
const { data, timestamp } = JSON.parse(cached);
|
const { data, timestamp } = JSON.parse(cached);
|
||||||
if (Date.now() - timestamp < CACHE_DURATION) {
|
if (Date.now() - timestamp < CACHE_DURATION) {
|
||||||
|
console.log(`[AreaService] 使用缓存 ${provincecode || 'all'}`);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否有正在进行的请求
|
||||||
|
if (this.pendingRequests.has(cacheKey)) {
|
||||||
|
console.log(`[AreaService] 等待正在进行的请求 ${provincecode || 'all'}`);
|
||||||
|
return this.pendingRequests.get(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新请求
|
||||||
|
const requestPromise = this.doGetAreaList(provincecode, cacheKey);
|
||||||
|
this.pendingRequests.set(cacheKey, requestPromise);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await requestPromise;
|
||||||
|
} finally {
|
||||||
|
// 请求完成后清除缓存
|
||||||
|
this.pendingRequests.delete(cacheKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行实际的区域列表请求
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static async doGetAreaList(provincecode, cacheKey) {
|
||||||
try {
|
try {
|
||||||
// 构建请求参数
|
// 构建请求参数
|
||||||
const params = {};
|
const params = {};
|
||||||
@@ -37,6 +64,8 @@ export class AreaService {
|
|||||||
params.provincecode = provincecode;
|
params.provincecode = provincecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[AreaService] 请求区域数据 ${provincecode || 'all'}`);
|
||||||
|
|
||||||
// 使用 ApiClient 发送请求
|
// 使用 ApiClient 发送请求
|
||||||
const result = await ApiClient.get(API_CONFIG.ENDPOINTS.AREA_LIST, params);
|
const result = await ApiClient.get(API_CONFIG.ENDPOINTS.AREA_LIST, params);
|
||||||
|
|
||||||
@@ -46,7 +75,7 @@ export class AreaService {
|
|||||||
|
|
||||||
const areaList = result.result || [];
|
const areaList = result.result || [];
|
||||||
|
|
||||||
// 保存到缓存
|
// 保存到 localStorage 缓存
|
||||||
localStorage.setItem(cacheKey, JSON.stringify({
|
localStorage.setItem(cacheKey, JSON.stringify({
|
||||||
data: areaList,
|
data: areaList,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
|
|||||||
124
src/js/services/location.service.js
Normal file
124
src/js/services/location.service.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* 定位服务
|
||||||
|
* 提供全局 IP 定位功能,避免重复请求
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { API_CONFIG } from '../config/api.config.js';
|
||||||
|
import { ApiClient } from '../core/api.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定位服务(单例模式)
|
||||||
|
*/
|
||||||
|
export class LocationService {
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
// 定位缓存
|
||||||
|
locationCache = null;
|
||||||
|
|
||||||
|
// 正在定位的 Promise
|
||||||
|
locatingPromise = null;
|
||||||
|
|
||||||
|
// 缓存有效期(10分钟)
|
||||||
|
CACHE_DURATION = 10 * 60 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单例实例
|
||||||
|
*/
|
||||||
|
static getInstance() {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new LocationService();
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前定位(带缓存)
|
||||||
|
* @returns {Promise<Object>} 定位结果 {province, city, code}
|
||||||
|
*/
|
||||||
|
async getLocation() {
|
||||||
|
// 如果有缓存且未过期,直接返回
|
||||||
|
if (this.locationCache) {
|
||||||
|
const { data, timestamp } = this.locationCache;
|
||||||
|
if (Date.now() - timestamp < this.CACHE_DURATION) {
|
||||||
|
console.log('[LocationService] 使用缓存定位:', data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在定位,返回同一个 Promise
|
||||||
|
if (this.locatingPromise) {
|
||||||
|
console.log('[LocationService] 定位进行中,等待结果...');
|
||||||
|
return this.locatingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始定位
|
||||||
|
this.locatingPromise = this.doLocation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.locatingPromise;
|
||||||
|
|
||||||
|
// 缓存结果
|
||||||
|
this.locationCache = {
|
||||||
|
data: result,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
// 清除定位 Promise
|
||||||
|
this.locatingPromise = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行 IP 定位
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async doLocation() {
|
||||||
|
try {
|
||||||
|
console.log('[LocationService] 开始 IP 定位...');
|
||||||
|
const result = await ApiClient.get(API_CONFIG.ENDPOINTS.IP_LOCATION);
|
||||||
|
|
||||||
|
if (result.retcode === 0 && result.result) {
|
||||||
|
const { province, city } = result.result;
|
||||||
|
|
||||||
|
if (province && city) {
|
||||||
|
console.log('[LocationService] IP 定位成功:', province, city);
|
||||||
|
return {
|
||||||
|
province,
|
||||||
|
city,
|
||||||
|
code: null // 稍后可以添加城市代码
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('定位失败:未获取到省市信息');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[LocationService] IP 定位失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除缓存
|
||||||
|
*/
|
||||||
|
clearCache() {
|
||||||
|
this.locationCache = null;
|
||||||
|
console.log('[LocationService] 缓存已清除');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置定位结果(手动设置)
|
||||||
|
* @param {Object} location 定位结果 {province, city, code}
|
||||||
|
*/
|
||||||
|
setLocation(location) {
|
||||||
|
this.locationCache = {
|
||||||
|
data: location,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
console.log('[LocationService] 手动设置定位:', location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例获取函数
|
||||||
|
export const getLocationService = () => LocationService.getInstance();
|
||||||
@@ -4,7 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {AreaService} from '../services/area.service.js';
|
import {AreaService} from '../services/area.service.js';
|
||||||
|
import {getLocationService} from '../services/location.service.js';
|
||||||
import {API_CONFIG} from '../config/api.config.js';
|
import {API_CONFIG} from '../config/api.config.js';
|
||||||
|
import {ApiClient} from '../core/api.js';
|
||||||
|
|
||||||
// 热门城市列表(12个核心城市)
|
// 热门城市列表(12个核心城市)
|
||||||
const HOT_CITIES = [
|
const HOT_CITIES = [
|
||||||
@@ -660,14 +662,12 @@ export class CityPicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 调用后端IP定位接口
|
// 使用全局定位服务
|
||||||
const response = await fetch(API_CONFIG.ENDPOINTS.IP_LOCATION);
|
const locationService = getLocationService();
|
||||||
const result = await response.json();
|
const location = await locationService.getLocation();
|
||||||
|
|
||||||
if (result.retcode === 0 && result.result) {
|
if (location) {
|
||||||
const { province, city } = result.result;
|
const {province, city} = location;
|
||||||
|
|
||||||
if (province && city) {
|
|
||||||
console.log('[CityPicker] IP定位成功:', province, city);
|
console.log('[CityPicker] IP定位成功:', province, city);
|
||||||
|
|
||||||
// 查找城市代码
|
// 查找城市代码
|
||||||
@@ -691,8 +691,6 @@ export class CityPicker {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[CityPicker] IP定位失败:', error);
|
console.error('[CityPicker] IP定位失败:', error);
|
||||||
// 定位失败时不显示错误,静默处理
|
// 定位失败时不显示错误,静默处理
|
||||||
|
|||||||
Reference in New Issue
Block a user