import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../providers/auth_provider.dart'; /// 登录页——手机号 + 验证码 class LoginPage extends ConsumerStatefulWidget { const LoginPage({super.key}); @override ConsumerState createState() => _LoginPageState(); } class _LoginPageState extends ConsumerState { final _phoneCtrl = TextEditingController(); final _codeCtrl = TextEditingController(); bool _agreed = false; bool _sending = false; int _countdown = 0; bool _loading = false; String? _error; @override void dispose() { _phoneCtrl.dispose(); _codeCtrl.dispose(); super.dispose(); } Future _sendSms() async { final phone = _phoneCtrl.text.trim(); if (phone.length != 11 || !phone.startsWith('1')) { setState(() => _error = '请输入正确的手机号'); return; } setState(() { _sending = true; _error = null; }); final result = await ref.read(authProvider.notifier).sendSms(phone); setState(() { _sending = false; }); if (result.error != null) { setState(() => _error = result.error); return; } // 开发阶段自动填充验证码 if (result.devCode != null) { _codeCtrl.text = result.devCode!; } setState(() => _countdown = 60); _startCountdown(); } void _startCountdown() async { for (var i = 60; i > 0; i--) { await Future.delayed(const Duration(seconds: 1)); if (!mounted) return; setState(() => _countdown = i - 1); } } Future _login() async { if (!_agreed) { setState(() => _error = '请阅读并同意服务协议和隐私政策'); return; } setState(() { _loading = true; _error = null; }); final err = await ref.read(authProvider.notifier).login( _phoneCtrl.text.trim(), _codeCtrl.text.trim(), ); setState(() => _loading = false); if (err != null) { setState(() => _error = err); return; } if (mounted) context.go('/home'); } @override Widget build(BuildContext context) { final authState = ref.watch(authProvider); // 已登录直接跳转 if (authState.isLoggedIn && !authState.isLoading) { WidgetsBinding.instance.addPostFrameCallback((_) => context.go('/home')); } return Scaffold( body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( children: [ const SizedBox(height: 80), // Logo Icon(Icons.local_hospital, size: 64, color: Theme.of(context).colorScheme.primary), const SizedBox(height: 16), Text('健康管家', style: Theme.of(context).textTheme.headlineLarge), const SizedBox(height: 8), Text('您的 AI 健康陪伴助手', style: Theme.of(context).textTheme.bodyMedium), const SizedBox(height: 48), // 手机号 TextField( controller: _phoneCtrl, keyboardType: TextInputType.phone, maxLength: 11, decoration: const InputDecoration( hintText: '手机号', prefixText: '+86 ', counterText: '', ), ), const SizedBox(height: 16), // 验证码 Row( children: [ Expanded( child: TextField( controller: _codeCtrl, keyboardType: TextInputType.number, maxLength: 6, decoration: const InputDecoration(hintText: '验证码', counterText: ''), ), ), const SizedBox(width: 12), SizedBox( width: 120, height: 48, child: ElevatedButton( onPressed: (_countdown > 0 || _sending) ? null : _sendSms, style: ElevatedButton.styleFrom( backgroundColor: _countdown > 0 ? Colors.grey[300] : null, ), child: Text( _sending ? '发送中' : _countdown > 0 ? '${_countdown}s' : '获取验证码', style: TextStyle(fontSize: 14, color: _countdown > 0 ? Colors.grey[600] : null), ), ), ), ], ), const SizedBox(height: 16), // 协议勾选 Row( children: [ Checkbox(value: _agreed, onChanged: (v) => setState(() => _agreed = v ?? false)), GestureDetector( onTap: () => setState(() => _agreed = !_agreed), child: Text('已阅读并同意《服务协议》《隐私政策》', style: Theme.of(context).textTheme.labelMedium), ), ], ), const SizedBox(height: 24), // 登录按钮 if (_error != null) Padding( padding: const EdgeInsets.only(bottom: 12), child: Text(_error!, style: const TextStyle(color: AppColors.errorRed, fontSize: 14)), ), SizedBox( width: double.infinity, height: 48, child: ElevatedButton( onPressed: _loading ? null : _login, child: _loading ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Text('登 录'), ), ), const SizedBox(height: 80), ], ), ), ), ); } } /// 引用 AppTheme 颜色 class AppColors { static const Color errorRed = Color(0xFFE53935); }