Files
chat/client/flutter/lib/pages/register_page.dart

334 lines
14 KiB
Dart
Raw Normal View History

2026-04-25 16:36:34 +08:00
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sales_chat/providers/auth_provider.dart';
import 'package:sales_chat/services/api_service.dart';
import 'package:sales_chat/theme/app_theme.dart';
/// 注册页面 —— 微信风格,简约现代
class RegisterPage extends StatefulWidget {
const RegisterPage({super.key});
@override
State<RegisterPage> createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController();
final _emailController = TextEditingController();
final _nicknameController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
final _inviteCodeController = TextEditingController();
bool _obscurePassword = true;
bool _obscureConfirmPassword = true;
@override
void dispose() {
_usernameController.dispose();
_emailController.dispose();
_nicknameController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
_inviteCodeController.dispose();
super.dispose();
}
/// 处理注册请求
Future<void> _handleRegister() async {
if (!_formKey.currentState!.validate()) return;
final authProvider = context.read<AuthProvider>();
final apiService = context.read<ApiService>();
final inviteCode = _inviteCodeController.text.trim();
final success = await authProvider.register(
username: _usernameController.text.trim(),
email: _emailController.text.trim(),
nickname: _nicknameController.text.trim().isNotEmpty
? _nicknameController.text.trim()
: null,
password: _passwordController.text,
);
if (success && mounted) {
// 如果有邀请码,注册后自动加入群组
if (inviteCode.isNotEmpty) {
try {
await apiService.joinInvite(inviteCode);
} catch (e) {
// 加入群组失败不影响注册流程,静默处理
debugPrint('邀请码加入失败: $e');
}
}
Navigator.of(context).pushReplacementNamed('/home');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.scaffoldBackground,
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
children: [
// 顶部标题区域
const SizedBox(height: 32),
Text(
'注册',
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
fontSize: 28,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 28),
// 白色卡片容器 —— 表单区域
Container(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 24,
),
decoration: BoxDecoration(
color: AppTheme.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 用户名输入框
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(
labelText: '用户名',
hintText: '3-20位字母/数字/下划线',
prefixIcon: Icon(Icons.person_outline),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入用户名';
}
if (!RegExp(r'^[a-zA-Z0-9_]{3,20}$').hasMatch(value)) {
return '用户名3-20位字母/数字/下划线';
}
return null;
},
),
const SizedBox(height: 14),
// 邮箱输入框
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: '邮箱',
hintText: '请输入邮箱',
prefixIcon: Icon(Icons.email_outlined),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入邮箱';
}
if (!value.contains('@')) {
return '请输入有效的邮箱地址';
}
return null;
},
),
const SizedBox(height: 14),
// 昵称输入框(可选)
TextFormField(
controller: _nicknameController,
decoration: const InputDecoration(
labelText: '昵称(可选)',
hintText: '显示名称',
prefixIcon: Icon(Icons.badge_outlined),
),
),
const SizedBox(height: 14),
// 密码输入框
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: '密码',
hintText: '至少6个字符',
prefixIcon: const Icon(Icons.lock_outlined),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility_off
: Icons.visibility,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (value.length < 6) {
return '密码至少需要6个字符';
}
return null;
},
),
const SizedBox(height: 14),
// 确认密码输入框
TextFormField(
controller: _confirmPasswordController,
obscureText: _obscureConfirmPassword,
decoration: InputDecoration(
labelText: '确认密码',
hintText: '再次输入密码',
prefixIcon: const Icon(Icons.lock_outlined),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword
? Icons.visibility_off
: Icons.visibility,
),
onPressed: () {
setState(() {
_obscureConfirmPassword =
!_obscureConfirmPassword;
});
},
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请确认密码';
}
if (value != _passwordController.text) {
return '两次密码不一致';
}
return null;
},
),
const SizedBox(height: 14),
// 邀请码输入框(可选)
TextFormField(
controller: _inviteCodeController,
decoration: const InputDecoration(
labelText: '邀请码(可选)',
hintText: '输入邀请码加入群组',
prefixIcon: Icon(Icons.card_giftcard_outlined),
),
),
const SizedBox(height: 24),
// 注册按钮
Consumer<AuthProvider>(
builder: (context, auth, _) {
return ElevatedButton(
onPressed:
auth.isLoading ? null : _handleRegister,
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryColor,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: auth.isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white),
),
)
: const Text(
'注册',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
);
},
),
],
),
),
),
// 错误信息 —— 卡片下方,红色提示文字
Consumer<AuthProvider>(
builder: (context, auth, _) {
if (auth.error != null) {
return Padding(
padding: const EdgeInsets.only(top: 12),
child: Text(
auth.error!,
style: TextStyle(
color: AppTheme.errorColor,
fontSize: 13,
),
textAlign: TextAlign.center,
),
);
}
return const SizedBox.shrink();
},
),
const SizedBox(height: 20),
// 返回登录链接
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'已有账号?',
style: TextStyle(
color: AppTheme.textSecondary,
fontSize: 14,
),
),
TextButton(
onPressed: () {
Navigator.of(context).pushReplacementNamed('/login');
},
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: const Text(
'登录',
style: TextStyle(fontSize: 14),
),
),
],
),
],
),
),
),
),
),
);
}
}