import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/auth/auth_state.dart'; import '../../core/theme/app_theme.dart'; import '../../repositories/auth_repository.dart'; class LoginScreen extends ConsumerStatefulWidget { const LoginScreen({super.key}); @override ConsumerState createState() => _LoginScreenState(); } class _LoginScreenState extends ConsumerState { final _formKey = GlobalKey(); final _hotelCodeCtrl = TextEditingController(); final _usernameCtrl = TextEditingController(); final _passwordCtrl = TextEditingController(); bool _loading = false; bool _obscure = true; String? _errorMessage; @override void dispose() { _hotelCodeCtrl.dispose(); _usernameCtrl.dispose(); _passwordCtrl.dispose(); super.dispose(); } Future _login() async { if (!_formKey.currentState!.validate()) return; setState(() { _loading = true; _errorMessage = null; }); try { final user = await AuthRepository.login( hotelCode: _hotelCodeCtrl.text.trim(), username: _usernameCtrl.text.trim(), password: _passwordCtrl.text, ); await ref.read(authStateProvider.notifier).login(user); if (mounted) context.go('/stock-in'); } on AuthException catch (e) { setState(() => _errorMessage = e.message); } catch (e) { setState(() => _errorMessage = '登录失败:$e'); } finally { if (mounted) setState(() => _loading = false); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.primaryDark, body: Stack( children: [ // Background pattern Positioned.fill( child: CustomPaint(painter: _BackgroundPainter()), ), Center( child: Card( elevation: 12, shadowColor: Colors.black38, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8)), child: Container( width: 400, padding: const EdgeInsets.all(40), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ // Logo Container( width: 80, height: 80, decoration: BoxDecoration( gradient: const LinearGradient( colors: [AppTheme.primaryLight, AppTheme.primaryDark], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: AppTheme.primary.withAlpha(102), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: const Icon(Icons.wine_bar, color: Colors.white, size: 44), ), const SizedBox(height: 20), const Text( '酒库管理系统', style: TextStyle( fontSize: 24, fontWeight: FontWeight.w700, color: AppTheme.primaryDark, letterSpacing: 1, ), ), const SizedBox(height: 6), const Text( '酒店仓库管理解决方案', style: TextStyle( fontSize: 13, color: AppTheme.textSecondary), ), const SizedBox(height: 32), // Hotel code TextFormField( controller: _hotelCodeCtrl, decoration: const InputDecoration( labelText: '门店编号', hintText: '请输入门店编号', prefixIcon: Icon(Icons.store_outlined, size: 20), ), validator: (v) => (v == null || v.isEmpty) ? '请输入门店编号' : null, textInputAction: TextInputAction.next, ), const SizedBox(height: 14), // Username TextFormField( controller: _usernameCtrl, decoration: const InputDecoration( labelText: '用户名', hintText: '请输入用户名', prefixIcon: Icon(Icons.person_outline, size: 20), ), validator: (v) => (v == null || v.isEmpty) ? '请输入用户名' : null, textInputAction: TextInputAction.next, ), const SizedBox(height: 14), // Password TextFormField( controller: _passwordCtrl, obscureText: _obscure, decoration: InputDecoration( labelText: '密码', hintText: '请输入密码', prefixIcon: const Icon(Icons.lock_outline, size: 20), suffixIcon: IconButton( icon: Icon( _obscure ? Icons.visibility_off : Icons.visibility, size: 20), onPressed: () => setState(() => _obscure = !_obscure), ), ), validator: (v) => (v == null || v.isEmpty) ? '请输入密码' : null, onFieldSubmitted: (_) => _login(), ), // Inline error message if (_errorMessage != null) ...[ const SizedBox(height: 12), Container( width: double.infinity, padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10), decoration: BoxDecoration( color: AppTheme.danger.withAlpha(15), borderRadius: BorderRadius.circular(4), border: Border.all( color: AppTheme.danger.withAlpha(80)), ), child: Row( children: [ const Icon(Icons.error_outline, color: AppTheme.danger, size: 16), const SizedBox(width: 8), Expanded( child: Text( _errorMessage!, style: const TextStyle( color: AppTheme.danger, fontSize: 13), ), ), ], ), ), ], const SizedBox(height: 24), SizedBox( width: double.infinity, height: 46, child: ElevatedButton( onPressed: _loading ? null : _login, style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4)), ), child: _loading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white)) : const Text('登 录', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, letterSpacing: 2)), ), ), ], ), ), ), ), ), Positioned( bottom: 16, left: 0, right: 0, child: Center( child: Text( '© 2026 酒库管理系统 v1.0.0', style: TextStyle( color: Colors.white.withAlpha(102), fontSize: 12), ), ), ), ], ), ); } } class _BackgroundPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = const Color(0xFF1976D2).withAlpha(38) ..style = PaintingStyle.fill; canvas.drawCircle( Offset(size.width * 0.1, size.height * 0.2), 180, paint); canvas.drawCircle( Offset(size.width * 0.9, size.height * 0.8), 220, paint); paint.color = const Color(0xFF0D47A1).withAlpha(26); canvas.drawCircle( Offset(size.width * 0.8, size.height * 0.1), 140, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }