Selaa lähdekoodia

增加记住密码功能。

PC\19500 3 viikkoa sitten
vanhempi
commit
aaa46fab91

+ 9 - 0
lib/core/constants/app_constants.dart

@@ -33,6 +33,15 @@ class AppConstants {
   /// 首次启动 Key
   static const String keyFirstLaunch = 'first_launch';
 
+  /// 保存的登录手机号 Key
+  static const String keySavedPhone = 'saved_phone';
+  
+  /// 保存的登录密码 Key(原始密码;登录时再进行 MD5 加密)
+  static const String keySavedPassword = 'saved_password';
+  
+  /// 是否记住密码 Key
+  static const String keyRememberPassword = 'remember_password';
+
   // ==================== 验证码相关 ====================
   
   /// 验证码长度

+ 45 - 0
lib/core/storage/local_storage.dart

@@ -320,5 +320,50 @@ class LocalStorage {
   static Future<bool> removeInstitutionInfo() async {
     return await remove(AppConstants.keyInstitutionInfo);
   }
+
+  /// 保存登录手机号
+  static Future<bool> savePhone(String phone) async {
+    return await setString(AppConstants.keySavedPhone, phone);
+  }
+
+  /// 获取登录手机号
+  static Future<String?> getPhone() async {
+    return await getString(AppConstants.keySavedPhone);
+  }
+
+  /// 删除登录手机号
+  static Future<bool> removePhone() async {
+    return await remove(AppConstants.keySavedPhone);
+  }
+
+  /// 保存登录密码(原始密码;登录时再进行 MD5 加密)
+  static Future<bool> savePassword(String password) async {
+    return await setString(AppConstants.keySavedPassword, password);
+  }
+
+  /// 获取登录密码(原始密码;登录时再进行 MD5 加密)
+  static Future<String?> getPassword() async {
+    return await getString(AppConstants.keySavedPassword);
+  }
+
+  /// 删除登录密码
+  static Future<bool> removePassword() async {
+    return await remove(AppConstants.keySavedPassword);
+  }
+
+  /// 保存是否记住密码
+  static Future<bool> saveRememberPassword(bool remember) async {
+    return await setBool(AppConstants.keyRememberPassword, remember);
+  }
+
+  /// 获取是否记住密码
+  static Future<bool> getRememberPassword() async {
+    return await getBool(AppConstants.keyRememberPassword, defaultValue: false) ?? false;
+  }
+
+  /// 删除是否记住密码
+  static Future<bool> removeRememberPassword() async {
+    return await remove(AppConstants.keyRememberPassword);
+  }
 }
 

+ 4 - 0
lib/features/HomePage/domain/work_bench_service.dart

@@ -0,0 +1,4 @@
+
+class WorkBenchService {
+
+}

+ 6 - 0
lib/features/MinePage/domain/mine_service.dart

@@ -26,6 +26,12 @@ class MineService {
         await LocalStorage.removeToken();
         await LocalStorage.removeUserInfo();
         await LocalStorage.removeInstitutionInfo();
+        
+        // 注意:不清除手机号和密码(如果用户选择记住密码)
+        // 手机号会保留,用户下次登录时自动填充
+        // 密码只在用户选择"记住密码"时保留
+        AppLogger.d('退出登录,手机号和密码已保留');
+        
         return response;
       } else {
         final errorMsg = response.msg ?? '退出登录失败';

+ 3 - 12
lib/features/MinePage/presentation/mine_page.dart

@@ -60,20 +60,11 @@ class _MinePageState extends ConsumerState<MinePage>
     // 重置主页 Tab 索引
     ref.read(currentTabIndexProvider.notifier).state = 0;
     
-    // 重置登录页面状态
-    ref.read(loginTabIndexProvider.notifier).state = 0;
-    ref.read(passwordObscureProvider.notifier).state = true;
-    ref.read(smsCountdownProvider.notifier).state = 0;
-    ref.read(smsHasReceivedProvider.notifier).state = false;
-    ref.read(smsCodeFromServerProvider.notifier).state = '';
-    ref.read(passwordLoginPhoneProvider.notifier).state = '';
-    ref.read(passwordLoginPasswordProvider.notifier).state = '';
-    ref.read(smsLoginPhoneProvider.notifier).state = '';
-    ref.read(smsLoginCodeProvider.notifier).state = '';
-    
+    // 返回登录页(使用 go 重新创建登录页实例)
+    // 注意:不清空登录相关的 Provider,让新的 LoginPage 在 initState 时自己初始化
     Future.microtask(() {
       if (mounted) {
-        context.replace('/');
+        context.go('/');
       }
     });
   }

+ 2 - 0
lib/features/auth/data/login_provider.dart

@@ -27,4 +27,6 @@ final smsLoginPhoneProvider = StateProvider<String>((ref) => '');
 /// 验证码登录 - 验证码
 final smsLoginCodeProvider = StateProvider<String>((ref) => '');
 
+/// 密码登录 - 是否记住密码
+final rememberPasswordProvider = StateProvider<bool>((ref) => false);
 

+ 130 - 28
lib/features/auth/presentation/login_page.dart

@@ -7,6 +7,7 @@ import 'package:sino_med_cloud/core/constants/app_constants.dart';
 import 'package:sino_med_cloud/core/utils/toast_utils.dart'; // 添加 ToastUtils 引用
 import '../../../core/utils/logger.dart';
 import '../../../core/utils/crypto_utils.dart';
+import '../../../core/storage/local_storage.dart';
 import '../../MinePage/data/change_password_provider.dart';
 import '../../MinePage/presentation/change_password_page.dart';
 import '../domain/login_service.dart';
@@ -47,9 +48,57 @@ class _LoginPageState extends ConsumerState<LoginPage>
   void initState() {
     super.initState();
     _tabController = TabController(length: 2, vsync: this);
+    _loadSavedLoginInfo();
+  }
+
+  /// 加载保存的登录信息
+  void _loadSavedLoginInfo() async {
+    try {
+      // 加载保存的手机号
+      final savedPhone = await LocalStorage.getPhone();
+      if (savedPhone != null && savedPhone.isNotEmpty) {
+        _phoneController.text = savedPhone;
+        _phoneSmsController.text = savedPhone;
+        // 同步到 Provider,避免监听器尚未注册导致 Provider 仍为空
+        if (mounted) {
+          ref.read(passwordLoginPhoneProvider.notifier).state = savedPhone;
+          ref.read(smsLoginPhoneProvider.notifier).state = savedPhone;
+        }
+        AppLogger.d('加载保存的手机号: $savedPhone');
+      }
+
+      // 检查是否记住密码
+      final rememberPassword = await LocalStorage.getRememberPassword();
+      if (mounted) {
+        ref.read(rememberPasswordProvider.notifier).state = rememberPassword;
+        // 回到登录页时,强制隐藏密码(无论前一次是否显示)
+        ref.read(passwordObscureProvider.notifier).state = true;
+      }
+      if (rememberPassword) {
+        // 加载保存的密码(原始密码)
+        final savedPassword = await LocalStorage.getPassword();
+        if (savedPassword != null && savedPassword.isNotEmpty) {
+          // 设置密码(但保持隐藏状态)
+          _passwordController.text = savedPassword;
+          // 同步到 Provider,避免监听器尚未注册导致 Provider 仍为空
+          if (mounted) {
+            ref.read(passwordLoginPasswordProvider.notifier).state = savedPassword;
+          }
+          AppLogger.d('加载保存的密码(原始密码)');
+        }
+      }
+    } catch (e) {
+      AppLogger.e('加载保存的登录信息失败', e);
+    }
   }
 
   void _setupListeners() {
+    // 先强制同步一次当前 Controller 的值到 Provider(避免 _loadSavedLoginInfo 在 listener 注册前完成)
+    ref.read(passwordLoginPhoneProvider.notifier).state = _phoneController.text;
+    ref.read(passwordLoginPasswordProvider.notifier).state = _passwordController.text;
+    ref.read(smsLoginPhoneProvider.notifier).state = _phoneSmsController.text;
+    ref.read(smsLoginCodeProvider.notifier).state = _smsCodeController.text;
+    
     // 监听 Tab 切换,同步到 Provider
     _tabController.addListener(() {
       if (!_tabController.indexIsChanging) {
@@ -169,8 +218,9 @@ class _LoginPageState extends ConsumerState<LoginPage>
         // 在异步方法中应使用 ref.read
         final phoneNumber = ref.read(passwordLoginPhoneProvider);
         final password = ref.read(passwordLoginPasswordProvider);
+        final rememberPassword = ref.read(rememberPasswordProvider);
 
-        // 对密码进行 MD5 加密
+        // 每次登录请求时重新加密(不存 MD5)
         final encryptedPassword = CryptoUtils.md5(password);
 
         AppLogger.d('开始密码登录请求: $phoneNumber');
@@ -182,7 +232,24 @@ class _LoginPageState extends ConsumerState<LoginPage>
           loginType: _loginType,
         );
         AppLogger.d('密码登录成功,准备跳转主页');
-        // 登录成功,跳转到主页
+        
+        // 登录成功后,保存手机号(总是保存)
+        await LocalStorage.savePhone(phoneNumber);
+        
+        // 根据"记住密码"选项保存或删除密码
+        if (rememberPassword) {
+          // 保存原始密码(登录时再加密)
+          await LocalStorage.savePassword(password);
+          await LocalStorage.saveRememberPassword(true);
+          AppLogger.d('已保存手机号和密码');
+        } else {
+          // 不记住密码时,删除已保存的密码
+          await LocalStorage.removePassword();
+          await LocalStorage.saveRememberPassword(false);
+          AppLogger.d('已保存手机号,删除密码');
+        }
+        
+        // 跳转到主页(使用 go 替换当前路由,销毁登录页)
         if (mounted) {
           context.go('/mainTab');
         }
@@ -235,7 +302,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
         ref.read(smsHasReceivedProvider.notifier).state = false;
         ref.read(smsCodeFromServerProvider.notifier).state = '';
 
-        // 跳转到主页
+        // 跳转到主页(使用 go 替换当前路由,销毁登录页)
         if (mounted) {
           context.go('/mainTab');
         }
@@ -406,32 +473,67 @@ class _LoginPageState extends ConsumerState<LoginPage>
             },
           ),
           const SizedBox(height: 8),
-          // 忘记密码
-          Align(
-            alignment: Alignment.centerRight,
-            child: TextButton(
-              onPressed: () {
-                // 跳转到忘记密码页面
-                if (mounted) {
-                  // 打开对话框前重置所有状态,确保每次打开都是干净的
-                  ref.read(changePwdPhoneProvider.notifier).state = '';
-                  ref.read(changePwdServerSmsCodeProvider.notifier).state = '';
-                  ref.read(changePwdPasswordProvider.notifier).state = '';
-                  ref.read(changePwdPasswordVisibleProvider.notifier).state = false;
-                  ref.read(changePwdConfirmPasswordProvider.notifier).state = '';
-                  ref.read(changePwdConfirmPasswordVisibleProvider.notifier).state = false;
-                  ref.read(changePwdSmsHasSentProvider.notifier).state = false;
-                  ref.read(changePwdSmsCountdownProvider.notifier).state = 0;
-                  ref.read(changePwdShowPasswordErrorProvider.notifier).state = false;
-                  
-                  showDialog(
-                    context: context,
-                    builder: (context) => const ChangePasswordPage(),
+          // 记住密码 + 忘记密码
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              // 记住密码复选框
+              Consumer(
+                builder: (context, ref, child) {
+                  final rememberPassword = ref.watch(rememberPasswordProvider);
+                  return Row(
+                    mainAxisSize: MainAxisSize.min,
+                    children: [
+                      Checkbox(
+                        value: rememberPassword,
+                        onChanged: (value) {
+                          ref.read(rememberPasswordProvider.notifier).state = value ?? false;
+                        },
+                        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+                        visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
+                      ),
+                      const SizedBox(width: 4),
+                      GestureDetector(
+                        onTap: () {
+                          ref.read(rememberPasswordProvider.notifier).state = !rememberPassword;
+                        },
+                        child: Text(
+                          l10n.rememberPassword,
+                          style: const TextStyle(
+                            fontSize: 14,
+                            color: Color(0xFF6B7280),
+                          ),
+                        ),
+                      ),
+                    ],
                   );
-                }
-              },
-              child: Text(l10n.forgotPassword),
-            ),
+                },
+              ),
+              // 忘记密码
+              TextButton(
+                onPressed: () {
+                  // 跳转到忘记密码页面
+                  if (mounted) {
+                    // 打开对话框前重置所有状态,确保每次打开都是干净的
+                    ref.read(changePwdPhoneProvider.notifier).state = '';
+                    ref.read(changePwdServerSmsCodeProvider.notifier).state = '';
+                    ref.read(changePwdPasswordProvider.notifier).state = '';
+                    ref.read(changePwdPasswordVisibleProvider.notifier).state = false;
+                    ref.read(changePwdConfirmPasswordProvider.notifier).state = '';
+                    ref.read(changePwdConfirmPasswordVisibleProvider.notifier).state = false;
+                    ref.read(changePwdSmsHasSentProvider.notifier).state = false;
+                    ref.read(changePwdSmsCountdownProvider.notifier).state = 0;
+                    ref.read(changePwdShowPasswordErrorProvider.notifier).state = false;
+                    
+                    showDialog(
+                      context: context,
+                      builder: (context) => const ChangePasswordPage(),
+                    );
+                  }
+                },
+                child: Text(l10n.forgotPassword),
+              ),
+            ],
           ),
           const SizedBox(height: 24),
           // 登录按钮

+ 6 - 0
lib/l10n/app_localizations.dart

@@ -321,6 +321,12 @@ abstract class AppLocalizations {
   /// In zh, this message translates to:
   /// **'取消'**
   String get cancel;
+
+  /// 记住密码选项
+  ///
+  /// In zh, this message translates to:
+  /// **'记住密码'**
+  String get rememberPassword;
 }
 
 class _AppLocalizationsDelegate

+ 3 - 0
lib/l10n/app_localizations_zh.dart

@@ -123,4 +123,7 @@ class AppLocalizationsZh extends AppLocalizations {
 
   @override
   String get cancel => '取消';
+
+  @override
+  String get rememberPassword => '记住密码';
 }

+ 4 - 0
lib/l10n/app_zh.arb

@@ -156,6 +156,10 @@
   "cancel": "取消",
   "@cancel": {
     "description": "取消按钮文本"
+  },
+  "rememberPassword": "记住密码",
+  "@rememberPassword": {
+    "description": "记住密码选项"
   }
 }