407 lines
13 KiB
Dart
407 lines
13 KiB
Dart
import 'dart:async';
|
||
import 'package:elysia/plugin/HTTP.dart';
|
||
import 'package:elysia/plugin/LoadingOverlay.dart';
|
||
import 'package:elysia/plugin/Toast.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:image_picker/image_picker.dart';
|
||
import 'dart:io';
|
||
import 'package:oktoast/oktoast.dart';
|
||
|
||
import '../plugin/AvatarPicker.dart';
|
||
import '../plugin/C.dart';
|
||
|
||
class RegistrationPage extends StatefulWidget {
|
||
@override
|
||
_RegistrationPageState createState() => _RegistrationPageState();
|
||
}
|
||
|
||
class _RegistrationPageState extends State<RegistrationPage> {
|
||
final _formKey = GlobalKey<FormState>();
|
||
|
||
// 表单字段控制器
|
||
TextEditingController _nameController = TextEditingController();
|
||
TextEditingController _nicknameController = TextEditingController();
|
||
TextEditingController _usernameController = TextEditingController();
|
||
TextEditingController _passwordController = TextEditingController();
|
||
TextEditingController _emailController = TextEditingController();
|
||
TextEditingController _seepseekAPIController = TextEditingController();
|
||
TextEditingController _verificationCodeController = TextEditingController();
|
||
|
||
// 头像相关
|
||
File? _avatarImage;
|
||
bool _isPasswordVisible = false;
|
||
|
||
// 验证码倒计时
|
||
int _countdown = 0;
|
||
Timer? _timer;
|
||
|
||
@override
|
||
void dispose() {
|
||
_timer?.cancel();
|
||
super.dispose();
|
||
}
|
||
|
||
// 发送验证码
|
||
Future<void> _sendVerificationCode() async {
|
||
if (_emailController.text.isEmpty) {
|
||
Toast.show('请输入邮箱地址',type: ToastType.Error,position: ToastPosition.center);
|
||
return;
|
||
}
|
||
if (!RegExp(
|
||
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'
|
||
).hasMatch(_emailController.text)) {
|
||
Toast.show('邮箱格式错误',type: ToastType.Error,position: ToastPosition.center);
|
||
return;
|
||
}
|
||
LoadingOverlay.show(context: context);
|
||
try{
|
||
dynamic result = await HTTP.create("${C.BASE_URL}/authorization/register-mail").setParam({"mail":_emailController.text}).execute();
|
||
LoadingOverlay.hide();
|
||
if(result["code"]!=200){
|
||
Toast.show(result["message"],type: ToastType.Error,position: ToastPosition.center);
|
||
}else{
|
||
setState(() {
|
||
_countdown = 60*5;
|
||
});
|
||
// 模拟发送验证码
|
||
Toast.show('验证码已发送至 ${_emailController.text}',type: ToastType.Normal,position: ToastPosition.center);
|
||
// 开始倒计时
|
||
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||
setState(() {
|
||
if (_countdown > 0) {
|
||
_countdown--;
|
||
} else {
|
||
_timer?.cancel();
|
||
}
|
||
});
|
||
});
|
||
|
||
}
|
||
}catch(e){
|
||
LoadingOverlay.hide();
|
||
}
|
||
}
|
||
|
||
// 表单验证(不显示错误文本,使用Toast)
|
||
bool _validateForm() {
|
||
// 用户名验证
|
||
if(_avatarImage==null){
|
||
Toast.show('请选择头像',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
if (_usernameController.text.isEmpty) {
|
||
Toast.show('请输入用户名',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
if (_usernameController.text.length < 4) {
|
||
Toast.show('用户名至少4个字符',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
|
||
// 昵称验证
|
||
if (_nicknameController.text.isEmpty) {
|
||
Toast.show('请输入昵称',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
|
||
|
||
// 密码验证
|
||
if (_passwordController.text.isEmpty) {
|
||
Toast.show('请输入密码',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
if (_passwordController.text.length < 6) {
|
||
Toast.show('密码至少6个字符',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
|
||
// API密钥验证
|
||
if (_seepseekAPIController.text.isEmpty) {
|
||
Toast.show('请输入Deepseek API Key',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
|
||
// 邮箱验证
|
||
if (_emailController.text.isEmpty) {
|
||
Toast.show('请输入邮箱地址',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
if (!RegExp(
|
||
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'
|
||
).hasMatch(_emailController.text)) {
|
||
Toast.show('请输入有效的邮箱地址',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
|
||
// 验证码验证
|
||
if (_verificationCodeController.text.isEmpty) {
|
||
Toast.show('请输入验证码',type: ToastType.Error,position: ToastPosition.center);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// 提交表单
|
||
Future<void> _submitForm() async {
|
||
if (!_validateForm()){
|
||
return;
|
||
}
|
||
LoadingOverlay.show(
|
||
context: context,
|
||
barrierColor: Colors.black54,
|
||
);
|
||
try{
|
||
dynamic result = await HTTP
|
||
.create("${C.BASE_URL}/authorization/register")
|
||
.setHeader(C.TOKEN)
|
||
.setParam({
|
||
"avatar": _avatarImage,
|
||
"name": _nameController.text,
|
||
"nickname": _nicknameController.text,
|
||
"userName": _usernameController.text,
|
||
"password": _passwordController.text,
|
||
"email": _emailController.text,
|
||
"deepseekKey": _seepseekAPIController.text,
|
||
"emailCode": _verificationCodeController.text
|
||
})
|
||
.setRequestType(RequestType.POST)
|
||
.setContentType(ContentType("application", "multipart/form-data"))
|
||
.execute();
|
||
LoadingOverlay.hide();
|
||
|
||
if (result["code"] != 200) {
|
||
showToast(result["message"]);
|
||
} else {
|
||
Navigator.pop(context);
|
||
}
|
||
}catch(e){
|
||
showToast(e.toString());
|
||
LoadingOverlay.hide();
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: Text("注册", style: const TextStyle(color: Colors.black)),
|
||
centerTitle: true,
|
||
backgroundColor: Colors.grey[100],
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||
onPressed: () => Navigator.pop(context),
|
||
),
|
||
elevation: 0,
|
||
),
|
||
body: SingleChildScrollView(
|
||
padding: EdgeInsets.all(20),
|
||
child: Form(
|
||
key: _formKey,
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.center,
|
||
children: [
|
||
// 头像选择区域
|
||
SizedBox(
|
||
width: 90,
|
||
height: 90,
|
||
child: AvatarPicker(
|
||
onImageSelected: (file) {
|
||
setState(() {
|
||
_avatarImage = file;
|
||
});
|
||
},
|
||
),
|
||
),
|
||
|
||
SizedBox(height: 15),
|
||
// 用户名
|
||
_buildTextField(
|
||
controller: _usernameController,
|
||
required: true,
|
||
label: '用户名',
|
||
hintText: '请输入用户名',
|
||
prefixIcon: Icons.account_circle,
|
||
),
|
||
SizedBox(height: 30),
|
||
|
||
// 昵称
|
||
_buildTextField(
|
||
controller: _nicknameController,
|
||
required: true,
|
||
label: '昵称',
|
||
hintText: '请输入您的昵称',
|
||
prefixIcon: Icons.face,
|
||
),
|
||
SizedBox(height: 15),
|
||
|
||
// 姓名
|
||
_buildTextField(
|
||
controller: _nameController,
|
||
label: '姓名',
|
||
hintText: '请输入您的真实姓名',
|
||
prefixIcon: Icons.person,
|
||
),
|
||
SizedBox(height: 15),
|
||
|
||
// 密码
|
||
_buildTextField(
|
||
controller: _passwordController,
|
||
required: true,
|
||
label: '密码',
|
||
hintText: '请输入密码',
|
||
prefixIcon: Icons.lock,
|
||
obscureText: !_isPasswordVisible,
|
||
suffixIcon: IconButton(
|
||
icon: Icon(
|
||
_isPasswordVisible
|
||
? Icons.visibility
|
||
: Icons.visibility_off,
|
||
color: Colors.grey,
|
||
),
|
||
onPressed: () {
|
||
setState(() {
|
||
_isPasswordVisible = !_isPasswordVisible;
|
||
});
|
||
},
|
||
),
|
||
),
|
||
SizedBox(height: 15),
|
||
|
||
// API密钥
|
||
_buildTextField(
|
||
controller: _seepseekAPIController,
|
||
required: true,
|
||
label: 'Deepseek API Key',
|
||
hintText: '请输入Deepseek API Key',
|
||
prefixIcon: Icons.vpn_key,
|
||
),
|
||
SizedBox(height: 15),
|
||
|
||
// 邮箱
|
||
_buildTextField(
|
||
controller: _emailController,
|
||
label: '邮箱',
|
||
required: true,
|
||
hintText: '请输入邮箱地址',
|
||
prefixIcon: Icons.email,
|
||
keyboardType: TextInputType.emailAddress,
|
||
),
|
||
SizedBox(height: 15),
|
||
|
||
// 邮箱验证码
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
flex: 2,
|
||
child: _buildTextField(
|
||
controller: _verificationCodeController,
|
||
required: true,
|
||
label: '邮箱验证码',
|
||
hintText: '请输入验证码',
|
||
prefixIcon: Icons.verified_user,
|
||
),
|
||
),
|
||
SizedBox(width: 10),
|
||
Expanded(
|
||
flex: 1,
|
||
child: Container(
|
||
margin: EdgeInsets.only(top: 25),
|
||
child: ElevatedButton(
|
||
onPressed: _countdown > 0
|
||
? null
|
||
: _sendVerificationCode,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.blue[700],
|
||
foregroundColor: Colors.white,
|
||
padding: EdgeInsets.symmetric(vertical: 15),
|
||
),
|
||
child: Text(
|
||
_countdown > 0 ? '${(_countdown/60).floor()}分${_countdown%60}秒' : '获取验证码',
|
||
style: TextStyle(fontSize: 14),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(height: 30),
|
||
|
||
// 注册按钮
|
||
Container(
|
||
width: double.infinity,
|
||
height: 50,
|
||
child: ElevatedButton(
|
||
onPressed: _submitForm,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.blue[700],
|
||
foregroundColor: Colors.white,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(10),
|
||
),
|
||
),
|
||
child: Text(
|
||
'注册',
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// 简化后的文本输入框(移除validator参数)
|
||
Widget _buildTextField({
|
||
required TextEditingController controller,
|
||
required String label,
|
||
required String hintText,
|
||
required IconData prefixIcon,
|
||
Widget? suffixIcon,
|
||
bool obscureText = false,
|
||
bool required = false,
|
||
TextInputType keyboardType = TextInputType.text,
|
||
}) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(children: [
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.grey[700],
|
||
),
|
||
),SizedBox(width: 3,),
|
||
if(required)Text("*",style: TextStyle(color: Colors.red,fontSize: 16),)
|
||
],),
|
||
SizedBox(height: 5),
|
||
TextFormField(
|
||
controller: controller,
|
||
obscureText: obscureText,
|
||
keyboardType: keyboardType,
|
||
decoration: InputDecoration(
|
||
hintText: hintText,
|
||
prefixIcon: Icon(prefixIcon, color: Colors.blue[700]),
|
||
suffixIcon: suffixIcon,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(10),
|
||
borderSide: BorderSide(color: Colors.grey),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(10),
|
||
borderSide: BorderSide(color: Colors.blue, width: 2),
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 15),
|
||
),
|
||
// 移除validator参数,不显示错误文本
|
||
),
|
||
],
|
||
);
|
||
}
|
||
} |