在这里插入图片描述

个人统计功能展示了用户的合同统计数据,包括合同总数、已签数、待签数等关键指标。这个功能需要提供清晰的统计视图、图表展示和数据分析。在这篇文章中,我们将详细讲解如何实现一个功能完整的个人统计功能。

个人统计的设计目标

个人统计功能需要实现以下设计目标:

  1. 关键指标展示:清晰展示合同总数、已签数、待签数等关键指标,让用户一目了然
  2. 图表可视化:使用图表展示统计数据,帮助用户更直观地理解数据趋势
  3. 时间范围选择:支持按不同时间范围查看统计数据,如本周、本月、本年
  4. 数据导出:支持导出统计数据为CSV或PDF格式,方便用户保存和分享
  5. 趋势分析:展示合同签署趋势,帮助用户了解业务发展
  6. 性能优化:确保统计页面加载快速,不会导致应用卡顿

统计数据模型的设计

首先,我们需要定义统计数据的模型。这个模型会包含所有需要展示的统计信息。统计数据模型是应用与统计数据之间的桥梁。通过定义清晰的数据模型,我们可以确保数据的一致性和类型安全。这样可以避免数据类型错误和数据不一致的问题。ContractStatistics模型包含了所有需要的统计信息,包括合同总数、已签数、待签数等。dailyStatisticscategoryStatistics字段提供了详细的统计维度。通过这个模型,我们可以轻松地序列化和反序列化统计数据。

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 % 3index % 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参数用于设置进度条的高度。backgroundColorvalueColor分别设置进度条的背景颜色和填充颜色。通过使用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?>用于创建响应式的统计数据变量。RxBoolRxString用于创建响应式的布尔值和字符串变量。通过使用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格式。这样用户可以快速理解日期信息。monthday属性用于获取日期的月份和日期。通过使用/分隔符,我们可以创建一个标准的日期格式。这种格式对用户来说更加直观和易于理解。在实际应用中,我们可以根据不同的需求使用不同的日期格式。

关键功能说明

这个个人统计功能包含了以下核心功能:

  1. 关键指标展示:使用网格布局展示四个关键指标
  2. 进度可视化:使用进度条展示签署率
  3. 趋势分析:展示每日合同统计趋势
  4. 分类统计:按合同类别统计数据
  5. 时间范围选择:支持按周、月、年查看数据
  6. 响应式设计:适配不同屏幕尺寸

设计考虑

个人统计功能的设计需要考虑以下几个方面:

  1. 数据准确性:确保统计数据的准确性和实时性
  2. 视觉清晰:使用颜色和图表使数据更容易理解
  3. 性能优化:缓存统计数据,避免频繁的API调用
  4. 用户体验:提供直观的界面和快速的加载速度
  5. 可访问性:确保所有用户都能理解统计数据

总结

这个个人统计功能为用户提供了一个清晰的统计视图,帮助用户了解他们的合同情况和业务发展趋势。通过提供多种统计维度和可视化方式,用户能够更好地管理他们的合同。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐