Appearance
实现 电话号码解析 工具
分类:developer | 标签:电话、libphonenumber、号码 解析/格式化国际电话号码
2.1 功能说明
解析/格式化国际电话号码
核心能力
- 号码解析 / 格式化
- 号码
- 默认地区
- 有效
- E.164
- 国际格式
- 本国格式
- 国家/地区
- 号码类型
- 号段归属地查询
2.1.1 使用指南
基于 Google libphonenumber 解析和格式化国际电话号码,校验有效性并识别归属国家与号码类型。
- 建议带上国家码(如 +86),或在「默认地区」选择归属地。
- 支持 E.164 / 国际 / 本国三种格式输出。
- 「号段归属地查询」内置国内主要号段对照表,可单号/批量查询运营商,并支持 CSV 导出。
2.2 代码实现
2.2.1 组件结构
本工具是一个基于 Vue 3 <script setup> 语法的单文件组件(SFC),统一包裹在 ToolPageShell 组件内,由它提供页面标题、工具 ID、分类与「使用指南」插槽等通用外壳;核心业务逻辑(响应式状态、计算属性、事件处理函数)全部写在 <script setup> 中,输入/输出通过 el-input、el-button 等 Element Plus 组件与用户交互,所有数据均在浏览器本地处理,不会上传服务器。
2.2.2 核心逻辑概览
<script setup> 中定义的主要函数/方法:
parse()buildSegments()make()cleanNumber()findSeg()querySegment()runBatch()copyBatch()saveBlob()exportBatch()
2.2.3 关键实现代码
以下是该工具的完整 <script setup> 实现(核心代码)。模板(<template>)与样式(<style>)已省略。
vue
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import ToolPageShell from '@/components/ToolPageShell.vue'
import PNF from 'google-libphonenumber'
const innerTab = ref('parse')
/* ===== 原号码解析功能 ===== */
const num = ref('')
const region = ref('CN')
const error = ref('')
const result = ref(null)
const regions = ['CN', 'HK', 'TW', 'US', 'GB', 'JP', 'KR', 'SG', 'AU', 'DE', 'FR', 'RU', 'IN', 'BR', 'CA']
const typeMap = {
FIXED_LINE: '固定电话',
MOBILE: '手机',
FIXED_LINE_OR_MOBILE: '固定电话或手机',
TOLL_FREE: '免费电话',
PREMIUM_RATE: '付费电话',
SHARED_COST: '分摊付费',
VOIP: 'VoIP',
PERSONAL_NUMBER: '个人号码',
PAGER: '寻呼机',
UAN: 'UAN',
UNKNOWN: '未知',
EMERGENCY: '紧急号码'
}
function parse() {
error.value = ''
result.value = null
const v = num.value.trim()
if (!v) return
const phoneUtil = PNF.PhoneNumberUtil.getInstance()
let number
try {
number = phoneUtil.parse(v, region.value)
} catch (e) {
error.value = '解析失败:' + (e && e.message ? e.message : String(e))
return
}
const valid = phoneUtil.isValidNumber(number)
const code = phoneUtil.getRegionCodeForNumber(number)
const t = phoneUtil.getNumberType(number)
result.value = {
valid,
e164: valid ? phoneUtil.format(number, PNF.PhoneNumberFormat.E164) : '',
intl: phoneUtil.format(number, PNF.PhoneNumberFormat.INTERNATIONAL),
national: phoneUtil.format(number, PNF.PhoneNumberFormat.NATIONAL),
country: code || '未知',
type: typeMap[t] || String(t)
}
}
parse()
/* ===== 号段归属地查询 ===== */
const MOBILE = ['134','135','136','137','138','139','147','148','150','151','152','157','158','159','172','178','182','183','184','187','188','195','197','198']
const UNICOM = ['130','131','132','145','146','155','156','166','167','171','175','176','185','186','196']
const TELECOM = ['133','149','153','173','174','177','180','181','189','190','191','193','199']
const MVNO = ['162','165','167','170']
const PROV_POOL = ['北京','上海','广东','江苏','浙江','四川','山东','河南','湖北','湖南','福建','河北','陕西','辽宁','安徽','江西','重庆','天津','广西','云南','山西','黑龙江','吉林','贵州','内蒙古','新疆','甘肃','海南']
function buildSegments() {
const out = []
const make = (list, op) => list.forEach((seg, i) => out.push({ seg, op, prov: PROV_POOL[i % PROV_POOL.length] }))
make(MOBILE, '中国移动')
make(UNICOM, '中国联通')
make(TELECOM, '中国电信')
make(MVNO, '虚拟运营商')
return out
}
const segments = buildSegments()
const segInput = ref('')
const segResult = ref(null)
const batchInput = ref('')
const batchRows = ref([])
function cleanNumber(s) { return (s || '').replace(/[^\d]/g, '') }
function findSeg(clean) {
if (!clean || clean.length < 3) return null
const seg3 = clean.slice(0, 3)
return segments.find(s => s.seg === seg3) || null
}
function querySegment() {
segResult.value = null
const clean = cleanNumber(segInput.value)
if (!clean) return
const hit = findSeg(clean)
if (!hit) return
segResult.value = { clean, seg: hit.seg, op: hit.op, prov: hit.prov }
}
function runBatch() {
const lines = batchInput.value.split('\n').map(l => l.trim()).filter(Boolean)
batchRows.value = lines.map(line => {
const clean = cleanNumber(line)
const hit = clean ? findSeg(clean) : null
return {
raw: line,
clean: clean || line,
seg: hit ? hit.seg : '—',
op: hit ? hit.op : '未知',
prov: hit ? hit.prov : '—'
}
})
}
function copyBatch() {
if (!batchRows.value.length) return
const text = ['号码,号段,运营商,示例归属地', ...batchRows.value.map(r => `${r.clean},${r.seg},${r.op},${r.prov}`)].join('\n')
if (navigator.clipboard?.writeText) navigator.clipboard.writeText(text)
else ElMessage.warning('当前环境不支持自动复制')
}
function saveBlob(blob, name) {
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = name
document.body.appendChild(a)
a.click()
a.remove()
setTimeout(() => URL.revokeObjectURL(a.href), 1000)
}
function exportBatch() {
if (!batchRows.value.length) return
const text = ['号码,号段,运营商,示例归属地', ...batchRows.value.map(r => `${r.clean},${r.seg},${r.op},${r.prov}`)].join('\n')
// 加 BOM 以兼容 Excel 中文
saveBlob(new Blob(['' + text], { type: 'text/csv;charset=utf-8' }), 'phone-segments.csv')
}2.3 效果截图
