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 { final _formKey = GlobalKey(); // 表单字段控制器 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 _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 _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参数,不显示错误文本 ), ], ); } }