329 lines
11 KiB
Dart
329 lines
11 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/tools/tools.dart';
|
||
import '../../http/ApiService.dart'; // 假设你的 API 在这里
|
||
|
||
class RegisterPage extends StatefulWidget {
|
||
const RegisterPage({super.key});
|
||
|
||
@override
|
||
State<RegisterPage> createState() => _RegisterPageState();
|
||
}
|
||
|
||
class _RegisterPageState extends State<RegisterPage> {
|
||
final _formKey = GlobalKey<FormState>();
|
||
|
||
final TextEditingController _phoneController = TextEditingController();
|
||
final TextEditingController _codeController = TextEditingController();
|
||
final TextEditingController _pwdController = TextEditingController();
|
||
final TextEditingController _confirmPwdController = TextEditingController();
|
||
|
||
bool _obscurePwd = true;
|
||
bool _obscureConfirm = true;
|
||
|
||
// 验证码发送状态和倒计时
|
||
bool _isSendingCode = false;
|
||
int _secondsLeft = 0;
|
||
Timer? _timer;
|
||
|
||
String textString =
|
||
"*密码长度8-18位,必须包含大小写字母+数字+特殊字母,例如:Qa@123456";
|
||
|
||
@override
|
||
void dispose() {
|
||
_timer?.cancel();
|
||
_phoneController.dispose();
|
||
_codeController.dispose();
|
||
_pwdController.dispose();
|
||
_confirmPwdController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
// 验证密码复杂度
|
||
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);
|
||
}
|
||
|
||
// 手机号简单校验(11 位数字)
|
||
bool _isPhoneValid(String phone) {
|
||
final RegExp phoneReg = RegExp(r'^\d{11}$');
|
||
return phoneReg.hasMatch(phone);
|
||
}
|
||
|
||
void _startCountdown(int seconds) {
|
||
_timer?.cancel();
|
||
setState(() {
|
||
_secondsLeft = seconds;
|
||
});
|
||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||
if (!mounted) return;
|
||
setState(() {
|
||
_secondsLeft--;
|
||
if (_secondsLeft <= 0) {
|
||
_timer?.cancel();
|
||
_secondsLeft = 0;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
Future<void> _sendCode() async {
|
||
final phone = _phoneController.text.trim();
|
||
if (phone.isEmpty) {
|
||
ToastUtil.showNormal(context, '请输入手机号');
|
||
return;
|
||
}
|
||
if (!_isPhoneValid(phone)) {
|
||
ToastUtil.showNormal(context, '请输入有效的手机号(11位)');
|
||
return;
|
||
}
|
||
|
||
if (_isSendingCode || _secondsLeft > 0) return;
|
||
|
||
setState(() {
|
||
_isSendingCode = true;
|
||
});
|
||
|
||
LoadingDialogHelper.show();
|
||
try {
|
||
final resp = await BasicInfoApi.sendRegisterSms({'phone': phone});
|
||
LoadingDialogHelper.hide();
|
||
|
||
if (resp != null && resp['success'] == true) {
|
||
ToastUtil.showNormal(context, '验证码已发送');
|
||
_startCountdown(60);
|
||
} else {
|
||
ToastUtil.showNormal(context, resp?['message'] ?? '发送验证码失败');
|
||
}
|
||
} catch (e) {
|
||
LoadingDialogHelper.hide();
|
||
ToastUtil.showNormal(context, '发送验证码失败,请稍后重试');
|
||
} finally {
|
||
setState(() {
|
||
_isSendingCode = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
Future<void> _handleRegister() async {
|
||
if (!(_formKey.currentState?.validate() ?? false)) return;
|
||
|
||
final phone = _phoneController.text.trim();
|
||
final code = _codeController.text.trim();
|
||
final pwd = _pwdController.text.trim();
|
||
final confirm = _confirmPwdController.text.trim();
|
||
|
||
if (!_isPhoneValid(phone)) {
|
||
ToastUtil.showNormal(context, '请输入有效的手机号(11位)');
|
||
return;
|
||
}
|
||
if (code.isEmpty) {
|
||
ToastUtil.showNormal(context, '请输入验证码');
|
||
return;
|
||
}
|
||
if (pwd.isEmpty) {
|
||
ToastUtil.showNormal(context, '请输入密码');
|
||
return;
|
||
}
|
||
if (confirm.isEmpty) {
|
||
ToastUtil.showNormal(context, '请确认密码');
|
||
return;
|
||
}
|
||
if (pwd != confirm) {
|
||
ToastUtil.showNormal(context, '两次输入的密码不一致');
|
||
return;
|
||
}
|
||
if (pwd.length < 8) {
|
||
ToastUtil.showNormal(context, '密码长度需至少8位');
|
||
return;
|
||
}
|
||
if (pwd.length > 32) {
|
||
ToastUtil.showNormal(context, '密码长度需小于32位');
|
||
return;
|
||
}
|
||
if (!isPasswordValid(pwd)) {
|
||
ToastUtil.showNormal(context, '密码必须包含大小写字母、数字和特殊符号');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
final data = {
|
||
'phone': phone,
|
||
'phoneCode': code,
|
||
'newPassword': pwd,
|
||
'confirmPassword': pwd,
|
||
};
|
||
LoadingDialogHelper.show();
|
||
final resp = await BasicInfoApi.register(data);
|
||
LoadingDialogHelper.hide();
|
||
|
||
if (resp != null && resp['success'] == true) {
|
||
ToastUtil.showNormal(context, '注册成功,请登录');
|
||
// 跳转到登录页并移除当前页面栈
|
||
Navigator.pushAndRemoveUntil(
|
||
context,
|
||
MaterialPageRoute(builder: (context) => const LoginPage()),
|
||
(route) => false,
|
||
);
|
||
} else {
|
||
ToastUtil.showNormal(context, resp?['message'] ?? '注册失败,请重试');
|
||
}
|
||
} catch (e) {
|
||
LoadingDialogHelper.hide();
|
||
ToastUtil.showNormal(context, '注册失败,请稍后重试');
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: MyAppbar(title: '注册账号'),
|
||
backgroundColor: Colors.white,
|
||
body: SafeArea(
|
||
child: SingleChildScrollView(
|
||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
|
||
child: Form(
|
||
key: _formKey,
|
||
child: Column(
|
||
children: [
|
||
const SizedBox(height: 8),
|
||
|
||
// 手机号(使用 CustomInput)
|
||
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: 16),
|
||
|
||
// 验证码 + 发送按钮 行(验证码输入使用 CustomInput)
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
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: (_secondsLeft > 0 || _isSendingCode) ? null : _sendCode,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: (_secondsLeft > 0) ? Colors.grey.shade400 : const Color(0xFF2A75F8),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||
),
|
||
child: Text(
|
||
_secondsLeft > 0 ? '$_secondsLeft s后可重发' : '发送验证码',
|
||
style: const TextStyle(fontSize: 14, color: Colors.white),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
)
|
||
],
|
||
),
|
||
|
||
const SizedBox(height: 16),
|
||
|
||
// 密码
|
||
CustomInput.buildInput(
|
||
_pwdController,
|
||
title: '密码',
|
||
hint: '请输入密码',
|
||
obscure: _obscurePwd,
|
||
suffix: IconButton(
|
||
icon: Icon(_obscurePwd ? Icons.visibility_off : Icons.visibility, color: Colors.grey),
|
||
onPressed: () => setState(() => _obscurePwd = !_obscurePwd),
|
||
),
|
||
validator: (v) {
|
||
if (v == null || v.isEmpty) return '请输入密码';
|
||
if (v.length < 8) return '密码长度至少 8 位';
|
||
return null;
|
||
},
|
||
),
|
||
|
||
const SizedBox(height: 16),
|
||
|
||
// 确认密码
|
||
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: 12),
|
||
|
||
// 密码提示语
|
||
Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
textString,
|
||
style: const TextStyle(color: Colors.red, fontSize: 13),
|
||
),
|
||
),
|
||
|
||
const SizedBox(height: 30),
|
||
|
||
// 注册确认按钮
|
||
SizedBox(
|
||
width: double.infinity,
|
||
height: 45,
|
||
child: CustomButton(
|
||
onPressed: _handleRegister,
|
||
text: '确认',
|
||
backgroundColor: Colors.blue,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|