import 'package:flutter/material.dart'; /// 错误提示组件库 - 提供多种样式的错误状态显示 /// 包括网络错误、服务器错误、权限错误等不同场景的错误提示 /// 基础错误状态组件 - 通用的错误状态显示组件 /// [errorType] 错误类型,决定图标和颜色 /// [title] 错误标题文本 /// [message] 错误描述文本 /// [actionText] 操作按钮文本,null时不显示按钮 /// [onAction] 操作按钮点击回调,null时按钮不可点击 /// [iconSize] 图标大小,null时使用默认大小 /// [showDetails] 是否显示详细错误信息 /// [details] 详细错误信息,用于调试 class ErrorState extends StatelessWidget { /// 错误类型,决定图标和颜色 final ErrorType errorType; /// 错误标题文本 final String title; /// 错误描述文本 final String? message; /// 操作按钮文本,null时不显示按钮 final String? actionText; /// 操作按钮点击回调,null时按钮不可点击 final VoidCallback? onAction; /// 图标大小,null时使用默认大小 final double? iconSize; /// 自定义图标组件,优先级高于内置图标 final Widget? customIcon; /// 自定义操作组件,优先级高于actionText参数 final Widget? customAction; /// 主要内容垂直间距 final double spacing; /// 主要内容内边距 final EdgeInsetsGeometry? padding; /// 是否显示边框装饰 final bool showBorder; /// 边框圆角半径 final double borderRadius; /// 背景颜色,null时使用透明背景 final Color? backgroundColor; /// 是否显示详细错误信息 final bool showDetails; /// 详细错误信息,用于调试 final String? details; const ErrorState({ super.key, this.errorType = ErrorType.general, required this.title, this.message, this.actionText, this.onAction, this.iconSize, this.customIcon, this.customAction, this.spacing = 16, this.padding, this.showBorder = false, this.borderRadius = 12, this.backgroundColor, this.showDetails = false, this.details, }); /// 创建网络错误状态 - 用于网络连接失败场景 factory ErrorState.networkError({ VoidCallback? onRetry, String? details, bool showDetails = false, }) { return ErrorState( errorType: ErrorType.network, title: '网络连接失败', message: '请检查网络连接后重试', actionText: '重新加载', onAction: onRetry, showDetails: showDetails, details: details, ); } /// 创建服务器错误状态 - 用于服务器异常场景 factory ErrorState.serverError({ VoidCallback? onRetry, String? details, bool showDetails = false, }) { return ErrorState( errorType: ErrorType.server, title: '服务器异常', message: '服务器暂时无法访问,请稍后重试', actionText: '重新加载', onAction: onRetry, showDetails: showDetails, details: details, ); } /// 创建权限错误状态 - 用于无权限访问场景 factory ErrorState.permissionError({ VoidCallback? onRequestPermission, String? details, bool showDetails = false, }) { return ErrorState( errorType: ErrorType.permission, title: '权限不足', message: '您没有访问此内容的权限', actionText: onRequestPermission != null ? '申请权限' : null, onAction: onRequestPermission, showDetails: showDetails, details: details, ); } /// 创建数据错误状态 - 用于数据解析失败场景 factory ErrorState.dataError({ VoidCallback? onRetry, String? details, bool showDetails = false, }) { return ErrorState( errorType: ErrorType.data, title: '数据解析失败', message: '数据格式错误,无法正确解析', actionText: '重新加载', onAction: onRetry, showDetails: showDetails, details: details, ); } /// 创建超时错误状态 - 用于请求超时场景 factory ErrorState.timeoutError({ VoidCallback? onRetry, String? details, bool showDetails = false, }) { return ErrorState( errorType: ErrorType.timeout, title: '请求超时', message: '请求时间过长,请重试', actionText: '重新加载', onAction: onRetry, showDetails: showDetails, details: details, ); } /// 创建未找到错误状态 - 用于资源未找到场景 factory ErrorState.notFoundError({ VoidCallback? onGoBack, String? details, bool showDetails = false, }) { return ErrorState( errorType: ErrorType.notFound, title: '页面不存在', message: '您访问的内容可能已被删除或移动', actionText: onGoBack != null ? '返回' : null, onAction: onGoBack, showDetails: showDetails, details: details, ); } /// 创建未知错误状态 - 用于未分类的错误场景 factory ErrorState.unknownError({ VoidCallback? onRetry, String? details, bool showDetails = false, }) { return ErrorState( errorType: ErrorType.unknown, title: '发生未知错误', message: '抱歉,发生了一些意外情况', actionText: '重新加载', onAction: onRetry, showDetails: showDetails, details: details, ); } /// 获取错误类型对应的图标 IconData _getErrorIcon() { switch (errorType) { case ErrorType.network: return Icons.wifi_off_outlined; case ErrorType.server: return Icons.cloud_off_outlined; case ErrorType.permission: return Icons.lock_outline; case ErrorType.data: return Icons.data_array_outlined; case ErrorType.timeout: return Icons.timer_off_outlined; case ErrorType.notFound: return Icons.search_off_outlined; case ErrorType.unknown: default: return Icons.error_outline; } } /// 获取错误类型对应的颜色 Color _getErrorColor(BuildContext context) { final theme = Theme.of(context); switch (errorType) { case ErrorType.network: return Colors.orange; case ErrorType.server: return Colors.red; case ErrorType.permission: return Colors.orange; case ErrorType.data: return Colors.red; case ErrorType.timeout: return Colors.orange; case ErrorType.notFound: return Colors.grey; case ErrorType.unknown: default: return theme.colorScheme.error; } } @override Widget build(BuildContext context) { final theme = Theme.of(context); final errorColor = _getErrorColor(context); final errorIcon = _getErrorIcon(); Widget content = Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ // 图标部分 if (customIcon != null) ...[ customIcon!, SizedBox(height: spacing), ] else ...[ Icon( errorIcon, size: iconSize ?? 64, color: errorColor, ), SizedBox(height: spacing), ], // 标题部分 Text( title, style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w600, color: errorColor, ), textAlign: TextAlign.center, ), // 描述文本部分 if (message != null) ...[ SizedBox(height: spacing * 0.5), Text( message!, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.6), ), textAlign: TextAlign.center, ), ], // 详细错误信息部分 if (showDetails && details != null) ...[ SizedBox(height: spacing * 0.5), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: errorColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: errorColor.withOpacity(0.3), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '详细信息:', style: theme.textTheme.bodySmall?.copyWith( color: errorColor, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 4), Text( details!, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.7), ), ), ], ), ), ], // 操作按钮部分 if (customAction != null) ...[ SizedBox(height: spacing * 1.5), customAction!, ] else if (actionText != null) ...[ SizedBox(height: spacing * 1.5), ElevatedButton.icon( onPressed: onAction, icon: const Icon(Icons.refresh, size: 18), label: Text(actionText!), style: ElevatedButton.styleFrom( backgroundColor: errorColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ), ], ], ); // 添加内边距 content = Padding( padding: padding ?? const EdgeInsets.all(24), child: content, ); // 添加边框装饰 if (showBorder) { content = Container( decoration: BoxDecoration( color: backgroundColor ?? errorColor.withOpacity(0.05), border: Border.all( color: errorColor.withOpacity(0.3), width: 1, ), borderRadius: BorderRadius.circular(borderRadius), ), child: content, ); } else if (backgroundColor != null) { content = Container( decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(borderRadius), ), child: content, ); } return content; } } /// 错误状态枚举 - 定义不同类型的错误 enum ErrorType { /// 一般错误 - 通用错误类型 general, /// 网络错误 - 网络连接问题 network, /// 服务器错误 - 服务端异常 server, /// 权限错误 - 权限不足 permission, /// 数据错误 - 数据解析或格式错误 data, /// 超时错误 - 请求超时 timeout, /// 未找到错误 - 资源不存在 notFound, /// 未知错误 - 未分类的错误 unknown, } /// 错误状态页面 - 全屏错误状态显示,包含图标、文本和操作按钮 /// [errorType] 错误类型 /// [title] 错误标题 /// [message] 错误描述 /// [actionText] 操作按钮文本 /// [onAction] 操作按钮点击回调 /// [showAppBar] 是否显示应用栏 /// [appBarTitle] 应用栏标题 /// [showDetails] 是否显示详细错误信息 /// [details] 详细错误信息 class ErrorStatePage extends StatelessWidget { /// 错误类型 final ErrorType errorType; /// 错误标题 final String title; /// 错误描述 final String? message; /// 操作按钮文本 final String? actionText; /// 操作按钮点击回调 final VoidCallback? onAction; /// 是否显示应用栏 final bool showAppBar; /// 应用栏标题 final String? appBarTitle; /// 自定义错误状态组件,优先级高于其他参数 final Widget? customErrorState; /// 背景颜色,null时使用主题背景色 final Color? backgroundColor; /// 是否显示详细错误信息 final bool showDetails; /// 详细错误信息,用于调试 final String? details; const ErrorStatePage({ super.key, this.errorType = ErrorType.general, required this.title, this.message, this.actionText, this.onAction, this.showAppBar = false, this.appBarTitle, this.customErrorState, this.backgroundColor, this.showDetails = false, this.details, }); /// 创建网络错误状态页面 factory ErrorStatePage.networkError({ VoidCallback? onRetry, bool showAppBar = false, String? appBarTitle, String? details, bool showDetails = false, }) { return ErrorStatePage( errorType: ErrorType.network, title: '网络连接失败', message: '请检查网络连接后重试', actionText: '重新加载', onAction: onRetry, showAppBar: showAppBar, appBarTitle: appBarTitle ?? '网络错误', showDetails: showDetails, details: details, ); } /// 创建服务器错误状态页面 factory ErrorStatePage.serverError({ VoidCallback? onRetry, bool showAppBar = false, String? appBarTitle, String? details, bool showDetails = false, }) { return ErrorStatePage( errorType: ErrorType.server, title: '服务器异常', message: '服务器暂时无法访问,请稍后重试', actionText: '重新加载', onAction: onRetry, showAppBar: showAppBar, appBarTitle: appBarTitle ?? '服务器错误', showDetails: showDetails, details: details, ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( backgroundColor: backgroundColor ?? theme.colorScheme.background, appBar: showAppBar ? AppBar( title: Text(appBarTitle ?? title), centerTitle: true, ) : null, body: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: customErrorState ?? ErrorState( errorType: errorType, title: title, message: message, actionText: actionText, onAction: onAction, showBorder: false, showDetails: showDetails, details: details, ), ), ), ); } } /// 错误状态构建器 - 根据错误状态显示不同内容 /// [hasError] 是否有错误 /// [errorState] 错误状态组件 /// [child] 正常内容组件 class ErrorStateBuilder extends StatelessWidget { /// 是否有错误 final bool hasError; /// 错误状态组件 final Widget errorState; /// 正常内容组件 final Widget child; const ErrorStateBuilder({ super.key, required this.hasError, required this.errorState, required this.child, }); @override Widget build(BuildContext context) { if (hasError) { return errorState; } return child; } } /// 错误状态包装器 - 简化版本,自动构建错误状态 /// [hasError] 是否有错误 /// [errorType] 错误类型 /// [title] 错误标题 /// [message] 错误描述 /// [actionText] 操作按钮文本 /// [onAction] 操作按钮点击回调 /// [child] 正常内容组件 /// [showDetails] 是否显示详细错误信息 /// [details] 详细错误信息 class ErrorStateWrapper extends StatelessWidget { /// 是否有错误 final bool hasError; /// 错误类型 final ErrorType errorType; /// 错误标题 final String title; /// 错误描述 final String? message; /// 操作按钮文本 final String? actionText; /// 操作按钮点击回调 final VoidCallback? onAction; /// 正常内容组件 final Widget child; /// 自定义错误状态组件,优先级高于其他参数 final Widget? customErrorState; /// 是否显示详细错误信息 final bool showDetails; /// 详细错误信息 final String? details; const ErrorStateWrapper({ super.key, required this.hasError, this.errorType = ErrorType.general, required this.title, required this.child, this.message, this.actionText, this.onAction, this.customErrorState, this.showDetails = false, this.details, }); @override Widget build(BuildContext context) { if (hasError) { return customErrorState ?? ErrorState( errorType: errorType, title: title, message: message, actionText: actionText, onAction: onAction, showDetails: showDetails, details: details, ); } return child; } }