QinGang_interested/lib/pages/mine/forgot_pwd_page.dart

346 lines
12 KiB
Dart
Raw Normal View History

2025-12-12 09:11:30 +08:00
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<ForgotPwdPage> createState() => _ForgotPwdPageState();
}
class _ForgotPwdPageState extends State<ForgotPwdPage> {
final _formKey = GlobalKey<FormState>();
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<void> _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<void> _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<dynamic> route) => false,
);
} else {
ToastUtil.showNormal(context, res?['message'] ?? '重置密码失败');
}
} catch (e) {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, '重置密码失败: $e');
}
}
Future<void> _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,
),
),
],
),
),
),
),
);
}
}