智慧水务管理系统 - 精河县供水工程综合管理平台

login_page.dart 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. import '../../services/auth_service.dart';
  4. /// 登录页 —— Material Design 3 风格
  5. class LoginPage extends StatefulWidget {
  6. const LoginPage({super.key});
  7. @override
  8. State<LoginPage> createState() => _LoginPageState();
  9. }
  10. class _LoginPageState extends State<LoginPage> {
  11. final _formKey = GlobalKey<FormState>();
  12. final _userCtrl = TextEditingController(text: 'admin');
  13. final _passCtrl = TextEditingController(text: 'admin123');
  14. bool _obscure = true;
  15. bool _loading = false;
  16. String? _errorText;
  17. Future<void> _login() async {
  18. if (!_formKey.currentState!.validate()) return;
  19. setState(() {
  20. _loading = true;
  21. _errorText = null;
  22. });
  23. final auth = context.read<AuthService>();
  24. final ok = await auth.login(_userCtrl.text.trim(), _passCtrl.text);
  25. if (!mounted) return;
  26. setState(() => _loading = false);
  27. if (!ok) {
  28. setState(() => _errorText = '用户名或密码错误,请重试');
  29. }
  30. // 成功时 AuthService.notifyListeners 会自动触发 MaterialApp 切换到 HomePage
  31. }
  32. @override
  33. void dispose() {
  34. _userCtrl.dispose();
  35. _passCtrl.dispose();
  36. super.dispose();
  37. }
  38. @override
  39. Widget build(BuildContext context) {
  40. final theme = Theme.of(context);
  41. final colorScheme = theme.colorScheme;
  42. return Scaffold(
  43. body: SafeArea(
  44. child: Center(
  45. child: SingleChildScrollView(
  46. padding: const EdgeInsets.symmetric(horizontal: 32),
  47. child: Form(
  48. key: _formKey,
  49. child: Column(
  50. mainAxisSize: MainAxisSize.min,
  51. children: [
  52. // ---------- Logo ----------
  53. Container(
  54. width: 96,
  55. height: 96,
  56. decoration: BoxDecoration(
  57. color: colorScheme.primaryContainer,
  58. shape: BoxShape.circle,
  59. ),
  60. child: Icon(
  61. Icons.water_drop,
  62. size: 48,
  63. color: colorScheme.onPrimaryContainer,
  64. ),
  65. ),
  66. const SizedBox(height: 24),
  67. // ---------- 标题 ----------
  68. Text(
  69. '智慧水务管理系统',
  70. style: theme.textTheme.headlineSmall?.copyWith(
  71. fontWeight: FontWeight.bold,
  72. color: colorScheme.onSurface,
  73. ),
  74. ),
  75. const SizedBox(height: 8),
  76. Text(
  77. '请登录您的账号',
  78. style: theme.textTheme.bodyMedium?.copyWith(
  79. color: colorScheme.onSurfaceVariant,
  80. ),
  81. ),
  82. const SizedBox(height: 40),
  83. // ---------- 用户名 ----------
  84. TextFormField(
  85. controller: _userCtrl,
  86. textInputAction: TextInputAction.next,
  87. decoration: InputDecoration(
  88. labelText: '用户名',
  89. hintText: '请输入用户名',
  90. prefixIcon: const Icon(Icons.person_outline),
  91. border: OutlineInputBorder(
  92. borderRadius: BorderRadius.circular(12),
  93. ),
  94. ),
  95. validator: (v) => (v == null || v.trim().isEmpty) ? '请输入用户名' : null,
  96. ),
  97. const SizedBox(height: 16),
  98. // ---------- 密码 ----------
  99. TextFormField(
  100. controller: _passCtrl,
  101. obscureText: _obscure,
  102. textInputAction: TextInputAction.done,
  103. onFieldSubmitted: (_) => _login(),
  104. decoration: InputDecoration(
  105. labelText: '密码',
  106. hintText: '请输入密码',
  107. prefixIcon: const Icon(Icons.lock_outline),
  108. suffixIcon: IconButton(
  109. icon: Icon(_obscure ? Icons.visibility_off : Icons.visibility),
  110. onPressed: () => setState(() => _obscure = !_obscure),
  111. ),
  112. border: OutlineInputBorder(
  113. borderRadius: BorderRadius.circular(12),
  114. ),
  115. ),
  116. validator: (v) => (v == null || v.isEmpty) ? '请输入密码' : null,
  117. ),
  118. const SizedBox(height: 8),
  119. // ---------- 错误提示 ----------
  120. if (_errorText != null) ...[
  121. Align(
  122. alignment: Alignment.centerLeft,
  123. child: Text(
  124. _errorText!,
  125. style: TextStyle(color: colorScheme.error, fontSize: 13),
  126. ),
  127. ),
  128. const SizedBox(height: 8),
  129. ],
  130. // ---------- 登录按钮 ----------
  131. const SizedBox(height: 16),
  132. SizedBox(
  133. width: double.infinity,
  134. height: 52,
  135. child: FilledButton(
  136. onPressed: _loading ? null : _login,
  137. style: FilledButton.styleFrom(
  138. shape: RoundedRectangleBorder(
  139. borderRadius: BorderRadius.circular(12),
  140. ),
  141. ),
  142. child: _loading
  143. ? const SizedBox(
  144. width: 24,
  145. height: 24,
  146. child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.white),
  147. )
  148. : const Text('登 录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
  149. ),
  150. ),
  151. const SizedBox(height: 24),
  152. // ---------- 底部版本信息 ----------
  153. Text(
  154. 'v1.0.0',
  155. style: theme.textTheme.bodySmall?.copyWith(
  156. color: colorScheme.onSurfaceVariant.withAlpha(128),
  157. ),
  158. ),
  159. ],
  160. ),
  161. ),
  162. ),
  163. ),
  164. ),
  165. );
  166. }
  167. }