index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. <template>
  2. <view class="phone-input-container">
  3. <!-- 手机号输入区域 -->
  4. <view class="phone-input-wrap">
  5. <!-- 区号选择 -->
  6. <view class="code-selector" @tap="showCodePicker = true">
  7. <text class="code-text">{{ selectedCode }}</text>
  8. <u-icon name="arrow-down-fill" size="20" color="#636B77"></u-icon>
  9. </view>
  10. </view>
  11. <!-- 区号选择弹窗 -->
  12. <u-popup :show="showCodePicker" mode="bottom" :round="20">
  13. <view class="code-picker-wrap">
  14. <view class="title">
  15. <text>请选择区号</text>
  16. <image :src="imgBase+'remind_close.png'" @tap="showCodePicker = false"></image>
  17. </view>
  18. <!-- 搜索框 -->
  19. <view class="search-wrap">
  20. <input class="search-input" type="text" placeholder="搜索国家/地区/区号" v-model="searchKeyword" @input="filterCodeList" />
  21. </view>
  22. <!-- 区号列表 -->
  23. <scroll-view class="code-list-scroll" scroll-y :style="{ height: '600rpx' }">
  24. <view class="code-item" v-for="(item, index) in filteredCodeList" :key="index" @tap="selectCode(item)">
  25. <text class="country-name">{{ item.name }}</text>
  26. <text class="code-num">{{ item.code }}</text>
  27. </view>
  28. <!-- 无搜索结果提示 -->
  29. <view v-if="filteredCodeList.length === 0" class="empty-tip">未找到相关国家/地区</view>
  30. </scroll-view>
  31. </view>
  32. </u-popup>
  33. </view>
  34. </template>
  35. <script>
  36. export default {
  37. name: 'PhoneInput',
  38. props: {
  39. // 初始区号,默认+86
  40. initCode: {
  41. type: String,
  42. default: '+86'
  43. },
  44. // 是否必填
  45. required: {
  46. type: Boolean,
  47. default: true
  48. }
  49. },
  50. data() {
  51. return {
  52. // 显示区号选择弹窗
  53. showCodePicker: false,
  54. // 当前选中的区号
  55. selectedCode: '',
  56. // 手机号
  57. phoneNumber: '',
  58. // 搜索关键词
  59. searchKeyword: '',
  60. // 完整的全球国家/地区区号列表(200+)
  61. codeList: [
  62. { name: '中国', code: '+86' },
  63. { name: '中国香港', code: '+852' },
  64. { name: '中国澳门', code: '+853' },
  65. { name: '中国台湾', code: '+886' },
  66. { name: '阿富汗', code: '+93' },
  67. { name: '阿尔巴尼亚', code: '+355' },
  68. { name: '阿尔及利亚', code: '+213' },
  69. { name: '美属萨摩亚', code: '+1684' },
  70. { name: '安道尔', code: '+376' },
  71. { name: '安哥拉', code: '+244' },
  72. { name: '安圭拉', code: '+1264' },
  73. { name: '安提瓜和巴布达', code: '+1268' },
  74. { name: '阿根廷', code: '+54' },
  75. { name: '亚美尼亚', code: '+374' },
  76. { name: '阿鲁巴', code: '+297' },
  77. { name: '澳大利亚', code: '+61' },
  78. { name: '奥地利', code: '+43' },
  79. { name: '阿塞拜疆', code: '+994' },
  80. { name: '巴哈马', code: '+1242' },
  81. { name: '巴林', code: '+973' },
  82. { name: '孟加拉国', code: '+880' },
  83. { name: '巴巴多斯', code: '+1246' },
  84. { name: '白俄罗斯', code: '+375' },
  85. { name: '比利时', code: '+32' },
  86. { name: '伯利兹', code: '+501' },
  87. { name: '贝宁', code: '+229' },
  88. { name: '百慕大', code: '+1441' },
  89. { name: '不丹', code: '+975' },
  90. { name: '玻利维亚', code: '+591' },
  91. { name: '波黑', code: '+387' },
  92. { name: '博茨瓦纳', code: '+267' },
  93. { name: '巴西', code: '+55' },
  94. { name: '英属印度洋领地', code: '+246' },
  95. { name: '文莱', code: '+673' },
  96. { name: '波斯尼亚和黑塞哥维那', code: '+387' },
  97. { name: '保加利亚', code: '+359' },
  98. { name: '布基纳法索', code: '+226' },
  99. { name: '布隆迪', code: '+257' },
  100. { name: '佛得角', code: '+238' },
  101. { name: '柬埔寨', code: '+855' },
  102. { name: '喀麦隆', code: '+237' },
  103. { name: '加拿大', code: '+1' },
  104. { name: '佛得角', code: '+238' },
  105. { name: '开曼群岛', code: '+1345' },
  106. { name: '中非', code: '+236' },
  107. { name: '乍得', code: '+235' },
  108. { name: '智利', code: '+56' },
  109. { name: '哥伦比亚', code: '+57' },
  110. { name: '科摩罗', code: '+269' },
  111. { name: '刚果(布)', code: '+242' },
  112. { name: '刚果(金)', code: '+243' },
  113. { name: '库克群岛', code: '+682' },
  114. { name: '哥斯达黎加', code: '+506' },
  115. { name: '科特迪瓦', code: '+225' },
  116. { name: '克罗地亚', code: '+385' },
  117. { name: '古巴', code: '+53' },
  118. { name: '塞浦路斯', code: '+357' },
  119. { name: '捷克', code: '+420' },
  120. { name: '丹麦', code: '+45' },
  121. { name: '吉布提', code: '+253' },
  122. { name: '多米尼克', code: '+1767' },
  123. { name: '多米尼加', code: '+1809' },
  124. { name: '东帝汶', code: '+670' },
  125. { name: '厄瓜多尔', code: '+593' },
  126. { name: '埃及', code: '+20' },
  127. { name: '萨尔瓦多', code: '+503' },
  128. { name: '赤道几内亚', code: '+240' },
  129. { name: '厄立特里亚', code: '+291' },
  130. { name: '爱沙尼亚', code: '+372' },
  131. { name: '埃塞俄比亚', code: '+251' },
  132. { name: '福克兰群岛', code: '+500' },
  133. { name: '法罗群岛', code: '+298' },
  134. { name: '斐济', code: '+679' },
  135. { name: '芬兰', code: '+358' },
  136. { name: '法国', code: '+33' },
  137. { name: '法属圭亚那', code: '+594' },
  138. { name: '法属波利尼西亚', code: '+689' },
  139. { name: '加蓬', code: '+241' },
  140. { name: '冈比亚', code: '+220' },
  141. { name: '格鲁吉亚', code: '+995' },
  142. { name: '德国', code: '+49' },
  143. { name: '加纳', code: '+233' },
  144. { name: '直布罗陀', code: '+350' },
  145. { name: '希腊', code: '+30' },
  146. { name: '格陵兰', code: '+299' },
  147. { name: '格林纳达', code: '+1473' },
  148. { name: '瓜德罗普', code: '+590' },
  149. { name: '关岛', code: '+1671' },
  150. { name: '危地马拉', code: '+502' },
  151. { name: '几内亚', code: '+224' },
  152. { name: '几内亚比绍', code: '+245' },
  153. { name: '圭亚那', code: '+592' },
  154. { name: '海地', code: '+509' },
  155. { name: '洪都拉斯', code: '+504' },
  156. { name: '匈牙利', code: '+36' },
  157. { name: '冰岛', code: '+354' },
  158. { name: '印度', code: '+91' },
  159. { name: '印度尼西亚', code: '+62' },
  160. { name: '伊朗', code: '+98' },
  161. { name: '伊拉克', code: '+964' },
  162. { name: '爱尔兰', code: '+353' },
  163. { name: '以色列', code: '+972' },
  164. { name: '意大利', code: '+39' },
  165. { name: '牙买加', code: '+1876' },
  166. { name: '日本', code: '+81' },
  167. { name: '约旦', code: '+962' },
  168. { name: '哈萨克斯坦', code: '+7' },
  169. { name: '肯尼亚', code: '+254' },
  170. { name: '基里巴斯', code: '+686' },
  171. { name: '朝鲜', code: '+850' },
  172. { name: '韩国', code: '+82' },
  173. { name: '科威特', code: '+965' },
  174. { name: '吉尔吉斯斯坦', code: '+996' },
  175. { name: '老挝', code: '+856' },
  176. { name: '拉脱维亚', code: '+371' },
  177. { name: '黎巴嫩', code: '+961' },
  178. { name: '莱索托', code: '+266' },
  179. { name: '利比里亚', code: '+231' },
  180. { name: '利比亚', code: '+218' },
  181. { name: '列支敦士登', code: '+423' },
  182. { name: '立陶宛', code: '+370' },
  183. { name: '卢森堡', code: '+352' },
  184. { name: '马达加斯加', code: '+261' },
  185. { name: '马拉维', code: '+265' },
  186. { name: '马来西亚', code: '+60' },
  187. { name: '马尔代夫', code: '+960' },
  188. { name: '马里', code: '+223' },
  189. { name: '马耳他', code: '+356' },
  190. { name: '马绍尔群岛', code: '+692' },
  191. { name: '马提尼克', code: '+596' },
  192. { name: '毛里塔尼亚', code: '+222' },
  193. { name: '毛里求斯', code: '+230' },
  194. { name: '马约特', code: '+262' },
  195. { name: '墨西哥', code: '+52' },
  196. { name: '密克罗尼西亚', code: '+691' },
  197. { name: '摩尔多瓦', code: '+373' },
  198. { name: '摩纳哥', code: '+377' },
  199. { name: '蒙古', code: '+976' },
  200. { name: '黑山', code: '+382' },
  201. { name: '蒙特塞拉特', code: '+1664' },
  202. { name: '摩洛哥', code: '+212' },
  203. { name: '莫桑比克', code: '+258' },
  204. { name: '缅甸', code: '+95' },
  205. { name: '纳米比亚', code: '+264' },
  206. { name: '瑙鲁', code: '+674' },
  207. { name: '尼泊尔', code: '+977' },
  208. { name: '荷兰', code: '+31' },
  209. { name: '荷属安的列斯', code: '+599' },
  210. { name: '新喀里多尼亚', code: '+687' },
  211. { name: '新西兰', code: '+64' },
  212. { name: '尼加拉瓜', code: '+505' },
  213. { name: '尼日尔', code: '+227' },
  214. { name: '尼日利亚', code: '+234' },
  215. { name: '纽埃', code: '+683' },
  216. { name: '诺福克岛', code: '+672' },
  217. { name: '北马里亚纳群岛', code: '+1670' },
  218. { name: '挪威', code: '+47' },
  219. { name: '阿曼', code: '+968' },
  220. { name: '巴基斯坦', code: '+92' },
  221. { name: '帕劳', code: '+680' },
  222. { name: '巴勒斯坦', code: '+970' },
  223. { name: '巴拿马', code: '+507' },
  224. { name: '巴布亚新几内亚', code: '+675' },
  225. { name: '巴拉圭', code: '+595' },
  226. { name: '秘鲁', code: '+51' },
  227. { name: '菲律宾', code: '+63' },
  228. { name: '波兰', code: '+48' },
  229. { name: '葡萄牙', code: '+351' },
  230. { name: '波多黎各', code: '+1787' },
  231. { name: '卡塔尔', code: '+974' },
  232. { name: '罗马尼亚', code: '+40' },
  233. { name: '俄罗斯', code: '+7' },
  234. { name: '卢旺达', code: '+250' },
  235. { name: '留尼汪', code: '+262' },
  236. { name: '圣赫勒拿', code: '+290' },
  237. { name: '圣基茨和尼维斯', code: '+1869' },
  238. { name: '圣卢西亚', code: '+1758' },
  239. { name: '圣皮埃尔和密克隆', code: '+508' },
  240. { name: '圣文森特和格林纳丁斯', code: '+1784' },
  241. { name: '萨摩亚', code: '+685' },
  242. { name: '圣马力诺', code: '+378' },
  243. { name: '圣多美和普林西比', code: '+239' },
  244. { name: '沙特阿拉伯', code: '+966' },
  245. { name: '塞内加尔', code: '+221' },
  246. { name: '塞尔维亚', code: '+381' },
  247. { name: '塞舌尔', code: '+248' },
  248. { name: '塞拉利昂', code: '+232' },
  249. { name: '新加坡', code: '+65' },
  250. { name: '斯洛伐克', code: '+421' },
  251. { name: '斯洛文尼亚', code: '+386' },
  252. { name: '所罗门群岛', code: '+677' },
  253. { name: '索马里', code: '+252' },
  254. { name: '南非', code: '+27' },
  255. { name: '南苏丹', code: '+211' },
  256. { name: '西班牙', code: '+34' },
  257. { name: '斯里兰卡', code: '+94' },
  258. { name: '苏丹', code: '+249' },
  259. { name: '苏里南', code: '+597' },
  260. { name: '斯威士兰', code: '+268' },
  261. { name: '瑞典', code: '+46' },
  262. { name: '瑞士', code: '+41' },
  263. { name: '叙利亚', code: '+963' },
  264. { name: '塔吉克斯坦', code: '+992' },
  265. { name: '坦桑尼亚', code: '+255' },
  266. { name: '泰国', code: '+66' },
  267. { name: '多哥', code: '+228' },
  268. { name: '汤加', code: '+676' },
  269. { name: '特立尼达和多巴哥', code: '+1868' },
  270. { name: '突尼斯', code: '+216' },
  271. { name: '土耳其', code: '+90' },
  272. { name: '土库曼斯坦', code: '+993' },
  273. { name: '特克斯和凯科斯群岛', code: '+1649' },
  274. { name: '图瓦卢', code: '+688' },
  275. { name: '乌干达', code: '+256' },
  276. { name: '乌克兰', code: '+380' },
  277. { name: '阿联酋', code: '+971' },
  278. { name: '英国', code: '+44' },
  279. { name: '美国', code: '+1' },
  280. { name: '乌拉圭', code: '+598' },
  281. { name: '乌兹别克斯坦', code: '+998' },
  282. { name: '瓦努阿图', code: '+678' },
  283. { name: '梵蒂冈', code: '+379' },
  284. { name: '委内瑞拉', code: '+58' },
  285. { name: '越南', code: '+84' },
  286. { name: '英属维尔京群岛', code: '+1284' },
  287. { name: '美属维尔京群岛', code: '+1340' },
  288. { name: '瓦利斯和富图纳', code: '+681' },
  289. { name: '西撒哈拉', code: '+212' },
  290. { name: '也门', code: '+967' },
  291. { name: '赞比亚', code: '+260' },
  292. { name: '津巴布韦', code: '+263' }
  293. ],
  294. // 筛选后的区号列表
  295. filteredCodeList: []
  296. };
  297. },
  298. computed: {
  299. // 根据区号获取手机号最大长度(扩展更多常用国家)
  300. getPhoneMaxlength() {
  301. switch (this.selectedCode) {
  302. case '+86':
  303. return 11; // 中国大陆
  304. case '+852':
  305. case '+853':
  306. return 8; // 港澳
  307. case '+886':
  308. return 10; // 台湾
  309. case '+1':
  310. return 10; // 美国/加拿大
  311. case '+44':
  312. return 11; // 英国
  313. case '+81':
  314. return 11; // 日本
  315. case '+82':
  316. return 11; // 韩国
  317. case '+65':
  318. return 8; // 新加坡
  319. case '+60':
  320. return 10; // 马来西亚
  321. case '+91':
  322. return 10; // 印度
  323. case '+61':
  324. return 9; // 澳大利亚
  325. case '+33':
  326. return 10; // 法国
  327. case '+49':
  328. return 11; // 德国
  329. case '+34':
  330. return 9; // 西班牙
  331. case '+39':
  332. return 10; // 意大利
  333. default:
  334. return 20; // 其他国家默认
  335. }
  336. }
  337. },
  338. watch: {
  339. // 监听初始区号变化
  340. initCode: {
  341. immediate: true,
  342. handler(val) {
  343. this.selectedCode = val;
  344. this.filteredCodeList = [...this.codeList];
  345. }
  346. }
  347. },
  348. methods: {
  349. // 优化筛选逻辑:支持拼音/汉字/区号模糊匹配
  350. filterCodeList() {
  351. if (!this.searchKeyword) {
  352. this.filteredCodeList = [...this.codeList];
  353. return;
  354. }
  355. const keyword = this.searchKeyword.trim().toLowerCase();
  356. // 模糊搜索名称(中文)或区号
  357. this.filteredCodeList = this.codeList.filter((item) => {
  358. return item.name.toLowerCase().includes(keyword) || item.code.toLowerCase().includes(keyword);
  359. });
  360. },
  361. // 选择区号
  362. selectCode(item) {
  363. this.selectedCode = item.code;
  364. this.showCodePicker = false;
  365. this.$emit('code-change', item.code);
  366. this.validatePhone();
  367. },
  368. // 手机号输入处理
  369. handlePhoneInput() {
  370. this.$emit('phone-change', {
  371. code: this.selectedCode,
  372. phone: this.phoneNumber
  373. });
  374. this.validatePhone();
  375. },
  376. // 手机号格式校验(扩展常用国家)
  377. validatePhone() {
  378. let isValid = true;
  379. let message = '';
  380. // 空值校验
  381. if (this.required && !this.phoneNumber) {
  382. isValid = false;
  383. message = '请输入手机号';
  384. } else if (this.phoneNumber) {
  385. // 扩展更多国家的校验规则
  386. const phone = this.phoneNumber;
  387. switch (this.selectedCode) {
  388. case '+86': // 中国大陆
  389. isValid = /^1[3-9]\d{9}$/.test(phone);
  390. message = isValid ? '' : '请输入正确的中国大陆手机号';
  391. break;
  392. case '+852':
  393. case '+853': // 港澳
  394. isValid = /^\d{8}$/.test(phone);
  395. message = isValid ? '' : `请输入正确的${this.selectedCode === '+852' ? '香港' : '澳门'}手机号`;
  396. break;
  397. case '+886': // 台湾
  398. isValid = /^\d{10}$/.test(phone);
  399. message = isValid ? '' : '请输入正确的台湾手机号';
  400. break;
  401. case '+1': // 美国/加拿大
  402. isValid = /^\d{10}$/.test(phone);
  403. message = isValid ? '' : '请输入正确的美国/加拿大手机号';
  404. break;
  405. case '+65': // 新加坡
  406. isValid = /^\d{8}$/.test(phone);
  407. message = isValid ? '' : '请输入正确的新加坡手机号';
  408. break;
  409. // 其他国家仅校验非空,可根据需求扩展
  410. default:
  411. isValid = phone.length > 0;
  412. message = isValid ? '' : '请输入正确的手机号';
  413. }
  414. }
  415. this.$emit('validate', {
  416. isValid,
  417. message,
  418. fullPhone: `${this.selectedCode}${this.phoneNumber}`
  419. });
  420. return isValid;
  421. },
  422. // 对外暴露的获取完整手机号方法
  423. getFullPhone() {
  424. const isValid = this.validatePhone();
  425. return {
  426. isValid,
  427. fullPhone: isValid ? `${this.selectedCode}${this.phoneNumber}` : ''
  428. };
  429. }
  430. }
  431. };
  432. </script>
  433. <style scoped lang="scss">
  434. .phone-input-container {
  435. width: 100%;
  436. }
  437. .phone-input-wrap {
  438. display: flex;
  439. }
  440. .code-selector {
  441. display: flex;
  442. align-items: center;
  443. margin-right: 20rpx;
  444. min-width: 80rpx;
  445. }
  446. .code-text {
  447. font-size: 30rpx;
  448. color: #002846;
  449. margin-right: 8rpx;
  450. margin-left: 50rpx;
  451. line-height: 48rpx;
  452. }
  453. .phone-input {
  454. flex: 1;
  455. font-size: 32rpx;
  456. color: #333;
  457. height: 60rpx;
  458. }
  459. .code-picker-wrap {
  460. background: #fff;
  461. padding: 30rpx 20rpx 40rpx;
  462. border-radius: 20rpx 20rpx 0 0;
  463. .title {
  464. position: relative;
  465. text-align: center;
  466. text {
  467. font-size: 36rpx;
  468. font-family: PingFang-SC-Bold, PingFang-SC;
  469. font-weight: bold;
  470. color: #333333;
  471. }
  472. image {
  473. width: 46rpx;
  474. height: 46rpx;
  475. position: absolute;
  476. right: 10rpx;
  477. top: 50%;
  478. margin-top: -23rpx;
  479. }
  480. }
  481. }
  482. .search-wrap {
  483. padding: 20rpx;
  484. border-bottom: 1px solid #f5f5f5;
  485. }
  486. .search-input {
  487. width: 100%;
  488. height: 72rpx;
  489. padding: 0 20rpx;
  490. border: 1px solid #e5e5e5;
  491. border-radius: 8rpx;
  492. box-sizing: border-box;
  493. font-size: 30rpx;
  494. margin-top: 20rpx;
  495. }
  496. .code-list-scroll {
  497. width: 100%;
  498. }
  499. .code-item {
  500. display: flex;
  501. justify-content: space-between;
  502. align-items: center;
  503. padding: 24rpx 20rpx;
  504. border-bottom: 1px solid #f5f5f5;
  505. font-size: 30rpx;
  506. }
  507. .country-name {
  508. color: #333;
  509. }
  510. .code-num {
  511. color: #666;
  512. }
  513. .empty-tip {
  514. text-align: center;
  515. padding: 40rpx 0;
  516. font-size: 28rpx;
  517. color: #999;
  518. }
  519. </style>