diff --git a/frontend/mobile-app/lib/core/di/injection_container.dart b/frontend/mobile-app/lib/core/di/injection_container.dart index ca4d6528..e7c828b9 100644 --- a/frontend/mobile-app/lib/core/di/injection_container.dart +++ b/frontend/mobile-app/lib/core/di/injection_container.dart @@ -3,6 +3,7 @@ import '../storage/secure_storage.dart'; import '../storage/local_storage.dart'; import '../network/api_client.dart'; import '../services/account_service.dart'; +import '../services/multi_account_service.dart'; import '../services/referral_service.dart'; import '../services/authorization_service.dart'; import '../services/deposit_service.dart'; @@ -36,6 +37,12 @@ final accountServiceProvider = Provider((ref) { ); }); +// Multi Account Service Provider +final multiAccountServiceProvider = Provider((ref) { + final secureStorage = ref.watch(secureStorageProvider); + return MultiAccountService(secureStorage); +}); + // Referral Service Provider final referralServiceProvider = Provider((ref) { final apiClient = ref.watch(apiClientProvider); diff --git a/frontend/mobile-app/lib/core/services/multi_account_service.dart b/frontend/mobile-app/lib/core/services/multi_account_service.dart new file mode 100644 index 00000000..39e0eac0 --- /dev/null +++ b/frontend/mobile-app/lib/core/services/multi_account_service.dart @@ -0,0 +1,349 @@ +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import '../storage/secure_storage.dart'; +import '../storage/storage_keys.dart'; + +/// 账号信息摘要(用于账号列表显示) +class AccountSummary { + final String userSerialNum; // 用户序列号,作为唯一标识 + final String username; // 用户名 + final String? avatarSvg; // SVG 头像 + final String? avatarUrl; // 自定义头像 URL + final DateTime createdAt; // 创建时间 + + AccountSummary({ + required this.userSerialNum, + required this.username, + this.avatarSvg, + this.avatarUrl, + required this.createdAt, + }); + + Map toJson() => { + 'userSerialNum': userSerialNum, + 'username': username, + 'avatarSvg': avatarSvg, + 'avatarUrl': avatarUrl, + 'createdAt': createdAt.toIso8601String(), + }; + + factory AccountSummary.fromJson(Map json) { + return AccountSummary( + userSerialNum: json['userSerialNum'] as String, + username: json['username'] as String, + avatarSvg: json['avatarSvg'] as String?, + avatarUrl: json['avatarUrl'] as String?, + createdAt: DateTime.parse(json['createdAt'] as String), + ); + } + + @override + String toString() => 'AccountSummary(userSerialNum: $userSerialNum, username: $username)'; +} + +/// 多账号管理服务 +/// 负责管理多个账号的存储和切换 +class MultiAccountService { + static const String _tag = '[MultiAccountService]'; + + final SecureStorage _secureStorage; + + MultiAccountService(this._secureStorage); + + /// 获取账号列表 + Future> getAccountList() async { + debugPrint('$_tag getAccountList() - 获取账号列表'); + try { + final jsonStr = await _secureStorage.read(key: StorageKeys.accountList); + if (jsonStr == null || jsonStr.isEmpty) { + debugPrint('$_tag getAccountList() - 账号列表为空'); + return []; + } + final List jsonList = jsonDecode(jsonStr); + final accounts = jsonList + .map((e) => AccountSummary.fromJson(e as Map)) + .toList(); + debugPrint('$_tag getAccountList() - 找到 ${accounts.length} 个账号'); + return accounts; + } catch (e) { + debugPrint('$_tag getAccountList() - 解析失败: $e'); + return []; + } + } + + /// 保存账号列表 + Future _saveAccountList(List accounts) async { + final jsonStr = jsonEncode(accounts.map((e) => e.toJson()).toList()); + await _secureStorage.write(key: StorageKeys.accountList, value: jsonStr); + debugPrint('$_tag _saveAccountList() - 保存 ${accounts.length} 个账号'); + } + + /// 添加账号到列表 + Future addAccount(AccountSummary account) async { + debugPrint('$_tag addAccount() - 添加账号: ${account.userSerialNum}'); + final accounts = await getAccountList(); + + // 检查是否已存在 + final existingIndex = accounts.indexWhere( + (a) => a.userSerialNum == account.userSerialNum, + ); + + if (existingIndex >= 0) { + // 更新现有账号 + accounts[existingIndex] = account; + debugPrint('$_tag addAccount() - 更新现有账号'); + } else { + // 添加新账号 + accounts.add(account); + debugPrint('$_tag addAccount() - 添加新账号'); + } + + await _saveAccountList(accounts); + } + + /// 从列表中移除账号 + Future removeAccount(String userSerialNum) async { + debugPrint('$_tag removeAccount() - 移除账号: $userSerialNum'); + final accounts = await getAccountList(); + accounts.removeWhere((a) => a.userSerialNum == userSerialNum); + await _saveAccountList(accounts); + } + + /// 获取当前活跃账号ID + Future getCurrentAccountId() async { + return await _secureStorage.read(key: StorageKeys.currentAccountId); + } + + /// 设置当前活跃账号ID + Future setCurrentAccountId(String? accountId) async { + if (accountId == null) { + await _secureStorage.delete(key: StorageKeys.currentAccountId); + } else { + await _secureStorage.write(key: StorageKeys.currentAccountId, value: accountId); + } + debugPrint('$_tag setCurrentAccountId() - 设置为: $accountId'); + } + + /// 切换到指定账号 + /// 返回 true 表示切换成功 + Future switchToAccount(String userSerialNum) async { + debugPrint('$_tag switchToAccount() - 切换到账号: $userSerialNum'); + + // 验证账号存在 + final accounts = await getAccountList(); + final account = accounts.where((a) => a.userSerialNum == userSerialNum).firstOrNull; + + if (account == null) { + debugPrint('$_tag switchToAccount() - 账号不存在'); + return false; + } + + // 从账号专用存储中恢复数据到当前存储 + await _restoreAccountData(userSerialNum); + + // 设置当前账号 + await setCurrentAccountId(userSerialNum); + + debugPrint('$_tag switchToAccount() - 切换成功'); + return true; + } + + /// 保存当前账号数据到账号专用存储 + Future saveCurrentAccountData() async { + final currentId = await getCurrentAccountId(); + if (currentId == null) { + debugPrint('$_tag saveCurrentAccountData() - 无当前账号'); + return; + } + + debugPrint('$_tag saveCurrentAccountData() - 保存账号数据: $currentId'); + + // 需要保存的账号相关键 + final keysToSave = [ + StorageKeys.userSerialNum, + StorageKeys.username, + StorageKeys.avatarSvg, + StorageKeys.avatarUrl, + StorageKeys.referralCode, + StorageKeys.inviterSequence, + StorageKeys.isAccountCreated, + StorageKeys.walletAddressBsc, + StorageKeys.walletAddressKava, + StorageKeys.walletAddressDst, + StorageKeys.mnemonic, + StorageKeys.isWalletReady, + StorageKeys.isMnemonicBackedUp, + StorageKeys.accessToken, + StorageKeys.refreshToken, + ]; + + for (final key in keysToSave) { + final value = await _secureStorage.read(key: key); + if (value != null) { + final prefixedKey = StorageKeys.withAccountPrefix(currentId, key); + await _secureStorage.write(key: prefixedKey, value: value); + } + } + + debugPrint('$_tag saveCurrentAccountData() - 保存完成'); + } + + /// 从账号专用存储恢复数据到当前存储 + Future _restoreAccountData(String accountId) async { + debugPrint('$_tag _restoreAccountData() - 恢复账号数据: $accountId'); + + // 需要恢复的账号相关键 + final keysToRestore = [ + StorageKeys.userSerialNum, + StorageKeys.username, + StorageKeys.avatarSvg, + StorageKeys.avatarUrl, + StorageKeys.referralCode, + StorageKeys.inviterSequence, + StorageKeys.isAccountCreated, + StorageKeys.walletAddressBsc, + StorageKeys.walletAddressKava, + StorageKeys.walletAddressDst, + StorageKeys.mnemonic, + StorageKeys.isWalletReady, + StorageKeys.isMnemonicBackedUp, + StorageKeys.accessToken, + StorageKeys.refreshToken, + ]; + + for (final key in keysToRestore) { + final prefixedKey = StorageKeys.withAccountPrefix(accountId, key); + final value = await _secureStorage.read(key: prefixedKey); + if (value != null) { + await _secureStorage.write(key: key, value: value); + } else { + // 如果账号存储中没有该键,删除当前存储中的值 + await _secureStorage.delete(key: key); + } + } + + debugPrint('$_tag _restoreAccountData() - 恢复完成'); + } + + /// 退出当前账号(不删除账号数据) + /// 清除当前会话状态,但保留账号在列表中 + Future logoutCurrentAccount() async { + debugPrint('$_tag logoutCurrentAccount() - 退出当前账号'); + + // 保存当前账号数据 + await saveCurrentAccountData(); + + // 清除当前会话的 Token(但保留在账号存储中) + await _secureStorage.delete(key: StorageKeys.accessToken); + await _secureStorage.delete(key: StorageKeys.refreshToken); + + // 清除当前账号标记 + await setCurrentAccountId(null); + + // 清除当前账号信息(但不删除账号列表) + await _secureStorage.delete(key: StorageKeys.userSerialNum); + await _secureStorage.delete(key: StorageKeys.username); + await _secureStorage.delete(key: StorageKeys.isAccountCreated); + + debugPrint('$_tag logoutCurrentAccount() - 退出完成'); + } + + /// 完全删除账号(包括所有数据) + Future deleteAccount(String userSerialNum) async { + debugPrint('$_tag deleteAccount() - 删除账号: $userSerialNum'); + + // 从列表中移除 + await removeAccount(userSerialNum); + + // 删除账号专用存储的所有数据 + final keysToDelete = [ + StorageKeys.userSerialNum, + StorageKeys.username, + StorageKeys.avatarSvg, + StorageKeys.avatarUrl, + StorageKeys.referralCode, + StorageKeys.inviterSequence, + StorageKeys.isAccountCreated, + StorageKeys.walletAddressBsc, + StorageKeys.walletAddressKava, + StorageKeys.walletAddressDst, + StorageKeys.mnemonic, + StorageKeys.isWalletReady, + StorageKeys.isMnemonicBackedUp, + StorageKeys.accessToken, + StorageKeys.refreshToken, + ]; + + for (final key in keysToDelete) { + final prefixedKey = StorageKeys.withAccountPrefix(userSerialNum, key); + await _secureStorage.delete(key: prefixedKey); + } + + // 如果删除的是当前账号,清除当前账号标记 + final currentId = await getCurrentAccountId(); + if (currentId == userSerialNum) { + await logoutCurrentAccount(); + } + + debugPrint('$_tag deleteAccount() - 删除完成'); + } + + /// 迁移旧数据到多账号架构 + /// 如果存在旧的单账号数据,将其添加到账号列表 + Future migrateFromSingleAccount() async { + debugPrint('$_tag migrateFromSingleAccount() - 检查是否需要迁移'); + + // 检查是否已有账号列表 + final accounts = await getAccountList(); + if (accounts.isNotEmpty) { + debugPrint('$_tag migrateFromSingleAccount() - 已有账号列表,无需迁移'); + return; + } + + // 检查是否存在旧的单账号数据 + final userSerialNum = await _secureStorage.read(key: StorageKeys.userSerialNum); + final isAccountCreated = await _secureStorage.read(key: StorageKeys.isAccountCreated); + + if (userSerialNum == null || isAccountCreated != 'true') { + debugPrint('$_tag migrateFromSingleAccount() - 无旧账号数据'); + return; + } + + // 获取账号信息 + final username = await _secureStorage.read(key: StorageKeys.username) ?? '未知用户'; + final avatarSvg = await _secureStorage.read(key: StorageKeys.avatarSvg); + final avatarUrl = await _secureStorage.read(key: StorageKeys.avatarUrl); + + // 创建账号摘要 + final account = AccountSummary( + userSerialNum: userSerialNum, + username: username, + avatarSvg: avatarSvg, + avatarUrl: avatarUrl, + createdAt: DateTime.now(), // 旧数据没有创建时间,使用当前时间 + ); + + // 添加到账号列表 + await addAccount(account); + + // 设置为当前账号 + await setCurrentAccountId(userSerialNum); + + // 保存当前账号数据到账号专用存储 + await saveCurrentAccountData(); + + debugPrint('$_tag migrateFromSingleAccount() - 迁移完成: $userSerialNum'); + } + + /// 检查是否有任何已登录的账号 + Future hasAnyAccount() async { + final accounts = await getAccountList(); + return accounts.isNotEmpty; + } + + /// 获取账号数量 + Future getAccountCount() async { + final accounts = await getAccountList(); + return accounts.length; + } +} diff --git a/frontend/mobile-app/lib/core/storage/storage_keys.dart b/frontend/mobile-app/lib/core/storage/storage_keys.dart index 5a662812..5505d595 100644 --- a/frontend/mobile-app/lib/core/storage/storage_keys.dart +++ b/frontend/mobile-app/lib/core/storage/storage_keys.dart @@ -1,6 +1,10 @@ class StorageKeys { StorageKeys._(); + // ===== 多账号管理 ===== + static const String accountList = 'account_list'; // 账号列表 (JSON 数组) + static const String currentAccountId = 'current_account_id'; // 当前活跃账号ID (userSerialNum) + // 账号信息 static const String userSerialNum = 'user_serial_num'; // 用户序列号 static const String username = 'username'; // 随机用户名 @@ -11,6 +15,12 @@ class StorageKeys { static const String inviterReferralCode = 'inviter_referral_code'; // 邀请人推荐码(注册前临时存储) static const String isAccountCreated = 'is_account_created'; // 账号是否已创建 + /// 生成带账号前缀的存储键 + /// 用于多账号隔离存储 + static String withAccountPrefix(String accountId, String key) { + return 'account_${accountId}_$key'; + } + // 钱包信息 static const String walletAddressBsc = 'wallet_address_bsc'; static const String walletAddressKava = 'wallet_address_kava'; diff --git a/frontend/mobile-app/lib/features/account/presentation/pages/account_switch_page.dart b/frontend/mobile-app/lib/features/account/presentation/pages/account_switch_page.dart new file mode 100644 index 00000000..a55f7c8e --- /dev/null +++ b/frontend/mobile-app/lib/features/account/presentation/pages/account_switch_page.dart @@ -0,0 +1,392 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import '../../../../core/di/injection_container.dart'; +import '../../../../core/services/multi_account_service.dart'; +import '../../../../routes/route_paths.dart'; + +/// 账号切换页面 +/// 显示所有已登录的账号列表,支持切换和添加新账号 +class AccountSwitchPage extends ConsumerStatefulWidget { + const AccountSwitchPage({super.key}); + + @override + ConsumerState createState() => _AccountSwitchPageState(); +} + +class _AccountSwitchPageState extends ConsumerState { + List _accounts = []; + String? _currentAccountId; + bool _isLoading = true; + bool _isSwitching = false; + + @override + void initState() { + super.initState(); + _loadAccounts(); + } + + Future _loadAccounts() async { + setState(() => _isLoading = true); + + final multiAccountService = ref.read(multiAccountServiceProvider); + + // 先执行迁移(如果需要) + await multiAccountService.migrateFromSingleAccount(); + + final accounts = await multiAccountService.getAccountList(); + final currentId = await multiAccountService.getCurrentAccountId(); + + if (mounted) { + setState(() { + _accounts = accounts; + _currentAccountId = currentId; + _isLoading = false; + }); + } + } + + Future _switchToAccount(AccountSummary account) async { + if (account.userSerialNum == _currentAccountId) { + // 已是当前账号,直接返回 + context.pop(); + return; + } + + setState(() => _isSwitching = true); + + try { + final multiAccountService = ref.read(multiAccountServiceProvider); + + // 保存当前账号数据 + await multiAccountService.saveCurrentAccountData(); + + // 切换到新账号 + final success = await multiAccountService.switchToAccount(account.userSerialNum); + + if (success && mounted) { + // 切换成功,跳转到主页刷新状态 + context.go(RoutePaths.ranking); + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('切换账号失败')), + ); + } + } catch (e) { + debugPrint('[AccountSwitchPage] 切换账号失败: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('切换账号失败: $e')), + ); + } + } finally { + if (mounted) { + setState(() => _isSwitching = false); + } + } + } + + Future _addNewAccount() async { + // 保存当前账号数据 + final multiAccountService = ref.read(multiAccountServiceProvider); + await multiAccountService.saveCurrentAccountData(); + + // 退出当前账号但保留数据 + await multiAccountService.logoutCurrentAccount(); + + // 跳转到向导页创建新账号 + if (mounted) { + context.go(RoutePaths.guide); + } + } + + Future _deleteAccount(AccountSummary account) async { + // 显示确认对话框 + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('删除账号'), + content: Text( + '确定要删除账号 "${account.username}" 吗?\n\n' + '删除后该账号的所有本地数据将被清除,' + '如需恢复请使用助记词重新导入。', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('取消'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + style: TextButton.styleFrom( + foregroundColor: const Color(0xFFE57373), + ), + child: const Text('删除'), + ), + ], + ), + ); + + if (confirmed != true) return; + + try { + final multiAccountService = ref.read(multiAccountServiceProvider); + await multiAccountService.deleteAccount(account.userSerialNum); + + // 重新加载账号列表 + await _loadAccounts(); + + // 如果没有账号了,跳转到向导页 + if (_accounts.isEmpty && mounted) { + context.go(RoutePaths.guide); + } + } catch (e) { + debugPrint('[AccountSwitchPage] 删除账号失败: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('删除账号失败: $e')), + ); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFFFFBF5), + appBar: AppBar( + backgroundColor: const Color(0xFFFFFBF5), + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, color: Color(0xFF5D4037)), + onPressed: () => context.pop(), + ), + title: const Text( + '切换账号', + style: TextStyle( + fontSize: 18, + fontFamily: 'Inter', + fontWeight: FontWeight.w600, + color: Color(0xFF5D4037), + ), + ), + centerTitle: true, + ), + body: _isLoading + ? const Center( + child: CircularProgressIndicator( + color: Color(0xFFD4AF37), + ), + ) + : _isSwitching + ? const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator( + color: Color(0xFFD4AF37), + ), + SizedBox(height: 16), + Text( + '正在切换账号...', + style: TextStyle( + fontSize: 14, + color: Color(0xFF5D4037), + ), + ), + ], + ), + ) + : ListView( + padding: const EdgeInsets.all(16), + children: [ + // 账号列表标题 + const Padding( + padding: EdgeInsets.only(bottom: 12), + child: Text( + '已登录的账号', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + color: Color(0x995D4037), + ), + ), + ), + // 账号列表 + ..._accounts.map((account) => _buildAccountItem(account)), + const SizedBox(height: 16), + // 添加新账号按钮 + _buildAddAccountButton(), + ], + ), + ); + } + + Widget _buildAccountItem(AccountSummary account) { + final isCurrent = account.userSerialNum == _currentAccountId; + + return Container( + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: isCurrent ? const Color(0xFFD4AF37).withValues(alpha: 0.1) : const Color(0xFFFFF5E6), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isCurrent ? const Color(0xFFD4AF37) : const Color(0x33D4AF37), + width: isCurrent ? 2 : 1, + ), + ), + child: InkWell( + onTap: () => _switchToAccount(account), + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + // 头像 + _buildAvatar(account), + const SizedBox(width: 12), + // 账号信息 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + account.username, + style: const TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w600, + color: Color(0xFF5D4037), + ), + ), + if (isCurrent) ...[ + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: const Color(0xFFD4AF37), + borderRadius: BorderRadius.circular(10), + ), + child: const Text( + '当前', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ], + ], + ), + const SizedBox(height: 4), + Text( + account.userSerialNum, + style: const TextStyle( + fontSize: 12, + fontFamily: 'Inter', + color: Color(0x995D4037), + ), + ), + ], + ), + ), + // 删除按钮(非当前账号才显示) + if (!isCurrent) + IconButton( + icon: const Icon( + Icons.delete_outline, + color: Color(0xFFE57373), + size: 20, + ), + onPressed: () => _deleteAccount(account), + ), + // 当前账号显示勾选图标 + if (isCurrent) + const Icon( + Icons.check_circle, + color: Color(0xFFD4AF37), + size: 24, + ), + ], + ), + ), + ), + ); + } + + Widget _buildAvatar(AccountSummary account) { + return Container( + width: 48, + height: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: const Color(0xFFE8D5B7), + ), + clipBehavior: Clip.antiAlias, + child: account.avatarSvg != null + ? SvgPicture.string( + account.avatarSvg!, + fit: BoxFit.cover, + ) + : const Icon( + Icons.person, + size: 28, + color: Color(0xFF8B5A2B), + ), + ); + } + + Widget _buildAddAccountButton() { + return GestureDetector( + onTap: _addNewAccount, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFFFF5E6), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0x33D4AF37), + width: 1, + style: BorderStyle.solid, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: const Color(0xFFD4AF37).withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(16), + ), + child: const Icon( + Icons.add, + size: 20, + color: Color(0xFFD4AF37), + ), + ), + const SizedBox(width: 12), + const Text( + '添加新账号', + style: TextStyle( + fontSize: 15, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + color: Color(0xFF5D4037), + ), + ), + ], + ), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart index 166ff2d4..8c26b8ff 100644 --- a/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart @@ -6,6 +6,7 @@ import '../../../../routes/route_paths.dart'; import '../../../../routes/app_router.dart'; import '../../../../core/di/injection_container.dart'; import '../../../../core/storage/storage_keys.dart'; +import '../../../../core/services/multi_account_service.dart'; /// 创建账号页面 - 用户首次进入应用时的引导页面 /// 提供创建钱包和导入助记词两种选项 @@ -130,6 +131,19 @@ class _OnboardingPageState extends ConsumerState { debugPrint('[OnboardingPage] _createAccount - 已清除临时邀请人推荐码'); } + // 将账号添加到多账号列表 + final multiAccountService = ref.read(multiAccountServiceProvider); + await multiAccountService.addAccount( + AccountSummary( + userSerialNum: response.userSerialNum, + username: response.username, + avatarSvg: response.avatarSvg, + createdAt: DateTime.now(), + ), + ); + await multiAccountService.setCurrentAccountId(response.userSerialNum); + debugPrint('[OnboardingPage] _createAccount - 已添加到多账号列表'); + if (!mounted) { debugPrint('[OnboardingPage] _createAccount - Widget已卸载,忽略响应'); return; diff --git a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart index 360dc3c2..94382bf0 100644 --- a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart +++ b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart @@ -2867,21 +2867,9 @@ class _ProfilePageState extends ConsumerState { ); } - /// 切换账号 + /// 切换账号 - 跳转到账号切换页面 void _onSwitchAccount() { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('切换账号'), - content: const Text('多账号管理功能即将上线,敬请期待。'), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('确定'), - ), - ], - ), - ); + context.push(RoutePaths.accountSwitch); } /// 退出登录 @@ -2890,7 +2878,7 @@ class _ProfilePageState extends ConsumerState { context: context, builder: (context) => AlertDialog( title: const Text('退出登录'), - content: const Text('确定要退出当前账号吗?\n\n退出后需要重新导入助记词才能恢复账号。'), + content: const Text('确定要退出当前账号吗?\n\n退出后账号数据会保留在本地,可在账号切换页面重新登录。'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), @@ -2914,9 +2902,9 @@ class _ProfilePageState extends ConsumerState { /// 执行退出登录 Future _performLogout() async { try { - final accountService = ref.read(accountServiceProvider); - // 清除所有本地数据 - await accountService.logout(); + final multiAccountService = ref.read(multiAccountServiceProvider); + // 退出当前账号(保留账号数据) + await multiAccountService.logoutCurrentAccount(); // 导航到向导页 if (mounted) { context.go(RoutePaths.guide); diff --git a/frontend/mobile-app/lib/routes/app_router.dart b/frontend/mobile-app/lib/routes/app_router.dart index 81270c65..b79cb50a 100644 --- a/frontend/mobile-app/lib/routes/app_router.dart +++ b/frontend/mobile-app/lib/routes/app_router.dart @@ -25,6 +25,7 @@ import '../features/security/presentation/pages/bind_email_page.dart'; import '../features/withdraw/presentation/pages/withdraw_usdt_page.dart'; import '../features/withdraw/presentation/pages/withdraw_confirm_page.dart'; import '../features/notification/presentation/pages/notification_inbox_page.dart'; +import '../features/account/presentation/pages/account_switch_page.dart'; import 'route_paths.dart'; import 'route_names.dart'; @@ -187,6 +188,13 @@ final appRouterProvider = Provider((ref) { builder: (context, state) => const NotificationInboxPage(), ), + // Account Switch Page (账号切换) + GoRoute( + path: RoutePaths.accountSwitch, + name: RouteNames.accountSwitch, + builder: (context, state) => const AccountSwitchPage(), + ), + // Share Page (分享页面) GoRoute( path: RoutePaths.share, diff --git a/frontend/mobile-app/lib/routes/route_names.dart b/frontend/mobile-app/lib/routes/route_names.dart index e9bd4d02..99c0aabf 100644 --- a/frontend/mobile-app/lib/routes/route_names.dart +++ b/frontend/mobile-app/lib/routes/route_names.dart @@ -23,6 +23,7 @@ class RouteNames { static const referralList = 'referral-list'; static const earningsDetail = 'earnings-detail'; static const notifications = 'notifications'; + static const accountSwitch = 'account-switch'; static const deposit = 'deposit'; static const depositUsdt = 'deposit-usdt'; static const plantingQuantity = 'planting-quantity'; diff --git a/frontend/mobile-app/lib/routes/route_paths.dart b/frontend/mobile-app/lib/routes/route_paths.dart index 55b11e56..9ff5c92c 100644 --- a/frontend/mobile-app/lib/routes/route_paths.dart +++ b/frontend/mobile-app/lib/routes/route_paths.dart @@ -23,6 +23,7 @@ class RoutePaths { static const referralList = '/profile/referrals'; static const earningsDetail = '/profile/earnings'; static const notifications = '/notifications'; + static const accountSwitch = '/account/switch'; static const deposit = '/deposit'; static const depositUsdt = '/deposit/usdt'; static const plantingQuantity = '/planting/quantity';