feat:增加公共组件

This commit is contained in:
2025-11-22 15:19:34 +08:00
parent bc08235794
commit f5ddc4ee04
17 changed files with 1762 additions and 3 deletions

435
lib/widget/chart.dart Normal file
View 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; // 普通颜色
}
}