import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/app_colors.dart'; import '../../core/navigation_provider.dart'; import '../../providers/auth_provider.dart'; import '../../providers/data_providers.dart'; const _doctorBlue = Color(0xFF0891B2); class LoginPage extends ConsumerStatefulWidget { const LoginPage({super.key}); @override ConsumerState createState() => _LoginPageState(); } class _LoginPageState extends ConsumerState { final _phoneCtrl = TextEditingController(); final _codeCtrl = TextEditingController(); final _inviteCtrl = TextEditingController(); String _role = 'User'; bool _showInvite = false; bool _agreed = false; bool _sending = false; int _countdown = 0; bool _loading = false; String? _error; bool _isLogin = true; String? _successMsg; @override void dispose() { _phoneCtrl.dispose(); _codeCtrl.dispose(); _inviteCtrl.dispose(); super.dispose(); } Color get _accent => _role == 'Doctor' ? _doctorBlue : AppColors.primary; 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) setState(() => _countdown = i - 1); } } Future _submit() async { if (!_agreed) { setState(() => _error = '请阅读并同意服务协议和隐私政策'); return; } if (_phoneCtrl.text.trim().isEmpty || _codeCtrl.text.trim().isEmpty) { setState(() => _error = '请输入手机号和验证码'); return; } setState(() { _loading = true; _error = null; _successMsg = null; }); String? err; if (_isLogin) { err = await ref.read(authProvider.notifier).login(_phoneCtrl.text.trim(), _codeCtrl.text.trim()); } else { if (_role == 'Doctor' && _inviteCtrl.text.trim() != '6666') { setState(() { _loading = false; _error = '审核码错误'; }); return; } err = await ref.read(authProvider.notifier).register(_phoneCtrl.text.trim(), _codeCtrl.text.trim(), _role, inviteCode: _role == 'Doctor' ? _inviteCtrl.text.trim() : null); } setState(() => _loading = false); if (err != null) { if (err.contains('未注册')) { setState(() { _isLogin = false; }); } setState(() => _error = err); return; } if (!_isLogin) { // 注册成功 → 回登录页 setState(() { _isLogin = true; _successMsg = '注册成功,请登录'; _error = null; _phoneCtrl.clear(); _codeCtrl.clear(); _inviteCtrl.clear(); _showInvite = false; }); ref.read(authProvider.notifier).logout(); } else { _goHome(); } } void _goHome() { ref.invalidate(latestHealthProvider); ref.invalidate(medicationListProvider); ref.invalidate(medicationReminderProvider); ref.invalidate(currentExercisePlanProvider); final role = ref.read(authProvider).user?.role ?? 'User'; goRoute(ref, role == 'Doctor' ? 'doctorHome' : 'home'); } @override Widget build(BuildContext context) { final authState = ref.watch(authProvider); if (authState.isLoggedIn && !authState.isLoading) { WidgetsBinding.instance.addPostFrameCallback((_) => _goHome()); } return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column(mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 30), Icon(Icons.favorite, size: 48, color: _accent), const SizedBox(height: 12), const Text('健康管家', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: AppColors.textPrimary)), const SizedBox(height: 4), Text(_isLogin ? '登录你的账号' : '创建新账号', style: const TextStyle(fontSize: 14, color: AppColors.textSecondary)), const SizedBox(height: 32), if (_successMsg != null) Padding(padding: const EdgeInsets.only(bottom: 16), child: Container(padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: const Color(0xFFD1FAE5), borderRadius: BorderRadius.circular(10)), child: Row(mainAxisSize: MainAxisSize.min, children: [const Icon(Icons.check_circle, color: Color(0xFF059669), size: 18), const SizedBox(width: 8), Text(_successMsg!, style: const TextStyle(color: Color(0xFF059669), fontSize: 14))]))), // 角色选择(注册模式) if (!_isLogin) ...[ Row(children: [ Expanded(child: _RoleCard(selected: _role == 'User', icon: Icons.person, label: '用户', desc: '健康管理', color: AppColors.primary, onTap: () => setState(() { _role = 'User'; _showInvite = false; }))), const SizedBox(width: 12), Expanded(child: _RoleCard(selected: _role == 'Doctor', icon: Icons.local_hospital, label: '医生', desc: '接诊管理', color: _doctorBlue, onTap: () => setState(() { _role = 'Doctor'; _showInvite = true; }))), ]), const SizedBox(height: 20), ], // 手机号 _TextField(_phoneCtrl, '手机号', TextInputType.phone, 11, _accent), const SizedBox(height: 12), // 验证码 Row(children: [ Expanded(child: _TextField(_codeCtrl, '验证码', TextInputType.number, 6, _accent)), const SizedBox(width: 12), GestureDetector( onTap: (_countdown > 0 || _sending) ? null : _sendSms, child: Container( width: 110, height: 50, alignment: Alignment.center, decoration: BoxDecoration(color: _countdown > 0 || _sending ? const Color(0xFFF5F5F5) : _accent, borderRadius: BorderRadius.circular(10)), child: Text(_sending ? '发送中' : _countdown > 0 ? '${_countdown}s' : '获取验证码', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: (_countdown > 0 || _sending) ? _accent : Colors.white)), ), ), ]), if (_showInvite && !_isLogin) ...[const SizedBox(height: 12), _TextField(_inviteCtrl, '医生审核码', TextInputType.number, 6, _accent)], const SizedBox(height: 14), // 协议 GestureDetector( onTap: () => setState(() => _agreed = !_agreed), child: Row(mainAxisSize: MainAxisSize.min, children: [ Container(width: 18, height: 18, margin: const EdgeInsets.only(right: 6), decoration: BoxDecoration(color: _agreed ? _accent : Colors.white, borderRadius: BorderRadius.circular(4), border: Border.all(color: _agreed ? _accent : AppColors.border)), child: _agreed ? const Icon(Icons.check, size: 14, color: Colors.white) : null), RichText(text: TextSpan(children: [const TextSpan(text: '已阅读并同意', style: TextStyle(fontSize: 13, color: AppColors.textHint)), TextSpan(text: '《服务协议》', style: TextStyle(fontSize: 13, color: _accent)), const TextSpan(text: '和', style: TextStyle(fontSize: 13, color: AppColors.textHint)), TextSpan(text: '《隐私政策》', style: TextStyle(fontSize: 13, color: _accent))])), ]), ), if (_error != null) Padding(padding: const EdgeInsets.only(top: 12), child: Text(_error!, style: const TextStyle(color: AppColors.error, fontSize: 14))), const SizedBox(height: 24), // 提交按钮 GestureDetector( onTap: _loading ? null : _submit, child: Container( width: double.infinity, height: 52, alignment: Alignment.center, decoration: BoxDecoration(color: _accent, borderRadius: BorderRadius.circular(12)), child: _loading ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.white)) : Text(_isLogin ? '登 录' : '注 册', style: const TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.w600, letterSpacing: 4)), ), ), const SizedBox(height: 16), GestureDetector( onTap: () => setState(() { _isLogin = !_isLogin; _error = null; _successMsg = null; if (_isLogin) _showInvite = false; }), child: Text(_isLogin ? '没有账号?去注册' : '已有账号?去登录', style: TextStyle(fontSize: 14, color: _accent)), ), const SizedBox(height: 30), ]), ), ), ), ); } } class _TextField extends StatelessWidget { final TextEditingController ctrl; final String hint; final TextInputType type; final int maxLen; final Color accent; const _TextField(this.ctrl, this.hint, this.type, this.maxLen, this.accent); @override Widget build(BuildContext context) => Container( height: 50, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: TextField( controller: ctrl, keyboardType: type, maxLength: maxLen, style: const TextStyle(fontSize: 16, color: AppColors.textPrimary), decoration: InputDecoration(hintText: hint, hintStyle: const TextStyle(fontSize: 15, color: AppColors.textHint), border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, counterText: '', contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13)), ), ); } class _RoleCard extends StatelessWidget { final bool selected; final IconData icon; final String label, desc; final Color color; final VoidCallback onTap; const _RoleCard({required this.selected, required this.icon, required this.label, required this.desc, required this.color, required this.onTap}); @override Widget build(BuildContext context) => GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 250), padding: const EdgeInsets.symmetric(vertical: 18), decoration: BoxDecoration(color: selected ? color : Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: selected ? color : const Color(0xFFE5E7EB), width: 1.5)), child: Column(children: [Icon(icon, size: 32, color: selected ? Colors.white : AppColors.textHint), const SizedBox(height: 6), Text(label, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: selected ? Colors.white : AppColors.textPrimary)), Text(desc, style: TextStyle(fontSize: 12, color: selected ? Colors.white70 : AppColors.textHint))]), ), ); }