This commit is contained in:
sjk
2025-11-17 13:39:05 +08:00
commit d4cfe2b9de
479 changed files with 109324 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
/// 应用异常基类
abstract class AppException implements Exception {
final String message;
final String? code;
final dynamic details;
const AppException(this.message, {this.code, this.details});
@override
String toString() => 'AppException: $message';
}
/// 网络异常
class NetworkException extends AppException {
const NetworkException(super.message, {super.code, super.details});
@override
String toString() => 'NetworkException: $message';
}
/// 认证异常
class AuthException extends AppException {
const AuthException(super.message, {super.code, super.details});
@override
String toString() => 'AuthException: $message';
}
/// 验证异常
class ValidationException extends AppException {
const ValidationException(super.message, {super.code, super.details});
@override
String toString() => 'ValidationException: $message';
}
/// 服务器异常
class ServerException extends AppException {
const ServerException(super.message, {super.code, super.details});
@override
String toString() => 'ServerException: $message';
}
/// 缓存异常
class CacheException extends AppException {
const CacheException(super.message, {super.code, super.details});
@override
String toString() => 'CacheException: $message';
}
/// 文件异常
class FileException extends AppException {
const FileException(super.message, {super.code, super.details});
@override
String toString() => 'FileException: $message';
}
/// 权限异常
class PermissionException extends AppException {
const PermissionException(super.message, {super.code, super.details});
@override
String toString() => 'PermissionException: $message';
}
/// 业务逻辑异常
class BusinessException extends AppException {
const BusinessException(super.message, {super.code, super.details});
@override
String toString() => 'BusinessException: $message';
}
/// 超时异常
class TimeoutException extends AppException {
const TimeoutException(super.message, {super.code, super.details});
@override
String toString() => 'TimeoutException: $message';
}
/// 数据解析异常
class ParseException extends AppException {
const ParseException(super.message, {super.code, super.details});
@override
String toString() => 'ParseException: $message';
}
/// 通用应用异常
class GeneralAppException extends AppException {
const GeneralAppException(super.message, {super.code, super.details});
@override
String toString() => 'GeneralAppException: $message';
}
/// 异常处理工具类
class ExceptionHandler {
/// 处理异常并返回用户友好的错误信息
static String getErrorMessage(dynamic error) {
if (error is AppException) {
return error.message;
} else if (error is Exception) {
return '发生了未知错误,请稍后重试';
} else {
return '系统错误,请联系客服';
}
}
/// 记录异常
static void logException(dynamic error, {StackTrace? stackTrace}) {
// 这里可以集成日志记录服务如Firebase Crashlytics
print('Exception: $error');
if (stackTrace != null) {
print('StackTrace: $stackTrace');
}
}
/// 判断是否为网络相关异常
static bool isNetworkError(dynamic error) {
return error is NetworkException ||
error is TimeoutException ||
(error is AppException && error.code?.contains('network') == true);
}
/// 判断是否为认证相关异常
static bool isAuthError(dynamic error) {
return error is AuthException ||
(error is AppException && error.code?.contains('auth') == true);
}
/// 判断是否为验证相关异常
static bool isValidationError(dynamic error) {
return error is ValidationException ||
(error is AppException && error.code?.contains('validation') == true);
}
}

View File

@@ -0,0 +1,185 @@
import 'package:flutter/material.dart';
/// 全局消息提示工具类
///
/// 特性:
/// - 新消息自动取消之前的提示
/// - 统一的样式和动画
/// - 缩短显示时间,避免堆积
class MessageUtils {
MessageUtils._();
/// 显示普通消息
static void showMessage(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 2),
SnackBarAction? action,
}) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
// 清除之前的所有SnackBar
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(message),
duration: duration,
action: action,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
margin: const EdgeInsets.all(16),
),
);
}
/// 显示成功消息
static void showSuccess(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 2),
}) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.check_circle, color: Colors.white, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
backgroundColor: Colors.green,
duration: duration,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
margin: const EdgeInsets.all(16),
),
);
}
/// 显示错误消息
static void showError(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 2),
}) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.error, color: Colors.white, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
backgroundColor: Colors.red,
duration: duration,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
margin: const EdgeInsets.all(16),
),
);
}
/// 显示警告消息
static void showWarning(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 2),
}) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.warning, color: Colors.white, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
backgroundColor: Colors.orange,
duration: duration,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
margin: const EdgeInsets.all(16),
),
);
}
/// 显示信息消息
static void showInfo(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 2),
}) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.info, color: Colors.white, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
backgroundColor: Colors.blue,
duration: duration,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
margin: const EdgeInsets.all(16),
),
);
}
/// 显示加载消息(不自动消失)
static void showLoading(
BuildContext context,
String message,
) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Row(
children: [
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
duration: const Duration(days: 1), // 长时间显示
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
margin: const EdgeInsets.all(16),
),
);
}
/// 隐藏所有消息
static void hideAll(BuildContext context) {
ScaffoldMessenger.of(context).clearSnackBars();
}
}

View File

@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
/// 响应式布局工具类
class ResponsiveUtils {
// 断点定义
static const double mobileBreakpoint = 600;
static const double tabletBreakpoint = 900;
static const double desktopBreakpoint = 1200;
/// 判断是否为移动端
static bool isMobile(BuildContext context) {
return MediaQuery.of(context).size.width < mobileBreakpoint;
}
/// 判断是否为平板端
static bool isTablet(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width >= mobileBreakpoint && width < desktopBreakpoint;
}
/// 判断是否为桌面端
static bool isDesktop(BuildContext context) {
return MediaQuery.of(context).size.width >= desktopBreakpoint;
}
/// 获取网格列数
static int getGridColumns(BuildContext context, {
int mobileColumns = 1,
int tabletColumns = 2,
int desktopColumns = 3,
}) {
if (isMobile(context)) return mobileColumns;
if (isTablet(context)) return tabletColumns;
return desktopColumns;
}
/// 获取响应式边距
static EdgeInsets getResponsivePadding(BuildContext context, {
EdgeInsets? mobile,
EdgeInsets? tablet,
EdgeInsets? desktop,
}) {
if (isMobile(context)) return mobile ?? const EdgeInsets.all(16);
if (isTablet(context)) return tablet ?? const EdgeInsets.all(20);
return desktop ?? const EdgeInsets.all(24);
}
/// 获取响应式字体大小
static double getResponsiveFontSize(BuildContext context, {
double mobileSize = 14,
double tabletSize = 16,
double desktopSize = 18,
}) {
if (isMobile(context)) return mobileSize;
if (isTablet(context)) return tabletSize;
return desktopSize;
}
/// 获取分类卡片宽度
static double getCategoryCardWidth(BuildContext context) {
if (isMobile(context)) return 80;
if (isTablet(context)) return 100;
return 120;
}
/// 获取分类卡片高度
static double getCategoryCardHeight(BuildContext context) {
if (isMobile(context)) return 90;
if (isTablet(context)) return 100;
return 110;
}
/// 根据屏幕宽度返回不同的值
static T getValueForScreenSize<T>(
BuildContext context, {
required T mobile,
T? tablet,
T? desktop,
}) {
if (isMobile(context)) return mobile;
if (isTablet(context)) return tablet ?? mobile;
return desktop ?? tablet ?? mobile;
}
}
/// 响应式布局构建器
class ResponsiveBuilder extends StatelessWidget {
final Widget Function(BuildContext context, bool isMobile, bool isTablet, bool isDesktop) builder;
const ResponsiveBuilder({
super.key,
required this.builder,
});
@override
Widget build(BuildContext context) {
final isMobile = ResponsiveUtils.isMobile(context);
final isTablet = ResponsiveUtils.isTablet(context);
final isDesktop = ResponsiveUtils.isDesktop(context);
return builder(context, isMobile, isTablet, isDesktop);
}
}
/// 响应式网格视图
class ResponsiveGridView extends StatelessWidget {
final List<Widget> children;
final int mobileColumns;
final int tabletColumns;
final int desktopColumns;
final double spacing;
final EdgeInsets padding;
final double childAspectRatio;
const ResponsiveGridView({
super.key,
required this.children,
this.mobileColumns = 1,
this.tabletColumns = 2,
this.desktopColumns = 3,
this.spacing = 16,
this.padding = const EdgeInsets.all(16),
this.childAspectRatio = 1.0,
});
@override
Widget build(BuildContext context) {
final columns = ResponsiveUtils.getGridColumns(
context,
mobileColumns: mobileColumns,
tabletColumns: tabletColumns,
desktopColumns: desktopColumns,
);
return Padding(
padding: padding,
child: GridView.count(
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
childAspectRatio: childAspectRatio,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: children,
),
);
}
}