Files
chat/client/flutter/lib/pages/register_page.dart
2026-04-25 16:36:34 +08:00

334 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
),
),
],
),
],
),
),
),
),
),
);
}
}