@arcblock/ux 2.12.44 → 2.12.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -143,6 +143,7 @@ function Dashboard({
|
|
143
143
|
})
|
144
144
|
}), /*#__PURE__*/_jsxs(Box, {
|
145
145
|
className: "dashboard-main",
|
146
|
+
id: "arc__dashboard-main",
|
146
147
|
children: [showToggleButton && /*#__PURE__*/_jsx(Box, {
|
147
148
|
sx: {
|
148
149
|
position: 'absolute',
|
@@ -170,6 +171,7 @@ function Dashboard({
|
|
170
171
|
})
|
171
172
|
}), /*#__PURE__*/_jsx(Container, {
|
172
173
|
className: "dashboard-content",
|
174
|
+
id: "arc__dashboard-content",
|
173
175
|
...(fullWidth && {
|
174
176
|
maxWidth: false
|
175
177
|
}),
|
@@ -12,7 +12,8 @@ export interface PhoneInputProps extends Omit<TextFieldProps, 'value' | 'onChang
|
|
12
12
|
preview?: boolean;
|
13
13
|
allowDial?: boolean;
|
14
14
|
}
|
15
|
-
export declare function validatePhoneNumber(phone: string): boolean;
|
15
|
+
export declare function validatePhoneNumber(phone: string, iso2: CountryIso2, dialCode?: string): boolean;
|
16
16
|
export declare function getDialCodeByCountry(iso2: CountryIso2): string;
|
17
17
|
export declare function getCountryNameByCountry(iso2: CountryIso2): string;
|
18
|
+
export declare function detectCountryFromPhone(phoneNumber?: string): CountryIso2 | undefined;
|
18
19
|
export default function PhoneInput({ value, onChange, placeholder, countryDisplayOptions, preview, allowDial, ...props }: PhoneInputProps): import("react/jsx-runtime").JSX.Element;
|
package/lib/PhoneInput/index.js
CHANGED
@@ -1,29 +1,28 @@
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
2
2
|
import { useCallback, useMemo, useState } from 'react';
|
3
3
|
import { Box, TextField, Typography } from '@mui/material';
|
4
|
+
import { parsePhoneNumber } from 'awesome-phonenumber';
|
4
5
|
import { defaultCountries, usePhoneInput, parseCountry } from 'react-international-phone';
|
5
|
-
import isMobilePhone from 'validator/lib/isMobilePhone';
|
6
6
|
import CountrySelect from './country-select';
|
7
7
|
import { mergeSx } from '../Util/style';
|
8
|
-
export function validatePhoneNumber(phone) {
|
8
|
+
export function validatePhoneNumber(phone, iso2, dialCode = '') {
|
9
9
|
// 如果没有输入电话号码,返回false
|
10
10
|
if (!phone) return false;
|
11
11
|
|
12
|
-
// 检查是否只有国家区号
|
13
|
-
// 电话号码格式通常为 +区号 后跟电话号码
|
14
12
|
// 如果去除所有格式化字符后,只剩下区号,则认为只有区号
|
15
13
|
const cleanedPhone = phone.replace(/[\s\-()]+/g, '');
|
16
|
-
|
17
|
-
// 使用正则表达式匹配:以+开头,后面全是数字,但长度不超过5(大多数国家区号在1-4位)
|
14
|
+
const phoneDialCode = dialCode || getDialCodeByCountry(iso2);
|
18
15
|
// 这表示电话号码只有区号部分
|
19
|
-
if (
|
16
|
+
if (getDialCodeWithoutPlus(phoneDialCode) === getDialCodeWithoutPlus(cleanedPhone)) {
|
20
17
|
return true; // 如果只有区号,则视为有效
|
21
18
|
}
|
22
19
|
|
23
20
|
// 否则使用validator进行完整校验
|
24
|
-
|
25
|
-
|
21
|
+
const pn = parsePhoneNumber(phone, {
|
22
|
+
regionCode: iso2
|
26
23
|
});
|
24
|
+
const cleanedParsedPhone = pn.number?.input.replace(/[\s\-()]+/g, '');
|
25
|
+
return pn.valid && (pn.type === 'mobile' || pn.type === 'fixed-line-or-mobile') && cleanedParsedPhone === pn.number?.e164;
|
27
26
|
}
|
28
27
|
|
29
28
|
// 从带区号的电话号码中提取纯号码部分
|
@@ -32,7 +31,7 @@ function extractPhoneWithoutCode(phone, dialCode) {
|
|
32
31
|
// 先去除区号
|
33
32
|
const phoneWithoutCode = phone.replace(new RegExp(`^\\+${dialCode}`), '');
|
34
33
|
// 去除非数字字符,但保留括号
|
35
|
-
return phoneWithoutCode
|
34
|
+
return phoneWithoutCode;
|
36
35
|
}
|
37
36
|
|
38
37
|
// 添加区号到纯号码
|
@@ -67,6 +66,44 @@ export function getDialCodeByCountry(iso2) {
|
|
67
66
|
export function getCountryNameByCountry(iso2) {
|
68
67
|
return getCountryInfoByIso2(iso2, 'name') || '';
|
69
68
|
}
|
69
|
+
|
70
|
+
// 根据手机号识别国家,返回国家的 iso2 码
|
71
|
+
// 如果手机号带有区号,通过 parsePhoneNumber 识别
|
72
|
+
// 如果手机号不带区号,通过碰撞识别
|
73
|
+
export function detectCountryFromPhone(phoneNumber) {
|
74
|
+
try {
|
75
|
+
if (!phoneNumber) return undefined;
|
76
|
+
|
77
|
+
// 如果已经带了+号,尝试从前缀匹配
|
78
|
+
if (phoneNumber.startsWith('+')) {
|
79
|
+
const parsedPhone = parsePhoneNumber(phoneNumber);
|
80
|
+
if (parsedPhone.valid) {
|
81
|
+
return parsedPhone.regionCode.toLowerCase();
|
82
|
+
}
|
83
|
+
for (const country of defaultCountries) {
|
84
|
+
const parsed = parseCountry(country);
|
85
|
+
if (phoneNumber.startsWith(`+${parsed.dialCode}`)) {
|
86
|
+
const phoneWithCode = phoneNumber;
|
87
|
+
// 验证该号码是否有效
|
88
|
+
if (validatePhoneNumber(phoneWithCode, parsed.dialCode, parsed.iso2)) {
|
89
|
+
return parsed.iso2;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
} else {
|
94
|
+
for (const country of defaultCountries) {
|
95
|
+
const parsed = parseCountry(country);
|
96
|
+
const phoneWithCode = `+${parsed.dialCode}${phoneNumber}`;
|
97
|
+
if (validatePhoneNumber(phoneWithCode, parsed.dialCode, parsed.iso2)) {
|
98
|
+
return parsed.iso2;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
return undefined;
|
103
|
+
} catch {
|
104
|
+
return undefined;
|
105
|
+
}
|
106
|
+
}
|
70
107
|
export default function PhoneInput({
|
71
108
|
value = {
|
72
109
|
country: 'us',
|
@@ -131,7 +168,7 @@ export default function PhoneInput({
|
|
131
168
|
});
|
132
169
|
|
133
170
|
// 从完整电话号码中提取不带区号的部分用于显示,并去除格式化字符
|
134
|
-
const displayPhone = useMemo(() => phone
|
171
|
+
const displayPhone = useMemo(() => phone || extractPhoneWithoutCode(value.phone, currentDialCode), [phone, value.phone, currentDialCode]);
|
135
172
|
|
136
173
|
// 处理国家变更
|
137
174
|
const onCountryChange = newCountry => {
|
@@ -141,7 +178,7 @@ export default function PhoneInput({
|
|
141
178
|
|
142
179
|
// 预览模式
|
143
180
|
if (preview) {
|
144
|
-
const isValid = phone && validatePhoneNumber(phone);
|
181
|
+
const isValid = phone && validatePhoneNumber(phone, country);
|
145
182
|
const canDial = allowDial && isValid;
|
146
183
|
return /*#__PURE__*/_jsxs(Box, {
|
147
184
|
display: "flex",
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@arcblock/ux",
|
3
|
-
"version": "2.12.
|
3
|
+
"version": "2.12.46",
|
4
4
|
"description": "Common used react components for arcblock products",
|
5
5
|
"keywords": [
|
6
6
|
"react",
|
@@ -68,12 +68,12 @@
|
|
68
68
|
"react": ">=18.2.0",
|
69
69
|
"react-router-dom": ">=6.22.3"
|
70
70
|
},
|
71
|
-
"gitHead": "
|
71
|
+
"gitHead": "d93b2d0fe1c937b2f9864cd6b5ee231d1eb05ad4",
|
72
72
|
"dependencies": {
|
73
73
|
"@arcblock/did-motif": "^1.1.13",
|
74
|
-
"@arcblock/icons": "^2.12.
|
75
|
-
"@arcblock/nft-display": "^2.12.
|
76
|
-
"@arcblock/react-hooks": "^2.12.
|
74
|
+
"@arcblock/icons": "^2.12.46",
|
75
|
+
"@arcblock/nft-display": "^2.12.46",
|
76
|
+
"@arcblock/react-hooks": "^2.12.46",
|
77
77
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
78
78
|
"@fontsource/inter": "^5.0.16",
|
79
79
|
"@fontsource/ubuntu-mono": "^5.0.18",
|
@@ -87,6 +87,7 @@
|
|
87
87
|
"@types/dompurify": "^3.2.0",
|
88
88
|
"@types/mui-datatables": "^4.3.12",
|
89
89
|
"ahooks": "^3.7.10",
|
90
|
+
"awesome-phonenumber": "^7.4.0",
|
90
91
|
"axios": "^1.7.5",
|
91
92
|
"base64-url": "^2.3.3",
|
92
93
|
"copy-to-clipboard": "^3.3.2",
|
@@ -136,7 +136,7 @@ function Dashboard({
|
|
136
136
|
</Box>
|
137
137
|
)}
|
138
138
|
</Hidden>
|
139
|
-
<Box className="dashboard-main">
|
139
|
+
<Box className="dashboard-main" id="arc__dashboard-main">
|
140
140
|
{showToggleButton && (
|
141
141
|
<Box
|
142
142
|
sx={{
|
@@ -161,7 +161,7 @@ function Dashboard({
|
|
161
161
|
)}
|
162
162
|
</Box>
|
163
163
|
)}
|
164
|
-
<Container className="dashboard-content" {...(fullWidth && { maxWidth: false })}>
|
164
|
+
<Container className="dashboard-content" id="arc__dashboard-content" {...(fullWidth && { maxWidth: false })}>
|
165
165
|
{children}
|
166
166
|
</Container>
|
167
167
|
{footerVisible && <Footer {...footerProps} />}
|
package/src/PhoneInput/index.tsx
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { useCallback, useMemo, useState } from 'react';
|
2
2
|
import { Box, TextField, TextFieldProps, Typography } from '@mui/material';
|
3
|
+
import { parsePhoneNumber } from 'awesome-phonenumber';
|
3
4
|
import { defaultCountries, CountryIso2, usePhoneInput, parseCountry } from 'react-international-phone';
|
4
|
-
import isMobilePhone from 'validator/lib/isMobilePhone';
|
5
5
|
import CountrySelect, { CountryDisplayOptions } from './country-select';
|
6
6
|
import { mergeSx } from '../Util/style';
|
7
7
|
|
@@ -18,23 +18,25 @@ export interface PhoneInputProps extends Omit<TextFieldProps, 'value' | 'onChang
|
|
18
18
|
allowDial?: boolean; // 是否允许拨号, 只在 preview 为 true 时有效
|
19
19
|
}
|
20
20
|
|
21
|
-
export function validatePhoneNumber(phone: string): boolean {
|
21
|
+
export function validatePhoneNumber(phone: string, iso2: CountryIso2, dialCode: string = ''): boolean {
|
22
22
|
// 如果没有输入电话号码,返回false
|
23
23
|
if (!phone) return false;
|
24
24
|
|
25
|
-
// 检查是否只有国家区号
|
26
|
-
// 电话号码格式通常为 +区号 后跟电话号码
|
27
25
|
// 如果去除所有格式化字符后,只剩下区号,则认为只有区号
|
28
26
|
const cleanedPhone = phone.replace(/[\s\-()]+/g, '');
|
29
27
|
|
30
|
-
|
28
|
+
const phoneDialCode = dialCode || getDialCodeByCountry(iso2);
|
31
29
|
// 这表示电话号码只有区号部分
|
32
|
-
if (
|
30
|
+
if (getDialCodeWithoutPlus(phoneDialCode) === getDialCodeWithoutPlus(cleanedPhone)) {
|
33
31
|
return true; // 如果只有区号,则视为有效
|
34
32
|
}
|
35
33
|
|
36
34
|
// 否则使用validator进行完整校验
|
37
|
-
|
35
|
+
const pn = parsePhoneNumber(phone, { regionCode: iso2 });
|
36
|
+
const cleanedParsedPhone = pn.number?.input.replace(/[\s\-()]+/g, '');
|
37
|
+
return (
|
38
|
+
pn.valid && (pn.type === 'mobile' || pn.type === 'fixed-line-or-mobile') && cleanedParsedPhone === pn.number?.e164
|
39
|
+
);
|
38
40
|
}
|
39
41
|
|
40
42
|
// 从带区号的电话号码中提取纯号码部分
|
@@ -43,7 +45,7 @@ function extractPhoneWithoutCode(phone: string, dialCode: string): string {
|
|
43
45
|
// 先去除区号
|
44
46
|
const phoneWithoutCode = phone.replace(new RegExp(`^\\+${dialCode}`), '');
|
45
47
|
// 去除非数字字符,但保留括号
|
46
|
-
return phoneWithoutCode
|
48
|
+
return phoneWithoutCode;
|
47
49
|
}
|
48
50
|
|
49
51
|
// 添加区号到纯号码
|
@@ -83,6 +85,44 @@ export function getCountryNameByCountry(iso2: CountryIso2): string {
|
|
83
85
|
return getCountryInfoByIso2(iso2, 'name') || '';
|
84
86
|
}
|
85
87
|
|
88
|
+
// 根据手机号识别国家,返回国家的 iso2 码
|
89
|
+
// 如果手机号带有区号,通过 parsePhoneNumber 识别
|
90
|
+
// 如果手机号不带区号,通过碰撞识别
|
91
|
+
export function detectCountryFromPhone(phoneNumber?: string): CountryIso2 | undefined {
|
92
|
+
try {
|
93
|
+
if (!phoneNumber) return undefined;
|
94
|
+
|
95
|
+
// 如果已经带了+号,尝试从前缀匹配
|
96
|
+
if (phoneNumber.startsWith('+')) {
|
97
|
+
const parsedPhone = parsePhoneNumber(phoneNumber);
|
98
|
+
if (parsedPhone.valid) {
|
99
|
+
return parsedPhone.regionCode.toLowerCase() as CountryIso2;
|
100
|
+
}
|
101
|
+
for (const country of defaultCountries) {
|
102
|
+
const parsed = parseCountry(country);
|
103
|
+
if (phoneNumber.startsWith(`+${parsed.dialCode}`)) {
|
104
|
+
const phoneWithCode = phoneNumber;
|
105
|
+
// 验证该号码是否有效
|
106
|
+
if (validatePhoneNumber(phoneWithCode, parsed.dialCode, parsed.iso2)) {
|
107
|
+
return parsed.iso2;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}
|
111
|
+
} else {
|
112
|
+
for (const country of defaultCountries) {
|
113
|
+
const parsed = parseCountry(country);
|
114
|
+
const phoneWithCode = `+${parsed.dialCode}${phoneNumber}`;
|
115
|
+
if (validatePhoneNumber(phoneWithCode, parsed.dialCode, parsed.iso2)) {
|
116
|
+
return parsed.iso2;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
return undefined;
|
121
|
+
} catch {
|
122
|
+
return undefined;
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
86
126
|
export default function PhoneInput({
|
87
127
|
value = { country: 'us', phone: '' },
|
88
128
|
onChange,
|
@@ -148,7 +188,7 @@ export default function PhoneInput({
|
|
148
188
|
|
149
189
|
// 从完整电话号码中提取不带区号的部分用于显示,并去除格式化字符
|
150
190
|
const displayPhone = useMemo(
|
151
|
-
() =>
|
191
|
+
() => phone || extractPhoneWithoutCode(value.phone, currentDialCode),
|
152
192
|
[phone, value.phone, currentDialCode]
|
153
193
|
);
|
154
194
|
|
@@ -160,7 +200,7 @@ export default function PhoneInput({
|
|
160
200
|
|
161
201
|
// 预览模式
|
162
202
|
if (preview) {
|
163
|
-
const isValid = phone && validatePhoneNumber(phone);
|
203
|
+
const isValid = phone && validatePhoneNumber(phone, country);
|
164
204
|
const canDial = allowDial && isValid;
|
165
205
|
|
166
206
|
return (
|