Преглед изворни кода

修改密码页面完善及接口调用。

PC\19500 пре 3 недеља
родитељ
комит
8440a25422

+ 4 - 1
README.md

@@ -6,4 +6,7 @@ flutter pub run build_runner build --delete-conflicting-outputs
 
 # 监听模式
 # 开发时终端打开,监听model文件的变化并自动更新.g.dart文件
-flutter pub run build_runner watch
+flutter pub run build_runner watch
+
+# 生成字段
+flutter gen-l10n

+ 2 - 7
lib/app/router.dart

@@ -1,6 +1,5 @@
 import 'package:go_router/go_router.dart';
 import 'package:sino_med_cloud/features/main_tab_page.dart';
-import '../features/MinePage/presentation/change_password_page.dart';
 import '../features/auth/presentation/login_page.dart';
 
 class AppRouter {
@@ -8,15 +7,11 @@ class AppRouter {
     routes: [
       GoRoute(
         path: '/',
-        builder: (_, __) => const LoginPage(),
+        builder: (_, _) => const LoginPage(),
       ),
       GoRoute(
         path: '/mainTab',
-        builder: (_, __) => const MainTabPage(),
-      ),
-      GoRoute(
-        path: '/ChangePassword',
-        builder: (_, __) => const ChangePasswordPage(),
+        builder: (_, _) => const MainTabPage(),
       ),
     ]
   );

+ 42 - 0
lib/core/utils/common_utils.dart

@@ -0,0 +1,42 @@
+// 修改密码格式校验
+import 'package:flutter/material.dart';
+
+import '../../l10n/app_localizations.dart';
+
+bool isValidPassword(String value) {
+  final reg = RegExp(
+    r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{8,16}$',
+  );
+  return reg.hasMatch(value);
+}
+
+String? validateForm({
+  required BuildContext context,  // 添加 context 参数
+  required String phone,
+  required String code,
+  required String password,
+  required String confirmPassword,
+}) {
+  final l10n = AppLocalizations.of(context)!;
+  if (phone.isEmpty) return l10n.phoneNumberRequired;
+  if (!RegExp(r'^1\d{10}$').hasMatch(phone)) {
+    return l10n.phoneNumberInvalid;
+  }
+
+  if (code.isEmpty) return l10n.smsCodeRequired;
+  if (!RegExp(r'^\d{4}$').hasMatch(code)) {
+    return l10n.smsCodeInvalid;
+  }
+
+  if (password.isEmpty) return l10n.newPasswordRequired;
+  if (!isValidPassword(password)) {
+    return l10n.passwordFormatError;
+  }
+
+  if (confirmPassword.isEmpty) return l10n.confirmPasswordRequired;
+  if (password != confirmPassword) {
+    return l10n.confirmPasswordError;
+  }
+
+  return null;
+}

+ 28 - 0
lib/features/MinePage/data/change_password_provider.dart

@@ -0,0 +1,28 @@
+import 'package:flutter_riverpod/legacy.dart';
+
+// 修改密码页面 - 手机号
+final changePwdPhoneProvider = StateProvider<String>((ref) => '');
+
+// 修改密码页面 - 新密码
+final changePwdPasswordProvider = StateProvider<String>((ref) => '');
+
+// 修改密码页面 - 确认密码
+final changePwdConfirmPasswordProvider = StateProvider<String>((ref) => '');
+
+// 修改密码页面 - 新密码是否可见
+final changePwdPasswordVisibleProvider = StateProvider<bool>((ref) => false);
+
+// 修改密码页面 - 确认密码是否可见
+final changePwdConfirmPasswordVisibleProvider = StateProvider<bool>((ref) => false);
+
+// 修改密码页面 - 验证码倒计时 (0 = 可获取)
+final changePwdSmsCountdownProvider = StateProvider<int>((ref) => 0);
+
+// 修改密码页面 - 是否已发送过验证码
+final changePwdSmsHasSentProvider = StateProvider<bool>((ref) => false);
+
+// 修改密码页面 - 从服务器返回的验证码(用于提交修改密码请求)
+final changePwdServerSmsCodeProvider = StateProvider<String>((ref) => '');
+
+// 修改密码页面 - 是否显示密码不一致错误(输入框下方提示)
+final changePwdShowPasswordErrorProvider = StateProvider<bool>((ref) => false);

+ 1 - 9
lib/features/MinePage/domain/change_password_service.dart

@@ -29,15 +29,7 @@ class ChangePasswordService {
           fromJsonT: (json) => json as Map<String, dynamic>,
           data: parame
       );
-
-      if (response.success && response.code == 20000) {
-        AppLogger.d('修改密码成功');
-        return response;
-      } else {
-        final errorMsg = response.msg ?? '修改密码失败';
-        AppLogger.d('修改密码失败: $errorMsg');
-        throw Exception(errorMsg);
-      }
+      return response;
     } catch (e) {
       AppLogger.e('修改密码错误', e);
       rethrow;

+ 275 - 11
lib/features/MinePage/presentation/change_password_page.dart

@@ -1,6 +1,14 @@
+import 'dart:async';
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
+import '../../../core/utils/common_utils.dart';
+import '../../../core/utils/toast_utils.dart';
+import '../../../core/utils/logger.dart';
 import '../../../l10n/app_localizations.dart';
+import '../../../module/common_input_field.dart';
+import '../../auth/domain/login_service.dart';
+import '../data/change_password_provider.dart';
+import '../domain/change_password_service.dart';
 
 class ChangePasswordPage extends ConsumerStatefulWidget {
   const ChangePasswordPage({super.key});
@@ -9,21 +17,277 @@ class ChangePasswordPage extends ConsumerStatefulWidget {
   ConsumerState<ChangePasswordPage> createState() => _ChangePasswordPageState();
 }
 
-class _ChangePasswordPageState extends ConsumerState<ChangePasswordPage>
-    with SingleTickerProviderStateMixin {
+class _ChangePasswordPageState extends ConsumerState<ChangePasswordPage> {
+  Timer? _smsTimer;
+
+  // 验证密码一致性(回车键/Tab键触发)
+  void _validatePasswordMatch() {
+    final password = ref.read(changePwdPasswordProvider);
+    final confirmPassword = ref.read(changePwdConfirmPasswordProvider);
+    ref.read(changePwdShowPasswordErrorProvider.notifier).state = 
+        password.isNotEmpty && 
+        confirmPassword.isNotEmpty && 
+        password != confirmPassword;
+  }
+
+  void startSmsCountdown() {
+    ref.read(changePwdSmsCountdownProvider.notifier).state = 60;
+
+    _smsTimer?.cancel();
+    _smsTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
+      final value = ref.read(changePwdSmsCountdownProvider);
+      if (value <= 1) {
+        timer.cancel();
+        ref.read(changePwdSmsCountdownProvider.notifier).state = 0;
+      } else {
+        ref.read(changePwdSmsCountdownProvider.notifier).state = value - 1;
+      }
+    });
+  }
+
+  void submit(BuildContext context) async {
+    final l10n = AppLocalizations.of(context)!;
+
+    final phone = ref.read(changePwdPhoneProvider);
+    final smsCode = ref.read(changePwdServerSmsCodeProvider);
+    final password = ref.read(changePwdPasswordProvider);
+    final confirmPassword = ref.read(changePwdConfirmPasswordProvider);
+
+    final error = validateForm(
+      context: context,
+      phone: phone,
+      code: smsCode,
+      password: password,
+      confirmPassword: confirmPassword,
+    );
+
+    if (error != null) {
+      ToastUtils.showError(error);
+      return;
+    }
+
+    // 调用重置密码接口
+    try {
+      final response = await ChangePasswordService.changePassword(
+        mobile: phone,
+        oldPassword: password,
+        newPassword: confirmPassword,
+        smsCode: smsCode,
+      );
+      if (response.success && response.code == 20000) {
+        AppLogger.d('修改密码成功');
+        ToastUtils.showSuccess(l10n.changePasswordSuccess);
+        if (mounted) {
+          Navigator.of(context).pop();
+        }
+      } else {
+        final errorMsg = response.msg ?? l10n.changePasswordFailed;
+        AppLogger.e(errorMsg);
+        ToastUtils.showError(errorMsg);
+      }
+    } catch (e) {
+      AppLogger.e('修改密码错误', e);
+      ToastUtils.showError(l10n.changePasswordFailed);
+    }
+  }
+
+  @override
+  void dispose() {
+    _smsTimer?.cancel();
+    super.dispose();
+  }
+
   @override
   Widget build(BuildContext context) {
+    final phone = ref.watch(changePwdPhoneProvider);
+    final smsCode = ref.watch(changePwdServerSmsCodeProvider);
+    final password = ref.watch(changePwdPasswordProvider);
+    final confirmPassword = ref.watch(changePwdConfirmPasswordProvider);
+
+    final pwdVisible = ref.watch(changePwdPasswordVisibleProvider);
+    final confirmPwdVisible = ref.watch(changePwdConfirmPasswordVisibleProvider);
+    final countdown = ref.watch(changePwdSmsCountdownProvider);
+    final smsHasSent = ref.watch(changePwdSmsHasSentProvider);
+    final showPasswordError = ref.watch(changePwdShowPasswordErrorProvider);
+
+    final canSubmit = validateForm(
+        context: context,
+        phone: phone,
+        code: smsCode,
+        password: password,
+        confirmPassword: confirmPassword) == null;
+
     final l10n = AppLocalizations.of(context)!;
-    return Scaffold(
-        appBar: AppBar(
-          title: Text(l10n.changePassword),
-          elevation: 0,
+    return Dialog(
+      insetPadding: const EdgeInsets.all(24),
+      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+      child: Padding(
+        padding: const EdgeInsets.all(20),
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            /// 标题
+            Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                Text(
+                  l10n.forgotPassword,
+                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+                ),
+                IconButton(
+                  icon: const Icon(Icons.close),
+                  onPressed: () => Navigator.of(context).pop(),
+                )
+              ],
+            ),
+            const SizedBox(height: 16),
+
+            /// 手机号
+            CommonInputField(
+              label: l10n.phoneNumber,
+              required: true,
+              value: phone,
+              hintText: l10n.phoneNumberRequired,
+              keyboardType: TextInputType.phone,
+              onChanged: (v) =>
+              ref.read(changePwdPhoneProvider.notifier).state = v,
+            ),
+            const SizedBox(height: 16),
+
+            /// 验证码
+            CommonInputField(
+              label: l10n.smsCode,
+              required: true,
+              value: smsCode,
+              hintText: l10n.smsCodeRequired,
+              keyboardType: TextInputType.number,
+              onChanged: (v) =>
+              ref.read(changePwdServerSmsCodeProvider.notifier).state = v,
+              suffix: TextButton(
+                onPressed: countdown > 0
+                    ? null
+                    : () async {
+                  // 校验手机号
+                  if (phone.isEmpty) {
+                    ToastUtils.showError(l10n.phoneNumberRequired);
+                    return;
+                  }
+                  if (!RegExp(r'^1\d{10}$').hasMatch(phone)) {
+                    ToastUtils.showError(l10n.phoneNumberInvalid);
+                    return;
+                  }
+                  
+                  // 发送验证码
+                  try {
+                    AppLogger.d('修改密码 - 发送验证码,手机号: $phone');
+                    final smsCode = await LoginService.sendSmsCode(
+                      mobile: phone,
+                      scope: 'yun-his-forget-password-sms-send',
+                    );
+                    // 保存服务器返回的验证码
+                    ref.read(changePwdServerSmsCodeProvider.notifier).state = smsCode;
+                    AppLogger.d('验证码已保存: $smsCode');
+                    ToastUtils.showSuccess(l10n.smsCodeSent);
+                    ref.read(changePwdSmsHasSentProvider.notifier).state = true;
+                    startSmsCountdown();
+                  } catch (e) {
+                    AppLogger.e('发送验证码失败', e);
+                    ToastUtils.showError(e.toString().replaceAll('Exception: ', ''));
+                  }
+                },
+                child: Text(
+                  countdown > 0 
+                      ? '${countdown}s' 
+                      : (smsHasSent ? l10n.resendSmsCode : l10n.getSmsCode),
+                ),
+              ),
+            ),
+            const SizedBox(height: 16),
+
+            /// 新密码
+            CommonInputField(
+              label: l10n.newPassword,
+              required: true,
+              value: password,
+              hintText: l10n.newPasswordRequired,
+              obscureText: !pwdVisible,
+              onChanged: (v) =>
+              ref.read(changePwdPasswordProvider.notifier).state = v,
+              suffix: IconButton(
+                icon: Icon(
+                    pwdVisible ? Icons.visibility : Icons.visibility_off),
+                onPressed: () => ref
+                    .read(changePwdPasswordVisibleProvider.notifier)
+                    .state = !pwdVisible,
+              ),
+            ),
+            const SizedBox(height: 6),
+            Align(
+              alignment: Alignment.centerLeft,
+              child: Text(
+                l10n.passwordFormatError,
+                style: TextStyle(fontSize: 12, color: Colors.grey),
+              ),
+            ),
+            const SizedBox(height: 16),
+
+            /// 确认密码
+            CommonInputField(
+              label: l10n.confirmPassword,
+              required: true,
+              value: confirmPassword,
+              hintText: l10n.confirmPasswordRequired,
+              obscureText: !confirmPwdVisible,
+              onChanged: (v) {
+                ref.read(changePwdConfirmPasswordProvider.notifier).state = v;
+                // 用户正在输入,清除错误提示
+                ref.read(changePwdShowPasswordErrorProvider.notifier).state = false;
+              },
+              onFieldSubmitted: (v) {
+                // 回车键或 Tab 键时验证密码一致性
+                _validatePasswordMatch();
+              },
+              suffix: IconButton(
+                icon: Icon(confirmPwdVisible
+                    ? Icons.visibility
+                    : Icons.visibility_off),
+                onPressed: () => ref
+                    .read(changePwdConfirmPasswordVisibleProvider.notifier)
+                    .state = !confirmPwdVisible,
+              ),
+            ),
+            // 显示密码不一致错误提示(仅在回车/Tab时显示)
+            if (showPasswordError)
+              Padding(
+                padding: const EdgeInsets.only(top: 8.0, left: 4.0),
+                child: Text(
+                  l10n.confirmPasswordError,
+                  style: const TextStyle(
+                    color: Colors.red,
+                    fontSize: 12,
+                  ),
+                ),
+              ),
+            const SizedBox(height: 24),
+
+            /// 按钮
+            Row(
+              mainAxisAlignment: MainAxisAlignment.end,
+              children: [
+                OutlinedButton(
+                  onPressed: () => Navigator.of(context).pop(),
+                  child: Text(l10n.cancel),
+                ),
+                const SizedBox(width: 12),
+                ElevatedButton(
+                  onPressed: canSubmit ? () => submit(context) : null,
+                  child: Text(l10n.confirm),
+                ),
+              ],
+            )
+          ],
         ),
-      body: ListView.builder(
-          itemCount: 5,
-          itemBuilder: (BuildContext context, int index) {
-            return const SizedBox.shrink();
-          }),
+      ),
     );
   }
 }

+ 32 - 1
lib/features/MinePage/presentation/mine_page.dart

@@ -4,7 +4,10 @@ import 'package:go_router/go_router.dart';
 import 'package:sino_med_cloud/l10n/app_localizations.dart';
 
 import '../../main_tab_provider.dart';
+import '../../auth/data/login_provider.dart';
+import '../data/change_password_provider.dart';
 import '../domain/mine_service.dart';
+import 'change_password_page.dart';
 
 class MinePage extends ConsumerStatefulWidget {
   const MinePage({super.key});
@@ -53,7 +56,21 @@ class _MinePageState extends ConsumerState<MinePage>
   // 退出登录
   void _handleLogout() async {
     await MineService.logout(loginSystem: "YUN_HIS_PC_WEB");
+    
+    // 重置主页 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 = '';
+    
     Future.microtask(() {
       if (mounted) {
         context.replace('/');
@@ -64,7 +81,21 @@ class _MinePageState extends ConsumerState<MinePage>
 
   void _handleChangePassword() {
     if (mounted) {
-      context.push('/ChangePassword');
+      // 打开对话框前重置所有状态,确保每次打开都是干净的
+      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(),
+      );
     }
   }
 }

+ 0 - 0
lib/features/MinePage/widget/enter_text_widget.dart


+ 23 - 7
lib/features/auth/presentation/login_page.dart

@@ -7,6 +7,8 @@ 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 '../../MinePage/data/change_password_provider.dart';
+import '../../MinePage/presentation/change_password_page.dart';
 import '../domain/login_service.dart';
 import '../data/login_provider.dart';
 
@@ -111,7 +113,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
     final l10n = AppLocalizations.of(context)!;
     final phone = ref.read(smsLoginPhoneProvider);
     if (phone.isEmpty) {
-      ToastUtils.show(l10n.phoneNumberRequiredForSms);
+      ToastUtils.show(l10n.phoneNumberRequired);
       return;
     }
 
@@ -357,7 +359,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
             keyboardType: TextInputType.phone,
             decoration: InputDecoration(
               labelText: l10n.phoneNumber,
-              hintText: l10n.phoneNumberHint,
+              hintText: l10n.phoneNumberRequired,
               prefixIcon: const Icon(Icons.phone_outlined),
             ),
             validator: (value) {
@@ -380,11 +382,11 @@ class _LoginPageState extends ConsumerState<LoginPage>
                 obscureText: obscurePassword,
                 decoration: InputDecoration(
                   labelText: l10n.password,
-                  hintText: l10n.passwordHint,
+                  hintText: l10n.passwordRequired,
                   prefixIcon: const Icon(Icons.lock_outline),
                   suffixIcon: IconButton(
                     icon: Icon(
-                      obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
+                      obscurePassword ? Icons.visibility_off_outlined : Icons.visibility_outlined,
                     ),
                     onPressed: () {
                       ref.read(passwordObscureProvider.notifier).state = !obscurePassword;
@@ -411,7 +413,21 @@ class _LoginPageState extends ConsumerState<LoginPage>
               onPressed: () {
                 // 跳转到忘记密码页面
                 if (mounted) {
-                  context.push('/ChangePassword');
+                  // 打开对话框前重置所有状态,确保每次打开都是干净的
+                  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),
@@ -446,7 +462,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
             keyboardType: TextInputType.phone,
             decoration: InputDecoration(
               labelText: l10n.phoneNumber,
-              hintText: l10n.phoneNumberHint,
+              hintText: l10n.phoneNumberRequired,
               prefixIcon: const Icon(Icons.phone_outlined),
             ),
             validator: (value) {
@@ -469,7 +485,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
                   keyboardType: TextInputType.number,
                   decoration: InputDecoration(
                     labelText: l10n.smsCode,
-                    hintText: l10n.smsCodeHint,
+                    hintText: l10n.smsCodeRequired,
                     prefixIcon: const Icon(Icons.sms_outlined),
                   ),
                   validator: (value) {

+ 66 - 24
lib/l10n/app_localizations.dart

@@ -124,13 +124,7 @@ abstract class AppLocalizations {
   /// **'手机号'**
   String get phoneNumber;
 
-  /// 手机号输入提示
-  ///
-  /// In zh, this message translates to:
-  /// **'请输入手机号'**
-  String get phoneNumberHint;
-
-  /// 手机号必填验证
+  /// 手机号输入提示/必填验证
   ///
   /// In zh, this message translates to:
   /// **'请输入手机号'**
@@ -148,13 +142,7 @@ abstract class AppLocalizations {
   /// **'密码'**
   String get password;
 
-  /// 密码输入提示
-  ///
-  /// In zh, this message translates to:
-  /// **'请输入密码'**
-  String get passwordHint;
-
-  /// 密码必填验证
+  /// 密码输入提示/必填验证
   ///
   /// In zh, this message translates to:
   /// **'请输入密码'**
@@ -202,6 +190,18 @@ abstract class AppLocalizations {
   /// **'修改密码'**
   String get changePassword;
 
+  /// 修改密码成功提示
+  ///
+  /// In zh, this message translates to:
+  /// **'修改密码成功'**
+  String get changePasswordSuccess;
+
+  /// 修改密码失败提示
+  ///
+  /// In zh, this message translates to:
+  /// **'修改密码失败'**
+  String get changePasswordFailed;
+
   /// 登录功能提示
   ///
   /// In zh, this message translates to:
@@ -214,13 +214,7 @@ abstract class AppLocalizations {
   /// **'验证码'**
   String get smsCode;
 
-  /// 验证码输入提示
-  ///
-  /// In zh, this message translates to:
-  /// **'请输入验证码'**
-  String get smsCodeHint;
-
-  /// 验证码必填验证
+  /// 验证码输入提示/必填验证
   ///
   /// In zh, this message translates to:
   /// **'请输入验证码'**
@@ -262,6 +256,12 @@ abstract class AppLocalizations {
   /// **'获取验证码'**
   String get getSmsCode;
 
+  /// 验证码发送成功提示
+  ///
+  /// In zh, this message translates to:
+  /// **'验证码已发送'**
+  String get smsCodeSent;
+
   /// 重新获取验证码按钮文本
   ///
   /// In zh, this message translates to:
@@ -274,11 +274,53 @@ abstract class AppLocalizations {
   /// **'{count}秒'**
   String smsCodeCountdown(int count);
 
-  /// 发送验证码时手机号必填提示
+  /// 输入新密码标题
   ///
   /// In zh, this message translates to:
-  /// **'请输入手机号'**
-  String get phoneNumberRequiredForSms;
+  /// **'新密码'**
+  String get newPassword;
+
+  /// 修改密码时新密码必填提示
+  ///
+  /// In zh, this message translates to:
+  /// **'请输入新密码'**
+  String get newPasswordRequired;
+
+  /// 密码格式验证
+  ///
+  /// In zh, this message translates to:
+  /// **'密码需8-16位,包含大小写字母和数字'**
+  String get passwordFormatError;
+
+  /// 修改密码时确认密码标题
+  ///
+  /// In zh, this message translates to:
+  /// **'再次确认密码'**
+  String get confirmPassword;
+
+  /// 修改密码时确认密码必填提示
+  ///
+  /// In zh, this message translates to:
+  /// **'请再次确认密码'**
+  String get confirmPasswordRequired;
+
+  /// 修改密码时确认密码不一致提示
+  ///
+  /// In zh, this message translates to:
+  /// **'两次密码不一致'**
+  String get confirmPasswordError;
+
+  /// 确定按钮文本
+  ///
+  /// In zh, this message translates to:
+  /// **'确定'**
+  String get confirm;
+
+  /// 取消按钮文本
+  ///
+  /// In zh, this message translates to:
+  /// **'取消'**
+  String get cancel;
 }
 
 class _AppLocalizationsDelegate

+ 31 - 10
lib/l10n/app_localizations_zh.dart

@@ -24,9 +24,6 @@ class AppLocalizationsZh extends AppLocalizations {
   String get phoneNumber => '手机号';
 
   @override
-  String get phoneNumberHint => '请输入手机号';
-
-  @override
   String get phoneNumberRequired => '请输入手机号';
 
   @override
@@ -36,9 +33,6 @@ class AppLocalizationsZh extends AppLocalizations {
   String get password => '密码';
 
   @override
-  String get passwordHint => '请输入密码';
-
-  @override
   String get passwordRequired => '请输入密码';
 
   @override
@@ -63,13 +57,16 @@ class AppLocalizationsZh extends AppLocalizations {
   String get changePassword => '修改密码';
 
   @override
-  String get loginNotImplemented => '登录功能待实现';
+  String get changePasswordSuccess => '修改密码成功';
 
   @override
-  String get smsCode => '验证码';
+  String get changePasswordFailed => '修改密码失败';
 
   @override
-  String get smsCodeHint => '请输入验证码';
+  String get loginNotImplemented => '登录功能待实现';
+
+  @override
+  String get smsCode => '验证码';
 
   @override
   String get smsCodeRequired => '请输入验证码';
@@ -93,6 +90,9 @@ class AppLocalizationsZh extends AppLocalizations {
   String get getSmsCode => '获取验证码';
 
   @override
+  String get smsCodeSent => '验证码已发送';
+
+  @override
   String get resendSmsCode => '请重新获取验证码';
 
   @override
@@ -101,5 +101,26 @@ class AppLocalizationsZh extends AppLocalizations {
   }
 
   @override
-  String get phoneNumberRequiredForSms => '请输入手机号';
+  String get newPassword => '新密码';
+
+  @override
+  String get newPasswordRequired => '请输入新密码';
+
+  @override
+  String get passwordFormatError => '密码需8-16位,包含大小写字母和数字';
+
+  @override
+  String get confirmPassword => '再次确认密码';
+
+  @override
+  String get confirmPasswordRequired => '请再次确认密码';
+
+  @override
+  String get confirmPasswordError => '两次密码不一致';
+
+  @override
+  String get confirm => '确定';
+
+  @override
+  String get cancel => '取消';
 }

+ 49 - 21
lib/l10n/app_zh.arb

@@ -20,13 +20,9 @@
   "@phoneNumber": {
     "description": "手机号标签"
   },
-  "phoneNumberHint": "请输入手机号",
-  "@phoneNumberHint": {
-    "description": "手机号输入提示"
-  },
   "phoneNumberRequired": "请输入手机号",
   "@phoneNumberRequired": {
-    "description": "手机号必填验证"
+    "description": "手机号输入提示/必填验证"
   },
   "phoneNumberInvalid": "请输入正确的手机号",
   "@phoneNumberInvalid": {
@@ -36,13 +32,9 @@
   "@password": {
     "description": "密码标签"
   },
-  "passwordHint": "请输入密码",
-  "@passwordHint": {
-    "description": "密码输入提示"
-  },
   "passwordRequired": "请输入密码",
   "@passwordRequired": {
-    "description": "密码必填验证"
+    "description": "密码输入提示/必填验证"
   },
   "passwordMinLength": "密码长度不能少于6位",
   "@passwordMinLength": {
@@ -72,6 +64,14 @@
   "@changePassword": {
     "description": "修改密码按钮"
   },
+  "changePasswordSuccess": "修改密码成功",
+  "@changePasswordSuccess": {
+    "description": "修改密码成功提示"
+  },
+  "changePasswordFailed": "修改密码失败",
+  "@changePasswordFailed": {
+    "description": "修改密码失败提示"
+  },
   "loginNotImplemented": "登录功能待实现",
   "@loginNotImplemented": {
     "description": "登录功能提示"
@@ -80,13 +80,9 @@
   "@smsCode": {
     "description": "验证码标签"
   },
-  "smsCodeHint": "请输入验证码",
-  "@smsCodeHint": {
-    "description": "验证码输入提示"
-  },
   "smsCodeRequired": "请输入验证码",
   "@smsCodeRequired": {
-    "description": "验证码必填验证"
+    "description": "验证码输入提示/必填验证"
   },
   "smsCodeLength": "验证码为4位数字",
   "@smsCodeLength": {
@@ -109,13 +105,17 @@
     "description": "验证码输入错误提示"
   },
   "getSmsCode": "获取验证码",
+  "@getSmsCode": {
+    "description": "获取验证码按钮"
+  },
+  "smsCodeSent": "验证码已发送",
+  "@smsCodeSent": {
+    "description": "验证码发送成功提示"
+  },
   "resendSmsCode": "请重新获取验证码",
   "@resendSmsCode": {
     "description": "重新获取验证码按钮文本"
   },
-  "@getSmsCode": {
-    "description": "获取验证码按钮"
-  },
   "smsCodeCountdown": "{count}秒",
   "@smsCodeCountdown": {
     "description": "验证码倒计时",
@@ -125,9 +125,37 @@
       }
     }
   },
-  "phoneNumberRequiredForSms": "请输入手机号",
-  "@phoneNumberRequiredForSms": {
-    "description": "发送验证码时手机号必填提示"
+  "newPassword": "新密码",
+  "@newPassword": {
+    "description": "输入新密码标题"
+  },
+  "newPasswordRequired": "请输入新密码",
+  "@newPasswordRequired": {
+    "description": "修改密码时新密码必填提示"
+  },
+  "passwordFormatError": "密码需8-16位,包含大小写字母和数字",
+  "@passwordFormatError": {
+    "description": "密码格式验证"
+  },
+  "confirmPassword": "再次确认密码",
+  "@confirmPassword": {
+    "description": "修改密码时确认密码标题"
+  },
+  "confirmPasswordRequired": "请再次确认密码",
+  "@confirmPasswordRequired": {
+    "description": "修改密码时确认密码必填提示"
+  },
+  "confirmPasswordError": "两次密码不一致",
+  "@confirmPasswordError": {
+    "description": "修改密码时确认密码不一致提示"
+  },
+  "confirm": "确定",
+  "@confirm": {
+    "description": "确定按钮文本"
+  },
+  "cancel": "取消",
+  "@cancel": {
+    "description": "取消按钮文本"
   }
 }
 

+ 65 - 0
lib/module/common_input_field.dart

@@ -0,0 +1,65 @@
+import 'package:flutter/material.dart';
+
+class CommonInputField extends StatelessWidget {
+  final String label;
+  final bool required;
+  final String value;
+  final ValueChanged<String> onChanged;
+  final ValueChanged<String>? onFieldSubmitted; // 回车键事件
+  final TextInputType keyboardType;
+  final bool obscureText;
+  final Widget? suffix;
+  final String? hintText;
+
+  const CommonInputField({
+    super.key,
+    required this.label,
+    this.required = false,
+    required this.value,
+    required this.onChanged,
+    this.onFieldSubmitted,
+    this.keyboardType = TextInputType.text,
+    this.obscureText = false,
+    this.suffix,
+    this.hintText
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        RichText(
+            text: TextSpan(
+              text: label,
+              style: const TextStyle(color: Colors.black87, fontSize: 14),
+              children: [
+                if (required)
+                  const TextSpan(
+                    text: '*',
+                    style: TextStyle(color: Colors.red),
+                  ),
+              ]
+            )
+        ),
+        const SizedBox(height: 8),
+        TextFormField(
+          initialValue: value,
+          onChanged: onChanged,
+          onFieldSubmitted: onFieldSubmitted,
+          keyboardType: keyboardType,
+          obscureText: obscureText,
+          decoration: InputDecoration(
+            hintText: hintText,
+            hintStyle: TextStyle(color: Colors.grey.shade400),
+            suffixIcon: suffix,
+            contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
+            border: OutlineInputBorder(
+              borderRadius: BorderRadius.circular(6),
+            ),
+          ),
+        )
+      ],
+    );
+  }
+}