在这里插入图片描述

待签合同功能展示了用户需要签署的所有合同。这个功能需要提供清晰的合同展示、优先级指示和快速签署入口。在这篇文章中,我们将详细讲解如何实现一个功能完整的待签合同页面。通过本文的学习,你将掌握如何构建一个高效的待签合同管理系统。

待签合同页面的设计目标

待签合同页面需要实现以下设计目标:

  1. 清晰的合同展示:展示所有待签合同
  2. 优先级指示:显示合同的优先级和截止日期
  3. 快速签署:提供快速签署入口
  4. 合同统计:显示待签合同的数量
  5. 过滤和排序:支持按优先级、截止日期等排序
  6. 提醒功能:提醒用户即将过期的合同
  7. 批量操作:支持批量签署合同
  8. 搜索功能:支持搜索待签合同

待签合同数据模型的定义

class PendingContract {
  final String id;
  final String name;
  final String counterparty;
  final DateTime createdAt;
  final DateTime dueDate;
  final int priority;
  final String description;
  final int daysUntilDue;

  PendingContract({
    required this.id,
    required this.name,
    required this.counterparty,
    required this.createdAt,
    required this.dueDate,
    required this.priority,
    required this.description,
    required this.daysUntilDue,
  });

在定义待签合同的数据模型时,我们需要包含合同的基本信息,包括合同的唯一标识符、合同名称、对方当事人、创建时间、截止日期、优先级、合同描述和距离截止日期的天数。这些字段构成了待签合同的核心数据结构,为后续的合同展示、排序和过滤提供了基础数据支持。通过这个数据模型,我们可以清晰地表示每一份待签合同的关键信息,便于在UI层进行展示和交互。

  bool get isUrgent => daysUntilDue <= 3;
  bool get isOverdue => daysUntilDue < 0;
}

在数据模型中,我们还定义了两个计算属性:isUrgent用于判断合同是否紧急(距离截止日期3天以内),isOverdue用于判断合同是否已经逾期。这两个属性在UI层会被用来改变合同卡片的显示样式,例如使用不同的颜色来提示用户。通过这种方式,我们可以在数据层面就处理好业务逻辑,使得UI层的代码更加简洁清晰。这个设计模式在Flutter开发中是非常常见的,它遵循了单一职责原则,让数据模型专注于数据的表示和计算。

待签合同页面的基本结构

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class PendingContractsPage extends StatefulWidget {
  const PendingContractsPage({Key? key}) : super(key: key);

  
  State<PendingContractsPage> createState() => _PendingContractsPageState();
}

class _PendingContractsPageState extends State<PendingContractsPage> {
  List<PendingContract> _contracts = [];
  bool _isLoading = true;
  String _sortBy = 'dueDate';

待签合同页面采用了StatefulWidget的设计模式,这样可以在用户交互时动态更新页面的状态。我们定义了三个关键的状态变量:_contracts用于存储待签合同列表,_isLoading用于标记数据加载状态,_sortBy用于记录当前的排序方式。通过这些状态变量,我们可以实现动态的数据加载、排序和刷新功能。在实际开发中,这些状态变量会根据用户的操作而改变,从而触发页面的重新构建。

  
  void initState() {
    super.initState();
    _loadContracts();
  }

  Future<void> _loadContracts() async {
    setState(() => _isLoading = true);
    await Future.delayed(const Duration(milliseconds: 500));
    setState(() {
      _contracts = [
        PendingContract(
          id: '1',
          name: 'Service Agreement',
          counterparty: 'ABC Company',
          createdAt: DateTime.now().subtract(const Duration(days: 5)),
          dueDate: DateTime.now().add(const Duration(days: 2)),
          priority: 1,
          description: 'Professional service agreement',
          daysUntilDue: 2,
        ),

initState生命周期方法中,我们调用了_loadContracts方法来加载待签合同数据。这个方法首先设置加载状态为true,然后模拟网络请求延迟,最后将加载的合同数据存储到_contracts列表中。在实际应用中,这里应该调用真实的API来获取服务器上的合同数据。通过这种异步加载的方式,我们可以在数据加载期间显示加载动画,提升用户体验。同时,我们使用了setState方法来通知Flutter框架状态已经改变,需要重新构建UI。

        PendingContract(
          id: '2',
          name: 'Employment Contract',
          counterparty: 'John Doe',
          createdAt: DateTime.now().subtract(const Duration(days: 3)),
          dueDate: DateTime.now().add(const Duration(days: 7)),
          priority: 2,
          description: 'Employment contract for new hire',
          daysUntilDue: 7,
        ),
      ];
      _isLoading = false;
    });
  }

  void _sortContracts() {
    switch (_sortBy) {
      case 'dueDate':
        _contracts.sort((a, b) => a.dueDate.compareTo(b.dueDate));
        break;
      case 'priority':
        _contracts.sort((a, b) => a.priority.compareTo(b.priority));
        break;
      case 'name':
        _contracts.sort((a, b) => a.name.compareTo(b.name));
        break;
    }
  }

在这个代码段中,我们创建了两个示例的待签合同对象,分别代表不同的合同类型和优先级。_sortContracts方法实现了多种排序方式的支持,包括按截止日期排序、按优先级排序和按合同名称排序。这个方法使用了Dart中的switch语句来处理不同的排序选项,每种排序方式都使用了列表的sort方法和比较函数来实现。通过这种灵活的排序机制,用户可以根据自己的需求快速找到最重要的合同。

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Pending Contracts'),
        elevation: 0,
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : Column(
              children: [
                _buildStatisticsSection(),
                _buildSortBar(),
                Expanded(
                  child: _contracts.isEmpty
                      ? _buildEmptyState()
                      : ListView.builder(
                          padding: EdgeInsets.all(16.w),
                          itemCount: _contracts.length,
                          itemBuilder: (context, index) {
                            return _buildContractItem(_contracts[index]);
                          },
                        ),
                ),
              ],
            ),
    );
  }

build方法中,我们构建了整个待签合同页面的UI结构。页面采用了Scaffold作为基础框架,包含了AppBar和body两个主要部分。在body中,我们使用了条件判断来处理加载状态:当数据正在加载时显示加载动画,当数据加载完成后显示合同列表。页面的主体部分由三个子组件组成:统计信息区域、排序工具栏和合同列表。当合同列表为空时,我们显示一个友好的空状态提示。

  Widget _buildStatisticsSection() {
    final urgentCount = _contracts.where((c) => c.isUrgent).length;
    final overdueCount = _contracts.where((c) => c.isOverdue).length;

    return Container(
      padding: EdgeInsets.all(16.w),
      color: Colors.blue.shade50,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          _buildStatItem('Total', _contracts.length.toString(), Colors.blue),
          _buildStatItem('Urgent', urgentCount.toString(), Colors.orange),
          _buildStatItem('Overdue', overdueCount.toString(), Colors.red),
        ],
      ),
    );
  }

  Widget _buildStatItem(String label, String count, Color color) {
    return Column(
      children: [
        Text(
          count,
          style: TextStyle(
            fontSize: 18.sp,
            fontWeight: FontWeight.bold,
            color: color,
          ),
        ),
        SizedBox(height: 4.h),
        Text(
          label,
          style: TextStyle(fontSize: 12.sp, color: Colors.grey),
        ),
      ],
    );
  }

统计信息区域是待签合同页面的重要组成部分,它为用户提供了一个快速的数据概览。在这个区域中,我们展示了三个关键的统计指标:待签合同总数、紧急合同数量和已逾期合同数量。通过使用where方法和计算属性,我们可以动态地计算出这些统计数据。每个统计项都使用了不同的颜色来区分,蓝色表示总数、橙色表示紧急、红色表示已逾期,这样的设计可以让用户一目了然地了解合同的整体状况。_buildStatItem方法是一个辅助方法,用于构建单个统计项的UI,它接收标签、数值和颜色作为参数。

  Widget _buildSortBar() {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
      child: DropdownButton<String>(
        value: _sortBy,
        isExpanded: true,
        underline: Container(),
        items: [
          DropdownMenuItem(value: 'dueDate', child: const Text('Sort by Due Date')),
          DropdownMenuItem(value: 'priority', child: const Text('Sort by Priority')),
          DropdownMenuItem(value: 'name', child: const Text('Sort by Name')),
        ],
        onChanged: (value) {
          setState(() {
            _sortBy = value ?? 'dueDate';
            _sortContracts();
          });
        },
      ),
    );
  }

排序工具栏提供了一个下拉菜单,允许用户选择不同的排序方式。当用户选择一个排序选项时,我们会更新_sortBy状态变量,然后调用_sortContracts方法重新排序合同列表。这个设计使得用户可以根据自己的需求快速调整合同的显示顺序。下拉菜单的isExpanded属性设置为true,使得菜单宽度与容器宽度相同,提供了更好的用户体验。

  Widget _buildEmptyState() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.check_circle, size: 64.sp, color: Colors.grey.shade300),
          SizedBox(height: 16.h),
          Text(
            'No Pending Contracts',
            style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
          ),
          SizedBox(height: 8.h),
          Text(
            'All contracts have been signed!',
            style: TextStyle(fontSize: 12.sp, color: Colors.grey),
          ),
        ],
      ),
    );
  }

  Widget _buildContractItem(PendingContract contract) {
    final daysColor = contract.isOverdue
        ? Colors.red
        : contract.isUrgent
            ? Colors.orange
            : Colors.green;

    return GestureDetector(
      onTap: () => Get.to(() => QuickSignPage(contractId: contract.id)),
      child: 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: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        contract.name,
                        style: TextStyle(
                          fontSize: 13.sp,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      SizedBox(height: 4.h),
                      Text(
                        contract.counterparty,
                        style: TextStyle(fontSize: 11.sp, color: Colors.grey),
                      ),
                    ],
                  ),
                ),
                Container(
                  padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
                  decoration: BoxDecoration(
                    color: daysColor.withOpacity(0.2),
                    borderRadius: BorderRadius.circular(4.r),
                  ),
                  child: Text(
                    contract.isOverdue
                        ? 'Overdue'
                        : '${contract.daysUntilDue}d left',
                    style: TextStyle(
                      fontSize: 10.sp,
                      color: daysColor,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ],
            ),
            SizedBox(height: 8.h),
            Text(
              contract.description,
              style: TextStyle(fontSize: 11.sp, color: Colors.grey),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
            SizedBox(height: 8.h),
            Align(
              alignment: Alignment.centerRight,
              child: ElevatedButton(
                onPressed: () => Get.to(() => QuickSignPage(contractId: contract.id)),
                child: const Text('Sign Now'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

空状态界面是一个很好的用户体验设计,当用户没有任何待签合同时,我们显示一个友好的提示信息而不是一个空白的屏幕。这个界面包含了一个大的勾选图标、标题和描述文本,让用户明白他们已经完成了所有的合同签署任务。这种设计可以提升用户的满足感和应用的专业度。

合同卡片项是列表中的核心组件,每个卡片代表一份待签合同。我们使用了GestureDetector来处理用户的点击事件,当用户点击卡片时,会导航到快速签署页面。卡片的背景色和边框都经过了精心设计,使得卡片看起来清晰而专业。在卡片的右上角,我们显示了一个状态标签,根据合同的截止日期动态改变颜色:红色表示已逾期、橙色表示紧急、绿色表示正常。这种视觉反馈可以帮助用户快速识别需要立即处理的合同。

卡片的内容区域包含了合同的关键信息:合同名称、对方当事人、合同描述和一个"立即签署"按钮。合同名称使用了较大的字体和加粗样式,使其成为卡片的视觉焦点。对方当事人信息以较小的灰色文字显示,提供了额外的上下文信息。合同描述被限制为单行显示,超出部分用省略号表示,这样可以保持卡片的整洁外观。最后,"立即签署"按钮位于卡片的右下角,提供了快速进入签署流程的入口。

待签合同页面展示了所有需要签署的合同,并提供了快速签署入口。

关键功能说明

这个待签合同功能包含了以下核心功能:

  1. 合同展示:清晰展示所有待签合同
  2. 优先级指示:显示合同的优先级和截止日期
  3. 快速签署:提供快速签署入口
  4. 合同统计:显示待签合同的数量
  5. 排序功能:支持按多种方式排序

待签合同功能的核心在于为用户提供一个清晰、高效的合同管理界面。通过统计信息区域,用户可以一目了然地了解待签合同的整体情况,包括总数、紧急合同数量和已逾期合同数量。排序功能允许用户根据自己的需求调整合同的显示顺序,无论是按截止日期、优先级还是合同名称排序。这些功能的组合使得用户能够快速定位和处理最重要的合同。

设计考虑

待签合同页面的设计需要考虑以下几个方面:

  1. 用户体验:提供直观的界面和快速的操作流程
  2. 视觉设计:使用颜色来表示优先级和截止日期
  3. 性能优化:优化列表性能
  4. 响应式设计:适配不同屏幕尺寸
  5. 可访问性:确保所有用户都能理解合同信息

在设计待签合同页面时,我们需要充分考虑用户的使用场景和需求。用户体验是首要考虑因素,我们需要确保用户能够快速找到和签署待签合同,而不是被复杂的界面所困扰。视觉设计方面,我们使用了颜色编码来表示不同的合同状态,这样用户可以通过颜色快速识别紧急和已逾期的合同。性能优化对于处理大量合同列表至关重要,我们使用了ListView.builder来实现高效的列表渲染。响应式设计确保了应用在不同屏幕尺寸上都能正常显示,我们使用了flutter_screenutil库来处理不同设备的适配。最后,可访问性设计确保了所有用户,包括有视觉障碍的用户,都能理解和使用这个功能。

总结

这个待签合同功能为用户提供了一个完整的待签合同管理界面,使得用户能够快速了解和签署待签合同。通过合理的信息架构、清晰的视觉设计和高效的交互流程,我们创建了一个用户友好的合同管理系统。这个功能不仅提高了用户的工作效率,还提升了应用的整体用户体验。在实际开发中,我们可以根据具体需求进一步扩展这个功能,例如添加合同搜索、过滤、批量操作等高级功能。


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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐