添加验证码处理逻辑
This commit is contained in:
@@ -10,12 +10,13 @@ export default defineConfig<'webpack5'>(async (merge, { command, mode }) => {
|
|||||||
const baseConfig: UserConfigExport<'webpack5'> = {
|
const baseConfig: UserConfigExport<'webpack5'> = {
|
||||||
projectName: 'playBallTogether',
|
projectName: 'playBallTogether',
|
||||||
date: '2025-8-9',
|
date: '2025-8-9',
|
||||||
designWidth: 375,
|
designWidth: 390,
|
||||||
deviceRatio: {
|
deviceRatio: {
|
||||||
640: 2.34 / 2,
|
640: 2.34 / 2,
|
||||||
750: 1,
|
750: 1,
|
||||||
375: 2,
|
375: 2,
|
||||||
828: 1.81 / 2
|
828: 1.81 / 2,
|
||||||
|
390: 1.92
|
||||||
},
|
},
|
||||||
sourceRoot: 'src',
|
sourceRoot: 'src',
|
||||||
outputRoot: 'dist',
|
outputRoot: 'dist',
|
||||||
|
|||||||
@@ -273,6 +273,16 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.714;
|
line-height: 1.714;
|
||||||
color: rgba(60, 60, 67, 0.3);
|
color: rgba(60, 60, 67, 0.3);
|
||||||
|
|
||||||
|
.count_number {
|
||||||
|
color: rgba(60, 60, 67, 0.3);
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,6 +331,16 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.714;
|
line-height: 1.714;
|
||||||
color: rgba(60, 60, 67, 0.3);
|
color: rgba(60, 60, 67, 0.3);
|
||||||
|
|
||||||
|
.count_number {
|
||||||
|
color: rgba(60, 60, 67, 0.3);
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ const VerificationPage: React.FC = () => {
|
|||||||
const [countdown, setCountdown] = useState(0);
|
const [countdown, setCountdown] = useState(0);
|
||||||
const [can_send_code, setCanSendCode] = useState(true);
|
const [can_send_code, setCanSendCode] = useState(true);
|
||||||
const [is_loading, setIsLoading] = useState(false);
|
const [is_loading, setIsLoading] = useState(false);
|
||||||
|
const [code_input_focus, setCodeInputFocus] = useState(false);
|
||||||
|
|
||||||
|
// 计算登录按钮是否应该启用
|
||||||
|
const can_login = phone.length === 11 && verification_code.length === 6 && !is_loading;
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
const handle_go_back = () => {
|
|
||||||
Taro.navigateBack();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 发送验证码
|
// 发送验证码
|
||||||
const handle_send_code = async () => {
|
const handle_send_code = async () => {
|
||||||
@@ -30,10 +30,16 @@ const VerificationPage: React.FC = () => {
|
|||||||
if (!can_send_code) return;
|
if (!can_send_code) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('开始发送验证码,手机号:', phone);
|
||||||
|
|
||||||
// 调用发送短信接口
|
// 调用发送短信接口
|
||||||
const result = await send_sms_code(phone);
|
const result = await send_sms_code(phone);
|
||||||
|
|
||||||
|
console.log('发送验证码结果:', result);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
console.log('验证码发送成功,开始倒计时');
|
||||||
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '验证码已发送',
|
title: '验证码已发送',
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
@@ -43,7 +49,19 @@ const VerificationPage: React.FC = () => {
|
|||||||
// 开始倒计时
|
// 开始倒计时
|
||||||
setCanSendCode(false);
|
setCanSendCode(false);
|
||||||
setCountdown(60);
|
setCountdown(60);
|
||||||
|
|
||||||
|
console.log('设置状态: can_send_code = false, countdown = 60');
|
||||||
|
|
||||||
|
// 发送验证码成功后,让验证码输入框获得焦点并调用系统键盘
|
||||||
|
setTimeout(() => {
|
||||||
|
// 设置验证码输入框聚焦状态
|
||||||
|
setCodeInputFocus(true);
|
||||||
|
// 清空验证码,让用户重新输入
|
||||||
|
setVerificationCode('');
|
||||||
|
console.log('设置验证码输入框聚焦');
|
||||||
|
}, 500); // 延迟500ms确保Toast显示完成后再聚焦
|
||||||
} else {
|
} else {
|
||||||
|
console.log('验证码发送失败:', result.message);
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: result.message || '发送失败',
|
title: result.message || '发送失败',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
@@ -51,6 +69,7 @@ const VerificationPage: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('发送验证码异常:', error);
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '发送失败,请重试',
|
title: '发送失败,请重试',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
@@ -61,15 +80,19 @@ const VerificationPage: React.FC = () => {
|
|||||||
|
|
||||||
// 倒计时效果
|
// 倒计时效果
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('倒计时 useEffect 触发,countdown:', countdown);
|
||||||
|
|
||||||
if (countdown > 0) {
|
if (countdown > 0) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
|
console.log('倒计时减少,从', countdown, '到', countdown - 1);
|
||||||
setCountdown(countdown - 1);
|
setCountdown(countdown - 1);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
} else {
|
} else if (countdown === 0 && !can_send_code) {
|
||||||
|
console.log('倒计时结束,重新启用发送按钮');
|
||||||
setCanSendCode(true);
|
setCanSendCode(true);
|
||||||
}
|
}
|
||||||
}, [countdown]);
|
}, [countdown, can_send_code]);
|
||||||
|
|
||||||
// 手机号登录
|
// 手机号登录
|
||||||
const handle_phone_login = async () => {
|
const handle_phone_login = async () => {
|
||||||
@@ -96,7 +119,7 @@ const VerificationPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
// 调用登录服务
|
// 调用登录服务
|
||||||
const result = await phone_auth_login({ phone, verification_code });
|
const result = await phone_auth_login({ phone, verification_code });
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -122,12 +145,6 @@ const VerificationPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看条款
|
|
||||||
const handle_view_terms = (type: string = 'terms') => {
|
|
||||||
Taro.navigateTo({
|
|
||||||
url: `/pages/login/terms/index?type=${type}`
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="verification_page">
|
<View className="verification_page">
|
||||||
@@ -159,7 +176,12 @@ const VerificationPage: React.FC = () => {
|
|||||||
onInput={(e) => setPhone(e.detail.value)}
|
onInput={(e) => setPhone(e.detail.value)}
|
||||||
maxlength={11}
|
maxlength={11}
|
||||||
/>
|
/>
|
||||||
<View className="char_count">{phone.length}/11</View>
|
<View className="char_count">
|
||||||
|
<Text className={phone.length > 0 ? 'count_number active' : 'count_number'}>
|
||||||
|
{phone.length}
|
||||||
|
</Text>
|
||||||
|
/11
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -171,11 +193,20 @@ const VerificationPage: React.FC = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
placeholder="输入短信验证码"
|
placeholder="输入短信验证码"
|
||||||
placeholderClass="input_placeholder"
|
placeholderClass="input_placeholder"
|
||||||
|
placeholderStyle="color:#999999;"
|
||||||
|
focus={code_input_focus}
|
||||||
value={verification_code}
|
value={verification_code}
|
||||||
onInput={(e) => setVerificationCode(e.detail.value)}
|
onInput={(e) => setVerificationCode(e.detail.value)}
|
||||||
|
onFocus={() => setCodeInputFocus(true)}
|
||||||
|
onBlur={() => setCodeInputFocus(false)}
|
||||||
maxlength={6}
|
maxlength={6}
|
||||||
/>
|
/>
|
||||||
<View className="char_count">{verification_code.length}/6</View>
|
<View className="char_count">
|
||||||
|
<Text className={verification_code.length > 0 ? 'count_number active' : 'count_number'}>
|
||||||
|
{verification_code.length}
|
||||||
|
</Text>
|
||||||
|
/6
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
className={`send_code_button ${!can_send_code ? 'disabled' : ''}`}
|
className={`send_code_button ${!can_send_code ? 'disabled' : ''}`}
|
||||||
@@ -191,21 +222,33 @@ const VerificationPage: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
{/* 调试信息 */}
|
||||||
|
{/* {process.env.NODE_ENV === 'development' && (
|
||||||
|
<View style={{fontSize: '12px', color: '#999', marginTop: '5px'}}>
|
||||||
|
调试: can_send_code={can_send_code.toString()}, countdown={countdown}
|
||||||
|
</View>
|
||||||
|
)} */}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 登录按钮 */}
|
{/* 登录按钮 */}
|
||||||
<View className="button_section">
|
<View className="button_section">
|
||||||
<Button
|
<Button
|
||||||
className={`login_button ${is_loading ? 'loading' : ''}`}
|
className={`login_button ${is_loading ? 'loading' : ''} ${!can_login ? 'disabled' : ''}`}
|
||||||
onClick={handle_phone_login}
|
onClick={handle_phone_login}
|
||||||
disabled={is_loading}
|
disabled={!can_login}
|
||||||
>
|
>
|
||||||
{is_loading ? '登录中...' : '登录'}
|
{'登录'}
|
||||||
</Button>
|
</Button>
|
||||||
|
{/* 调试信息 */}
|
||||||
|
{/* {process.env.NODE_ENV === 'development' && (
|
||||||
|
<View style={{fontSize: '12px', color: '#999', marginTop: '5px', textAlign: 'center'}}>
|
||||||
|
调试: 手机号长度={phone.length}, 验证码长度={verification_code.length}, 可登录={can_login.toString()}
|
||||||
|
</View>
|
||||||
|
)} */}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 底部指示器 */}
|
{/* 底部指示器 */}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import httpService from './httpService';
|
import httpService from './httpService';
|
||||||
|
import tokenManager from '../utils/tokenManager';
|
||||||
|
|
||||||
// 微信用户信息接口
|
// 微信用户信息接口
|
||||||
export interface WechatUserInfo {
|
export interface WechatUserInfo {
|
||||||
@@ -130,7 +131,8 @@ export const send_sms_code = async (phone: string): Promise<SmsResponse> => {
|
|||||||
phone: phone
|
phone: phone
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.success) {
|
// 修复响应检查逻辑:检查 code === 0 或 success === true
|
||||||
|
if (response.code === 0 || response.success === true) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: '验证码发送成功'
|
message: '验证码发送成功'
|
||||||
@@ -199,7 +201,14 @@ export const get_user_profile = (): Promise<WechatUserInfo> => {
|
|||||||
// 保存用户登录状态
|
// 保存用户登录状态
|
||||||
export const save_login_state = (token: string, user_info: WechatUserInfo) => {
|
export const save_login_state = (token: string, user_info: WechatUserInfo) => {
|
||||||
try {
|
try {
|
||||||
Taro.setStorageSync('user_token', token);
|
// 使用 tokenManager 保存令牌信息,设置24小时过期
|
||||||
|
const expires_at = Date.now() + 24 * 60 * 60 * 1000; // 24小时后过期
|
||||||
|
tokenManager.setToken({
|
||||||
|
accessToken: token,
|
||||||
|
expiresAt: expires_at
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存用户信息
|
||||||
Taro.setStorageSync('user_info', user_info);
|
Taro.setStorageSync('user_info', user_info);
|
||||||
Taro.setStorageSync('is_logged_in', true);
|
Taro.setStorageSync('is_logged_in', true);
|
||||||
Taro.setStorageSync('login_time', Date.now());
|
Taro.setStorageSync('login_time', Date.now());
|
||||||
@@ -211,7 +220,10 @@ export const save_login_state = (token: string, user_info: WechatUserInfo) => {
|
|||||||
// 清除登录状态
|
// 清除登录状态
|
||||||
export const clear_login_state = () => {
|
export const clear_login_state = () => {
|
||||||
try {
|
try {
|
||||||
Taro.removeStorageSync('user_token');
|
// 使用 tokenManager 清除令牌
|
||||||
|
tokenManager.clearTokens();
|
||||||
|
|
||||||
|
// 清除其他登录状态
|
||||||
Taro.removeStorageSync('user_info');
|
Taro.removeStorageSync('user_info');
|
||||||
Taro.removeStorageSync('is_logged_in');
|
Taro.removeStorageSync('is_logged_in');
|
||||||
Taro.removeStorageSync('login_time');
|
Taro.removeStorageSync('login_time');
|
||||||
@@ -223,22 +235,53 @@ export const clear_login_state = () => {
|
|||||||
// 检查是否已登录
|
// 检查是否已登录
|
||||||
export const check_login_status = (): boolean => {
|
export const check_login_status = (): boolean => {
|
||||||
try {
|
try {
|
||||||
const is_logged_in = Taro.getStorageSync('is_logged_in');
|
// 使用 tokenManager 检查令牌有效性
|
||||||
const token = Taro.getStorageSync('user_token');
|
if (!tokenManager.hasValidToken()) {
|
||||||
const login_time = Taro.getStorageSync('login_time');
|
|
||||||
|
|
||||||
// 检查登录是否过期(7天)
|
|
||||||
if (login_time && Date.now() - login_time > 7 * 24 * 60 * 60 * 1000) {
|
|
||||||
clear_login_state();
|
clear_login_state();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!(is_logged_in && token);
|
const is_logged_in = Taro.getStorageSync('is_logged_in');
|
||||||
|
return !!is_logged_in;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 检查令牌是否需要刷新(剩余时间少于1小时时)
|
||||||
|
export const should_refresh_token = (): boolean => {
|
||||||
|
try {
|
||||||
|
const remaining_time = tokenManager.getTokenRemainingTime();
|
||||||
|
const one_hour = 60 * 60 * 1000; // 1小时
|
||||||
|
return remaining_time > 0 && remaining_time < one_hour;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取令牌状态信息
|
||||||
|
export const get_token_status = () => {
|
||||||
|
try {
|
||||||
|
const is_valid = tokenManager.hasValidToken();
|
||||||
|
const remaining_time = tokenManager.getTokenRemainingTime();
|
||||||
|
const is_expired = tokenManager.isTokenExpired();
|
||||||
|
|
||||||
|
return {
|
||||||
|
is_valid,
|
||||||
|
remaining_time,
|
||||||
|
is_expired,
|
||||||
|
expires_in_minutes: Math.floor(remaining_time / (60 * 1000))
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
is_valid: false,
|
||||||
|
remaining_time: 0,
|
||||||
|
is_expired: true,
|
||||||
|
expires_in_minutes: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
export const get_user_info = (): WechatUserInfo | null => {
|
export const get_user_info = (): WechatUserInfo | null => {
|
||||||
try {
|
try {
|
||||||
@@ -251,7 +294,8 @@ export const get_user_info = (): WechatUserInfo | null => {
|
|||||||
// 获取用户token
|
// 获取用户token
|
||||||
export const get_user_token = (): string | null => {
|
export const get_user_token = (): string | null => {
|
||||||
try {
|
try {
|
||||||
return Taro.getStorageSync('user_token') || null;
|
// 使用 tokenManager 获取令牌
|
||||||
|
return tokenManager.getAccessToken();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,17 @@ class TokenManager {
|
|||||||
return !!token && !this.isTokenExpired()
|
return !!token && !this.isTokenExpired()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取令牌剩余有效时间(毫秒)
|
||||||
|
getTokenRemainingTime(): number {
|
||||||
|
const expiresAt = this.getTokenExpires()
|
||||||
|
if (!expiresAt) {
|
||||||
|
return 0 // 如果没有过期时间,返回0
|
||||||
|
}
|
||||||
|
|
||||||
|
const remaining = expiresAt - Date.now()
|
||||||
|
return remaining > 0 ? remaining : 0
|
||||||
|
}
|
||||||
|
|
||||||
// 获取Authorization头
|
// 获取Authorization头
|
||||||
getAuthHeader(): Record<string, string> {
|
getAuthHeader(): Record<string, string> {
|
||||||
const token = this.getAccessToken()
|
const token = this.getAccessToken()
|
||||||
@@ -93,7 +104,7 @@ class TokenManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'Authorization': `Bearer ${token}`
|
'applet-token': `${token}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user