feat:增加公共组件
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
library flutter_common;
|
library flutter_common;
|
||||||
|
|
||||||
|
export 'layout/theme_layout.dart';
|
||||||
|
|
||||||
|
export 'provider/theme_provider.dart';
|
||||||
|
|
||||||
|
export 'widget/chart.dart';
|
||||||
|
|
||||||
export 'utils/date_utils.dart';
|
export 'utils/date_utils.dart';
|
||||||
export 'utils/sp_utils.dart';
|
export 'utils/sp_utils.dart';
|
||||||
|
|||||||
129
lib/layout/theme_layout.dart
Normal file
129
lib/layout/theme_layout.dart
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../provider/theme_provider.dart';
|
||||||
|
import '../utils/theme_utils.dart';
|
||||||
|
|
||||||
|
class ThemeLayout extends StatelessWidget {
|
||||||
|
final Widget? child;
|
||||||
|
|
||||||
|
const ThemeLayout({super.key, this.child});
|
||||||
|
|
||||||
|
Widget _buildThemeColorList(BuildContext context, ThemeProvider provider) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 4,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
childAspectRatio: 1.2,
|
||||||
|
),
|
||||||
|
itemCount: provider.availableThemes.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final themeColor = provider.availableThemes[index];
|
||||||
|
final isSelected = provider.currentTheme == themeColor;
|
||||||
|
|
||||||
|
return _buildThemeColorItem(
|
||||||
|
themeColor: themeColor,
|
||||||
|
isSelected: isSelected,
|
||||||
|
onTap: () => provider.changeTheme(themeColor),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildThemeColorItem({
|
||||||
|
required ThemeColor themeColor,
|
||||||
|
required bool isSelected,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
isSelected
|
||||||
|
? themeColor.primaryColor.withAlpha(50)
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? themeColor.primaryColor : Colors.grey.shade300,
|
||||||
|
width: isSelected ? 2 : 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// 颜色圆点
|
||||||
|
Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeColor.primaryColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 2),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withAlpha(10),
|
||||||
|
blurRadius: 2,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
themeColor.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||||
|
color:
|
||||||
|
isSelected ? themeColor.primaryColor : Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDarkModeSection(BuildContext context, ThemeProvider provider) {
|
||||||
|
return ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
provider.isDarkMode ? Icons.dark_mode : Icons.light_mode,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
title: const Text('暗黑模式'),
|
||||||
|
trailing: Switch(
|
||||||
|
value: provider.isDarkMode,
|
||||||
|
onChanged: (value) {
|
||||||
|
provider.toggleDarkMode(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
provider.toggleDarkMode(!provider.isDarkMode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final provider = context.watch<ThemeProvider>();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||||
|
child: Text('主题颜色', style: Theme.of(context).textTheme.titleMedium),
|
||||||
|
),
|
||||||
|
_buildDarkModeSection(context, provider),
|
||||||
|
_buildThemeColorList(context, provider),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/models/common_model.dart
Normal file
56
lib/models/common_model.dart
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
import '../widget/chart.dart';
|
||||||
|
|
||||||
|
part 'common_model.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable(genericArgumentFactories: true)
|
||||||
|
class PageResult<T> {
|
||||||
|
@JsonKey(name: 'records')
|
||||||
|
final List<T> records;
|
||||||
|
|
||||||
|
@JsonKey(name: 'total')
|
||||||
|
final int total;
|
||||||
|
|
||||||
|
@JsonKey(name: 'size')
|
||||||
|
final int size;
|
||||||
|
|
||||||
|
@JsonKey(name: 'current')
|
||||||
|
final int current;
|
||||||
|
|
||||||
|
@JsonKey(name: 'pages')
|
||||||
|
final int pages;
|
||||||
|
|
||||||
|
const PageResult({
|
||||||
|
required this.records,
|
||||||
|
required this.total,
|
||||||
|
required this.size,
|
||||||
|
required this.current,
|
||||||
|
required this.pages,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PageResult.fromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
T Function(Object? json) fromJsonT,
|
||||||
|
) =>
|
||||||
|
_$PageResultFromJson(json, fromJsonT);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
|
||||||
|
_$PageResultToJson(this, toJsonT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable(genericArgumentFactories: true)
|
||||||
|
class ChartData {
|
||||||
|
@JsonKey(name: 'name')
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@JsonKey(name: 'value')
|
||||||
|
final num value;
|
||||||
|
|
||||||
|
const ChartData({required this.name, required this.value});
|
||||||
|
|
||||||
|
factory ChartData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChartDataFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$ChartDataToJson(this);
|
||||||
|
}
|
||||||
41
lib/models/common_model.g.dart
Normal file
41
lib/models/common_model.g.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'common_model.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
PageResult<T> _$PageResultFromJson<T>(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
T Function(Object? json) fromJsonT,
|
||||||
|
) =>
|
||||||
|
PageResult<T>(
|
||||||
|
records: (json['records'] as List<dynamic>).map(fromJsonT).toList(),
|
||||||
|
total: (json['total'] as num).toInt(),
|
||||||
|
size: (json['size'] as num).toInt(),
|
||||||
|
current: (json['current'] as num).toInt(),
|
||||||
|
pages: (json['pages'] as num).toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$PageResultToJson<T>(
|
||||||
|
PageResult<T> instance,
|
||||||
|
Object? Function(T value) toJsonT,
|
||||||
|
) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'records': instance.records.map(toJsonT).toList(),
|
||||||
|
'total': instance.total,
|
||||||
|
'size': instance.size,
|
||||||
|
'current': instance.current,
|
||||||
|
'pages': instance.pages,
|
||||||
|
};
|
||||||
|
|
||||||
|
ChartData _$ChartDataFromJson(Map<String, dynamic> json) => ChartData(
|
||||||
|
name: json['name'] as String,
|
||||||
|
value: json['value'] as num,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ChartDataToJson(ChartData instance) => <String, dynamic>{
|
||||||
|
'name': instance.name,
|
||||||
|
'value': instance.value,
|
||||||
|
};
|
||||||
77
lib/provider/theme_provider.dart
Normal file
77
lib/provider/theme_provider.dart
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../utils/theme_utils.dart';
|
||||||
|
|
||||||
|
class ThemeProvider with ChangeNotifier {
|
||||||
|
static const String _themeKey = 'selected_theme';
|
||||||
|
static const String _darkModeKey = 'is_dark_mode';
|
||||||
|
|
||||||
|
bool isDarkMode = false;
|
||||||
|
ThemeColor currentTheme = defaultThemes[0];
|
||||||
|
|
||||||
|
late SharedPreferences _prefs;
|
||||||
|
bool isInitialized = false;
|
||||||
|
|
||||||
|
ThemeProvider() {
|
||||||
|
_initPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 SharedPreferences
|
||||||
|
Future<void> _initPreferences() async {
|
||||||
|
_prefs = await SharedPreferences.getInstance();
|
||||||
|
_loadPreferences();
|
||||||
|
|
||||||
|
isInitialized = true;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载存储的设置
|
||||||
|
void _loadPreferences() {
|
||||||
|
isDarkMode = _prefs.getBool(_darkModeKey) ?? false;
|
||||||
|
|
||||||
|
// 加载主题色
|
||||||
|
final themeName = _prefs.getString(_themeKey);
|
||||||
|
if (themeName != null) {
|
||||||
|
currentTheme = getThemeColor(themeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存主题色
|
||||||
|
Future<void> _saveTheme() async {
|
||||||
|
await _prefs.setString(_themeKey, currentTheme.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存暗黑模式
|
||||||
|
Future<void> _saveDarkMode() async {
|
||||||
|
await _prefs.setBool(_darkModeKey, isDarkMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ThemeColor> get availableThemes => defaultThemes;
|
||||||
|
|
||||||
|
// 切换明暗模式
|
||||||
|
void toggleDarkMode(bool value) {
|
||||||
|
isDarkMode = value;
|
||||||
|
_saveDarkMode();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更改主题色
|
||||||
|
void changeTheme(ThemeColor theme) {
|
||||||
|
currentTheme = theme;
|
||||||
|
_saveTheme();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeData get currentThemeData {
|
||||||
|
return ThemeData(
|
||||||
|
primarySwatch: currentTheme.materialColor,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: currentTheme.primaryColor,
|
||||||
|
brightness: isDarkMode ? Brightness.dark : Brightness.light,
|
||||||
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
fontFamily: 'CustomFont',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
lib/utils/convert_utils.dart
Normal file
28
lib/utils/convert_utils.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import '../models/common_model.dart';
|
||||||
|
|
||||||
|
List<T> convertList<T>(
|
||||||
|
dynamic data,
|
||||||
|
T Function(Map<String, dynamic>) fromJson,
|
||||||
|
) {
|
||||||
|
if (data is List) {
|
||||||
|
return data.map((item) => fromJson(item as Map<String, dynamic>)).toList();
|
||||||
|
}
|
||||||
|
throw FormatException(
|
||||||
|
'Expected a list of items for conversion, but got ${data.runtimeType}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PageResult<T> convertPage<T>(
|
||||||
|
dynamic data,
|
||||||
|
T Function(Map<String, dynamic>) fromJson,
|
||||||
|
) {
|
||||||
|
if (data is Map<String, dynamic>) {
|
||||||
|
return PageResult<T>.fromJson(
|
||||||
|
data,
|
||||||
|
(json) => fromJson(json as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw FormatException(
|
||||||
|
'Expected a Map for page response conversion, but got ${data.runtimeType}',
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -11,3 +11,20 @@ String formatDateString(String date) {
|
|||||||
String formatTime(DateTime datetime) {
|
String formatTime(DateTime datetime) {
|
||||||
return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime);
|
return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isToday(DateTime? date) {
|
||||||
|
if (date == null) return false;
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
return date.year == now.year &&
|
||||||
|
date.month == now.month &&
|
||||||
|
date.day == now.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAfterToday(DateTime? date) {
|
||||||
|
if (date == null) return false;
|
||||||
|
|
||||||
|
if (!isToday(date)) return false;
|
||||||
|
|
||||||
|
return date.isAfter(DateTime.now());
|
||||||
|
}
|
||||||
|
|||||||
251
lib/utils/http_utils.dart
Normal file
251
lib/utils/http_utils.dart
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import 'log_utils.dart';
|
||||||
|
import 'sp_utils.dart';
|
||||||
|
|
||||||
|
class HttpUtil {
|
||||||
|
static HttpUtil? _instance;
|
||||||
|
|
||||||
|
factory HttpUtil({
|
||||||
|
required String baseUrl,
|
||||||
|
int timeout = 5,
|
||||||
|
}) {
|
||||||
|
_instance ??= HttpUtil._internal(
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
timeout: timeout,
|
||||||
|
);
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String baseUrl;
|
||||||
|
final int timeout;
|
||||||
|
late Dio _dio;
|
||||||
|
|
||||||
|
// 请求头配置
|
||||||
|
Map<String, dynamic> headers = {
|
||||||
|
'Content-Type': 'application/json;charset=UTF-8',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpUtil._internal({
|
||||||
|
required this.baseUrl,
|
||||||
|
required this.timeout,
|
||||||
|
}) {
|
||||||
|
_initDio();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initDio() {
|
||||||
|
BaseOptions options = BaseOptions(
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
connectTimeout: Duration(seconds: timeout),
|
||||||
|
receiveTimeout: Duration(seconds: timeout),
|
||||||
|
sendTimeout: Duration(seconds: timeout),
|
||||||
|
headers: headers,
|
||||||
|
);
|
||||||
|
|
||||||
|
_dio = Dio(options);
|
||||||
|
|
||||||
|
// 添加请求拦截器
|
||||||
|
_dio.interceptors.add(
|
||||||
|
InterceptorsWrapper(
|
||||||
|
onRequest: (options, handler) {
|
||||||
|
logger.d("请求URL: ${options.uri}");
|
||||||
|
if (options.data != null) {
|
||||||
|
logger.d("请求参数: ${options.data}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SPUtil.getString('token').isNotEmpty) {
|
||||||
|
options.headers['satoken'] = SPUtil.getString('token');
|
||||||
|
}
|
||||||
|
return handler.next(options);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加响应拦截器
|
||||||
|
_dio.interceptors.add(
|
||||||
|
InterceptorsWrapper(
|
||||||
|
onResponse: (response, handler) {
|
||||||
|
logger.d("响应状态码: ${response.statusCode}");
|
||||||
|
// logger.d("响应数据: ${response.data}");
|
||||||
|
return handler.next(response);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加错误拦截器
|
||||||
|
_dio.interceptors.add(
|
||||||
|
InterceptorsWrapper(
|
||||||
|
onError: (DioException e, handler) {
|
||||||
|
_handleError(e);
|
||||||
|
return handler.next(e);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础请求方法(处理所有类型的请求)
|
||||||
|
Future<T> _request<T>(
|
||||||
|
String path, {
|
||||||
|
required String method,
|
||||||
|
dynamic data,
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
T Function(dynamic data)? converter,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
Response response = await _dio.request(
|
||||||
|
path,
|
||||||
|
data: data,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
options: Options(method: method),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 处理响应数据
|
||||||
|
if (converter != null) {
|
||||||
|
return converter(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有转换器时尝试直接返回(可能不安全,建议提供转换器)
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET请求
|
||||||
|
Future<T> get<T>(
|
||||||
|
String path, {
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
T Function(dynamic data)? converter,
|
||||||
|
}) =>
|
||||||
|
_request(
|
||||||
|
path,
|
||||||
|
method: "GET",
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
converter: converter,
|
||||||
|
);
|
||||||
|
|
||||||
|
// POST请求
|
||||||
|
Future<T> post<T>(
|
||||||
|
String path, {
|
||||||
|
dynamic data,
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
T Function(dynamic data)? converter,
|
||||||
|
}) =>
|
||||||
|
_request(
|
||||||
|
path,
|
||||||
|
method: "POST",
|
||||||
|
data: data,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
converter: converter,
|
||||||
|
);
|
||||||
|
|
||||||
|
// PUT请求
|
||||||
|
Future<T> put<T>(
|
||||||
|
String path, {
|
||||||
|
dynamic data,
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
T Function(dynamic data)? converter,
|
||||||
|
}) =>
|
||||||
|
_request(
|
||||||
|
path,
|
||||||
|
method: "PUT",
|
||||||
|
data: data,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
converter: converter,
|
||||||
|
);
|
||||||
|
|
||||||
|
// DELETE请求
|
||||||
|
Future<T> delete<T>(
|
||||||
|
String path, {
|
||||||
|
dynamic data,
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
T Function(dynamic data)? converter,
|
||||||
|
}) =>
|
||||||
|
_request(
|
||||||
|
path,
|
||||||
|
method: "DELETE",
|
||||||
|
data: data,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
converter: converter,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 错误处理
|
||||||
|
void _handleError(dynamic error) {
|
||||||
|
String errorMessage = '未知错误';
|
||||||
|
if (error is DioException) {
|
||||||
|
switch (error.type) {
|
||||||
|
case DioExceptionType.connectionTimeout:
|
||||||
|
errorMessage = '连接超时,请检查网络连接';
|
||||||
|
break;
|
||||||
|
case DioExceptionType.sendTimeout:
|
||||||
|
errorMessage = '发送超时,请检查网络连接';
|
||||||
|
break;
|
||||||
|
case DioExceptionType.receiveTimeout:
|
||||||
|
errorMessage = '接收超时,请检查网络连接';
|
||||||
|
break;
|
||||||
|
case DioExceptionType.cancel:
|
||||||
|
errorMessage = '请求已取消';
|
||||||
|
break;
|
||||||
|
case DioExceptionType.badCertificate:
|
||||||
|
errorMessage = '证书验证失败';
|
||||||
|
break;
|
||||||
|
case DioExceptionType.badResponse:
|
||||||
|
// 处理HTTP响应错误(4xx, 5xx)
|
||||||
|
final statusCode = error.response?.statusCode ?? 0;
|
||||||
|
final responseData = error.response?.data;
|
||||||
|
|
||||||
|
if (responseData is Map<String, dynamic> &&
|
||||||
|
responseData.containsKey('message')) {
|
||||||
|
// 服务器返回了自定义错误消息
|
||||||
|
errorMessage = responseData['message']?.toString() ?? '未知错误';
|
||||||
|
} else {
|
||||||
|
errorMessage = _getHttpErrorMessage(statusCode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DioExceptionType.connectionError:
|
||||||
|
errorMessage = '网络连接错误,请检查网络设置';
|
||||||
|
break;
|
||||||
|
case DioExceptionType.unknown:
|
||||||
|
if (error.error != null) {
|
||||||
|
errorMessage = '未知错误: ${error.error.toString()}';
|
||||||
|
}
|
||||||
|
errorMessage = '发生未知错误';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorMessage = '非Dio错误: $error';
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.e(errorMessage);
|
||||||
|
// showErrorToast(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getHttpErrorMessage(int statusCode) {
|
||||||
|
// 根据HTTP状态码返回对应的错误消息
|
||||||
|
switch (statusCode) {
|
||||||
|
case 400:
|
||||||
|
return '错误请求,请检查参数';
|
||||||
|
case 401:
|
||||||
|
return '未授权,请登录';
|
||||||
|
case 403:
|
||||||
|
return '禁止访问,权限不足';
|
||||||
|
case 404:
|
||||||
|
return '资源不存在';
|
||||||
|
case 405:
|
||||||
|
return '方法不允许';
|
||||||
|
case 408:
|
||||||
|
return '请求超时';
|
||||||
|
case 500:
|
||||||
|
return '服务器内部错误';
|
||||||
|
case 502:
|
||||||
|
return '网关错误';
|
||||||
|
case 503:
|
||||||
|
return '服务不可用';
|
||||||
|
case 504:
|
||||||
|
return '网关超时';
|
||||||
|
default:
|
||||||
|
return 'HTTP错误: $statusCode';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
lib/utils/log_utils.dart
Normal file
12
lib/utils/log_utils.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
// 全局日志实例,在整个项目中共享
|
||||||
|
final Logger logger = Logger(
|
||||||
|
printer: PrettyPrinter(
|
||||||
|
methodCount: 1,
|
||||||
|
colors: true,
|
||||||
|
dateTimeFormat: DateTimeFormat.dateAndTime,
|
||||||
|
),
|
||||||
|
// 可选:配置输出到文件(需配合文件操作库)
|
||||||
|
// output: FileOutput(file: File('logs/app.log')),
|
||||||
|
);
|
||||||
24
lib/utils/number_utils.dart
Normal file
24
lib/utils/number_utils.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/// 将数字格式化为中文单位(万/亿)
|
||||||
|
String formatChineseDecimal(num number, {int decimalPlaces = 2}) {
|
||||||
|
if (number < 10000) {
|
||||||
|
return number.toString();
|
||||||
|
} else if (number < 100000000) {
|
||||||
|
// 万单位
|
||||||
|
double result = number / 10000.0;
|
||||||
|
return '${_formatDecimal(result, decimalPlaces)}万';
|
||||||
|
} else {
|
||||||
|
// 亿单位
|
||||||
|
double result = number / 100000000.0;
|
||||||
|
return '${_formatDecimal(result, decimalPlaces)}亿';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDecimal(double number, int decimalPlaces) {
|
||||||
|
// 去除末尾的0和小数点
|
||||||
|
String formatted = number.toStringAsFixed(decimalPlaces);
|
||||||
|
if (formatted.contains('.')) {
|
||||||
|
formatted = formatted.replaceAll(RegExp(r'0*$'), '');
|
||||||
|
formatted = formatted.replaceAll(RegExp(r'\.$'), '');
|
||||||
|
}
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
87
lib/utils/theme_utils.dart
Normal file
87
lib/utils/theme_utils.dart
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ThemeColor {
|
||||||
|
final String name;
|
||||||
|
final Color primaryColor;
|
||||||
|
final MaterialColor materialColor;
|
||||||
|
|
||||||
|
ThemeColor({
|
||||||
|
required this.name,
|
||||||
|
required this.primaryColor,
|
||||||
|
required this.materialColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ThemeColor> defaultThemes = [
|
||||||
|
ThemeColor(
|
||||||
|
name: '梦幻紫',
|
||||||
|
primaryColor: Color(0xFF8B5CF6),
|
||||||
|
materialColor: MaterialColor(0xFF8B5CF6, {
|
||||||
|
50: Color(0xFFF5F3FF),
|
||||||
|
100: Color(0xFFEDE9FE),
|
||||||
|
200: Color(0xFFDDD6FE),
|
||||||
|
300: Color(0xFFC4B5FD),
|
||||||
|
400: Color(0xFFA78BFA),
|
||||||
|
500: Color(0xFF8B5CF6),
|
||||||
|
600: Color(0xFF7C3AED),
|
||||||
|
700: Color(0xFF6D28D9),
|
||||||
|
800: Color(0xFF5B21B6),
|
||||||
|
900: Color(0xFF4C1D95),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ThemeColor(
|
||||||
|
name: '活力橙',
|
||||||
|
primaryColor: Color(0xFFF59E0B),
|
||||||
|
materialColor: MaterialColor(0xFFF59E0B, {
|
||||||
|
50: Color(0xFFFFFBEB),
|
||||||
|
100: Color(0xFFFEF3C7),
|
||||||
|
200: Color(0xFFFDE68A),
|
||||||
|
300: Color(0xFFFCD34D),
|
||||||
|
400: Color(0xFFFBBF24),
|
||||||
|
500: Color(0xFFF59E0B),
|
||||||
|
600: Color(0xFFD97706),
|
||||||
|
700: Color(0xFFB45309),
|
||||||
|
800: Color(0xFF92400E),
|
||||||
|
900: Color(0xFF78350F),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ThemeColor(
|
||||||
|
name: '浪漫粉',
|
||||||
|
primaryColor: Color(0xFFEC4899),
|
||||||
|
materialColor: MaterialColor(0xFFEC4899, {
|
||||||
|
50: Color(0xFFFDF2F8),
|
||||||
|
100: Color(0xFFFCE7F3),
|
||||||
|
200: Color(0xFFFBCFE8),
|
||||||
|
300: Color(0xFFF9A8D4),
|
||||||
|
400: Color(0xFFF472B6),
|
||||||
|
500: Color(0xFFEC4899),
|
||||||
|
600: Color(0xFFDB2777),
|
||||||
|
700: Color(0xFFBE185D),
|
||||||
|
800: Color(0xFF9D174D),
|
||||||
|
900: Color(0xFF831843),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ThemeColor(
|
||||||
|
name: '清新青',
|
||||||
|
primaryColor: Color(0xFF06B6D4),
|
||||||
|
materialColor: MaterialColor(0xFF06B6D4, {
|
||||||
|
50: Color(0xFFF0FDFA),
|
||||||
|
100: Color(0xFFCCFBF1),
|
||||||
|
200: Color(0xFF99F6E4),
|
||||||
|
300: Color(0xFF5EEAD4),
|
||||||
|
400: Color(0xFF2DD4BF),
|
||||||
|
500: Color(0xFF06B6D4),
|
||||||
|
600: Color(0xFF0891B2),
|
||||||
|
700: Color(0xFF0E7490),
|
||||||
|
800: Color(0xFF155E75),
|
||||||
|
900: Color(0xFF164E63),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
ThemeColor getThemeColor(String themeName) {
|
||||||
|
return defaultThemes.firstWhere(
|
||||||
|
(theme) => theme.name == themeName,
|
||||||
|
orElse: () => defaultThemes[0],
|
||||||
|
);
|
||||||
|
}
|
||||||
435
lib/widget/chart.dart
Normal file
435
lib/widget/chart.dart
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
|
|
||||||
|
import '../models/common_model.dart';
|
||||||
|
|
||||||
|
class LineChart extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String xAxisName;
|
||||||
|
final String yAxisName;
|
||||||
|
final String unit;
|
||||||
|
final List<ChartData> data;
|
||||||
|
|
||||||
|
const LineChart(
|
||||||
|
{super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.xAxisName,
|
||||||
|
required this.yAxisName,
|
||||||
|
required this.unit,
|
||||||
|
required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_buildChartTitle(context, title, Icons.show_chart),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
_buildChartDivider(context),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Expanded(
|
||||||
|
child: SfCartesianChart(
|
||||||
|
// 图表标题
|
||||||
|
// title: ChartTitle(text: ''),
|
||||||
|
|
||||||
|
// X轴配置(类别轴)
|
||||||
|
primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)),
|
||||||
|
|
||||||
|
// Y轴配置(数值轴)
|
||||||
|
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')),
|
||||||
|
|
||||||
|
// 启用图例
|
||||||
|
legend: Legend(isVisible: true, position: LegendPosition.top),
|
||||||
|
|
||||||
|
// 启用交互提示(点击数据点显示详情)
|
||||||
|
tooltipBehavior: TooltipBehavior(
|
||||||
|
enable: true,
|
||||||
|
format: 'point.x: point.y $unit',
|
||||||
|
),
|
||||||
|
|
||||||
|
// 折线图数据系列
|
||||||
|
series: [
|
||||||
|
LineSeries<ChartData, String>(
|
||||||
|
dataSource: data,
|
||||||
|
// X轴数据映射
|
||||||
|
xValueMapper: (ChartData chart, _) => chart.name,
|
||||||
|
// Y轴数据映射
|
||||||
|
yValueMapper: (ChartData chart, _) => chart.value,
|
||||||
|
// 线条颜色
|
||||||
|
color: colors.primary,
|
||||||
|
// 线条宽度
|
||||||
|
width: 3,
|
||||||
|
|
||||||
|
// 数据点样式
|
||||||
|
markerSettings: const MarkerSettings(
|
||||||
|
isVisible: true,
|
||||||
|
color: Colors.white,
|
||||||
|
shape: DataMarkerType.circle,
|
||||||
|
height: 6,
|
||||||
|
width: 6,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 折线名称(会显示在图例中)
|
||||||
|
name: yAxisName,
|
||||||
|
|
||||||
|
// 启用数据标签(直接显示数值)
|
||||||
|
dataLabelSettings: const DataLabelSettings(
|
||||||
|
isVisible: true,
|
||||||
|
color: Colors.white,
|
||||||
|
opacity: 0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
animationDuration: 2000, // 动画时长(毫秒)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BarChart extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String xAxisName;
|
||||||
|
final String yAxisName;
|
||||||
|
final String unit;
|
||||||
|
final List<ChartData> data;
|
||||||
|
|
||||||
|
const BarChart(
|
||||||
|
{super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.xAxisName,
|
||||||
|
required this.yAxisName,
|
||||||
|
required this.unit,
|
||||||
|
required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Column(children: [
|
||||||
|
_buildChartTitle(context, title, Icons.bar_chart),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
_buildChartDivider(context),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Expanded(
|
||||||
|
child: SfCartesianChart(
|
||||||
|
// X轴配置(类别轴)
|
||||||
|
primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)),
|
||||||
|
|
||||||
|
// Y轴配置(数值轴)
|
||||||
|
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')),
|
||||||
|
|
||||||
|
// 启用交互提示
|
||||||
|
tooltipBehavior: TooltipBehavior(
|
||||||
|
enable: true,
|
||||||
|
format: 'point.x: point.y $unit',
|
||||||
|
),
|
||||||
|
|
||||||
|
// 柱状图数据系列
|
||||||
|
series: [
|
||||||
|
ColumnSeries<ChartData, String>(
|
||||||
|
dataSource: data,
|
||||||
|
// X轴数据映射
|
||||||
|
xValueMapper: (ChartData chart, _) => chart.name,
|
||||||
|
// Y轴数据映射
|
||||||
|
yValueMapper: (ChartData chart, _) => chart.value,
|
||||||
|
|
||||||
|
// 名称
|
||||||
|
name: yAxisName,
|
||||||
|
|
||||||
|
// 柱子颜色
|
||||||
|
color: colors.primary,
|
||||||
|
|
||||||
|
// 柱子宽度(0-1之间,1表示占满类别间隔)
|
||||||
|
width: 0.6,
|
||||||
|
|
||||||
|
// 柱子边框
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.black12,
|
||||||
|
|
||||||
|
// 数据标签
|
||||||
|
dataLabelSettings: const DataLabelSettings(
|
||||||
|
isVisible: true,
|
||||||
|
color: Colors.white,
|
||||||
|
opacity: 0,
|
||||||
|
alignment: ChartAlignment.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
animationDuration: 2000,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DoubleBarChart extends StatelessWidget {
|
||||||
|
final String xAxisName;
|
||||||
|
final String yAxisName;
|
||||||
|
final String unit;
|
||||||
|
final List<ChartData> data1;
|
||||||
|
final List<ChartData> data2;
|
||||||
|
final String series1Name;
|
||||||
|
final String series2Name;
|
||||||
|
|
||||||
|
const DoubleBarChart(
|
||||||
|
{super.key,
|
||||||
|
required this.xAxisName,
|
||||||
|
required this.yAxisName,
|
||||||
|
required this.unit,
|
||||||
|
required this.data1,
|
||||||
|
required this.data2,
|
||||||
|
required this.series1Name,
|
||||||
|
required this.series2Name});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return SfCartesianChart(
|
||||||
|
// X轴配置(类别轴)
|
||||||
|
primaryXAxis:
|
||||||
|
CategoryAxis(majorGridLines: const MajorGridLines(width: 0)),
|
||||||
|
|
||||||
|
// Y轴配置(数值轴)
|
||||||
|
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')),
|
||||||
|
|
||||||
|
// 图例配置
|
||||||
|
legend: Legend(
|
||||||
|
isVisible: true,
|
||||||
|
position: LegendPosition.top,
|
||||||
|
overflowMode: LegendItemOverflowMode.wrap,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 启用交互提示
|
||||||
|
tooltipBehavior: TooltipBehavior(
|
||||||
|
enable: true,
|
||||||
|
format: 'series.name: point.y $unit',
|
||||||
|
),
|
||||||
|
|
||||||
|
// 双柱状图数据系列
|
||||||
|
series: <ColumnSeries<ChartData, String>>[
|
||||||
|
ColumnSeries<ChartData, String>(
|
||||||
|
dataSource: data1,
|
||||||
|
// X轴数据映射
|
||||||
|
xValueMapper: (ChartData chart, _) => chart.name,
|
||||||
|
// Y轴数据映射
|
||||||
|
yValueMapper: (ChartData chart, _) => chart.value,
|
||||||
|
// 系列名称
|
||||||
|
name: series1Name,
|
||||||
|
// 柱子颜色
|
||||||
|
color: colors.primary,
|
||||||
|
// 柱子宽度
|
||||||
|
width: 0.3,
|
||||||
|
// 柱子边框
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.black12,
|
||||||
|
// 数据标签
|
||||||
|
dataLabelSettings: const DataLabelSettings(
|
||||||
|
isVisible: true,
|
||||||
|
color: Colors.white,
|
||||||
|
opacity: 0,
|
||||||
|
alignment: ChartAlignment.center,
|
||||||
|
),
|
||||||
|
// 动画效果
|
||||||
|
animationDuration: 2000,
|
||||||
|
),
|
||||||
|
ColumnSeries<ChartData, String>(
|
||||||
|
dataSource: data2,
|
||||||
|
// X轴数据映射
|
||||||
|
xValueMapper: (ChartData chart, _) => chart.name,
|
||||||
|
// Y轴数据映射
|
||||||
|
yValueMapper: (ChartData chart, _) => chart.value,
|
||||||
|
// 系列名称
|
||||||
|
name: series2Name,
|
||||||
|
// 柱子颜色
|
||||||
|
color: colors.inversePrimary,
|
||||||
|
// 柱子宽度
|
||||||
|
width: 0.3,
|
||||||
|
// 柱子边框
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.black12,
|
||||||
|
// 数据标签
|
||||||
|
dataLabelSettings: const DataLabelSettings(
|
||||||
|
isVisible: true,
|
||||||
|
color: Colors.white,
|
||||||
|
opacity: 0,
|
||||||
|
alignment: ChartAlignment.center,
|
||||||
|
),
|
||||||
|
// 动画效果
|
||||||
|
animationDuration: 2000,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PieChart extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String unit;
|
||||||
|
final List<ChartData> data;
|
||||||
|
|
||||||
|
const PieChart(
|
||||||
|
{super.key, required this.title, required this.unit, required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// 计算 value 的总和
|
||||||
|
double sumValue = data.fold(0.0, (sum, item) => sum + item.value);
|
||||||
|
|
||||||
|
return Column(children: [
|
||||||
|
_buildChartTitle(context, title, Icons.pie_chart),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
_buildChartDivider(context),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Expanded(
|
||||||
|
child: SfCircularChart(
|
||||||
|
// 饼图标题
|
||||||
|
// title: ChartTitle(text: ''),
|
||||||
|
|
||||||
|
// 启用图例
|
||||||
|
legend: const Legend(isVisible: true, position: LegendPosition.right),
|
||||||
|
|
||||||
|
// 启用交互提示(点击扇区显示详情)
|
||||||
|
tooltipBehavior: TooltipBehavior(
|
||||||
|
enable: true,
|
||||||
|
format: 'point.x: point.y $unit',
|
||||||
|
),
|
||||||
|
|
||||||
|
// 饼图系列配置
|
||||||
|
series: [
|
||||||
|
PieSeries<ChartData, String>(
|
||||||
|
dataSource: data,
|
||||||
|
// 类别映射(饼图扇区名称)
|
||||||
|
xValueMapper: (ChartData data, _) => data.name,
|
||||||
|
// 数值映射(扇区大小占比)
|
||||||
|
yValueMapper: (ChartData data, _) => data.value,
|
||||||
|
|
||||||
|
// 扇区半径(0-1之间,1表示充满容器)
|
||||||
|
// radius: '50%',
|
||||||
|
|
||||||
|
// 启用扇区分离效果
|
||||||
|
explode: true,
|
||||||
|
// 指定分离的扇区索引(这里分离第一个扇区)
|
||||||
|
explodeIndex: 0,
|
||||||
|
// 分离距离
|
||||||
|
explodeOffset: '5%',
|
||||||
|
|
||||||
|
dataLabelMapper: (ChartData data, _) {
|
||||||
|
final percentage =
|
||||||
|
(data.value / sumValue * 100).toStringAsFixed(0);
|
||||||
|
return '$percentage%';
|
||||||
|
},
|
||||||
|
|
||||||
|
// 数据标签(显示在扇区上的文本)
|
||||||
|
dataLabelSettings: DataLabelSettings(isVisible: true),
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
animationDuration: 2000,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RankChart extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String unit;
|
||||||
|
final List<ChartData> data;
|
||||||
|
|
||||||
|
const RankChart(
|
||||||
|
{super.key, required this.title, required this.unit, required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(children: [
|
||||||
|
_buildChartTitle(context, title, Icons.bar_chart),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
_buildChartDivider(context),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = data[index];
|
||||||
|
final rank = index + 1;
|
||||||
|
return SizedBox(
|
||||||
|
height: 35,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 25,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'$rank.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: _getRankColor(context, index),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight:
|
||||||
|
index < 3 ? FontWeight.bold : FontWeight.normal,
|
||||||
|
color: _getRankColor(context, index),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${item.value.toStringAsFixed(0)} $unit',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: index < 3 ? FontWeight.bold : FontWeight.normal,
|
||||||
|
color: _getRankColor(context, index),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildChartTitle(BuildContext context, String title, IconData icon) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: Theme.of(context).colorScheme.primary),
|
||||||
|
Text(title),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildChartDivider(BuildContext context) {
|
||||||
|
return Divider(
|
||||||
|
height: 1,
|
||||||
|
thickness: 1,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
indent: 0,
|
||||||
|
endIndent: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getRankColor(BuildContext context, int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0: // 金牌
|
||||||
|
return const Color(0xFFFFD700); // 标准金色
|
||||||
|
case 1: // 银牌
|
||||||
|
return const Color(0xFFC0C0C0); // 标准银色
|
||||||
|
case 2: // 铜牌
|
||||||
|
return const Color(0xFFCD7F32); // 标准铜色
|
||||||
|
default:
|
||||||
|
return Theme.of(context).colorScheme.onSurface; // 普通颜色
|
||||||
|
}
|
||||||
|
}
|
||||||
66
lib/widget/common_widget.dart
Normal file
66
lib/widget/common_widget.dart
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CommonCard extends StatelessWidget {
|
||||||
|
final Widget? child;
|
||||||
|
|
||||||
|
const CommonCard({super.key, this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.surfaceContainer,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircleIconButton extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
const CircleIconButton(
|
||||||
|
{super.key, required this.icon, required this.onPressed});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: CircleBorder(),
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
minimumSize: const Size(0, 0),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: Colors.white),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Widget buildErrorInfo({
|
||||||
|
required String errorInfo,
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
}) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('加载失败: $errorInfo'),
|
||||||
|
ElevatedButton(onPressed: onPressed, child: Text('重试')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildLoadingIndicator() {
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
117
lib/widget/year_selector.dart
Normal file
117
lib/widget/year_selector.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'common_widget.dart';
|
||||||
|
|
||||||
|
class YearSelector extends StatefulWidget {
|
||||||
|
final int initialYear;
|
||||||
|
final int? minYear;
|
||||||
|
final int? maxYear;
|
||||||
|
final Function(int) onYearChanged;
|
||||||
|
|
||||||
|
const YearSelector({
|
||||||
|
super.key,
|
||||||
|
required this.initialYear,
|
||||||
|
required this.onYearChanged,
|
||||||
|
this.minYear,
|
||||||
|
this.maxYear,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<YearSelector> createState() => _YearSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SingleTickerProviderStateMixin 动画控制器
|
||||||
|
class _YearSelectorState extends State<YearSelector>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late int _currentYear;
|
||||||
|
|
||||||
|
// 用于动画效果
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_currentYear = widget.initialYear;
|
||||||
|
|
||||||
|
// 初始化动画控制器
|
||||||
|
_animationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 缩放动画
|
||||||
|
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.1).animate(
|
||||||
|
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 切换到上一年
|
||||||
|
void _previousYear() {
|
||||||
|
if (widget.minYear == null || _currentYear > widget.minYear!) {
|
||||||
|
_animateYearChange(() {
|
||||||
|
setState(() {
|
||||||
|
_currentYear--;
|
||||||
|
});
|
||||||
|
widget.onYearChanged(_currentYear);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 切换到下一年
|
||||||
|
void _nextYear() {
|
||||||
|
if (widget.maxYear == null || _currentYear < widget.maxYear!) {
|
||||||
|
_animateYearChange(() {
|
||||||
|
setState(() {
|
||||||
|
_currentYear++;
|
||||||
|
});
|
||||||
|
widget.onYearChanged(_currentYear);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 年份变化时的动画效果
|
||||||
|
void _animateYearChange(VoidCallback onComplete) {
|
||||||
|
_animationController.forward().then((_) {
|
||||||
|
onComplete();
|
||||||
|
_animationController.reverse();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
CircleIconButton(
|
||||||
|
icon: Icons.chevron_left,
|
||||||
|
onPressed: () => _previousYear(),
|
||||||
|
),
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _scaleAnimation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.scale(scale: _scaleAnimation.value, child: child);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'$_currentYear',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CircleIconButton(
|
||||||
|
icon: Icons.chevron_right,
|
||||||
|
onPressed: () => _nextYear(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
408
pubspec.lock
408
pubspec.lock
@@ -1,6 +1,30 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "85.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "7.7.1"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -17,6 +41,70 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
|
build_resolvers:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_resolvers
|
||||||
|
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4"
|
||||||
|
build_runner_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_runner_core
|
||||||
|
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "9.1.2"
|
||||||
|
built_collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
built_value:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "8.12.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -25,6 +113,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -33,6 +129,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
|
code_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.11.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -41,6 +145,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -49,6 +169,30 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.8"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "5.9.0"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -73,6 +217,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -96,6 +248,54 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -104,6 +304,38 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.19.0"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.2"
|
||||||
|
json_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.0"
|
||||||
|
json_serializable:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: json_serializable
|
||||||
|
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.9.5"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -136,6 +368,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.1"
|
||||||
|
logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: logger
|
||||||
|
sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.2"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -160,6 +408,30 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
nested:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nested
|
||||||
|
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -208,6 +480,38 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.2"
|
||||||
|
provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: provider
|
||||||
|
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.5+1"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -264,11 +568,43 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.1"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
source_helper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_helper
|
||||||
|
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.7"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -293,6 +629,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -301,6 +645,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
syncfusion_flutter_charts:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: syncfusion_flutter_charts
|
||||||
|
sha256: "68fdb029dad34a46e4c9cfad8ad66fe29db7b303bd96849261ab2b23a168d0e8"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "30.2.7"
|
||||||
|
syncfusion_flutter_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: syncfusion_flutter_core
|
||||||
|
sha256: bfd026c0f9822b49ff26fed11cd3334519acb6a6ad4b0c81d9cd18df6af1c4c0
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "30.2.7"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -317,6 +677,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.4"
|
version: "0.7.4"
|
||||||
|
timing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timing
|
||||||
|
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -333,6 +709,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.1"
|
version: "14.3.1"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -341,6 +725,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -349,6 +749,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0 <4.0.0"
|
dart: ">=3.7.0 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.29.0"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ version: 1.0.0+1
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
flutter: ">=3.0.0"
|
flutter: '>=3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -13,11 +13,18 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
shared_preferences: ^2.3.0
|
shared_preferences: ^2.3.0
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
|
provider: ^6.1.1
|
||||||
|
syncfusion_flutter_charts: ^30.1.41
|
||||||
|
dio: ^5.7.0
|
||||||
|
logger: ^2.6.0
|
||||||
|
json_annotation: ^4.9.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
|
build_runner: ^2.4.5
|
||||||
|
json_serializable: ^6.7.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:flutter_common/flutter_common.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user