feat: 国际手机号输入框

This commit is contained in:
TsMask
2024-04-23 14:55:57 +08:00
parent 979b18092d
commit 7a9b38dc66
8 changed files with 687 additions and 0 deletions

View File

@@ -26,6 +26,7 @@
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"echarts": "~5.5.0", "echarts": "~5.5.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"intl-tel-input": "^21.2.5",
"js-base64": "^3.7.7", "js-base64": "^3.7.7",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"localforage": "^1.10.0", "localforage": "^1.10.0",

View File

@@ -0,0 +1,252 @@
//* THIS FILE IS AUTO-GENERATED. DO NOT EDIT.
export default {
af: "Afghanistan",
ax: "Åland Islands",
al: "Albania",
dz: "Algeria",
as: "American Samoa",
ad: "Andorra",
ao: "Angola",
ai: "Anguilla",
aq: "Antarctica",
ag: "Antigua & Barbuda",
ar: "Argentina",
am: "Armenia",
aw: "Aruba",
au: "Australia",
at: "Austria",
az: "Azerbaijan",
bs: "Bahamas",
bh: "Bahrain",
bd: "Bangladesh",
bb: "Barbados",
by: "Belarus",
be: "Belgium",
bz: "Belize",
bj: "Benin",
bm: "Bermuda",
bt: "Bhutan",
bo: "Bolivia",
ba: "Bosnia & Herzegovina",
bw: "Botswana",
bv: "Bouvet Island",
br: "Brazil",
io: "British Indian Ocean Territory",
vg: "British Virgin Islands",
bn: "Brunei",
bg: "Bulgaria",
bf: "Burkina Faso",
bi: "Burundi",
kh: "Cambodia",
cm: "Cameroon",
ca: "Canada",
cv: "Cape Verde",
bq: "Caribbean Netherlands",
ky: "Cayman Islands",
cf: "Central African Republic",
td: "Chad",
cl: "Chile",
cn: "China",
cx: "Christmas Island",
cc: "Cocos (Keeling) Islands",
co: "Colombia",
km: "Comoros",
cg: "Congo - Brazzaville",
cd: "Congo - Kinshasa",
ck: "Cook Islands",
cr: "Costa Rica",
ci: "Côte dIvoire",
hr: "Croatia",
cu: "Cuba",
cw: "Curaçao",
cy: "Cyprus",
cz: "Czechia",
dk: "Denmark",
dj: "Djibouti",
dm: "Dominica",
do: "Dominican Republic",
ec: "Ecuador",
eg: "Egypt",
sv: "El Salvador",
gq: "Equatorial Guinea",
er: "Eritrea",
ee: "Estonia",
sz: "Eswatini",
et: "Ethiopia",
fk: "Falkland Islands",
fo: "Faroe Islands",
fj: "Fiji",
fi: "Finland",
fr: "France",
gf: "French Guiana",
pf: "French Polynesia",
tf: "French Southern Territories",
ga: "Gabon",
gm: "Gambia",
ge: "Georgia",
de: "Germany",
gh: "Ghana",
gi: "Gibraltar",
gr: "Greece",
gl: "Greenland",
gd: "Grenada",
gp: "Guadeloupe",
gu: "Guam",
gt: "Guatemala",
gg: "Guernsey",
gn: "Guinea",
gw: "Guinea-Bissau",
gy: "Guyana",
ht: "Haiti",
hm: "Heard & McDonald Islands",
hn: "Honduras",
hk: "Hong Kong SAR China",
hu: "Hungary",
is: "Iceland",
in: "India",
id: "Indonesia",
ir: "Iran",
iq: "Iraq",
ie: "Ireland",
im: "Isle of Man",
il: "Israel",
it: "Italy",
jm: "Jamaica",
jp: "Japan",
je: "Jersey",
jo: "Jordan",
kz: "Kazakhstan",
ke: "Kenya",
ki: "Kiribati",
kw: "Kuwait",
kg: "Kyrgyzstan",
la: "Laos",
lv: "Latvia",
lb: "Lebanon",
ls: "Lesotho",
lr: "Liberia",
ly: "Libya",
li: "Liechtenstein",
lt: "Lithuania",
lu: "Luxembourg",
mo: "Macao SAR China",
mg: "Madagascar",
mw: "Malawi",
my: "Malaysia",
mv: "Maldives",
ml: "Mali",
mt: "Malta",
mh: "Marshall Islands",
mq: "Martinique",
mr: "Mauritania",
mu: "Mauritius",
yt: "Mayotte",
mx: "Mexico",
fm: "Micronesia",
md: "Moldova",
mc: "Monaco",
mn: "Mongolia",
me: "Montenegro",
ms: "Montserrat",
ma: "Morocco",
mz: "Mozambique",
mm: "Myanmar (Burma)",
na: "Namibia",
nr: "Nauru",
np: "Nepal",
nl: "Netherlands",
nc: "New Caledonia",
nz: "New Zealand",
ni: "Nicaragua",
ne: "Niger",
ng: "Nigeria",
nu: "Niue",
nf: "Norfolk Island",
kp: "North Korea",
mk: "North Macedonia",
mp: "Northern Mariana Islands",
no: "Norway",
om: "Oman",
pk: "Pakistan",
pw: "Palau",
ps: "Palestinian Territories",
pa: "Panama",
pg: "Papua New Guinea",
py: "Paraguay",
pe: "Peru",
ph: "Philippines",
pn: "Pitcairn Islands",
pl: "Poland",
pt: "Portugal",
pr: "Puerto Rico",
qa: "Qatar",
re: "Réunion",
ro: "Romania",
ru: "Russia",
rw: "Rwanda",
ws: "Samoa",
sm: "San Marino",
st: "São Tomé & Príncipe",
sa: "Saudi Arabia",
sn: "Senegal",
rs: "Serbia",
sc: "Seychelles",
sl: "Sierra Leone",
sg: "Singapore",
sx: "Sint Maarten",
sk: "Slovakia",
si: "Slovenia",
sb: "Solomon Islands",
so: "Somalia",
za: "South Africa",
gs: "South Georgia & South Sandwich Islands",
kr: "South Korea",
ss: "South Sudan",
es: "Spain",
lk: "Sri Lanka",
bl: "St. Barthélemy",
sh: "St. Helena",
kn: "St. Kitts & Nevis",
lc: "St. Lucia",
mf: "St. Martin",
pm: "St. Pierre & Miquelon",
vc: "St. Vincent & Grenadines",
sd: "Sudan",
sr: "Suriname",
sj: "Svalbard & Jan Mayen",
se: "Sweden",
ch: "Switzerland",
sy: "Syria",
tw: "Taiwan",
tj: "Tajikistan",
tz: "Tanzania",
th: "Thailand",
tl: "Timor-Leste",
tg: "Togo",
tk: "Tokelau",
to: "Tonga",
tt: "Trinidad & Tobago",
tn: "Tunisia",
tr: "Turkey",
tm: "Turkmenistan",
tc: "Turks & Caicos Islands",
tv: "Tuvalu",
um: "U.S. Outlying Islands",
vi: "U.S. Virgin Islands",
ug: "Uganda",
ua: "Ukraine",
ae: "United Arab Emirates",
gb: "United Kingdom",
us: "United States",
uy: "Uruguay",
uz: "Uzbekistan",
vu: "Vanuatu",
va: "Vatican City",
ve: "Venezuela",
vn: "Vietnam",
wf: "Wallis & Futuna",
eh: "Western Sahara",
ye: "Yemen",
zm: "Zambia",
zw: "Zimbabwe",
};

View File

@@ -0,0 +1,4 @@
import countryTranslations from './countries';
import interfaceTranslations from './interface';
export default { ...countryTranslations, ...interfaceTranslations };

View File

@@ -0,0 +1,14 @@
//* English. Translated by: Jack O'Connor (jackocnr).
export default {
selectedCountryAriaLabel: "Selected country",
noCountrySelected: "No country selected",
countryListAriaLabel: "List of countries",
searchPlaceholder: "Search",
zeroSearchResults: "No results found",
oneSearchResult: "1 result found",
multipleSearchResults: "${count} results found",
// additional countries (not supported by country-list library)
ac: "Ascension Island",
xk: "Kosovo",
};

View File

@@ -0,0 +1,252 @@
//* THIS FILE IS AUTO-GENERATED. DO NOT EDIT.
export default {
al: "阿尔巴尼亚",
dz: "阿尔及利亚",
af: "阿富汗",
ar: "阿根廷",
ae: "阿拉伯联合酋长国",
aw: "阿鲁巴",
om: "阿曼",
az: "阿塞拜疆",
eg: "埃及",
et: "埃塞俄比亚",
ie: "爱尔兰",
ee: "爱沙尼亚",
ad: "安道尔",
ao: "安哥拉",
ai: "安圭拉",
ag: "安提瓜和巴布达",
at: "奥地利",
ax: "奥兰群岛",
au: "澳大利亚",
bb: "巴巴多斯",
pg: "巴布亚新几内亚",
bs: "巴哈马",
pk: "巴基斯坦",
py: "巴拉圭",
ps: "巴勒斯坦领土",
bh: "巴林",
pa: "巴拿马",
br: "巴西",
by: "白俄罗斯",
bm: "百慕大",
bg: "保加利亚",
mp: "北马里亚纳群岛",
mk: "北马其顿",
bj: "贝宁",
be: "比利时",
is: "冰岛",
pr: "波多黎各",
pl: "波兰",
ba: "波斯尼亚和黑塞哥维那",
bo: "玻利维亚",
bz: "伯利兹",
bw: "博茨瓦纳",
bt: "不丹",
bf: "布基纳法索",
bi: "布隆迪",
bv: "布韦岛",
kp: "朝鲜",
gq: "赤道几内亚",
dk: "丹麦",
de: "德国",
tl: "东帝汶",
tg: "多哥",
do: "多米尼加共和国",
dm: "多米尼克",
ru: "俄罗斯",
ec: "厄瓜多尔",
er: "厄立特里亚",
fr: "法国",
fo: "法罗群岛",
pf: "法属波利尼西亚",
gf: "法属圭亚那",
tf: "法属南部领地",
mf: "法属圣马丁",
va: "梵蒂冈",
ph: "菲律宾",
fj: "斐济",
fi: "芬兰",
cv: "佛得角",
fk: "福克兰群岛",
gm: "冈比亚",
cg: "刚果(布)",
cd: "刚果(金)",
co: "哥伦比亚",
cr: "哥斯达黎加",
gd: "格林纳达",
gl: "格陵兰",
ge: "格鲁吉亚",
gg: "根西岛",
cu: "古巴",
gp: "瓜德罗普",
gu: "关岛",
gy: "圭亚那",
kz: "哈萨克斯坦",
ht: "海地",
kr: "韩国",
nl: "荷兰",
bq: "荷属加勒比区",
sx: "荷属圣马丁",
hm: "赫德岛和麦克唐纳群岛",
me: "黑山",
hn: "洪都拉斯",
ki: "基里巴斯",
dj: "吉布提",
kg: "吉尔吉斯斯坦",
gn: "几内亚",
gw: "几内亚比绍",
ca: "加拿大",
gh: "加纳",
ga: "加蓬",
kh: "柬埔寨",
cz: "捷克",
zw: "津巴布韦",
cm: "喀麦隆",
qa: "卡塔尔",
ky: "开曼群岛",
cc: "科科斯(基林)群岛",
km: "科摩罗",
ci: "科特迪瓦",
kw: "科威特",
hr: "克罗地亚",
ke: "肯尼亚",
ck: "库克群岛",
cw: "库拉索",
lv: "拉脱维亚",
ls: "莱索托",
la: "老挝",
lb: "黎巴嫩",
lt: "立陶宛",
lr: "利比里亚",
ly: "利比亚",
li: "列支敦士登",
re: "留尼汪",
lu: "卢森堡",
rw: "卢旺达",
ro: "罗马尼亚",
mg: "马达加斯加",
im: "马恩岛",
mv: "马尔代夫",
mt: "马耳他",
mw: "马拉维",
my: "马来西亚",
ml: "马里",
mh: "马绍尔群岛",
mq: "马提尼克",
yt: "马约特",
mu: "毛里求斯",
mr: "毛里塔尼亚",
us: "美国",
um: "美国本土外小岛屿",
as: "美属萨摩亚",
vi: "美属维尔京群岛",
mn: "蒙古",
ms: "蒙特塞拉特",
bd: "孟加拉国",
pe: "秘鲁",
fm: "密克罗尼西亚",
mm: "缅甸",
md: "摩尔多瓦",
ma: "摩洛哥",
mc: "摩纳哥",
mz: "莫桑比克",
mx: "墨西哥",
na: "纳米比亚",
za: "南非",
aq: "南极洲",
gs: "南乔治亚和南桑威奇群岛",
ss: "南苏丹",
nr: "瑙鲁",
ni: "尼加拉瓜",
np: "尼泊尔",
ne: "尼日尔",
ng: "尼日利亚",
nu: "纽埃",
no: "挪威",
nf: "诺福克岛",
pw: "帕劳",
pn: "皮特凯恩群岛",
pt: "葡萄牙",
jp: "日本",
se: "瑞典",
ch: "瑞士",
sv: "萨尔瓦多",
ws: "萨摩亚",
rs: "塞尔维亚",
sl: "塞拉利昂",
sn: "塞内加尔",
cy: "塞浦路斯",
sc: "塞舌尔",
sa: "沙特阿拉伯",
bl: "圣巴泰勒米",
cx: "圣诞岛",
st: "圣多美和普林西比",
sh: "圣赫勒拿",
kn: "圣基茨和尼维斯",
lc: "圣卢西亚",
sm: "圣马力诺",
pm: "圣皮埃尔和密克隆群岛",
vc: "圣文森特和格林纳丁斯",
lk: "斯里兰卡",
sk: "斯洛伐克",
si: "斯洛文尼亚",
sj: "斯瓦尔巴和扬马延",
sz: "斯威士兰",
sd: "苏丹",
sr: "苏里南",
sb: "所罗门群岛",
so: "索马里",
tj: "塔吉克斯坦",
tw: "台湾",
th: "泰国",
tz: "坦桑尼亚",
to: "汤加",
tc: "特克斯和凯科斯群岛",
tt: "特立尼达和多巴哥",
tn: "突尼斯",
tv: "图瓦卢",
tr: "土耳其",
tm: "土库曼斯坦",
tk: "托克劳",
wf: "瓦利斯和富图纳",
vu: "瓦努阿图",
gt: "危地马拉",
ve: "委内瑞拉",
bn: "文莱",
ug: "乌干达",
ua: "乌克兰",
uy: "乌拉圭",
uz: "乌兹别克斯坦",
es: "西班牙",
eh: "西撒哈拉",
gr: "希腊",
sg: "新加坡",
nc: "新喀里多尼亚",
nz: "新西兰",
hu: "匈牙利",
sy: "叙利亚",
jm: "牙买加",
am: "亚美尼亚",
ye: "也门",
iq: "伊拉克",
ir: "伊朗",
il: "以色列",
it: "意大利",
in: "印度",
id: "印度尼西亚",
gb: "英国",
vg: "英属维尔京群岛",
io: "英属印度洋领地",
jo: "约旦",
vn: "越南",
zm: "赞比亚",
je: "泽西岛",
td: "乍得",
gi: "直布罗陀",
cl: "智利",
cf: "中非共和国",
cn: "中国",
mo: "中国澳门特别行政区",
hk: "中国香港特别行政区",
};

View File

@@ -0,0 +1,4 @@
import countryTranslations from './countries';
import interfaceTranslations from './interface';
export default { ...countryTranslations, ...interfaceTranslations };

View File

@@ -0,0 +1,15 @@
//* Chinese (Simplified). Translated by: Google Translate.
export default {
selectedCountryAriaLabel: "所选国家",
noCountrySelected: "未选择国家/地区",
countryListAriaLabel: "国家名单",
searchPlaceholder: "搜索",
zeroSearchResults: "未找到结果",
oneSearchResult: "找到 1 个结果",
multipleSearchResults: "找到 ${count} 个结果",
// additional countries (not supported by country-list library)
ac: "阿森松岛",
xk: "科索沃",
};

View File

@@ -0,0 +1,145 @@
<!-- https://github.com/jackocnr/intl-tel-input/blob/master/react/src/intl-tel-input/react.tsx -->
<script lang="ts" setup>
import intlTelInput, { Iti, SomeOptions } from 'intl-tel-input';
import 'intl-tel-input/build/css/intlTelInput.min.css';
import 'intl-tel-input/build/js/utils.js';
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import useI18n from '@/hooks/useI18n';
const { currentLocale } = useI18n();
const emit = defineEmits(['update:value', 'update:change']);
const props = defineProps({
/**有效检验 */
preciseValidation: {
type: Boolean,
default: false,
},
/**手机号 */
value: {
type: String,
default: '',
},
/**禁用输入 */
disabled: {
type: Boolean,
default: false,
},
/**手机号输入提示 */
placeholder: {
type: String,
default: '',
},
/**手机号输入最大字符串长度 */
maxlength: {
type: Number,
default: 255,
},
/**允许清空手机号输入框 */
allowClear: {
type: Boolean,
},
});
const inputRef = ref<HTMLInputElement | null>(null);
const itiRef = ref<Iti | null>(null);
function fnChange() {
if (!itiRef.value) return;
const num = itiRef.value?.getNumber() || '';
const countryIso = itiRef.value?.getSelectedCountryData().iso2 || '';
// note: this number will be in standard E164 format, but any container component can use
// intlTelInputUtils.formatNumber() to convert this to another format
// as well as intlTelInputUtils.getNumberType() etc. if need be
let data = {
num,
countryIso,
validity: false,
errorCode: -1,
};
const isValid = props.preciseValidation
? itiRef.value.isValidNumberPrecise()
: itiRef.value.isValidNumber();
if (isValid) {
data.validity = true;
data.errorCode = 0;
} else {
const errorCode = itiRef.value.getValidationError();
data.validity = false;
data.errorCode = errorCode;
}
// console.log(data);
emit('update:value', num);
emit('update:change', data);
}
watch(
() => props.value,
v => {
if (v) {
itiRef.value?.setNumber(v);
} else {
itiRef.value?.setNumber('');
}
}
);
onMounted(() => {
nextTick(async () => {
if (inputRef.value) {
let i18n = undefined;
let initialCountry = 'us';
// 语言文件导入问题只能复制到项目内处理
// import fr from "intl-tel-input/i18n/fr";
if (currentLocale.value.startsWith('zh')) {
const { default: zh } = await import('./i18n/zh');
i18n = zh;
initialCountry = 'cn';
} else {
const { default: en } = await import('./i18n/en');
i18n = en;
initialCountry = 'us';
}
itiRef.value = intlTelInput(inputRef.value, {
initialCountry: initialCountry,
formatOnDisplay: true,
autoPlaceholder: 'polite',
i18n: i18n,
} as SomeOptions);
inputRef.value.addEventListener('countrychange', fnChange);
}
});
});
onBeforeUnmount(() => {
if (inputRef.value) {
inputRef.value.removeEventListener('countrychange', fnChange);
}
itiRef.value?.destroy();
});
</script>
<template>
<input
type="tel"
class="ant-input"
ref="inputRef"
:value="value"
:disabled="disabled"
:placeholder="placeholder"
:maxlength="maxlength"
:allow-clear="allowClear"
@input="fnChange"
/>
</template>
<style lang="css">
.iti {
display: block;
}
.iti .iti__country-container .iti__search-input {
padding: 4px 8px;
}
</style>