import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/pages/user/CustomInput.dart'; import 'package:qhd_prevention/pages/user/login_page.dart'; import 'package:qhd_prevention/services/SessionService.dart'; import 'package:qhd_prevention/tools/tools.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../http/ApiService.dart'; import 'package:qhd_prevention/services/auth_service.dart'; // 如果你 APIs 在别处,请替换/调整 class ForgotPwdPage extends StatefulWidget { const ForgotPwdPage(this.type, {super.key}); final String type; @override State createState() => _ForgotPwdPageState(); } class _ForgotPwdPageState extends State { final _formKey = GlobalKey(); final _phoneController = TextEditingController(); final _codeController = TextEditingController(); final _newPwdController = TextEditingController(); final _confirmPwdController = TextEditingController(); bool _obscureNew = true; bool _obscureConfirm = true; String textString = "为了您的账户安全,请确保密码长度为 8-18 位,必须包含大小写字母+数字+特殊字符,例如:Aa@123456"; // 验证码倒计时 Timer? _timer; int _secondsLeft = 0; bool _isSending = false; @override void initState() { super.initState(); // 根据 type 设置提示文案(保持原逻辑) switch (widget.type) { case "0": textString = "密码长度8-18位,需包含数字、字母、英文符号至少2种或以上元素"; break; case "1": textString = "检测到您的密码为弱密码,请修改密码后重新登录。为了您的账户安全,请确保密码长度为 8-18 位,必须包含大小写字母+数字+特殊字符,例如:Aa@123456"; break; case "2": textString = "检测到您30天内未修改密码,请修改密码后重新登录。为了您的账户安全,请确保密码长度为 8-18 位,必须包含大小写字母+数字+特殊字符,例如:Aa@123456"; break; case "3": textString = "检测到您的密码为弱密码,请修改密码后重新登录。为了您的账户安全,请确保密码长度为 8-18 位,必须包含大小写字母+数字+特殊字符,例如:Aa@123456"; break; case "4": textString = "检测到您30天内未修改密码,请修改密码后重新登录。为了您的账户安全,请确保密码长度为 8-18 位,必须包含大小写字母+数字+特殊字符,例如:Aa@123456"; break; } // 可选:预填手机号(如果 session 中有) _phoneController.text = SessionService.instance.loginPhone ?? ''; } @override void dispose() { _timer?.cancel(); _phoneController.dispose(); _codeController.dispose(); _newPwdController.dispose(); _confirmPwdController.dispose(); super.dispose(); } bool get _canSend => _secondsLeft == 0 && !_isSending; String get _sendText => _secondsLeft > 0 ? '$_secondsLeft s后可重发' : '发送验证码'; void _startCountdown(int seconds) { _timer?.cancel(); setState(() { _secondsLeft = seconds; }); _timer = Timer.periodic(const Duration(seconds: 1), (t) { if (!mounted) return; setState(() { _secondsLeft--; if (_secondsLeft <= 0) { _timer?.cancel(); _secondsLeft = 0; } }); }); } // 手机号简单校验(11位) bool _isPhoneValid(String phone) { final RegExp phoneReg = RegExp(r'^\d{11}$'); return phoneReg.hasMatch(phone); } // 密码复杂度校验 bool isPasswordValid(String password) { final hasUpperCase = RegExp(r'[A-Z]'); final hasLowerCase = RegExp(r'[a-z]'); final hasNumber = RegExp(r'[0-9]'); final hasSpecialChar = RegExp(r'[!@#\$%\^&\*\(\)_\+\-=\[\]\{\};:"\\|,.<>\/\?~`]'); return hasUpperCase.hasMatch(password) && hasLowerCase.hasMatch(password) && hasNumber.hasMatch(password) && hasSpecialChar.hasMatch(password); } Future _sendVerificationCode() async { final phone = _phoneController.text.trim(); if (phone.isEmpty) { ToastUtil.showNormal(context, '请输入手机号'); return; } if (!_isPhoneValid(phone)) { ToastUtil.showNormal(context, '请输入有效手机号(11位)'); return; } if (!_canSend) return; setState(() => _isSending = true); LoadingDialogHelper.show(); try { final res = await BasicInfoApi.sendRegisterSms({'phone': phone}); LoadingDialogHelper.hide(); if (res['success'] == true) { ToastUtil.showNormal(context, '验证码已发送'); _startCountdown(60); } else { ToastUtil.showNormal(context, res?['message'] ?? '发送验证码失败'); } } catch (e) { LoadingDialogHelper.hide(); ToastUtil.showNormal(context, '发送验证码失败: $e'); } finally { if (mounted) setState(() => _isSending = false); } } Future _handleSubmit() async { if (!(_formKey.currentState?.validate() ?? false)) return; final phone = _phoneController.text.trim(); final code = _codeController.text.trim(); final newPwd = _newPwdController.text.trim(); final confirm = _confirmPwdController.text.trim(); if (phone.isEmpty || code.isEmpty || newPwd.isEmpty || confirm.isEmpty) { ToastUtil.showNormal(context, '请完整填写表单'); return; } if (!_isPhoneValid(phone)) { ToastUtil.showNormal(context, '请输入有效手机号(11位)'); return; } if (newPwd != confirm) { ToastUtil.showNormal(context, '两次输入的密码不一致'); return; } if (newPwd.length < 8) { ToastUtil.showNormal(context, '新密码需要大于8位'); return; } if (newPwd.length > 32) { ToastUtil.showNormal(context, '新密码需要小于32位'); return; } if (!isPasswordValid(newPwd)) { ToastUtil.showNormal(context, '新密码必须包含大小写字母、数字和特殊符号。'); return; } LoadingDialogHelper.show(); try { final res = await AuthApi.passwordRecover({'phone': phone, 'phoneCode': code, 'newPassword': newPwd, 'confirmPassword': confirm}); LoadingDialogHelper.hide(); if (res != null && (res['success'] == true || res['code'] == 200)) { ToastUtil.showNormal(context, '密码重置成功,请使用新密码登录'); await _clearUserSession(); // 跳转到登录页并清除历史 Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => const LoginPage()), (Route route) => false, ); } else { ToastUtil.showNormal(context, res?['message'] ?? '重置密码失败'); } } catch (e) { LoadingDialogHelper.hide(); ToastUtil.showNormal(context, '重置密码失败: $e'); } } Future _clearUserSession() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove('isLoggedIn'); // 如果你有 token 等需要清除,也在这里移除 } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: const MyAppbar(title: '密码找回'), body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), // 手机号 CustomInput.buildInput( _phoneController, title: '手机号', hint: '请输入手机号', keyboardType: TextInputType.phone, validator: (v) { if (v == null || v.isEmpty) return '请输入手机号'; if (!_isPhoneValid(v.trim())) return '请输入有效手机号'; return null; }, ), const SizedBox(height: 12), // 验证码行 Row( children: [ Expanded( child: CustomInput.buildInput( _codeController, title: '验证码', hint: '请输入验证码', keyboardType: TextInputType.number, validator: (v) { if (v == null || v.isEmpty) return '请输入验证码'; return null; }, ), ), const SizedBox(width: 12), Column( children: [ const SizedBox(height: 40,), SizedBox( height: 40, child: ElevatedButton( onPressed: _canSend ? _sendVerificationCode : null, style: ElevatedButton.styleFrom( backgroundColor: _canSend ? const Color(0xFF2A75F8) : Colors.grey, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 12), ), child: Text(_sendText, style: const TextStyle(color: Colors.white)), ), ), ], ) ], ), const SizedBox(height: 12), // 新密码 CustomInput.buildInput( _newPwdController, title: '新密码', hint: '请输入新密码', obscure: _obscureNew, suffix: IconButton( icon: Icon(_obscureNew ? Icons.visibility_off : Icons.visibility, color: Colors.grey), onPressed: () => setState(() => _obscureNew = !_obscureNew), ), validator: (v) { if (v == null || v.isEmpty) return '请输入新密码'; if (v.length < 8) return '密码长度至少8位'; return null; }, ), const SizedBox(height: 12), // 确认密码 CustomInput.buildInput( _confirmPwdController, title: '确认新密码', hint: '请再次输入新密码', obscure: _obscureConfirm, suffix: IconButton( icon: Icon(_obscureConfirm ? Icons.visibility_off : Icons.visibility, color: Colors.grey), onPressed: () => setState(() => _obscureConfirm = !_obscureConfirm), ), validator: (v) { if (v == null || v.isEmpty) return '请确认新密码'; return null; }, ), const SizedBox(height: 16), Text(textString, style: const TextStyle(color: Colors.red, fontSize: 13)), const SizedBox(height: 24), SizedBox( width: double.infinity, height: 46, child: CustomButton( onPressed: _handleSubmit, text: "提交", backgroundColor: const Color(0xFF2A75F8), borderRadius: 8, ), ), ], ), ), ), ), ); } }