feat:增加对话框工具类
This commit is contained in:
@@ -12,6 +12,15 @@ List<T> convertList<T>(
|
||||
);
|
||||
}
|
||||
|
||||
List<String> convertStringList(dynamic data) {
|
||||
if (data is List) {
|
||||
return data.map((item) => item.toString()).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,
|
||||
|
||||
50
lib/utils/file_utils.dart
Normal file
50
lib/utils/file_utils.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
|
||||
import 'log_utils.dart';
|
||||
|
||||
String getFileExtension(String fileName) {
|
||||
if (fileName.contains('.')) {
|
||||
return '.${fileName.split('.').last.toLowerCase()}';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
Future<String> generateMD5HashName(String filePath) async {
|
||||
final file = File(filePath);
|
||||
final bytes = await file.readAsBytes();
|
||||
final hash = md5.convert(bytes);
|
||||
return hash.toString();
|
||||
}
|
||||
|
||||
// 图片压缩方法
|
||||
Future<File> compressImage(File file) async {
|
||||
try {
|
||||
// 获取压缩后的文件路径
|
||||
final result = await FlutterImageCompress.compressAndGetFile(
|
||||
file.absolute.path,
|
||||
'${file.parent.path}/compressed_${DateTime.now().millisecondsSinceEpoch}.jpg',
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
quality: 70,
|
||||
format: CompressFormat.jpeg,
|
||||
);
|
||||
|
||||
if (result == null) {
|
||||
throw Exception('图片压缩失败');
|
||||
}
|
||||
|
||||
return File(result.path);
|
||||
} catch (e) {
|
||||
logger.e('图片压缩失败,使用原文件: $e');
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否为图片文件
|
||||
bool isImageFile(String fileName) {
|
||||
final imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
|
||||
final extension = fileName.toLowerCase().substring(fileName.lastIndexOf('.'));
|
||||
return imageExtensions.contains(extension);
|
||||
}
|
||||
58
lib/utils/minio_utils.dart
Normal file
58
lib/utils/minio_utils.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:minio/io.dart';
|
||||
import 'package:minio/minio.dart';
|
||||
|
||||
import 'file_utils.dart';
|
||||
|
||||
class MinIOHelper {
|
||||
static final MinIOHelper _instance = MinIOHelper._internal();
|
||||
|
||||
factory MinIOHelper() => _instance;
|
||||
|
||||
final String ip = '14.103.235.151';
|
||||
final String fileUrl = 'http://14.103.235.151:9100';
|
||||
|
||||
MinIOHelper._internal() {
|
||||
_minio = Minio(
|
||||
endPoint: ip,
|
||||
port: 9100,
|
||||
accessKey: "tHSFfcDW8qpCzKa2Xg6Y",
|
||||
secretKey: "oq79EeYJ4jdczRp2IHUMCnbKtSw58NgDlG3sOkvX",
|
||||
useSSL: false,
|
||||
);
|
||||
}
|
||||
|
||||
late Minio _minio;
|
||||
|
||||
Future<String> uploadFile({
|
||||
required PlatformFile file,
|
||||
required String bucketName,
|
||||
Function(double)? onProgress,
|
||||
}) async {
|
||||
try {
|
||||
if (isImageFile(file.name)) {
|
||||
// 压缩图片
|
||||
final compressedFile = await compressImage(File(file.path!));
|
||||
|
||||
String hashName = await generateMD5HashName(compressedFile.path);
|
||||
String fileName = '$hashName${getFileExtension(file.name)}';
|
||||
|
||||
await _minio.fPutObject(bucketName, fileName, compressedFile.path);
|
||||
await compressedFile.delete();
|
||||
|
||||
return fileName;
|
||||
} else {
|
||||
String hashName = await generateMD5HashName(file.path!);
|
||||
String fileName = '$hashName${getFileExtension(file.name)}';
|
||||
|
||||
await _minio.fPutObject(bucketName, fileName, file.path!);
|
||||
|
||||
return fileName;
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('文件上传失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
48
lib/utils/toast_util.dart
Normal file
48
lib/utils/toast_util.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
class ToastUtil {
|
||||
static void success(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.TOP,
|
||||
backgroundColor: Colors.green,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
}
|
||||
|
||||
static void error(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.TOP,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
}
|
||||
|
||||
static void warning(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.TOP,
|
||||
backgroundColor: Colors.orange,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
}
|
||||
|
||||
static void info(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.TOP,
|
||||
backgroundColor: Colors.blue,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,83 @@ import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
import '../models/common_model.dart';
|
||||
import 'common_widget.dart';
|
||||
|
||||
class StatsCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String value;
|
||||
final String unit;
|
||||
final IconData icon;
|
||||
|
||||
const StatsCard(
|
||||
{super.key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.unit,
|
||||
required this.icon});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return CommonCard(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.primary.withAlpha(50),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(icon, size: 26, color: colors.primary),
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colors.primary,
|
||||
height: 1.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
unit,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LineChart extends StatelessWidget {
|
||||
final String title;
|
||||
@@ -168,6 +245,7 @@ class BarChart extends StatelessWidget {
|
||||
}
|
||||
|
||||
class DoubleBarChart extends StatelessWidget {
|
||||
final String title;
|
||||
final String xAxisName;
|
||||
final String yAxisName;
|
||||
final String unit;
|
||||
@@ -178,6 +256,7 @@ class DoubleBarChart extends StatelessWidget {
|
||||
|
||||
const DoubleBarChart(
|
||||
{super.key,
|
||||
required this.title,
|
||||
required this.xAxisName,
|
||||
required this.yAxisName,
|
||||
required this.unit,
|
||||
@@ -190,81 +269,88 @@ class DoubleBarChart extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return SfCartesianChart(
|
||||
// X轴配置(类别轴)
|
||||
primaryXAxis:
|
||||
CategoryAxis(majorGridLines: const MajorGridLines(width: 0)),
|
||||
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: const MajorGridLines(width: 0)),
|
||||
|
||||
// Y轴配置(数值轴)
|
||||
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')),
|
||||
// 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,
|
||||
// 图例配置
|
||||
legend: Legend(
|
||||
isVisible: true,
|
||||
position: LegendPosition.top,
|
||||
overflowMode: LegendItemOverflowMode.wrap,
|
||||
),
|
||||
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,
|
||||
|
||||
// 启用交互提示
|
||||
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,
|
||||
),
|
||||
],
|
||||
))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,3 +64,9 @@ Widget buildErrorInfo({
|
||||
Widget buildLoadingIndicator() {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
Widget buildEmptyData() {
|
||||
return Center(
|
||||
child: Text('暂无数据', style: TextStyle(fontSize: 16, color: Colors.grey)),
|
||||
);
|
||||
}
|
||||
|
||||
100
lib/widget/dialog_widget.dart
Normal file
100
lib/widget/dialog_widget.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
import 'package:awesome_dialog/awesome_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showAwesomeDialog({
|
||||
required BuildContext context,
|
||||
required Widget body,
|
||||
required VoidCallback onOk,
|
||||
required VoidCallback onCancel,
|
||||
}) {
|
||||
AwesomeDialog(
|
||||
context: context,
|
||||
dialogType: DialogType.noHeader,
|
||||
animType: AnimType.scale,
|
||||
body: body,
|
||||
dialogBackgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||
btnOkText: "确认",
|
||||
btnCancelText: "取消",
|
||||
btnOkColor: Colors.orange,
|
||||
btnCancelColor: Colors.grey,
|
||||
buttonsBorderRadius: BorderRadius.circular(10),
|
||||
headerAnimationLoop: false,
|
||||
dismissOnTouchOutside: false,
|
||||
dismissOnBackKeyPress: true,
|
||||
btnOk: ElevatedButton(
|
||||
onPressed: onOk,
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 6),
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'CustomFont'),
|
||||
),
|
||||
child: Text("确认"),
|
||||
),
|
||||
btnCancelOnPress: onCancel,
|
||||
).show();
|
||||
}
|
||||
|
||||
// 显示成功提示
|
||||
void showSuccessTip(BuildContext context, String message) {
|
||||
AwesomeDialog(
|
||||
context: context,
|
||||
dialogType: DialogType.success,
|
||||
animType: AnimType.scale,
|
||||
title: message,
|
||||
btnOkText: "好的",
|
||||
btnOkColor: Colors.green,
|
||||
btnOkOnPress: () {},
|
||||
autoHide: Duration(seconds: 2),
|
||||
).show();
|
||||
}
|
||||
|
||||
// 显示失败提示
|
||||
void showErrorTip(BuildContext context, String message) {
|
||||
AwesomeDialog(
|
||||
context: context,
|
||||
dialogType: DialogType.error,
|
||||
animType: AnimType.scale,
|
||||
title: message,
|
||||
btnOkText: "好的",
|
||||
btnOkColor: Colors.red,
|
||||
btnOkOnPress: () {},
|
||||
autoHide: Duration(seconds: 2),
|
||||
).show();
|
||||
}
|
||||
|
||||
// 确认对话框
|
||||
Future<bool?> showConfirmDialog(BuildContext context, String text) async {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('确认'),
|
||||
backgroundColor: colors.surfaceContainer,
|
||||
content: Text(text),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: colors.secondary.withAlpha(150)),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text('确认', style: TextStyle(color: colors.primary)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
52
lib/widget/loading_widget.dart
Normal file
52
lib/widget/loading_widget.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoadingDialog {
|
||||
static void show(BuildContext context, {String? message}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: AlertDialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
content: _buildLoadingContent(context, message),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static void hide(BuildContext context) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
}
|
||||
|
||||
static Widget _buildLoadingContent(BuildContext context, String? message) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(colors.primary),
|
||||
strokeWidth: 3,
|
||||
),
|
||||
if (message != null) ...[
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
message,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,26 +3,21 @@ import 'package:flutter/material.dart';
|
||||
import 'common_widget.dart';
|
||||
|
||||
class YearSelector extends StatefulWidget {
|
||||
final int initialYear;
|
||||
final int? minYear;
|
||||
final int? maxYear;
|
||||
final int currentYear;
|
||||
final Function(int) onYearChanged;
|
||||
|
||||
const YearSelector({
|
||||
super.key,
|
||||
required this.initialYear,
|
||||
required this.onYearChanged,
|
||||
this.minYear,
|
||||
this.maxYear,
|
||||
});
|
||||
const YearSelector(
|
||||
{super.key, required this.currentYear, required this.onYearChanged});
|
||||
|
||||
@override
|
||||
State<YearSelector> createState() => _YearSelectorState();
|
||||
}
|
||||
|
||||
// SingleTickerProviderStateMixin 动画控制器
|
||||
class _YearSelectorState extends State<YearSelector>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final int minYear = 2000;
|
||||
final int maxYear = 2100;
|
||||
|
||||
late int _currentYear;
|
||||
|
||||
// 用于动画效果
|
||||
@@ -32,7 +27,8 @@ class _YearSelectorState extends State<YearSelector>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentYear = widget.initialYear;
|
||||
|
||||
_currentYear = widget.currentYear;
|
||||
|
||||
// 初始化动画控制器
|
||||
_animationController = AnimationController(
|
||||
@@ -46,6 +42,17 @@ class _YearSelectorState extends State<YearSelector>
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(YearSelector oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.currentYear != widget.currentYear) {
|
||||
setState(() {
|
||||
_currentYear = widget.currentYear;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
@@ -54,24 +61,26 @@ class _YearSelectorState extends State<YearSelector>
|
||||
|
||||
/// 切换到上一年
|
||||
void _previousYear() {
|
||||
if (widget.minYear == null || _currentYear > widget.minYear!) {
|
||||
if (_currentYear > minYear) {
|
||||
_animateYearChange(() {
|
||||
final newYear = _currentYear - 1;
|
||||
setState(() {
|
||||
_currentYear--;
|
||||
_currentYear = newYear;
|
||||
});
|
||||
widget.onYearChanged(_currentYear);
|
||||
widget.onYearChanged(newYear);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换到下一年
|
||||
void _nextYear() {
|
||||
if (widget.maxYear == null || _currentYear < widget.maxYear!) {
|
||||
if (_currentYear < maxYear) {
|
||||
_animateYearChange(() {
|
||||
final newYear = _currentYear + 1;
|
||||
setState(() {
|
||||
_currentYear++;
|
||||
_currentYear = newYear;
|
||||
});
|
||||
widget.onYearChanged(_currentYear);
|
||||
widget.onYearChanged(newYear);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -91,7 +100,7 @@ class _YearSelectorState extends State<YearSelector>
|
||||
children: [
|
||||
CircleIconButton(
|
||||
icon: Icons.chevron_left,
|
||||
onPressed: () => _previousYear(),
|
||||
onPressed: _previousYear,
|
||||
),
|
||||
AnimatedBuilder(
|
||||
animation: _scaleAnimation,
|
||||
@@ -109,8 +118,8 @@ class _YearSelectorState extends State<YearSelector>
|
||||
),
|
||||
CircleIconButton(
|
||||
icon: Icons.chevron_right,
|
||||
onPressed: () => _nextYear(),
|
||||
)
|
||||
onPressed: _nextYear,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user