init
This commit is contained in:
141
client/lib/core/utils/exceptions.dart
Normal file
141
client/lib/core/utils/exceptions.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
185
client/lib/core/utils/message_utils.dart
Normal file
185
client/lib/core/utils/message_utils.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
148
client/lib/core/utils/responsive_utils.dart
Normal file
148
client/lib/core/utils/responsive_utils.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user