import 'package:flutter/material.dart'; import 'package:ainoval/config/app_config.dart'; import 'package:ainoval/utils/web_theme.dart'; import 'package:ainoval/widgets/forms/change_password_form.dart'; import 'package:ainoval/widgets/common/top_toast.dart'; import 'package:ainoval/services/auth_service.dart'; /// 账户管理面板 /// 集成在设置面板中的账户相关功能 class AccountManagementPanel extends StatefulWidget { const AccountManagementPanel({Key? key}) : super(key: key); @override State createState() => _AccountManagementPanelState(); } class _AccountManagementPanelState extends State { int _selectedTabIndex = 0; Map? _userInfo; bool _isLoadingUserInfo = false; bool _isEditingPersonalInfo = false; bool _isSavingPersonalInfo = false; final GlobalKey _personalInfoFormKey = GlobalKey(); final TextEditingController _displayNameController = TextEditingController(); final TextEditingController _emailController = TextEditingController(); final TextEditingController _phoneController = TextEditingController(); final List _tabs = ['个人信息', '修改密码', '安全设置']; @override void initState() { super.initState(); _loadUserInfo(); } @override void dispose() { _displayNameController.dispose(); _emailController.dispose(); _phoneController.dispose(); super.dispose(); } /// 加载用户信息 Future _loadUserInfo() async { setState(() { _isLoadingUserInfo = true; }); try { final authService = AuthService(); // 确保初始化以加载本地存储中的登录状态 await authService.init(); final userInfo = await authService.getCurrentUser(); if (!mounted) return; setState(() { _userInfo = userInfo; _isLoadingUserInfo = false; }); _populateControllersFromUserInfo(userInfo); } catch (e) { if (mounted) { setState(() { _isLoadingUserInfo = false; }); TopToast.error(context, '加载用户信息失败:${e.toString().replaceAll('AuthException: ', '')}'); } } } void _populateControllersFromUserInfo(Map info) { try { _displayNameController.text = (info['displayName'] ?? '').toString(); _emailController.text = (info['email'] ?? '').toString(); _phoneController.text = (info['phone'] ?? '').toString(); } catch (_) {} } void _toggleEditing() { setState(() { _isEditingPersonalInfo = !_isEditingPersonalInfo; if (_isEditingPersonalInfo && _userInfo != null) { _populateControllersFromUserInfo(_userInfo!); } }); } Future _savePersonalInfo() async { if (!_isEditingPersonalInfo) return; final form = _personalInfoFormKey.currentState; if (form == null || !form.validate()) { return; } setState(() { _isSavingPersonalInfo = true; }); try { final authService = AuthService(); await authService.init(); final updated = await authService.updateUserProfile({ 'displayName': _displayNameController.text.trim(), 'email': _emailController.text.trim().isEmpty ? null : _emailController.text.trim(), 'phone': _phoneController.text.trim().isEmpty ? null : _phoneController.text.trim(), }); if (!mounted) return; setState(() { _userInfo = updated; _isEditingPersonalInfo = false; _isSavingPersonalInfo = false; }); TopToast.success(context, '个人信息已保存'); } catch (e) { if (!mounted) return; setState(() { _isSavingPersonalInfo = false; }); TopToast.error(context, e.toString().replaceAll('AuthException: ', '保存失败:')); } } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题 Text( '账户管理', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), const SizedBox(height: 16), // 用户信息概览卡片 _buildUserOverviewCard(), const SizedBox(height: 24), // Tab导航 _buildTabNavigation(), const SizedBox(height: 16), // Tab内容 Expanded( child: _buildTabContent(), ), ], ); } /// 构建用户概览卡片 Widget _buildUserOverviewCard() { final username = AppConfig.username ?? '游客'; final userId = AppConfig.userId ?? '未知'; return Card( elevation: 2, shadowColor: WebTheme.getShadowColor(context, opacity: 0.1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), color: WebTheme.getSurfaceColor(context), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ // 头像 Container( width: 50, height: 50, decoration: BoxDecoration( shape: BoxShape.circle, color: WebTheme.getPrimaryColor(context).withOpacity(0.1), ), child: Icon( Icons.person, size: 25, color: WebTheme.getPrimaryColor(context), ), ), const SizedBox(width: 16), // 用户信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( username, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), const SizedBox(height: 4), Text( 'ID: $userId', style: TextStyle( fontSize: 14, color: WebTheme.getSecondaryTextColor(context), ), ), if (_userInfo != null) ...[ const SizedBox(height: 4), Text( '积分: ${_userInfo!['credits'] ?? 0}', style: TextStyle( fontSize: 14, color: WebTheme.getPrimaryColor(context), fontWeight: FontWeight.w500, ), ), ], ], ), ), // 刷新按钮 IconButton( onPressed: _isLoadingUserInfo ? null : _loadUserInfo, icon: _isLoadingUserInfo ? SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( WebTheme.getSecondaryTextColor(context), ), ), ) : Icon( Icons.refresh, color: WebTheme.getSecondaryTextColor(context), ), tooltip: '刷新用户信息', ), ], ), ), ); } /// 构建Tab导航 Widget _buildTabNavigation() { return Container( height: 48, decoration: BoxDecoration( color: WebTheme.getSurfaceColor(context), borderRadius: BorderRadius.circular(8), border: Border.all( color: WebTheme.getBorderColor(context), width: 1, ), ), child: Row( children: _tabs.asMap().entries.map((entry) { final index = entry.key; final title = entry.value; final isSelected = _selectedTabIndex == index; return Expanded( child: GestureDetector( onTap: () { setState(() { _selectedTabIndex = index; }); }, child: Container( height: double.infinity, decoration: BoxDecoration( color: isSelected ? WebTheme.getPrimaryColor(context).withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(6), ), child: Center( child: Text( title, style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, color: isSelected ? WebTheme.getPrimaryColor(context) : WebTheme.getSecondaryTextColor(context), ), ), ), ), ), ); }).toList(), ), ); } /// 构建Tab内容 Widget _buildTabContent() { switch (_selectedTabIndex) { case 0: return _buildPersonalInfoTab(); case 1: return _buildChangePasswordTab(); case 2: return _buildSecuritySettingsTab(); default: return Container(); } } /// 个人信息Tab Widget _buildPersonalInfoTab() { return SingleChildScrollView( child: Card( elevation: 2, shadowColor: WebTheme.getShadowColor(context, opacity: 0.1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), color: WebTheme.getSurfaceColor(context), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.person_outline, color: WebTheme.getPrimaryColor(context), size: 24, ), const SizedBox(width: 8), Text( '个人信息', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: WebTheme.getTextColor(context), ), ), const Spacer(), if (_userInfo != null && !_isLoadingUserInfo && !_isEditingPersonalInfo) OutlinedButton.icon( onPressed: _toggleEditing, icon: const Icon(Icons.edit, size: 16), label: const Text('编辑'), ), if (_isEditingPersonalInfo) ...[ TextButton( onPressed: _isSavingPersonalInfo ? null : _toggleEditing, child: const Text('取消'), ), const SizedBox(width: 8), ElevatedButton.icon( onPressed: _isSavingPersonalInfo ? null : _savePersonalInfo, icon: _isSavingPersonalInfo ? const SizedBox( width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save, size: 16), label: const Text('保存'), ), ], ], ), const SizedBox(height: 20), if (_isLoadingUserInfo) const Center(child: CircularProgressIndicator()) else if (_userInfo != null && !_isEditingPersonalInfo) ...[ _buildInfoField('用户名', AppConfig.username ?? '未知'), const SizedBox(height: 16), _buildInfoField('显示名称', (_userInfo!['displayName'] ?? '未设置').toString()), const SizedBox(height: 16), _buildInfoField('邮箱', (_userInfo!['email'] ?? '未设置').toString()), const SizedBox(height: 16), _buildInfoField('手机号', (_userInfo!['phone'] ?? '未设置').toString()), const SizedBox(height: 16), _buildInfoField('注册时间', _formatDateTime(_userInfo!['createdAt'])), const SizedBox(height: 16), _buildInfoField('最后登录', _formatDateTime(_userInfo!['lastLoginAt'])), ] else if (_userInfo != null && _isEditingPersonalInfo) ...[ Form( key: _personalInfoFormKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildEditableTextField( label: '显示名称', controller: _displayNameController, hintText: '请输入显示名称', validator: (v) { if (v == null || v.trim().isEmpty) { return '显示名称不能为空'; } if (v.trim().length > 32) { return '显示名称过长(最多32个字符)'; } return null; }, ), const SizedBox(height: 16), _buildEditableTextField( label: '邮箱', controller: _emailController, hintText: '请输入邮箱(可留空)', keyboardType: TextInputType.emailAddress, validator: (v) { final value = (v ?? '').trim(); if (value.isEmpty) return null; // 允许空 final emailRegex = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$'); if (!emailRegex.hasMatch(value)) { return '邮箱格式不正确'; } return null; }, ), const SizedBox(height: 16), _buildEditableTextField( label: '手机号', controller: _phoneController, hintText: '请输入手机号(可留空)', keyboardType: TextInputType.phone, validator: (v) { final value = (v ?? '').trim(); if (value.isEmpty) return null; // 允许空 final phoneRegex = RegExp(r'^[0-9+\-\s]{6,20}$'); if (!phoneRegex.hasMatch(value)) { return '手机号格式不正确'; } return null; }, ), ], ), ), ] else ...[ Center( child: Column( children: [ Icon( Icons.error_outline, size: 48, color: WebTheme.getSecondaryTextColor(context), ), const SizedBox(height: 16), Text( '无法加载用户信息', style: TextStyle( fontSize: 16, color: WebTheme.getSecondaryTextColor(context), ), ), const SizedBox(height: 12), ElevatedButton( onPressed: _loadUserInfo, child: const Text('重试'), ), ], ), ), ], ], ), ), ), ); } /// 修改密码Tab Widget _buildChangePasswordTab() { return Card( elevation: 2, shadowColor: WebTheme.getShadowColor(context, opacity: 0.1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), color: WebTheme.getSurfaceColor(context), child: ChangePasswordForm( showTitle: false, onSuccess: () { TopToast.success(context, '密码修改成功'); }, ), ); } /// 安全设置Tab Widget _buildSecuritySettingsTab() { return SingleChildScrollView( child: Card( elevation: 2, shadowColor: WebTheme.getShadowColor(context, opacity: 0.1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), color: WebTheme.getSurfaceColor(context), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.security, color: WebTheme.getPrimaryColor(context), size: 24, ), const SizedBox(width: 8), Text( '安全设置', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: WebTheme.getTextColor(context), ), ), ], ), const SizedBox(height: 20), _buildSecurityItem( icon: Icons.device_unknown, title: '登录设备管理', subtitle: '查看和管理登录设备', onTap: () { TopToast.info(context, '登录设备管理功能开发中'); }, ), const Divider(height: 32), _buildSecurityItem( icon: Icons.history, title: '登录历史', subtitle: '查看最近的登录记录', onTap: () { TopToast.info(context, '登录历史功能开发中'); }, ), const Divider(height: 32), _buildSecurityItem( icon: Icons.key, title: 'API密钥管理', subtitle: '管理第三方API访问密钥', onTap: () { TopToast.info(context, 'API密钥管理功能开发中'); }, ), const Divider(height: 32), _buildSecurityItem( icon: Icons.privacy_tip, title: '隐私设置', subtitle: '管理数据使用和隐私偏好', onTap: () { TopToast.info(context, '隐私设置功能开发中'); }, ), ], ), ), ), ); } /// 构建信息字段 Widget _buildInfoField(String label, String value) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: WebTheme.getSecondaryTextColor(context), ), ), const SizedBox(height: 8), Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: WebTheme.getBackgroundColor(context), borderRadius: BorderRadius.circular(8), border: Border.all( color: WebTheme.getBorderColor(context), width: 1, ), ), child: Text( value, style: TextStyle( fontSize: 14, color: WebTheme.getTextColor(context), ), ), ), ], ); } /// 构建可编辑文本字段 Widget _buildEditableTextField({ required String label, required TextEditingController controller, String? hintText, TextInputType? keyboardType, String? Function(String?)? validator, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: WebTheme.getSecondaryTextColor(context), ), ), const SizedBox(height: 8), TextFormField( controller: controller, keyboardType: keyboardType, validator: validator, decoration: InputDecoration( hintText: hintText, filled: true, fillColor: WebTheme.getBackgroundColor(context), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: WebTheme.getBorderColor(context)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: WebTheme.getPrimaryColor(context), width: 2), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), ), ], ); } /// 构建安全设置项 Widget _buildSecurityItem({ required IconData icon, required String title, required String subtitle, required VoidCallback onTap, }) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(12), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( shape: BoxShape.circle, color: WebTheme.getPrimaryColor(context).withOpacity(0.1), ), child: Icon( icon, size: 20, color: WebTheme.getPrimaryColor(context), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: WebTheme.getTextColor(context), ), ), const SizedBox(height: 4), Text( subtitle, style: TextStyle( fontSize: 14, color: WebTheme.getSecondaryTextColor(context), ), ), ], ), ), Icon( Icons.arrow_forward_ios, size: 16, color: WebTheme.getSecondaryTextColor(context), ), ], ), ), ); } /// 格式化日期时间(兼容多种后端返回格式) String _formatDateTime(dynamic value) { if (value == null) return '未知'; try { DateTime dateTime; if (value is String) { dateTime = DateTime.parse(value); } else if (value is int) { // 兼容时间戳(秒/毫秒) if (value > 1000000000000) { dateTime = DateTime.fromMillisecondsSinceEpoch(value); } else if (value > 1000000000) { dateTime = DateTime.fromMillisecondsSinceEpoch(value * 1000); } else { return '未知'; } } else if (value is List) { // 兼容 [year, month, day, hour?, minute?, second?] final year = _toInt(value, 0); final month = _toInt(value, 1); final day = _toInt(value, 2); final hour = _toInt(value, 3) ?? 0; final minute = _toInt(value, 4) ?? 0; final second = _toInt(value, 5) ?? 0; if (year != null && month != null && day != null) { dateTime = DateTime(year, month, day, hour, minute, second); } else { return '未知'; } } else if (value is Map && value.containsKey('\$date')) { final d = value['\$date']; if (d is String) { dateTime = DateTime.parse(d); } else if (d is int) { dateTime = DateTime.fromMillisecondsSinceEpoch(d); } else { return '未知'; } } else { return '未知'; } return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; } catch (_) { return '未知'; } } int? _toInt(List list, int index) { if (index >= list.length) return null; final v = list[index]; if (v is int) return v; if (v is String) return int.tryParse(v); return null; } }