feat(auth): 实现修改密码API和Token过期自动跳转登录
后端: - 新增 ChangePasswordCommand 和 ChangePasswordDto - 新增 POST /user/change-password 接口 - 实现 changePassword() 方法,验证旧密码后更新新密码 前端: - 新增 AuthEventService 认证事件服务,处理 token 过期事件 - api_client 在 token 刷新失败时发送过期事件 - App 监听认证事件,token 过期时清除账号状态并跳转登录页 - splash_page 优化路由逻辑:退出登录后跳转手机登录页而非向导页 - change_password_page 调用真实 API 修改密码 - account_service 新增 changePassword() 方法 - multi_account_service 退出登录时清除 phoneNumber 和 isPasswordSet 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
19bd804a21
commit
69fa43ebee
|
|
@ -50,6 +50,7 @@ import {
|
||||||
MarkMnemonicBackedUpCommand,
|
MarkMnemonicBackedUpCommand,
|
||||||
VerifySmsCodeCommand,
|
VerifySmsCodeCommand,
|
||||||
SetPasswordCommand,
|
SetPasswordCommand,
|
||||||
|
ChangePasswordCommand,
|
||||||
} from '@/application/commands';
|
} from '@/application/commands';
|
||||||
import {
|
import {
|
||||||
AutoCreateAccountDto,
|
AutoCreateAccountDto,
|
||||||
|
|
@ -80,6 +81,7 @@ import {
|
||||||
WalletStatusGeneratingResponseDto,
|
WalletStatusGeneratingResponseDto,
|
||||||
VerifySmsCodeDto,
|
VerifySmsCodeDto,
|
||||||
SetPasswordDto,
|
SetPasswordDto,
|
||||||
|
ChangePasswordDto,
|
||||||
LoginWithPasswordDto,
|
LoginWithPasswordDto,
|
||||||
ResetPasswordDto,
|
ResetPasswordDto,
|
||||||
} from '@/api/dto';
|
} from '@/api/dto';
|
||||||
|
|
@ -295,6 +297,24 @@ export class UserAccountController {
|
||||||
return { message: '密码设置成功' };
|
return { message: '密码设置成功' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('change-password')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '修改登录密码',
|
||||||
|
description: '验证旧密码后修改为新密码',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: '密码修改成功' })
|
||||||
|
@ApiResponse({ status: 400, description: '旧密码错误' })
|
||||||
|
async changePassword(
|
||||||
|
@CurrentUser() user: CurrentUserData,
|
||||||
|
@Body() dto: ChangePasswordDto,
|
||||||
|
) {
|
||||||
|
await this.userService.changePassword(
|
||||||
|
new ChangePasswordCommand(user.userId, dto.oldPassword, dto.newPassword),
|
||||||
|
);
|
||||||
|
return { message: '密码修改成功' };
|
||||||
|
}
|
||||||
|
|
||||||
@Get('my-profile')
|
@Get('my-profile')
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiOperation({ summary: '查询我的资料' })
|
@ApiOperation({ summary: '查询我的资料' })
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { IsString, MinLength, MaxLength, Matches } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class ChangePasswordDto {
|
||||||
|
@ApiProperty({
|
||||||
|
example: 'OldPass123',
|
||||||
|
description: '当前密码',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
oldPassword: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: 'NewPass456',
|
||||||
|
description: '新密码,6-20位,包含字母和数字',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@MinLength(6, { message: '密码长度至少6位' })
|
||||||
|
@MaxLength(20, { message: '密码长度不能超过20位' })
|
||||||
|
@Matches(/^(?=.*[A-Za-z])(?=.*\d).+$/, {
|
||||||
|
message: '密码需包含字母和数字',
|
||||||
|
})
|
||||||
|
newPassword: string;
|
||||||
|
}
|
||||||
|
|
@ -12,4 +12,5 @@ export * from './generate-backup-codes.dto';
|
||||||
export * from './recover-by-backup-code.dto';
|
export * from './recover-by-backup-code.dto';
|
||||||
export * from './verify-sms-code.dto';
|
export * from './verify-sms-code.dto';
|
||||||
export * from './set-password.dto';
|
export * from './set-password.dto';
|
||||||
|
export * from './change-password.dto';
|
||||||
export * from './login-with-password.dto';
|
export * from './login-with-password.dto';
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,14 @@ export class SetPasswordCommand {
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ChangePasswordCommand {
|
||||||
|
constructor(
|
||||||
|
public readonly userId: string,
|
||||||
|
public readonly oldPassword: string,
|
||||||
|
public readonly newPassword: string,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
// ============ Results ============
|
// ============ Results ============
|
||||||
|
|
||||||
// 钱包状态
|
// 钱包状态
|
||||||
|
|
|
||||||
|
|
@ -1984,6 +1984,60 @@ export class UserApplicationService {
|
||||||
this.logger.log(`Password set successfully for user: ${command.userId}`);
|
this.logger.log(`Password set successfully for user: ${command.userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改登录密码
|
||||||
|
*/
|
||||||
|
async changePassword(command: {
|
||||||
|
userId: string;
|
||||||
|
oldPassword: string;
|
||||||
|
newPassword: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
this.logger.log(`Changing password for user: ${command.userId}`);
|
||||||
|
|
||||||
|
const userId = UserId.create(command.userId);
|
||||||
|
const user = await this.userRepository.findById(userId);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApplicationError('用户不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前密码哈希
|
||||||
|
const account = await this.prisma.userAccount.findUnique({
|
||||||
|
where: { userId: BigInt(command.userId) },
|
||||||
|
select: { passwordHash: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account?.passwordHash) {
|
||||||
|
throw new ApplicationError('尚未设置密码');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证旧密码
|
||||||
|
const bcrypt = await import('bcrypt');
|
||||||
|
const isOldPasswordValid = await bcrypt.compare(
|
||||||
|
command.oldPassword,
|
||||||
|
account.passwordHash,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isOldPasswordValid) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Invalid old password for user: ${command.userId}`,
|
||||||
|
);
|
||||||
|
throw new ApplicationError('当前密码错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 bcrypt 哈希新密码
|
||||||
|
const saltRounds = 10;
|
||||||
|
const newPasswordHash = await bcrypt.hash(command.newPassword, saltRounds);
|
||||||
|
|
||||||
|
// 更新数据库
|
||||||
|
await this.prisma.userAccount.update({
|
||||||
|
where: { userId: BigInt(command.userId) },
|
||||||
|
data: { passwordHash: newPasswordHash },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(`Password changed successfully for user: ${command.userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送提取验证短信
|
* 发送提取验证短信
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,76 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'core/theme/app_theme.dart';
|
import 'core/theme/app_theme.dart';
|
||||||
|
import 'core/services/auth_event_service.dart';
|
||||||
|
import 'core/services/multi_account_service.dart';
|
||||||
|
import 'core/storage/secure_storage.dart';
|
||||||
import 'routes/app_router.dart';
|
import 'routes/app_router.dart';
|
||||||
|
import 'routes/route_paths.dart';
|
||||||
|
|
||||||
class App extends ConsumerWidget {
|
class App extends ConsumerStatefulWidget {
|
||||||
const App({super.key});
|
const App({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState<App> createState() => _AppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppState extends ConsumerState<App> {
|
||||||
|
StreamSubscription<AuthEvent>? _authEventSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_listenToAuthEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_authEventSubscription?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 监听认证事件
|
||||||
|
void _listenToAuthEvents() {
|
||||||
|
final authEventService = ref.read(authEventServiceProvider);
|
||||||
|
_authEventSubscription = authEventService.events.listen((event) {
|
||||||
|
if (event.type == AuthEventType.tokenExpired) {
|
||||||
|
_handleTokenExpired(event.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理 token 过期
|
||||||
|
Future<void> _handleTokenExpired(String? message) async {
|
||||||
|
debugPrint('[App] Token expired, navigating to login page');
|
||||||
|
|
||||||
|
// 清除当前账号状态(但保留账号列表和向导页标识)
|
||||||
|
final secureStorage = ref.read(secureStorageProvider);
|
||||||
|
final multiAccountService = MultiAccountService(secureStorage);
|
||||||
|
await multiAccountService.logoutCurrentAccount();
|
||||||
|
|
||||||
|
// 使用全局 Navigator Key 跳转到登录页面
|
||||||
|
final navigatorState = rootNavigatorKey.currentState;
|
||||||
|
if (navigatorState != null) {
|
||||||
|
// 显示提示消息
|
||||||
|
if (message != null) {
|
||||||
|
ScaffoldMessenger.of(navigatorState.context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 跳转到登录页面
|
||||||
|
navigatorState.context.go(RoutePaths.phoneLogin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
final router = ref.watch(appRouterProvider);
|
final router = ref.watch(appRouterProvider);
|
||||||
|
|
||||||
return ScreenUtilInit(
|
return ScreenUtilInit(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import '../storage/secure_storage.dart';
|
||||||
import '../storage/storage_keys.dart';
|
import '../storage/storage_keys.dart';
|
||||||
import '../errors/exceptions.dart';
|
import '../errors/exceptions.dart';
|
||||||
import '../config/app_config.dart';
|
import '../config/app_config.dart';
|
||||||
|
import '../services/auth_event_service.dart';
|
||||||
|
|
||||||
/// API 客户端
|
/// API 客户端
|
||||||
///
|
///
|
||||||
|
|
@ -106,19 +107,28 @@ class ApiClient {
|
||||||
final response = await _retryRequest(error.requestOptions);
|
final response = await _retryRequest(error.requestOptions);
|
||||||
return handler.resolve(response);
|
return handler.resolve(response);
|
||||||
} else {
|
} else {
|
||||||
// refresh token 不存在,不清除数据,让用户重新登录
|
// refresh token 不存在,需要重新登录
|
||||||
debugPrint('Token refresh failed: no refresh token available');
|
debugPrint('Token refresh failed: no refresh token available');
|
||||||
|
_notifyTokenExpired();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Token refresh exception: $e');
|
debugPrint('Token refresh exception: $e');
|
||||||
// 只有当 refresh token 明确过期(401)时才清除数据
|
// refresh token 刷新失败(可能过期),需要重新登录
|
||||||
// 其他错误(如网络错误)不清除,避免误清除
|
if (e is DioException && e.response?.statusCode == 401) {
|
||||||
|
debugPrint('Refresh token expired, notifying token expired event');
|
||||||
|
_notifyTokenExpired();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.next(error);
|
handler.next(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 通知 token 过期事件
|
||||||
|
void _notifyTokenExpired() {
|
||||||
|
AuthEventService().emitTokenExpired(message: '登录已过期,请重新登录');
|
||||||
|
}
|
||||||
|
|
||||||
/// 尝试刷新 Token
|
/// 尝试刷新 Token
|
||||||
Future<bool> _tryRefreshToken() async {
|
Future<bool> _tryRefreshToken() async {
|
||||||
final refreshToken = await _secureStorage.read(key: StorageKeys.refreshToken);
|
final refreshToken = await _secureStorage.read(key: StorageKeys.refreshToken);
|
||||||
|
|
|
||||||
|
|
@ -1941,6 +1941,38 @@ class AccountService {
|
||||||
debugPrint('$_tag isPasswordSet() - 结果: $result');
|
debugPrint('$_tag isPasswordSet() - 结果: $result');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 修改登录密码
|
||||||
|
///
|
||||||
|
/// [oldPassword] - 当前密码
|
||||||
|
/// [newPassword] - 新密码(6-20位,包含字母和数字)
|
||||||
|
Future<void> changePassword({
|
||||||
|
required String oldPassword,
|
||||||
|
required String newPassword,
|
||||||
|
}) async {
|
||||||
|
debugPrint('$_tag changePassword() - 开始修改登录密码');
|
||||||
|
|
||||||
|
try {
|
||||||
|
debugPrint('$_tag changePassword() - 调用 POST /user/change-password');
|
||||||
|
final response = await _apiClient.post(
|
||||||
|
'/user/change-password',
|
||||||
|
data: {
|
||||||
|
'oldPassword': oldPassword,
|
||||||
|
'newPassword': newPassword,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
debugPrint('$_tag changePassword() - API 响应状态码: ${response.statusCode}');
|
||||||
|
|
||||||
|
debugPrint('$_tag changePassword() - 登录密码修改完成');
|
||||||
|
} on ApiException catch (e) {
|
||||||
|
debugPrint('$_tag changePassword() - API 异常: $e');
|
||||||
|
rethrow;
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
debugPrint('$_tag changePassword() - 未知异常: $e');
|
||||||
|
debugPrint('$_tag changePassword() - 堆栈: $stackTrace');
|
||||||
|
throw ApiException('修改密码失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 遮蔽手机号中间部分,用于日志输出
|
/// 遮蔽手机号中间部分,用于日志输出
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
/// 认证事件类型
|
||||||
|
enum AuthEventType {
|
||||||
|
/// Token 过期,需要重新登录
|
||||||
|
tokenExpired,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 认证事件
|
||||||
|
class AuthEvent {
|
||||||
|
final AuthEventType type;
|
||||||
|
final String? message;
|
||||||
|
|
||||||
|
AuthEvent({
|
||||||
|
required this.type,
|
||||||
|
this.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 认证事件服务
|
||||||
|
///
|
||||||
|
/// 用于在 token 过期等情况下通知 UI 层进行页面跳转
|
||||||
|
class AuthEventService {
|
||||||
|
/// 单例实例
|
||||||
|
static final AuthEventService _instance = AuthEventService._internal();
|
||||||
|
|
||||||
|
factory AuthEventService() => _instance;
|
||||||
|
|
||||||
|
AuthEventService._internal();
|
||||||
|
|
||||||
|
/// 事件流控制器
|
||||||
|
final _eventController = StreamController<AuthEvent>.broadcast();
|
||||||
|
|
||||||
|
/// 获取事件流
|
||||||
|
Stream<AuthEvent> get events => _eventController.stream;
|
||||||
|
|
||||||
|
/// 全局 Navigator Key(由 AppRouter 设置)
|
||||||
|
GlobalKey<NavigatorState>? navigatorKey;
|
||||||
|
|
||||||
|
/// 发送 token 过期事件
|
||||||
|
void emitTokenExpired({String? message}) {
|
||||||
|
debugPrint('[AuthEventService] Emitting token expired event');
|
||||||
|
_eventController.add(AuthEvent(
|
||||||
|
type: AuthEventType.tokenExpired,
|
||||||
|
message: message ?? '登录已过期,请重新登录',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 释放资源
|
||||||
|
void dispose() {
|
||||||
|
_eventController.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 认证事件服务 Provider
|
||||||
|
final authEventServiceProvider = Provider<AuthEventService>((ref) {
|
||||||
|
return AuthEventService();
|
||||||
|
});
|
||||||
|
|
@ -264,6 +264,8 @@ class MultiAccountService {
|
||||||
StorageKeys.referralCode,
|
StorageKeys.referralCode,
|
||||||
StorageKeys.inviterSequence,
|
StorageKeys.inviterSequence,
|
||||||
StorageKeys.isAccountCreated,
|
StorageKeys.isAccountCreated,
|
||||||
|
StorageKeys.phoneNumber,
|
||||||
|
StorageKeys.isPasswordSet,
|
||||||
// 钱包信息
|
// 钱包信息
|
||||||
StorageKeys.walletAddressBsc,
|
StorageKeys.walletAddressBsc,
|
||||||
StorageKeys.walletAddressKava,
|
StorageKeys.walletAddressKava,
|
||||||
|
|
@ -308,6 +310,8 @@ class MultiAccountService {
|
||||||
StorageKeys.referralCode,
|
StorageKeys.referralCode,
|
||||||
StorageKeys.inviterSequence,
|
StorageKeys.inviterSequence,
|
||||||
StorageKeys.isAccountCreated,
|
StorageKeys.isAccountCreated,
|
||||||
|
StorageKeys.phoneNumber,
|
||||||
|
StorageKeys.isPasswordSet,
|
||||||
StorageKeys.walletAddressBsc,
|
StorageKeys.walletAddressBsc,
|
||||||
StorageKeys.walletAddressKava,
|
StorageKeys.walletAddressKava,
|
||||||
StorageKeys.walletAddressDst,
|
StorageKeys.walletAddressDst,
|
||||||
|
|
|
||||||
|
|
@ -132,20 +132,20 @@ class _SplashPageState extends ConsumerState<SplashPage> {
|
||||||
// 根据认证状态决定跳转目标
|
// 根据认证状态决定跳转目标
|
||||||
// 优先级:
|
// 优先级:
|
||||||
// 1. 账号已创建 → 主页面(龙虎榜)
|
// 1. 账号已创建 → 主页面(龙虎榜)
|
||||||
// 2. 首次打开或未看过向导 → 向导页
|
// 2. 首次打开 → 向导页
|
||||||
// 3. 其他情况 → 主页面(龙虎榜)
|
// 3. 已看过向导但账号未创建(退出登录后) → 登录页面
|
||||||
if (authState.isAccountCreated) {
|
if (authState.isAccountCreated) {
|
||||||
// 账号已创建,进入主页面(龙虎榜)
|
// 账号已创建,进入主页面(龙虎榜)
|
||||||
debugPrint('[SplashPage] 账号已创建 → 跳转到龙虎榜');
|
debugPrint('[SplashPage] 账号已创建 → 跳转到龙虎榜');
|
||||||
context.go(RoutePaths.ranking);
|
context.go(RoutePaths.ranking);
|
||||||
} else if (authState.isFirstLaunch || !authState.hasSeenGuide) {
|
} else if (authState.isFirstLaunch) {
|
||||||
// 首次打开或未看过向导,进入向导页
|
// 首次打开,进入向导页
|
||||||
debugPrint('[SplashPage] 首次打开或未看过向导 → 跳转到向导页');
|
debugPrint('[SplashPage] 首次打开 → 跳转到向导页');
|
||||||
context.go(RoutePaths.guide);
|
context.go(RoutePaths.guide);
|
||||||
} else {
|
} else {
|
||||||
// 其他情况,直接进入主页面(龙虎榜)
|
// 已看过向导但账号未创建(退出登录后),跳转到登录页面
|
||||||
debugPrint('[SplashPage] 其他情况 → 跳转到龙虎榜');
|
debugPrint('[SplashPage] 已看过向导,账号未创建 → 跳转到登录页面');
|
||||||
context.go(RoutePaths.ranking);
|
context.go(RoutePaths.phoneLogin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import '../../../../core/di/injection_container.dart';
|
||||||
|
|
||||||
/// 修改密码页面
|
/// 修改密码页面
|
||||||
/// 支持设置或修改登录密码
|
/// 支持设置或修改登录密码
|
||||||
|
|
@ -60,22 +61,20 @@ class _ChangePasswordPageState extends ConsumerState<ChangePasswordPage> {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: 调用API获取密码状态
|
final accountService = ref.read(accountServiceProvider);
|
||||||
// final accountService = ref.read(accountServiceProvider);
|
final hasPassword = await accountService.isPasswordSet();
|
||||||
// final hasPassword = await accountService.hasPassword();
|
|
||||||
|
|
||||||
// 模拟数据
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_hasPassword = false; // 模拟未设置密码
|
_hasPassword = hasPassword;
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
// 出错时默认已设置密码(注册时必须设置)
|
||||||
|
_hasPassword = true;
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -144,15 +143,18 @@ class _ChangePasswordPageState extends ConsumerState<ChangePasswordPage> {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: 调用API修改密码
|
final accountService = ref.read(accountServiceProvider);
|
||||||
// final accountService = ref.read(accountServiceProvider);
|
|
||||||
// await accountService.changePassword(
|
|
||||||
// oldPassword: _hasPassword ? oldPassword : null,
|
|
||||||
// newPassword: newPassword,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// 模拟请求
|
if (_hasPassword) {
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
// 已有密码,调用修改密码 API
|
||||||
|
await accountService.changePassword(
|
||||||
|
oldPassword: oldPassword,
|
||||||
|
newPassword: newPassword,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 未设置过密码,调用设置密码 API
|
||||||
|
await accountService.setLoginPassword(newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -174,7 +176,7 @@ class _ChangePasswordPageState extends ConsumerState<ChangePasswordPage> {
|
||||||
_isSubmitting = false;
|
_isSubmitting = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
_showErrorSnackBar('操作失败: ${e.toString()}');
|
_showErrorSnackBar('操作失败: ${e.toString().replaceAll('Exception: ', '')}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ import '../features/account/presentation/pages/account_switch_page.dart';
|
||||||
import 'route_paths.dart';
|
import 'route_paths.dart';
|
||||||
import 'route_names.dart';
|
import 'route_names.dart';
|
||||||
|
|
||||||
final _rootNavigatorKey = GlobalKey<NavigatorState>();
|
/// 全局根导航 Key,用于在非 Widget 上下文中进行导航
|
||||||
|
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
final _shellNavigatorKey = GlobalKey<NavigatorState>();
|
final _shellNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
/// 备份助记词页面参数 (新版 - 只需要用户序列号)
|
/// 备份助记词页面参数 (新版 - 只需要用户序列号)
|
||||||
|
|
@ -99,7 +100,7 @@ class SharePageParams {
|
||||||
|
|
||||||
final appRouterProvider = Provider<GoRouter>((ref) {
|
final appRouterProvider = Provider<GoRouter>((ref) {
|
||||||
return GoRouter(
|
return GoRouter(
|
||||||
navigatorKey: _rootNavigatorKey,
|
navigatorKey: rootNavigatorKey,
|
||||||
initialLocation: RoutePaths.splash,
|
initialLocation: RoutePaths.splash,
|
||||||
debugLogDiagnostics: true,
|
debugLogDiagnostics: true,
|
||||||
routes: [
|
routes: [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue