feat:增加对话框工具类

This commit is contained in:
2025-11-23 23:07:36 +08:00
parent 98b389a6ff
commit 1953e43ca4
11 changed files with 669 additions and 93 deletions

View File

@@ -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,
),
],
))
]);
}
}

View File

@@ -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)),
);
}

View 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)),
),
],
);
},
);
}

View 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]),
),
],
],
),
);
}
}

View File

@@ -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,
),
],
);
}