
在全球化Web应用中,自动检测用户所在国家并显示相应的电话前缀(如中国+86)是提升用户体验的重要功能。本文将详细介绍实现这一功能的完整技术方案。

IP定位是目前最常用的方案,优势明显:
服务商 | 免费额度 | 返回电话前缀 | 响应速度 | 可靠性 |
|---|---|---|---|---|
ipapi.co | 1,000次/天 | ✅ 直接返回 | 快 | 高 |
ip-api.com | 不限 | ❌ 需要自己映射 | 中等 | 高 |
GeoIP2 | 有限免费 | ✅ 直接返回 | 快 | 很高(商业级) |
推荐使用 ipapi.co:因为它直接返回电话前缀,无需额外映射。
// 伪代码展示核心逻辑
async function detectUserCountry() {
// 第一层:主要API(ipapi.co)
try {
return await fetchPrimaryAPI();
} catch (error) {
// 第二层:备用API(ip-api.com)
try {
return await fetchFallbackAPI();
} catch (error) {
// 第三层:浏览器语言推测
return detectByBrowserLanguage();
}
}
}const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
// data包含:country_name, country_code, country_calling_codeconst PHONE_PREFIXES = {
'CN': '+86', // 中国
'US': '+1', // 美国
'GB': '+44', // 英国
'JP': '+81', // 日本
'KR': '+82', // 韩国
// ... 其他100+国家
};function detectByBrowserLanguage() {
const language = navigator.language;
if (language.includes('zh-CN')) return 'CN';
if (language.includes('en-US')) return 'US';
// ... 更多语言-国家映射
}用户访问网站
↓
检测sessionStorage缓存
↓
有缓存? → 使用缓存数据
↓ 无缓存
调用主要API(ipapi.co)
↓
成功? → 更新UI并缓存
↓ 失败
调用备用API(ip-api.com)
↓
成功? → 更新UI并缓存
↓ 失败
根据浏览器语言推测
↓
使用默认国家(中国)
↓
更新UI并允许用户手动修改// 使用sessionStorage避免重复请求
function saveToCache(location) {
sessionStorage.setItem('userCountry', JSON.stringify(location));
}
function loadFromCache() {
const cached = sessionStorage.getItem('userCountry');
return cached ? JSON.parse(cached) : null;
}function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
)
]);
}<select id="country-selector" onchange="updatePhonePrefix()">
<option value="CN">中国 (+86)</option>
<option value="US">美国 (+1)</option>
<!-- 更多选项 -->
</select><div class="form-group">
<label>手机号码</label>
<div class="input-group">
<span class="input-group-text" id="phone-prefix">+86</span>
<input type="tel" class="form-control" placeholder="请输入手机号码">
</div>
<small class="text-muted" id="country-info">
检测到您来自中国
</small>
</div>const ERROR_HANDLING_STRATEGY = {
NETWORK_ERROR: '使用缓存或浏览器语言',
API_LIMIT: '切换到备用API',
TIMEOUT: '使用默认值并重试',
INVALID_DATA: '降级到手动选择'
};// 结合时区进行更精确的推测
function detectByTimezone() {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// Asia/Shanghai → CN
// America/New_York → US
}// 根据国家显示相应货币
const CURRENCY_MAP = {
'CN': '¥', 'US': '$', 'GB': '£', 'JP': '¥'
};
// 根据国家使用不同单位
const UNIT_MAP = {
'US': { weight: 'lb', distance: 'mile' },
'CN': { weight: 'kg', distance: 'km' }
};// 根据不同国家显示不同版本
function getPageVariant(countryCode) {
const variants = {
'CN': 'chinese_version',
'US': 'english_version',
'JP': 'japanese_version'
};
return variants[countryCode] || 'default_version';
} // 核心配置
const CONFIG = {
primaryAPI: 'https://ipapi.co/json/',
fallbackAPI: 'https://ip-api.com/json/?fields=country,countryCode',
timeout: 5000, // 5秒超时
defaultCountry: 'CN' // 默认中国
};
// 电话前缀映射表
const PHONE_PREFIXES = {
'CN': '+86', 'US': '+1', 'GB': '+44', 'JP': '+81',
'KR': '+82', 'HK': '+852', 'TW': '+886', 'SG': '+65',
'AU': '+61', 'IN': '+91', 'DE': '+49', 'FR': '+33',
'CA': '+1', 'RU': '+7', 'BR': '+55', 'MX': '+52'
};
// 国家名称映射
const COUNTRY_NAMES = {
'CN': '中国', 'US': '美国', 'GB': '英国', 'JP': '日本',
'KR': '韩国', 'HK': '香港', 'TW': '台湾', 'SG': '新加坡',
'AU': '澳大利亚', 'IN': '印度', 'DE': '德国', 'FR': '法国'
};
// 主检测函数
async function detectCountry() {
showStatus('⏳ 正在检测您的国家...', 'info');
try {
// 尝试主要API
let location = await tryPrimaryDetection();
// 如果失败,尝试备用方案
if (!location) {
location = await tryFallbackDetection();
}
// 更新UI
updateUI(location);
saveToCache(location);
} catch (error) {
console.warn('检测失败:', error);
showStatus('⚠️ 自动检测失败,使用默认设置', 'warning');
useDefaultCountry();
}
}
// 方法1:使用ipapi.co(推荐)
async function tryPrimaryDetection() {
try {
const response = await fetchWithTimeout(CONFIG.primaryAPI);
const data = await response.json();
if (data && data.country_code) {
return {
countryCode: data.country_code,
countryName: data.country_name,
phonePrefix: data.country_calling_code
};
}
} catch (error) {
console.log('主要API失败:', error);
}
return null;
}
// 方法2:备用方案
async function tryFallbackDetection() {
try {
const response = await fetch(CONFIG.fallbackAPI);
const data = await response.json();
if (data && data.countryCode) {
const countryCode = data.countryCode.toUpperCase();
return {
countryCode: countryCode,
countryName: COUNTRY_NAMES[countryCode] || data.country,
phonePrefix: PHONE_PREFIXES[countryCode] || '+1'
};
}
} catch (error) {
console.log('备用API失败:', error);
}
// 降级:使用浏览器语言
return detectByBrowserLanguage();
}
// 方法3:根据浏览器语言推测
function detectByBrowserLanguage() {
const language = navigator.language || navigator.userLanguage;
let countryCode = CONFIG.defaultCountry;
// 简单映射
if (language.includes('zh-CN')) countryCode = 'CN';
else if (language.includes('zh-TW')) countryCode = 'TW';
else if (language.includes('zh-HK')) countryCode = 'HK';
else if (language.includes('en-US')) countryCode = 'US';
else if (language.includes('en-GB')) countryCode = 'GB';
else if (language.includes('ja')) countryCode = 'JP';
else if (language.includes('ko')) countryCode = 'KR';
return {
countryCode: countryCode,
countryName: COUNTRY_NAMES[countryCode] || countryCode,
phonePrefix: PHONE_PREFIXES[countryCode] || '+86'
};
}
// 默认国家设置
function useDefaultCountry() {
const defaultLocation = {
countryCode: CONFIG.defaultCountry,
countryName: COUNTRY_NAMES[CONFIG.defaultCountry],
phonePrefix: PHONE_PREFIXES[CONFIG.defaultCountry]
};
updateUI(defaultLocation);
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。