rwadurian/frontend/mining-app/lib/presentation/pages/auth/login_page.dart

313 lines
11 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 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/router/routes.dart';
import '../../../core/constants/app_colors.dart';
import '../../../core/error/exceptions.dart';
import '../../providers/user_providers.dart';
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@override
ConsumerState<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginPage> {
// 设计色彩 - 与导航页面统一
static const Color _orange = Color(0xFFFF6B00);
final _formKey = GlobalKey<FormState>();
final _phoneController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
@override
void dispose() {
_phoneController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _login() async {
if (!_formKey.currentState!.validate()) return;
final phone = _phoneController.text.trim();
try {
// 仅支持密码登录
await ref.read(userNotifierProvider.notifier).loginWithPassword(
phone,
_passwordController.text,
);
if (mounted) {
context.go(Routes.contribution);
}
} catch (e) {
if (!mounted) return;
final message = e is UnauthorizedException
? e.message
: e is ForbiddenException
? e.message
: e is NetworkException
? e.message
: e.toString().replaceFirst(RegExp(r'^[A-Za-z]+Exception:\s*'), '');
ScaffoldMessenger.of(context).clearSnackBars();
if (message.contains('锁定')) {
// 账户已锁定 → 弹窗 + 找回密码5秒后自动关闭
showDialog<void>(
context: context,
builder: (ctx) {
Timer(const Duration(seconds: 5), () {
if (ctx.mounted) Navigator.of(ctx, rootNavigator: true).maybePop();
});
return AlertDialog(
title: const Text('账户已锁定'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: Text('知道了', style: TextStyle(color: AppColors.textSecondaryOf(context))),
),
ElevatedButton(
onPressed: () {
Navigator.pop(ctx);
context.push(Routes.forgotPassword, extra: _phoneController.text.trim());
},
style: ElevatedButton.styleFrom(backgroundColor: _orange),
child: const Text('找回密码', style: TextStyle(color: Colors.white)),
),
],
);
},
);
} else if (message.contains('还剩')) {
// 密码错误但还有机会 → SnackBar 提醒 + 找回密码入口4秒后强制关闭
final messenger = ScaffoldMessenger.of(context);
messenger.showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.orange.shade800,
duration: const Duration(seconds: 4),
action: SnackBarAction(
label: '找回密码',
textColor: Colors.white,
onPressed: () => context.push(Routes.forgotPassword, extra: _phoneController.text.trim()),
),
),
);
Future.delayed(const Duration(seconds: 4), () {
messenger.hideCurrentSnackBar();
});
} else {
final messenger = ScaffoldMessenger.of(context);
messenger.showSnackBar(SnackBar(content: Text(message)));
Future.delayed(const Duration(seconds: 3), () {
messenger.hideCurrentSnackBar();
});
}
}
}
@override
Widget build(BuildContext context) {
final userState = ref.watch(userNotifierProvider);
final isDark = AppColors.isDark(context);
return Scaffold(
backgroundColor: AppColors.backgroundOf(context),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 60),
// Logo
Center(
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: _orange.withOpacity(isDark ? 0.2 : 0.1),
borderRadius: BorderRadius.circular(20),
),
child: const Icon(
Icons.eco,
size: 48,
color: _orange,
),
),
),
const SizedBox(height: 24),
Text(
'欢迎回来',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColors.textPrimaryOf(context),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'登录您的股行账户',
style: TextStyle(
fontSize: 16,
color: AppColors.textSecondaryOf(context),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 48),
// 仅显示密码登录(验证码登录已隐藏)
const SizedBox(height: 24),
// 手机号
TextFormField(
controller: _phoneController,
keyboardType: TextInputType.phone,
style: TextStyle(color: AppColors.textPrimaryOf(context)),
decoration: InputDecoration(
labelText: '手机号',
labelStyle: TextStyle(color: AppColors.textSecondaryOf(context)),
prefixIcon: Icon(Icons.phone_outlined, color: AppColors.textSecondaryOf(context)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.borderOf(context)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.borderOf(context)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
filled: true,
fillColor: AppColors.cardOf(context),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入手机号';
}
if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(value)) {
return '请输入正确的手机号';
}
return null;
},
),
const SizedBox(height: 16),
// 密码输入
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
style: TextStyle(color: AppColors.textPrimaryOf(context)),
decoration: InputDecoration(
labelText: '密码',
labelStyle: TextStyle(color: AppColors.textSecondaryOf(context)),
prefixIcon: Icon(Icons.lock_outline, color: AppColors.textSecondaryOf(context)),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off_outlined : Icons.visibility_outlined,
color: AppColors.textSecondaryOf(context),
),
onPressed: () => setState(() => _obscurePassword = !_obscurePassword),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.borderOf(context)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.borderOf(context)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
filled: true,
fillColor: AppColors.cardOf(context),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
return null;
},
),
const SizedBox(height: 32),
// 登录按钮
SizedBox(
height: 52,
child: ElevatedButton(
onPressed: userState.isLoading ? null : _login,
style: ElevatedButton.styleFrom(
backgroundColor: _orange,
disabledBackgroundColor: _orange.withOpacity(0.5),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: userState.isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(
'登录',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(height: 16),
// 忘记密码
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => context.push(Routes.forgotPassword, extra: _phoneController.text.trim()),
child: Text(
'忘记密码?',
style: TextStyle(color: AppColors.textSecondaryOf(context)),
),
),
),
// 注册入口已隐藏
],
),
),
),
),
);
}
}