QinGang_interested/lib/pages/mine/forgot_pwd_page.dart

346 lines
12 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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,
),
),
],
),
),
),
),
);
}
}