message-verify 0.0.43-beta.0 → 0.0.45-beta.0
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.
- package/README.md +7 -49
- package/dist/compoments/input.d.ts +10 -1
- package/dist/compoments/input.js +40 -3
- package/dist/compoments/modal.d.ts +1 -0
- package/dist/compoments/modal.js +5 -2
- package/dist/index.d.ts +1 -0
- package/dist/locales/en.d.ts +10 -0
- package/dist/locales/en.js +9 -0
- package/dist/locales/id.d.ts +10 -0
- package/dist/locales/id.js +9 -0
- package/dist/locales/zh.d.ts +10 -0
- package/dist/locales/zh.js +9 -0
- package/dist/main.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/i18n.d.ts +3 -0
- package/dist/utils/i18n.js +20 -0
- package/dist/verify-modal.d.ts +3 -2
- package/dist/verify-modal.js +20 -16
- package/package.json +1 -1
- package/src/compoments/input.tsx +70 -7
- package/src/compoments/modal.tsx +6 -2
- package/src/index.tsx +1 -0
- package/src/locales/en.ts +9 -0
- package/src/locales/id.ts +9 -0
- package/src/locales/zh.ts +9 -0
- package/src/main.tsx +1 -0
- package/src/utils/i18n.ts +30 -0
- package/src/verify-modal.tsx +37 -29
package/README.md
CHANGED
@@ -1,54 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# MessageVerify 验证码弹窗组件接入说明
|
2
2
|
|
3
|
-
|
3
|
+
## 组件简介
|
4
4
|
|
5
|
-
|
5
|
+
`CreateMessageVerifyModal` 是一个用于短信验证码校验的弹窗组件,支持国际化,适用于敏感接口需要二次验证的场景。
|
6
6
|
|
7
|
-
|
8
|
-
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
7
|
+
## 快速接入
|
9
8
|
|
10
|
-
|
9
|
+
### 1. 引入组件
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
```js
|
15
|
-
export default tseslint.config({
|
16
|
-
extends: [
|
17
|
-
// Remove ...tseslint.configs.recommended and replace with this
|
18
|
-
...tseslint.configs.recommendedTypeChecked,
|
19
|
-
// Alternatively, use this for stricter rules
|
20
|
-
...tseslint.configs.strictTypeChecked,
|
21
|
-
// Optionally, add this for stylistic rules
|
22
|
-
...tseslint.configs.stylisticTypeChecked,
|
23
|
-
],
|
24
|
-
languageOptions: {
|
25
|
-
// other options...
|
26
|
-
parserOptions: {
|
27
|
-
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
28
|
-
tsconfigRootDir: import.meta.dirname,
|
29
|
-
},
|
30
|
-
},
|
31
|
-
})
|
32
|
-
```
|
33
|
-
|
34
|
-
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
35
|
-
|
36
|
-
```js
|
37
|
-
// eslint.config.js
|
38
|
-
import reactX from 'eslint-plugin-react-x'
|
39
|
-
import reactDom from 'eslint-plugin-react-dom'
|
40
|
-
|
41
|
-
export default tseslint.config({
|
42
|
-
plugins: {
|
43
|
-
// Add the react-x and react-dom plugins
|
44
|
-
'react-x': reactX,
|
45
|
-
'react-dom': reactDom,
|
46
|
-
},
|
47
|
-
rules: {
|
48
|
-
// other rules...
|
49
|
-
// Enable its recommended typescript rules
|
50
|
-
...reactX.configs['recommended-typescript'].rules,
|
51
|
-
...reactDom.configs.recommended.rules,
|
52
|
-
},
|
53
|
-
})
|
54
|
-
```
|
11
|
+
```typescript
|
12
|
+
import {createMessageVerifyModal} from 'message-verify';
|
@@ -6,5 +6,14 @@ interface CustomInputProps {
|
|
6
6
|
type?: string;
|
7
7
|
style?: React.CSSProperties;
|
8
8
|
}
|
9
|
-
|
9
|
+
interface OTPInputProps {
|
10
|
+
length: number;
|
11
|
+
value?: string;
|
12
|
+
onChange?: (val: string) => void;
|
13
|
+
style?: React.CSSProperties;
|
14
|
+
}
|
15
|
+
declare const OTPInput: React.FC<OTPInputProps>;
|
16
|
+
declare const Input: React.FC<CustomInputProps> & {
|
17
|
+
OTP: typeof OTPInput;
|
18
|
+
};
|
10
19
|
export default Input;
|
package/dist/compoments/input.js
CHANGED
@@ -1,6 +1,42 @@
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
-
|
3
|
-
|
2
|
+
import { useRef } from 'react';
|
3
|
+
const OTPInput = ({ length, value = '', onChange, style }) => {
|
4
|
+
const inputsRef = useRef([]);
|
5
|
+
// 拆分 value
|
6
|
+
const chars = [];
|
7
|
+
for (let i = 0; i < length; i++) {
|
8
|
+
chars.push(value[i] || '');
|
9
|
+
}
|
10
|
+
const handleChange = (idx, char) => {
|
11
|
+
if (!/^[0-9a-zA-Z]?$/.test(char))
|
12
|
+
return; // 只允许单字符
|
13
|
+
const arr = chars.slice();
|
14
|
+
arr[idx] = char;
|
15
|
+
const newVal = arr.join('');
|
16
|
+
onChange?.(newVal);
|
17
|
+
// 自动聚焦下一个
|
18
|
+
if (char && idx < length - 1) {
|
19
|
+
inputsRef.current[idx + 1]?.focus();
|
20
|
+
}
|
21
|
+
};
|
22
|
+
const handleKeyDown = (idx, e) => {
|
23
|
+
if (e.key === 'Backspace' && !chars[idx] && idx > 0) {
|
24
|
+
// 删除时自动聚焦上一个
|
25
|
+
inputsRef.current[idx - 1]?.focus();
|
26
|
+
}
|
27
|
+
};
|
28
|
+
return (_jsx("div", { style: { display: 'flex', gap: 8, ...style }, children: Array.from({ length }).map((_, idx) => (_jsx("input", { ref: el => { inputsRef.current[idx] = el; }, type: "text", maxLength: 1, value: chars[idx], onChange: e => handleChange(idx, e.target.value), onKeyDown: e => handleKeyDown(idx, e), style: {
|
29
|
+
width: 32,
|
30
|
+
height: 36,
|
31
|
+
textAlign: 'center',
|
32
|
+
fontSize: 18,
|
33
|
+
border: '1px solid #d9d9d9',
|
34
|
+
borderRadius: 8,
|
35
|
+
outline: 'none',
|
36
|
+
} }, idx))) }));
|
37
|
+
};
|
38
|
+
const Input = (props) => {
|
39
|
+
return (_jsx("input", { type: props.type || 'text', value: props.value, placeholder: props.placeholder, onChange: e => props.onChange?.(e.target.value), style: {
|
4
40
|
padding: '6px 12px',
|
5
41
|
border: '1px solid #d9d9d9',
|
6
42
|
borderRadius: 4,
|
@@ -9,7 +45,8 @@ const Input = ({ value, onChange, placeholder, type = 'text', style }) => {
|
|
9
45
|
width: '100%',
|
10
46
|
boxSizing: 'border-box',
|
11
47
|
height: 36,
|
12
|
-
...style,
|
48
|
+
...props.style,
|
13
49
|
} }));
|
14
50
|
};
|
51
|
+
Input.OTP = OTPInput;
|
15
52
|
export default Input;
|
package/dist/compoments/modal.js
CHANGED
@@ -32,9 +32,12 @@ const closeBtnStyle = {
|
|
32
32
|
cursor: 'pointer',
|
33
33
|
lineHeight: 1,
|
34
34
|
};
|
35
|
-
const Modal = ({ visible, onClose, children }) => {
|
35
|
+
const Modal = ({ visible, onClose, children, width }) => {
|
36
36
|
if (!visible)
|
37
37
|
return null;
|
38
|
-
return (_jsx("div", { style: modalStyle, children: _jsxs("div", { style:
|
38
|
+
return (_jsx("div", { style: modalStyle, children: _jsxs("div", { style: {
|
39
|
+
...contentStyle,
|
40
|
+
width: width ? (typeof width === 'number' ? `${width}px` : width) : undefined, // 应用width
|
41
|
+
}, children: [_jsx("button", { style: closeBtnStyle, onClick: onClose, "aria-label": "\u5173\u95ED", children: "\u00D7" }), children] }) }));
|
39
42
|
};
|
40
43
|
export default Modal;
|
package/dist/index.d.ts
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
export default {
|
2
|
+
pleaseEnterPicVerifyCode: 'Please enter the image verification code',
|
3
|
+
alreadySend: 'A verification SMS has been sent to your phone: {phone}',
|
4
|
+
verifyCode: 'Verification Code',
|
5
|
+
pleaseEnterAndSend: 'Please enter the image verification code first, then resend the code',
|
6
|
+
sendSuccess: 'Successfully sent',
|
7
|
+
reSend: 'Resend',
|
8
|
+
countDownSecound: '{countdown} seconds'
|
9
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export default {
|
2
|
+
pleaseEnterPicVerifyCode: 'Silakan masukkan kode verifikasi gambar',
|
3
|
+
alreadySend: 'SMS verifikasi telah dikirim ke nomor ponsel Anda: {phone}',
|
4
|
+
verifyCode: 'Kode Verifikasi',
|
5
|
+
pleaseEnterAndSend: 'Harap masukkan kode verifikasi gambar terlebih dahulu, lalu kirim ulang kode',
|
6
|
+
sendSuccess: 'Berhasil dikirim',
|
7
|
+
reSend: 'Kirim Ulang',
|
8
|
+
countDownSecound: '{countdown} detik'
|
9
|
+
};
|
package/dist/main.js
CHANGED
@@ -5,6 +5,7 @@ const data = "{\"mobile\":\"188****4035\",\"verifyCodeKey\":\"3f025b33-988e-47c0
|
|
5
5
|
createRoot(document.getElementById('root')).render(_jsx("button", { onClick: () => createMessageVerifyModal({
|
6
6
|
data,
|
7
7
|
config: {},
|
8
|
+
lang: 'en',
|
8
9
|
http: Object.assign(() => Promise.resolve({}), {
|
9
10
|
defaults: {
|
10
11
|
headers: {
|
@@ -1 +1 @@
|
|
1
|
-
{"root":["../src/index.tsx","../src/main.tsx","../src/resource.ts","../src/verify-modal.tsx","../src/vite-env.d.ts","../src/compoments/button.tsx","../src/compoments/index.ts","../src/compoments/input.tsx","../src/compoments/message.tsx","../src/compoments/modal.tsx","../src/utils/status.ts"],"version":"5.7.3"}
|
1
|
+
{"root":["../src/index.tsx","../src/main.tsx","../src/resource.ts","../src/verify-modal.tsx","../src/vite-env.d.ts","../src/compoments/button.tsx","../src/compoments/index.ts","../src/compoments/input.tsx","../src/compoments/message.tsx","../src/compoments/modal.tsx","../src/locales/en.ts","../src/locales/id.ts","../src/locales/zh.ts","../src/utils/i18n.ts","../src/utils/status.ts"],"version":"5.7.3"}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import zh from '../locales/zh';
|
2
|
+
import en from '../locales/en';
|
3
|
+
import id from '../locales/id';
|
4
|
+
const locales = {
|
5
|
+
zh,
|
6
|
+
en,
|
7
|
+
id
|
8
|
+
};
|
9
|
+
function t(key, language = 'zh', params) {
|
10
|
+
const messages = locales[language] || locales.zh;
|
11
|
+
let template = messages[key] || '';
|
12
|
+
if (params) {
|
13
|
+
Object.keys(params).forEach(paramKey => {
|
14
|
+
const reg = new RegExp(`{${paramKey}}`, 'g');
|
15
|
+
template = template.replace(reg, String(params[paramKey]));
|
16
|
+
});
|
17
|
+
}
|
18
|
+
return template;
|
19
|
+
}
|
20
|
+
export default t;
|
package/dist/verify-modal.d.ts
CHANGED
@@ -2,6 +2,7 @@ type ModalConfig = {
|
|
2
2
|
data: string;
|
3
3
|
onClose?: () => void;
|
4
4
|
config: object;
|
5
|
+
lang: 'zh' | 'en' | 'id';
|
5
6
|
http: {
|
6
7
|
(params: object): Promise<unknown>;
|
7
8
|
defaults: {
|
@@ -13,7 +14,7 @@ type ModalConfig = {
|
|
13
14
|
};
|
14
15
|
};
|
15
16
|
};
|
16
|
-
declare const
|
17
|
+
declare const CreateMessageVerifyModal: ({ props }: {
|
17
18
|
props: ModalConfig;
|
18
19
|
}) => import("react/jsx-runtime.js").JSX.Element;
|
19
|
-
export default
|
20
|
+
export default CreateMessageVerifyModal;
|
package/dist/verify-modal.js
CHANGED
@@ -1,24 +1,24 @@
|
|
1
|
-
import {
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
2
2
|
import { useEffect, useState } from 'react';
|
3
3
|
import { Modal, Input, Button, message } from './compoments/index.js';
|
4
4
|
import Api from './resource.js';
|
5
|
-
|
5
|
+
import t from './utils/i18n.js';
|
6
|
+
const CreateMessageVerifyModal = ({ props }) => {
|
6
7
|
const [visible, setVisible] = useState(true);
|
7
8
|
const [countdown, setCountdown] = useState(3);
|
9
|
+
const [otp, setOtp] = useState('');
|
8
10
|
const [captchaImage, setCaptchaImage] = useState('');
|
9
11
|
const [captchCode, setCaptchCode] = useState('');
|
10
12
|
const [captchaKey, setCaptchaKey] = useState('');
|
11
|
-
const { data, config, http } = props || {};
|
13
|
+
const { data, config, http, lang } = props || {};
|
12
14
|
const dataObj = JSON.parse(data || '{}');
|
13
|
-
const { mobile } = dataObj || {};
|
15
|
+
const { mobile, verifyCodeKey } = dataObj || {};
|
14
16
|
const getKey = async () => {
|
15
17
|
const res = await Api.getCaptchaKey();
|
16
18
|
const { body: key } = res.data || {};
|
17
19
|
setCaptchaKey(key);
|
18
20
|
if (key) {
|
19
|
-
console.log('res=>key', key);
|
20
21
|
const image = Api.getCaptchaImgUrl(key);
|
21
|
-
console.log('image', image);
|
22
22
|
setCaptchaImage(image);
|
23
23
|
}
|
24
24
|
};
|
@@ -37,25 +37,29 @@ const ModalDom = ({ props }) => {
|
|
37
37
|
clearInterval(timer);
|
38
38
|
};
|
39
39
|
}, [visible, countdown]);
|
40
|
-
useEffect(() => {
|
41
|
-
|
42
|
-
}, [mobile])
|
40
|
+
// useEffect(() => {
|
41
|
+
// console.log('mobile', mobile);
|
42
|
+
// }, [mobile])
|
43
43
|
const handleResend = async () => {
|
44
44
|
setCountdown(60);
|
45
45
|
const res = await Api.reSendCode({ captchaKey, captcha: captchCode });
|
46
46
|
console.log('handleResend=>res', res);
|
47
|
-
message.success('
|
47
|
+
message.success(t('sendSuccess', lang));
|
48
48
|
};
|
49
|
-
return (_jsxs(Modal, { visible: visible, onClose: () => {
|
49
|
+
return (_jsxs(Modal, { width: 420, visible: visible, onClose: () => {
|
50
50
|
setVisible(false);
|
51
51
|
props.onClose?.();
|
52
|
-
}, children: [
|
53
|
-
|
54
|
-
} }), captchaImage && _jsx("img", { src: captchaImage, alt: "\u9A8C\u8BC1\u7801", style: { width: 100, height: 30, marginLeft: 8 }, onClick: getKey })] }), (countdown > 0 || captchCode) ? null : _jsx("div", { style: { fontSize: 14, color: 'red' }, children: "\u8BF7\u8F93\u5165\u56FE\u5F62\u9A8C\u8BC1\u7801\u540E\uFF0C\u91CD\u65B0\u53D1\u9001\u9A8C\u8BC1\u7801" }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 }, children: [_jsx(Input, { placeholder: "\u8BF7\u8F93\u5165\u9A8C\u8BC1\u7801", onChange: (val) => {
|
52
|
+
}, children: [_jsx("div", { style: { fontSize: 14, color: '#666' }, children: t('alreadySend', lang, { phone: mobile }) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 12 }, children: [_jsx(Input.OTP, { length: 6, value: otp, onChange: (val) => {
|
53
|
+
setOtp(val);
|
55
54
|
if (val.length === 6) {
|
56
55
|
http.defaults.headers.common['sms-code'] = val;
|
56
|
+
http.defaults.headers.common['sms-code-key'] = verifyCodeKey;
|
57
57
|
http(config);
|
58
|
+
setVisible(false);
|
58
59
|
}
|
59
|
-
} }), _jsx(Button, { disabled: countdown > 0 || !captchCode, onClick: handleResend, children: countdown > 0 ?
|
60
|
+
} }), _jsx(Button, { disabled: countdown > 0 || !captchCode, onClick: handleResend, style: { padding: '10px' }, children: countdown > 0 ? t('countDownSecound', lang, { countdown }) : t('reSend', lang) })] }), countdown > 0 ? null :
|
61
|
+
_jsxs("div", { style: { marginTop: 12, display: 'flex', alignItems: 'center' }, children: [_jsx(Input, { placeholder: t('pleaseEnterPicVerifyCode', lang), onChange: (e) => {
|
62
|
+
setCaptchCode(e);
|
63
|
+
}, style: { width: 300 } }), captchaImage && _jsx("img", { src: captchaImage, alt: t('verifyCode', lang), style: { width: 100, height: 30, marginLeft: 8 }, onClick: getKey })] }), (countdown > 0 || captchCode) ? null : _jsx("div", { style: { fontSize: 14, color: 'red' }, children: t('pleaseEnterAndSend', lang) })] }));
|
60
64
|
};
|
61
|
-
export default
|
65
|
+
export default CreateMessageVerifyModal;
|
package/package.json
CHANGED
package/src/compoments/input.tsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import React from 'react';
|
1
|
+
import React, { useRef } from 'react';
|
2
2
|
|
3
3
|
interface CustomInputProps {
|
4
4
|
value?: string;
|
@@ -8,13 +8,74 @@ interface CustomInputProps {
|
|
8
8
|
style?: React.CSSProperties;
|
9
9
|
}
|
10
10
|
|
11
|
-
|
11
|
+
interface OTPInputProps {
|
12
|
+
length: number;
|
13
|
+
value?: string;
|
14
|
+
onChange?: (val: string) => void;
|
15
|
+
style?: React.CSSProperties;
|
16
|
+
}
|
17
|
+
|
18
|
+
const OTPInput: React.FC<OTPInputProps> = ({ length, value = '', onChange, style }) => {
|
19
|
+
const inputsRef = useRef<Array<HTMLInputElement | null>>([]);
|
20
|
+
|
21
|
+
// 拆分 value
|
22
|
+
const chars: string[] = [];
|
23
|
+
for (let i = 0; i < length; i++) {
|
24
|
+
chars.push(value[i] || '');
|
25
|
+
}
|
26
|
+
|
27
|
+
const handleChange = (idx: number, char: string) => {
|
28
|
+
if (!/^[0-9a-zA-Z]?$/.test(char)) return; // 只允许单字符
|
29
|
+
const arr = chars.slice();
|
30
|
+
arr[idx] = char;
|
31
|
+
const newVal = arr.join('');
|
32
|
+
onChange?.(newVal);
|
33
|
+
// 自动聚焦下一个
|
34
|
+
if (char && idx < length - 1) {
|
35
|
+
inputsRef.current[idx + 1]?.focus();
|
36
|
+
}
|
37
|
+
};
|
38
|
+
|
39
|
+
const handleKeyDown = (idx: number, e: React.KeyboardEvent<HTMLInputElement>) => {
|
40
|
+
if (e.key === 'Backspace' && !chars[idx] && idx > 0) {
|
41
|
+
// 删除时自动聚焦上一个
|
42
|
+
inputsRef.current[idx - 1]?.focus();
|
43
|
+
}
|
44
|
+
};
|
45
|
+
|
46
|
+
return (
|
47
|
+
<div style={{ display: 'flex', gap: 8, ...style }}>
|
48
|
+
{Array.from({ length }).map((_, idx) => (
|
49
|
+
<input
|
50
|
+
key={idx}
|
51
|
+
ref={el => { inputsRef.current[idx] = el; }}
|
52
|
+
type="text"
|
53
|
+
maxLength={1}
|
54
|
+
value={chars[idx]}
|
55
|
+
onChange={e => handleChange(idx, e.target.value)}
|
56
|
+
onKeyDown={e => handleKeyDown(idx, e)}
|
57
|
+
style={{
|
58
|
+
width: 32,
|
59
|
+
height: 36,
|
60
|
+
textAlign: 'center',
|
61
|
+
fontSize: 18,
|
62
|
+
border: '1px solid #d9d9d9',
|
63
|
+
borderRadius: 8,
|
64
|
+
outline: 'none',
|
65
|
+
}}
|
66
|
+
/>
|
67
|
+
))}
|
68
|
+
</div>
|
69
|
+
);
|
70
|
+
};
|
71
|
+
|
72
|
+
const Input: React.FC<CustomInputProps> & { OTP: typeof OTPInput } = (props) => {
|
12
73
|
return (
|
13
74
|
<input
|
14
|
-
type={type}
|
15
|
-
value={value}
|
16
|
-
placeholder={placeholder}
|
17
|
-
onChange={e => onChange?.(e.target.value)}
|
75
|
+
type={props.type || 'text'}
|
76
|
+
value={props.value}
|
77
|
+
placeholder={props.placeholder}
|
78
|
+
onChange={e => props.onChange?.(e.target.value)}
|
18
79
|
style={{
|
19
80
|
padding: '6px 12px',
|
20
81
|
border: '1px solid #d9d9d9',
|
@@ -24,10 +85,12 @@ const Input: React.FC<CustomInputProps> = ({ value, onChange, placeholder, type
|
|
24
85
|
width: '100%',
|
25
86
|
boxSizing: 'border-box',
|
26
87
|
height: 36,
|
27
|
-
...style,
|
88
|
+
...props.style,
|
28
89
|
}}
|
29
90
|
/>
|
30
91
|
);
|
31
92
|
};
|
32
93
|
|
94
|
+
Input.OTP = OTPInput;
|
95
|
+
|
33
96
|
export default Input;
|
package/src/compoments/modal.tsx
CHANGED
@@ -4,6 +4,7 @@ interface ModalProps {
|
|
4
4
|
visible: boolean;
|
5
5
|
onClose: () => void;
|
6
6
|
children: React.ReactNode;
|
7
|
+
width?: number | string;
|
7
8
|
}
|
8
9
|
|
9
10
|
const modalStyle: React.CSSProperties = {
|
@@ -42,12 +43,15 @@ const closeBtnStyle: React.CSSProperties = {
|
|
42
43
|
lineHeight: 1,
|
43
44
|
};
|
44
45
|
|
45
|
-
const Modal: React.FC<ModalProps> = ({ visible, onClose, children }) => {
|
46
|
+
const Modal: React.FC<ModalProps> = ({ visible, onClose, children, width }) => {
|
46
47
|
if (!visible) return null;
|
47
48
|
|
48
49
|
return (
|
49
50
|
<div style={modalStyle}>
|
50
|
-
<div style={
|
51
|
+
<div style={{
|
52
|
+
...contentStyle,
|
53
|
+
width: width ? (typeof width === 'number' ? `${width}px` : width) : undefined, // 应用width
|
54
|
+
}}>
|
51
55
|
<button style={closeBtnStyle} onClick={onClose} aria-label="关闭">
|
52
56
|
×
|
53
57
|
</button>
|
package/src/index.tsx
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
export default {
|
2
|
+
pleaseEnterPicVerifyCode: 'Please enter the image verification code',
|
3
|
+
alreadySend: 'A verification SMS has been sent to your phone: {phone}',
|
4
|
+
verifyCode: 'Verification Code',
|
5
|
+
pleaseEnterAndSend: 'Please enter the image verification code first, then resend the code',
|
6
|
+
sendSuccess: 'Successfully sent',
|
7
|
+
reSend: 'Resend',
|
8
|
+
countDownSecound: '{countdown} seconds'
|
9
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export default {
|
2
|
+
pleaseEnterPicVerifyCode: 'Silakan masukkan kode verifikasi gambar',
|
3
|
+
alreadySend: 'SMS verifikasi telah dikirim ke nomor ponsel Anda: {phone}',
|
4
|
+
verifyCode: 'Kode Verifikasi',
|
5
|
+
pleaseEnterAndSend: 'Harap masukkan kode verifikasi gambar terlebih dahulu, lalu kirim ulang kode',
|
6
|
+
sendSuccess: 'Berhasil dikirim',
|
7
|
+
reSend: 'Kirim Ulang',
|
8
|
+
countDownSecound: '{countdown} detik'
|
9
|
+
};
|
package/src/main.tsx
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
import zh from '../locales/zh';
|
2
|
+
import en from '../locales/en';
|
3
|
+
import id from '../locales/id';
|
4
|
+
|
5
|
+
type LocaleType = 'zh' | 'en' | 'id';
|
6
|
+
type LocaleMessages = Record<string, string>;
|
7
|
+
|
8
|
+
const locales: Record<LocaleType, LocaleMessages> = {
|
9
|
+
zh,
|
10
|
+
en,
|
11
|
+
id
|
12
|
+
};
|
13
|
+
|
14
|
+
function t(
|
15
|
+
key: string,
|
16
|
+
language: LocaleType = 'zh',
|
17
|
+
params?: Record<string, string | number>,
|
18
|
+
): string {
|
19
|
+
const messages = locales[language] || locales.zh;
|
20
|
+
let template = messages[key] || '';
|
21
|
+
if (params) {
|
22
|
+
Object.keys(params).forEach(paramKey => {
|
23
|
+
const reg = new RegExp(`{${paramKey}}`, 'g');
|
24
|
+
template = template.replace(reg, String(params[paramKey]));
|
25
|
+
});
|
26
|
+
}
|
27
|
+
return template;
|
28
|
+
}
|
29
|
+
|
30
|
+
export default t;
|
package/src/verify-modal.tsx
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
import { useEffect, useState } from 'react'
|
2
2
|
import { Modal, Input, Button, message } from './compoments/index.js'
|
3
3
|
import Api from './resource.js'
|
4
|
+
import t from './utils/i18n.js'
|
4
5
|
|
5
6
|
type ModalConfig = {
|
6
7
|
data: string;
|
7
8
|
onClose?: () => void;
|
8
9
|
config: object;
|
10
|
+
lang: 'zh' | 'en' | 'id';
|
9
11
|
http: {
|
10
12
|
(params: object): Promise<unknown>;
|
11
13
|
defaults: {
|
@@ -17,25 +19,23 @@ type ModalConfig = {
|
|
17
19
|
}
|
18
20
|
};
|
19
21
|
}
|
20
|
-
const
|
22
|
+
const CreateMessageVerifyModal = ({ props }: { props: ModalConfig }) => {
|
21
23
|
const [visible, setVisible] = useState(true);
|
22
24
|
const [countdown, setCountdown] = useState(3);
|
25
|
+
const [otp, setOtp] = useState('');
|
23
26
|
const [captchaImage, setCaptchaImage] = useState<string>('');
|
24
27
|
const [captchCode, setCaptchCode] = useState<string>('');
|
25
28
|
const [captchaKey, setCaptchaKey] = useState<string>('');
|
26
|
-
const { data, config, http } = props || {};
|
29
|
+
const { data, config, http, lang } = props || {};
|
27
30
|
const dataObj = JSON.parse(data || '{}');
|
28
|
-
const { mobile } = dataObj || {};
|
29
|
-
|
31
|
+
const { mobile, verifyCodeKey } = dataObj || {};
|
30
32
|
|
31
33
|
const getKey = async () => {
|
32
34
|
const res = await Api.getCaptchaKey();
|
33
35
|
const { body: key } = res.data || {};
|
34
36
|
setCaptchaKey(key);
|
35
37
|
if(key) {
|
36
|
-
console.log('res=>key', key);
|
37
38
|
const image = Api.getCaptchaImgUrl(key) as unknown;
|
38
|
-
console.log('image', image);
|
39
39
|
setCaptchaImage(image as string);
|
40
40
|
}
|
41
41
|
}
|
@@ -55,56 +55,64 @@ const ModalDom = ({ props }: { props: ModalConfig }) => {
|
|
55
55
|
};
|
56
56
|
}, [visible, countdown]);
|
57
57
|
|
58
|
-
useEffect(() => {
|
59
|
-
|
60
|
-
}, [mobile])
|
58
|
+
// useEffect(() => {
|
59
|
+
// console.log('mobile', mobile);
|
60
|
+
// }, [mobile])
|
61
61
|
|
62
62
|
const handleResend = async() => {
|
63
63
|
setCountdown(60);
|
64
64
|
const res = await Api.reSendCode({captchaKey, captcha: captchCode});
|
65
65
|
console.log('handleResend=>res', res);
|
66
|
-
message.success('
|
66
|
+
message.success(t('sendSuccess', lang));
|
67
67
|
}
|
68
68
|
|
69
69
|
return (
|
70
70
|
<Modal
|
71
|
+
width={420}
|
71
72
|
visible={visible}
|
72
73
|
onClose={() => {
|
73
74
|
setVisible(false);
|
74
75
|
props.onClose?.();
|
75
76
|
}}
|
76
77
|
>
|
77
|
-
<div style={{ fontSize: 14, color: '#666' }}
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
}}
|
84
|
-
/>
|
85
|
-
|
86
|
-
{captchaImage && <img src={captchaImage} alt="验证码" style={{ width: 100, height: 30, marginLeft: 8 }} onClick={getKey} />}
|
87
|
-
</div>}
|
88
|
-
{(countdown > 0 || captchCode) ? null : <div style={{ fontSize: 14, color: 'red' }}>请输入图形验证码后,重新发送验证码</div>}
|
89
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 }}>
|
90
|
-
<Input
|
91
|
-
placeholder="请输入验证码"
|
78
|
+
<div style={{ fontSize: 14, color: '#666' }}>{t('alreadySend', lang, { phone: mobile})}</div>
|
79
|
+
|
80
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 12 }}>
|
81
|
+
<Input.OTP
|
82
|
+
length={6}
|
83
|
+
value={otp}
|
92
84
|
onChange={(val) => {
|
85
|
+
setOtp(val);
|
93
86
|
if(val.length === 6) {
|
94
87
|
http.defaults.headers.common['sms-code'] = val as string;
|
88
|
+
http.defaults.headers.common['sms-code-key'] = verifyCodeKey as string;
|
95
89
|
http(config);
|
90
|
+
setVisible(false);
|
96
91
|
}
|
97
|
-
}}
|
98
|
-
/>
|
92
|
+
}} />
|
99
93
|
<Button
|
100
94
|
disabled={countdown > 0 || !captchCode}
|
101
95
|
onClick={handleResend}
|
96
|
+
style={{ padding: '10px' }}
|
102
97
|
>
|
103
|
-
{countdown > 0 ?
|
98
|
+
{countdown > 0 ? t('countDownSecound', lang, { countdown }) : t('reSend', lang)}
|
104
99
|
</Button>
|
105
100
|
</div>
|
101
|
+
{countdown > 0 ? null :
|
102
|
+
<div style={{ marginTop: 12, display: 'flex', alignItems: 'center' }}>
|
103
|
+
<Input
|
104
|
+
placeholder={t('pleaseEnterPicVerifyCode', lang)}
|
105
|
+
onChange={(e) => {
|
106
|
+
setCaptchCode(e as string)
|
107
|
+
}}
|
108
|
+
style={{ width: 300 }}
|
109
|
+
/>
|
110
|
+
|
111
|
+
{captchaImage && <img src={captchaImage} alt={t('verifyCode', lang)} style={{ width: 100, height: 30, marginLeft: 8 }} onClick={getKey} />}
|
112
|
+
</div>}
|
113
|
+
{(countdown > 0 || captchCode) ? null : <div style={{ fontSize: 14, color: 'red' }}>{t('pleaseEnterAndSend', lang)}</div>}
|
106
114
|
</Modal>
|
107
115
|
);
|
108
116
|
};
|
109
117
|
|
110
|
-
export default
|
118
|
+
export default CreateMessageVerifyModal;
|