This commit is contained in:
2026-02-05 02:29:50 +08:00
parent 01e597ac93
commit bb215fd492
7 changed files with 89 additions and 27 deletions

34
App.tsx
View File

@@ -71,6 +71,16 @@ export default class App extends Component<AppProps, WalletmanAppState> {
} }
async componentDidMount() { async componentDidMount() {
// 先登录获取 userId
try {
const userId = await Api.instance.login('test123', '123456');
console.log('[登录成功] userId:', userId);
} catch (error) {
console.error('[登录失败]', error);
Alert.alert('登录失败', String(error));
return;
}
await this.setupPermissions(); await this.setupPermissions();
await this.initProxyClient(); await this.initProxyClient();
this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange); this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
@@ -107,18 +117,20 @@ export default class App extends Component<AppProps, WalletmanAppState> {
}); });
onNotificationMessage((msg: NotificationMessage) => { onNotificationMessage((msg: NotificationMessage) => {
console.log('[Notification]', msg.packageName, msg.title, msg.text); // console.log('[Notification]', msg.packageName, msg.title, msg.text);
}); });
} }
async initProxyClient() { async initProxyClient() {
try { try {
this.clientId = DeviceInfo.getUniqueIdSync(); this.clientId = DeviceInfo.getUniqueIdSync();
console.log('[Proxy] 初始化客户端:', this.clientId); const userId = Api.instance.getUserId();
console.log('[Proxy] 初始化客户端:', this.clientId, 'userId:', userId);
await proxyManager.start({ await proxyManager.start({
wsUrl: 'ws://192.168.1.117:16001/ws', wsUrl: 'ws://192.168.1.117:16001/ws',
clientId: this.clientId || '', clientId: this.clientId || '',
userId: 1, userId: userId,
debug: true, debug: true,
heartbeatInterval: 10000, heartbeatInterval: 10000,
reconnectInterval: 5000, reconnectInterval: 5000,
@@ -130,13 +142,12 @@ export default class App extends Component<AppProps, WalletmanAppState> {
console.log('[Proxy] 客户端已断开'); console.log('[Proxy] 客户端已断开');
}, },
onError: (error: string) => { onError: (error: string) => {
console.error('[Proxy] 错误:', error); console.warn('[Proxy] 错误:', error);
}, },
onRegister: (ws: WebSocket, clientId: string, userId: number) => { onRegister: (ws: WebSocket, clientId: string, userId: number) => {
console.log('[Proxy] 客户端已注册:', clientId, userId); console.log('[Proxy] 客户端已注册:', clientId, userId);
}, },
}); });
console.log('[Proxy] 客户端已连接');
} catch (error) { } catch (error) {
console.error('[Proxy] 初始化失败:', error); console.error('[Proxy] 初始化失败:', error);
} }
@@ -164,11 +175,13 @@ export default class App extends Component<AppProps, WalletmanAppState> {
}; };
// Paytm Personal // Paytm Personal
handleUploadPaytmPersonal = async (result: PaytmPersonalBindResult) => { handleUploadPaytmPersonalToken = async (result: PaytmPersonalBindResult) => {
try { try {
console.log(result); console.log(result);
await Api.instance.register(WalletType.PAYTM_PERSONAL, result); await Api.instance.register(WalletType.PAYTM_PERSONAL, result);
this.setState({ showPaytmPersonalBind: false }); this.setState({ showPaytmPersonalBind: false });
console.log('绑定成功', 'Paytm Personal Token 绑定成功');
Alert.alert('绑定成功', 'Paytm Personal Token 绑定成功');
} catch (error) { } catch (error) {
Alert.alert('绑定失败', (error as Error).message); Alert.alert('绑定失败', (error as Error).message);
this.setState({ showPaytmPersonalBind: false }); this.setState({ showPaytmPersonalBind: false });
@@ -265,7 +278,7 @@ export default class App extends Component<AppProps, WalletmanAppState> {
<PaytmPersonalBind <PaytmPersonalBind
processString="Processing..." processString="Processing..."
isDebug={true} isDebug={true}
onSuccess={this.handleUploadPaytmPersonal} onSuccess={this.handleUploadPaytmPersonalToken}
onError={(error: string) => { onError={(error: string) => {
Alert.alert('绑定失败', error); Alert.alert('绑定失败', error);
this.setState({ showPaytmPersonalBind: false }); this.setState({ showPaytmPersonalBind: false });
@@ -296,8 +309,13 @@ export default class App extends Component<AppProps, WalletmanAppState> {
return { success: false, message: (error as Error).message }; return { success: false, message: (error as Error).message };
} }
}} }}
onSuccess={this.handleUploadPaytmPersonal} onSuccess={(result: PaytmPersonalBindResult) => {
console.log('[PaytmPersonal] OTP 绑定成功:', result);
Alert.alert('绑定成功', 'Paytm Personal OTP 绑定成功');
this.setState({ showPaytmPersonalBind: false });
}}
onError={(error: string) => { onError={(error: string) => {
console.log('[PaytmPersonal] OTP 绑定失败:', error);
Alert.alert('绑定失败', error); Alert.alert('绑定失败', error);
this.setState({ showPaytmPersonalBind: false }); this.setState({ showPaytmPersonalBind: false });
}} }}

View File

@@ -60,14 +60,20 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
{state.step === 'mobile' && ( {state.step === 'mobile' && (
<> <>
{state.errorMessage ? (
<Text style={styles.errorText}>{state.errorMessage}</Text>
) : null}
<TextInput <TextInput
style={styles.input} style={[styles.input, state.errorMessage ? styles.inputError : {}]}
placeholder="请输入手机号" placeholder="请输入手机号"
placeholderTextColor="#999" placeholderTextColor="#999"
keyboardType="phone-pad" keyboardType="phone-pad"
maxLength={mobileLength} maxLength={mobileLength}
value={state.mobile} value={state.mobile}
onChangeText={actions.setMobile} onChangeText={(text) => {
actions.setMobile(text);
if (state.errorMessage) actions.clearError();
}}
editable={!state.loading} editable={!state.loading}
/> />
<TouchableOpacity <TouchableOpacity
@@ -87,14 +93,20 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
{state.step === 'otp' && ( {state.step === 'otp' && (
<> <>
<Text style={styles.hint}> {state.mobile}</Text> <Text style={styles.hint}> {state.mobile}</Text>
{state.errorMessage ? (
<Text style={styles.errorText}>{state.errorMessage}</Text>
) : null}
<TextInput <TextInput
style={styles.input} style={[styles.input, state.errorMessage ? styles.inputError : {}]}
placeholder={`请输入 ${otpLength} 位验证码`} placeholder={`请输入 ${otpLength} 位验证码`}
placeholderTextColor="#999" placeholderTextColor="#999"
keyboardType="number-pad" keyboardType="number-pad"
maxLength={otpLength} maxLength={otpLength}
value={state.otp} value={state.otp}
onChangeText={actions.setOtp} onChangeText={(text) => {
actions.setOtp(text);
if (state.errorMessage) actions.clearError();
}}
editable={!state.loading} editable={!state.loading}
/> />
<TouchableOpacity <TouchableOpacity
@@ -149,6 +161,15 @@ const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
marginBottom: 15, marginBottom: 15,
}, },
inputError: {
borderColor: '#ff3b30',
},
errorText: {
color: '#ff3b30',
fontSize: 14,
marginBottom: 10,
textAlign: 'center',
},
button: { button: {
backgroundColor: '#007AFF', backgroundColor: '#007AFF',
borderRadius: 5, borderRadius: 5,

View File

@@ -66,7 +66,7 @@ export class PayTmPersonalOTPBind extends Component<{
<OTPBindUI <OTPBindUI
walletType={WalletType.PAYTM_PERSONAL} walletType={WalletType.PAYTM_PERSONAL}
title="Paytm 绑定" title="Paytm 绑定"
otpLength={8} otpLength={6}
onRequestOTP={this.props.onRequestOTP} onRequestOTP={this.props.onRequestOTP}
onVerifyOTP={this.props.onVerifyOTP} onVerifyOTP={this.props.onVerifyOTP}
onSuccess={this.props.onSuccess} onSuccess={this.props.onSuccess}

View File

@@ -15,6 +15,7 @@ export interface OTPBindState {
step: 'mobile' | 'otp' | 'processing'; step: 'mobile' | 'otp' | 'processing';
loading: boolean; loading: boolean;
otpData: any; otpData: any;
errorMessage: string;
} }
export interface OTPBindActions { export interface OTPBindActions {
@@ -23,6 +24,7 @@ export interface OTPBindActions {
requestOTP: () => Promise<void>; requestOTP: () => Promise<void>;
verifyOTP: () => Promise<void>; verifyOTP: () => Promise<void>;
resetToMobile: () => void; resetToMobile: () => void;
clearError: () => void;
} }
export function useOTPBind( export function useOTPBind(
@@ -39,10 +41,13 @@ export function useOTPBind(
const [step, setStep] = useState<'mobile' | 'otp' | 'processing'>('mobile'); const [step, setStep] = useState<'mobile' | 'otp' | 'processing'>('mobile');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [otpData, setOtpData] = useState<any>(null); const [otpData, setOtpData] = useState<any>(null);
const [errorMessage, setErrorMessage] = useState('');
const { onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug = false } = callbacks; const { onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug = false } = callbacks;
const { otpLength = 6, mobileLength = 10, additionalParams = {} } = config || {}; const { otpLength = 6, mobileLength = 10, additionalParams = {} } = config || {};
const clearError = () => setErrorMessage('');
const log = (...args: any[]) => { const log = (...args: any[]) => {
if (isDebug) console.log(`[${walletType}]`, ...args); if (isDebug) console.log(`[${walletType}]`, ...args);
}; };
@@ -53,11 +58,14 @@ export function useOTPBind(
const requestOTP = async () => { const requestOTP = async () => {
if (!mobile || mobile.length !== mobileLength) { if (!mobile || mobile.length !== mobileLength) {
onError('Invalid mobile number'); const msg = 'Invalid mobile number';
setErrorMessage(msg);
onError(msg);
return; return;
} }
setLoading(true); setLoading(true);
setErrorMessage('');
log('Requesting OTP for:', mobile); log('Requesting OTP for:', mobile);
try { try {
@@ -70,13 +78,18 @@ export function useOTPBind(
if (response.success) { if (response.success) {
setOtpData(response.data); setOtpData(response.data);
setStep('otp'); setStep('otp');
setErrorMessage('');
} else { } else {
error('OTP request failed:', response.message); error('OTP request failed:', response.message);
onError(response.message || 'Failed to request OTP'); const msg = response.message || 'Failed to request OTP';
setErrorMessage(msg);
onError(msg);
} }
} catch (e) { } catch (e) {
error('Request OTP error:', e); error('Request OTP error:', e);
onError(e instanceof Error ? e.message : 'Failed to request OTP'); const msg = e instanceof Error ? e.message : 'Failed to request OTP';
setErrorMessage(msg);
onError(msg);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -84,11 +97,14 @@ export function useOTPBind(
const verifyOTP = async () => { const verifyOTP = async () => {
if (!otp || otp.length !== otpLength) { if (!otp || otp.length !== otpLength) {
onError(`Invalid OTP (expected ${otpLength} digits)`); const msg = `请输入 ${otpLength} 位验证码`;
setErrorMessage(msg);
onError(msg);
return; return;
} }
setLoading(true); setLoading(true);
setErrorMessage('');
setStep('processing'); setStep('processing');
log('Verifying OTP:', otp); log('Verifying OTP:', otp);
@@ -102,16 +118,21 @@ export function useOTPBind(
log('Verify response:', response); log('Verify response:', response);
if (response.success) { if (response.success) {
setErrorMessage('');
onSuccess(response.data); onSuccess(response.data);
} else { } else {
error('Verify failed:', response.message); error('Verify failed:', response.message);
const msg = response.message || 'Failed to verify OTP';
setStep('otp'); setStep('otp');
onError(response.message || 'Failed to verify OTP'); setErrorMessage(msg);
onError(msg);
} }
} catch (e) { } catch (e) {
error('Verify OTP error:', e); error('Verify OTP error:', e);
const msg = e instanceof Error ? e.message : 'Failed to verify OTP';
setStep('otp'); setStep('otp');
onError(e instanceof Error ? e.message : 'Failed to verify OTP'); setErrorMessage(msg);
onError(msg);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -120,10 +141,11 @@ export function useOTPBind(
const resetToMobile = () => { const resetToMobile = () => {
setStep('mobile'); setStep('mobile');
setOtp(''); setOtp('');
setErrorMessage('');
}; };
return [ return [
{ mobile, otp, step, loading, otpData }, { mobile, otp, step, loading, otpData, errorMessage },
{ setMobile, setOtp, requestOTP, verifyOTP, resetToMobile }, { setMobile, setOtp, requestOTP, verifyOTP, resetToMobile, clearError },
]; ];
} }

View File

@@ -24,7 +24,9 @@ class Api {
private headers(): Record<string, string> { private headers(): Record<string, string> {
const h: Record<string, string> = { 'Content-Type': 'application/json' }; const h: Record<string, string> = { 'Content-Type': 'application/json' };
if (this.userId) h['X-User-ID'] = String(this.userId); if (this.userId > 0) {
h['X-User-ID'] = String(this.userId);
}
return h; return h;
} }

View File

@@ -28,19 +28,18 @@ export const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
}, },
bindButton: { bindButton: {
padding: 10,
marginTop: 10, marginTop: 10,
marginBottom: 10, marginBottom: 10,
backgroundColor: "#007AFF", backgroundColor: "#007AFF",
borderRadius: 5, borderRadius: 5,
width: "90%", width: "90%",
height: 55, height: 45,
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
bindButtonText: { bindButtonText: {
fontSize: 16, fontSize: 14,
fontWeight: "bold", // fontWeight: "bold",
color: "#fff", color: "#fff",
}, },
}); });