Flutter for OpenHarmony 电子合同签署App实战 - 个人统计实现
本文介绍了个人统计功能的实现方案,重点展示了合同管理系统的数据统计模块设计。文章首先明确了功能设计目标,包括关键指标展示、图表可视化、时间范围选择等6个方面。随后详细阐述了数据模型设计,包括主统计模型ContractStatistics以及日期和分类统计子模型。通过Dart代码示例展示了统计页面的实现过程,包括页面结构、数据加载和图表生成方法。该方案采用Flutter框架实现,支持多维度数据展示和

个人统计功能展示了用户的合同统计数据,包括合同总数、已签数、待签数等关键指标。这个功能需要提供清晰的统计视图、图表展示和数据分析。在这篇文章中,我们将详细讲解如何实现一个功能完整的个人统计功能。
个人统计的设计目标
个人统计功能需要实现以下设计目标:
- 关键指标展示:清晰展示合同总数、已签数、待签数等关键指标,让用户一目了然
- 图表可视化:使用图表展示统计数据,帮助用户更直观地理解数据趋势
- 时间范围选择:支持按不同时间范围查看统计数据,如本周、本月、本年
- 数据导出:支持导出统计数据为CSV或PDF格式,方便用户保存和分享
- 趋势分析:展示合同签署趋势,帮助用户了解业务发展
- 性能优化:确保统计页面加载快速,不会导致应用卡顿
统计数据模型的设计
首先,我们需要定义统计数据的模型。这个模型会包含所有需要展示的统计信息。统计数据模型是应用与统计数据之间的桥梁。通过定义清晰的数据模型,我们可以确保数据的一致性和类型安全。这样可以避免数据类型错误和数据不一致的问题。ContractStatistics模型包含了所有需要的统计信息,包括合同总数、已签数、待签数等。dailyStatistics和categoryStatistics字段提供了详细的统计维度。通过这个模型,我们可以轻松地序列化和反序列化统计数据。
class ContractStatistics {
final int totalContracts;
final int signedContracts;
final int pendingContracts;
final int expiredContracts;
final int rejectedContracts;
final double averageSigningTime;
final List<DailyStatistic> dailyStatistics;
final List<CategoryStatistic> categoryStatistics;
ContractStatistics({
required this.totalContracts,
required this.signedContracts,
required this.pendingContracts,
required this.expiredContracts,
required this.rejectedContracts,
required this.averageSigningTime,
required this.dailyStatistics,
required this.categoryStatistics,
});
}
这个数据模型包含了所有需要的统计信息。通过这个模型,我们可以统一管理统计数据,避免数据散落在各个地方。
日期统计和分类统计
为了支持更详细的统计分析,我们定义了日期统计和分类统计的模型。日期统计记录了每天的合同数量和已签署数量。分类统计记录了不同类别的合同统计信息。这样的设计使得我们可以灵活地展示不同维度的统计数据。
class DailyStatistic {
final DateTime date;
final int count;
final int signed;
DailyStatistic({
required this.date,
required this.count,
required this.signed,
});
factory DailyStatistic.fromJson(Map<String, dynamic> json) {
return DailyStatistic(
date: DateTime.parse(json['date'] as String),
count: json['count'] as int,
signed: json['signed'] as int,
);
}
}
class CategoryStatistic {
final String category;
final int count;
final int signed;
CategoryStatistic({
required this.category,
required this.count,
required this.signed,
});
}
日期统计和分类统计模型提供了结构化的方式来管理统计数据。通过这些模型,我们可以轻松地展示不同维度的统计信息。DailyStatistic模型包含了每天的合同数量和已签署数量。fromJson工厂构造函数用于从JSON数据创建模型实例。CategoryStatistic模型包含了不同类别的合同统计信息。通过使用这些模型,我们可以灵活地处理不同维度的统计数据。这种设计模式在处理复杂数据时非常有用。
统计页面的基本结构
现在让我们实现一个完整的统计页面。这个页面会展示关键指标、趋势图表和分类统计。统计页面是展示用户数据的重要界面。通过清晰的设计和合理的布局,我们可以帮助用户快速理解他们的数据。TabController用于管理多个标签页。通过使用SingleTickerProviderStateMixin,我们可以为TabController提供必要的TickerProvider。_selectedPeriod变量用于跟踪用户选择的时间范围。通过这种设计,用户可以在不同的统计视图之间切换。这种设计模式在展示多种统计维度时非常有效。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
class StatisticsPage extends StatefulWidget {
const StatisticsPage({Key? key}) : super(key: key);
State<StatisticsPage> createState() => _StatisticsPageState();
}
class _StatisticsPageState extends State<StatisticsPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
ContractStatistics? _statistics;
bool _isLoading = true;
String _selectedPeriod = 'month';
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_loadStatistics();
}
}
统计页面使用TabController来管理多个标签页。这样用户可以在不同的统计视图之间切换。通过标签页,我们可以在同一个页面中展示多种统计视图。
加载统计数据
加载统计数据是统计页面的核心功能。我们需要从API获取数据,然后更新UI。加载数据时,我们通常会显示加载指示器,让用户知道应用正在加载数据。当数据加载完成后,我们更新UI来显示统计数据。
Future<void> _loadStatistics() async {
setState(() {
_isLoading = true;
});
await Future.delayed(const Duration(milliseconds: 500));
setState(() {
_statistics = ContractStatistics(
totalContracts: 45,
signedContracts: 32,
pendingContracts: 10,
expiredContracts: 2,
rejectedContracts: 1,
averageSigningTime: 3.5,
dailyStatistics: _generateDailyStatistics(),
categoryStatistics: [
CategoryStatistic(category: 'Sales', count: 15, signed: 12),
CategoryStatistic(category: 'Purchase', count: 18, signed: 14),
CategoryStatistic(category: 'Service', count: 12, signed: 6),
],
);
_isLoading = false;
});
}
在这个方法中,我们模拟从API获取统计数据。实际应用中,我们会调用真实的API来获取数据。通过这样的设计,我们可以轻松地将模拟数据替换为真实数据。_generateDailyStatistics()方法生成日期统计数据。categoryStatistics列表包含了不同类别的统计信息。通过使用setState,我们可以在数据加载完成后更新UI。这种设计模式使得数据加载变得简单而高效。
生成日期统计数据
生成日期统计数据用于展示每日的合同统计趋势。这可以帮助用户了解业务的发展趋势。通过生成过去30天的统计数据,我们可以展示一个月的趋势。这样用户可以看到合同数量的变化趋势。
List<DailyStatistic> _generateDailyStatistics() {
final now = DateTime.now();
return List.generate(30, (index) {
final date = now.subtract(Duration(days: 29 - index));
return DailyStatistic(
date: date,
count: 1 + (index % 3),
signed: (index % 2 == 0) ? 1 : 0,
);
});
}
这个方法生成了过去30天的统计数据。通过这样的设计,我们可以展示一个月的趋势。List.generate方法用于生成指定数量的数据项。通过计算日期偏移,我们可以生成过去30天的日期。index % 3和index % 2用于生成不同的数据值。通过这种方式,我们可以快速生成测试数据。在实际应用中,这些数据应该从API获取。
关键指标卡片的展示
关键指标卡片是统计页面的重要组成部分。它们以清晰的方式展示了最重要的统计信息。关键指标卡片使用网格布局展示。这样可以在有限的空间内展示多个指标。通过使用不同的颜色来区分不同的指标,用户可以快速识别每个指标。
Widget _buildKeyMetricsSection() {
return Container(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Key Metrics',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12.h),
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 12.h,
crossAxisSpacing: 12.w,
childAspectRatio: 1.2,
children: [
_buildMetricCard(
title: 'Total',
value: _statistics!.totalContracts.toString(),
color: Colors.blue,
icon: Icons.description,
),
_buildMetricCard(
title: 'Signed',
value: _statistics!.signedContracts.toString(),
color: Colors.green,
icon: Icons.check_circle,
),
],
),
],
),
);
}
关键指标卡片使用网格布局展示。这样可以在有限的空间内展示多个指标。GridView.count用于创建网格布局。通过设置crossAxisCount: 2,我们可以创建两列的网格。shrinkWrap: true使网格只占据必要的空间。NeverScrollableScrollPhysics禁用网格的滚动。通过使用childAspectRatio,我们可以控制每个网格项的宽高比。这种设计模式可以有效地展示多个统计指标。
单个指标卡片的实现
每个指标卡片都包含一个图标、数值和标签。这样的设计使得用户可以快速理解每个指标的含义。指标卡片使用了颜色编码来区分不同的指标。这样用户可以快速识别每个指标。通过使用不同的颜色,我们可以使界面更加直观和易于理解。
Widget _buildMetricCard({
required String title,
required String value,
required Color color,
required IconData icon,
}) {
return Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(icon, color: color, size: 24.sp),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
title,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey,
),
),
],
),
],
),
);
}
指标卡片使用了颜色编码来区分不同的指标。这样用户可以快速识别每个指标。Container用于创建指标卡片的背景。通过使用withOpacity,我们可以创建半透明的背景。Column用于垂直排列图标和数值。通过使用mainAxisAlignment: MainAxisAlignment.spaceBetween,我们可以让图标和数值分别显示在顶部和底部。Icon用于显示指标的图标。这种设计模式使得指标卡片看起来更加清晰和易于理解。
签署率进度条
签署率进度条展示了已签署合同占总合同的比例。这是一个重要的指标,可以帮助用户了解签署进度。进度条使用LinearProgressIndicator来展示。这样用户可以直观地看到签署进度。通过进度条,用户可以快速了解签署的完成度。
Widget _buildProgressSection() {
final signedRate = _statistics!.totalContracts > 0
? (_statistics!.signedContracts / _statistics!.totalContracts) * 100
: 0.0;
return Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Signing Rate',
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold),
),
Text(
'${signedRate.toStringAsFixed(1)}%',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
SizedBox(height: 8.h),
ClipRRect(
borderRadius: BorderRadius.circular(4.r),
child: LinearProgressIndicator(
value: signedRate / 100,
minHeight: 8.h,
backgroundColor: Colors.grey.shade300,
valueColor: const AlwaysStoppedAnimation<Color>(Colors.green),
),
),
],
),
);
}
进度条使用LinearProgressIndicator来展示。这样用户可以直观地看到签署进度。LinearProgressIndicator是Flutter提供的标准进度条组件。通过设置value参数,我们可以控制进度条的填充比例。minHeight参数用于设置进度条的高度。backgroundColor和valueColor分别设置进度条的背景颜色和填充颜色。通过使用ClipRRect,我们可以为进度条添加圆角。这种设计模式使得进度条看起来更加美观。
日期趋势分析
日期趋势分析展示了每天的合同数量变化。这可以帮助用户了解业务的发展趋势。趋势标签页展示了每日的统计数据。这样用户可以看到合同数量的变化趋势。通过展示过去30天的数据,用户可以了解业务的发展方向。
Widget _buildTrendsTab() {
return SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Daily Trends',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12.h),
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8.r),
),
child: Column(
children: _statistics!.dailyStatistics
.map((stat) => _buildTrendItem(stat))
.toList(),
),
),
],
),
);
}
趋势标签页展示了每日的统计数据。这样用户可以看到合同数量的变化趋势。SingleChildScrollView用于包装趋势内容,确保当内容过长时能够滚动。Column组件用于垂直排列趋势项。通过使用_buildTrendItem方法,我们可以为每个日期创建一个趋势项。map方法用于遍历日期统计列表。通过展示过去30天的数据,用户可以了解业务的发展方向。
单个趋势项的实现
每个趋势项展示了特定日期的合同数量和已签署数量。趋势项使用进度条来展示合同数量。这样用户可以快速比较不同日期的数据。通过进度条,用户可以直观地看到每天的合同数量。
Widget _buildTrendItem(DailyStatistic stat) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
_formatDate(stat.date),
style: TextStyle(fontSize: 12.sp),
),
),
Expanded(
flex: 3,
child: Row(
children: [
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(2.r),
child: LinearProgressIndicator(
value: stat.count / 5,
minHeight: 6.h,
backgroundColor: Colors.grey.shade300,
valueColor: const AlwaysStoppedAnimation<Color>(
Colors.blue,
),
),
),
),
SizedBox(width: 8.w),
Text(
'${stat.count}',
style: TextStyle(fontSize: 12.sp),
),
],
),
),
SizedBox(width: 8.w),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
'${stat.signed}',
style: TextStyle(
fontSize: 12.sp,
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
趋势项使用进度条来展示合同数量。这样用户可以快速比较不同日期的数据。Row组件用于水平排列日期、进度条和已签署数量。通过使用Expanded,我们可以控制每个元素占据的空间。LinearProgressIndicator用于展示合同数量。通过使用_formatDate方法,我们可以将日期转换为易读的格式。Container用于创建已签署数量的背景。这种设计模式使得趋势项看起来更加清晰和易于理解。
分类统计展示
分类统计展示了不同类别的合同统计信息。这可以帮助用户了解不同类别的合同情况。分类标签页展示了不同类别的统计数据。这样用户可以了解各个类别的合同情况。通过展示不同类别的数据,用户可以了解业务的分布情况。
Widget _buildCategoriesTab() {
return SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'By Category',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12.h),
..._statistics!.categoryStatistics.map((category) {
final signedRate = category.count > 0
? (category.signed / category.count * 100)
: 0.0;
return _buildCategoryItem(category, signedRate);
}).toList(),
],
),
);
}
分类标签页展示了不同类别的统计数据。这样用户可以了解各个类别的合同情况。SingleChildScrollView用于包装分类内容,确保当内容过长时能够滚动。Column组件用于垂直排列分类项。通过使用_buildCategoryItem方法,我们可以为每个类别创建一个分类项。map方法用于遍历分类统计列表。通过展示不同类别的数据,用户可以了解业务的分布情况。
单个分类项的实现
每个分类项展示了特定类别的合同总数、已签署数和签署率。分类项使用进度条展示签署率。这样用户可以快速了解各个类别的签署情况。通过进度条,用户可以直观地看到每个类别的签署进度。
Widget _buildCategoryItem(
CategoryStatistic category,
double signedRate,
) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
category.category,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
),
),
Text(
'${category.signed}/${category.count}',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey,
),
),
],
),
SizedBox(height: 8.h),
ClipRRect(
borderRadius: BorderRadius.circular(4.r),
child: LinearProgressIndicator(
value: signedRate / 100,
minHeight: 6.h,
backgroundColor: Colors.grey.shade300,
valueColor: const AlwaysStoppedAnimation<Color>(
Colors.blue,
),
),
),
SizedBox(height: 4.h),
Text(
'${signedRate.toStringAsFixed(1)}% signed',
style: TextStyle(
fontSize: 11.sp,
color: Colors.grey,
),
),
],
),
);
}
分类项使用进度条展示签署率。这样用户可以快速了解各个类别的签署情况。Container用于创建分类项的背景。通过使用Border.all,我们可以为分类项添加边框。Row组件用于水平排列分类名称和已签署数量。通过使用LinearProgressIndicator,我们可以展示签署率。toStringAsFixed(1)用于格式化百分比。通过这种设计模式,用户可以直观地看到每个类别的签署进度。
时间范围选择
支持不同的时间范围选择可以让用户查看不同时期的统计数据。时间范围选择功能允许用户按周、月、年查看统计数据。这样用户可以了解不同时期的业务情况。通过提供多种时间范围选择,我们可以满足不同用户的需求。
void _changePeriod(String period) {
setState(() {
_selectedPeriod = period;
});
_loadStatistics();
}
当用户选择不同的时间范围时,我们重新加载统计数据。这样用户可以看到不同时期的统计信息。setState用于更新_selectedPeriod变量。通过调用_loadStatistics(),我们可以重新加载统计数据。这种设计模式使得用户可以灵活地查看不同时期的数据。通过提供多种时间范围选择,我们可以满足不同用户的需求。
统计控制器(使用GetX)
为了更好地管理统计数据,我们可以使用GetX框架创建一个控制器。GetX框架提供了响应式编程的能力。通过使用GetX,我们可以更好地管理应用的状态。这样可以避免在多个地方重复管理状态。Rx<ContractStatistics?>用于创建响应式的统计数据变量。RxBool和RxString用于创建响应式的布尔值和字符串变量。通过使用GetX,我们可以实现更高效的状态管理。
class StatisticsController extends GetxController {
final Rx<ContractStatistics?> statistics = Rx<ContractStatistics?>(null);
final RxBool isLoading = false.obs;
final RxString selectedPeriod = 'month'.obs;
void onInit() {
super.onInit();
loadStatistics();
}
Future<void> loadStatistics() async {
isLoading.value = true;
try {
// 从API加载统计数据
await Future.delayed(const Duration(milliseconds: 500));
// 设置统计数据
} finally {
isLoading.value = false;
}
}
void changePeriod(String period) {
selectedPeriod.value = period;
loadStatistics();
}
}
使用GetX框架可以更好地管理状态。这样可以避免在多个地方重复管理状态。
日期格式化功能
日期格式化功能用于将日期转换为可读的格式。日期格式化是展示日期的重要部分。通过格式化日期,我们可以使日期更加易读。这样用户可以快速理解日期信息。
String _formatDate(DateTime date) {
return '${date.month}/${date.day}';
}
日期格式化功能将日期转换为MM/DD格式。这样用户可以快速理解日期信息。month和day属性用于获取日期的月份和日期。通过使用/分隔符,我们可以创建一个标准的日期格式。这种格式对用户来说更加直观和易于理解。在实际应用中,我们可以根据不同的需求使用不同的日期格式。
关键功能说明
这个个人统计功能包含了以下核心功能:
- 关键指标展示:使用网格布局展示四个关键指标
- 进度可视化:使用进度条展示签署率
- 趋势分析:展示每日合同统计趋势
- 分类统计:按合同类别统计数据
- 时间范围选择:支持按周、月、年查看数据
- 响应式设计:适配不同屏幕尺寸
设计考虑
个人统计功能的设计需要考虑以下几个方面:
- 数据准确性:确保统计数据的准确性和实时性
- 视觉清晰:使用颜色和图表使数据更容易理解
- 性能优化:缓存统计数据,避免频繁的API调用
- 用户体验:提供直观的界面和快速的加载速度
- 可访问性:确保所有用户都能理解统计数据
总结
这个个人统计功能为用户提供了一个清晰的统计视图,帮助用户了解他们的合同情况和业务发展趋势。通过提供多种统计维度和可视化方式,用户能够更好地管理他们的合同。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)