346 lines
12 KiB
Dart
346 lines
12 KiB
Dart
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,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|