在这里插入图片描述

创建模板功能是电子合同应用的核心功能之一。这个功能允许用户根据自己的需求创建自定义的合同模板,包括模板的基本信息、内容编辑、分类设置等。通过创建模板功能,用户可以快速生成符合自己业务需求的合同模板,提高工作效率。在这篇文章中,我们将详细讲解如何实现一个功能完整、用户友好的创建模板页面。

创建模板功能的设计目标

创建模板功能需要实现以下设计目标:

  1. 模板基本信息输入:提供清晰的表单界面,让用户能够输入模板名称、描述、分类等基本信息。这些信息是模板的元数据,用于标识和分类模板。
  2. 模板内容编辑:提供富文本编辑器或简单的文本编辑功能,让用户能够编辑模板的具体内容。用户可以添加合同条款、条件等内容。
  3. 分类和标签管理:支持为模板添加分类和标签,方便用户后续查找和管理模板。分类可以帮助用户快速定位所需的模板。
  4. 模板预览:提供模板预览功能,让用户在保存前能够查看模板的最终效果。这样可以避免用户保存错误的模板。
  5. 保存和发布:支持保存模板为草稿或直接发布模板。用户可以先保存为草稿,后续继续编辑。
  6. 错误处理和验证:完善的表单验证和错误处理机制,确保用户输入的数据有效。

模板数据模型的设计

首先,我们需要定义模板的数据模型。这个模型会包含模板的所有必要信息,包括基本信息、内容、分类等。数据模型是应用与数据之间的桥梁,通过定义清晰的模型,我们可以确保数据的一致性和类型安全。

class TemplateModel {
  final String id;
  final String name;
  final String description;
  final String category;
  final String content;
  final List<String> tags;
  final DateTime createdDate;
  final DateTime updatedDate;
  final String status;
  final String author;
  final int version;

  TemplateModel({
    required this.id,
    required this.name,
    required this.description,
    required this.category,
    required this.content,
    required this.tags,
    required this.createdDate,
    required this.updatedDate,
    required this.status,
    required this.author,
    required this.version,
  });

  factory TemplateModel.fromJson(Map<String, dynamic> json) {
    return TemplateModel(
      id: json['id'] as String,
      name: json['name'] as String,
      description: json['description'] as String,
      category: json['category'] as String,
      content: json['content'] as String,
      tags: List<String>.from(json['tags'] as List),
      createdDate: DateTime.parse(json['createdDate'] as String),
      updatedDate: DateTime.parse(json['updatedDate'] as String),
      status: json['status'] as String,
      author: json['author'] as String,
      version: json['version'] as int,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'description': description,
      'category': category,
      'content': content,
      'tags': tags,
      'createdDate': createdDate.toIso8601String(),
      'updatedDate': updatedDate.toIso8601String(),
      'status': status,
      'author': author,
      'version': version,
    };
  }
}

这个数据模型包含了模板的所有必要信息。通过这个模型,我们可以统一管理模板数据,避免数据散落在各个地方。模型中的每个字段都有明确的含义,使得代码更加易于理解和维护。通过提供fromJsontoJson方法,我们可以轻松地进行数据序列化和反序列化,这是处理API响应的标准做法。version字段用于跟踪模板的版本,支持模板的版本管理和更新。status字段表示模板的状态(如草稿、已发布等),允许用户管理模板的生命周期。在实际应用中,这个模型可以与数据库ORM框架集成,实现自动的数据持久化。

创建模板页面的基本结构

现在让我们实现创建模板页面。这个页面会包含多个部分,包括模板基本信息输入、内容编辑、分类选择等。页面使用StatefulWidget来管理表单的状态,这样设计的好处是能够根据用户的输入动态更新页面。

创建模板页面的主要职责是收集用户输入的模板信息,进行验证,然后保存到数据库或发送到服务器。页面需要提供清晰的用户界面,让用户能够轻松完成模板创建的各个步骤。

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

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

  
  State<CreateTemplatePage> createState() => _CreateTemplatePageState();
}

class _CreateTemplatePageState extends State<CreateTemplatePage> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _descriptionController = TextEditingController();
  final _contentController = TextEditingController();
  String _selectedCategory = 'Business';
  List<String> _selectedTags = [];
  bool _isLoading = false;
  String? _errorMessage;

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

  void _initializeForm() {
    // 初始化表单数据
  }

  
  void dispose() {
    _nameController.dispose();
    _descriptionController.dispose();
    _contentController.dispose();
    super.dispose();
  }
}

页面使用StatefulWidget来管理表单的状态。通过在initState中初始化表单数据,我们可以确保页面加载时表单已经准备好了。在dispose方法中,我们释放控制器资源,避免内存泄漏。_formKey用于管理表单的验证状态,允许我们在需要时验证整个表单。多个TextEditingController分别管理不同输入字段的内容,这样可以精确控制每个字段的值。_isLoading_errorMessage状态变量用于跟踪异步操作的状态和错误信息。在dispose中正确释放资源是Flutter开发的最佳实践,可以防止内存泄漏。

模板名称和描述输入

模板名称和描述是模板的基本信息,用户需要输入这些信息来标识模板。模板名称应该简洁明了,能够清楚地表达模板的用途。描述可以提供更详细的信息,帮助用户了解模板的内容。

Widget _buildNameInput() {
  return TextFormField(
    controller: _nameController,
    decoration: InputDecoration(
      labelText: 'Template Name',
      hintText: 'Enter template name',
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8.r),
      ),
      prefixIcon: const Icon(Icons.description),
    ),
    validator: (value) {
      if (value == null || value.isEmpty) {
        return 'Please enter template name';
      }
      if (value.length < 3) {
        return 'Template name must be at least 3 characters';
      }
      return null;
    },
  );
}

模板名称输入使用TextFormField来实现。通过validator参数,我们可以验证用户输入的数据。如果用户输入的名称为空或长度不足,会显示错误信息。TextFormField是Flutter中用于表单输入的标准组件,它集成了验证功能。prefixIcon提供了视觉提示,帮助用户识别这是一个模板名称字段。通过OutlineInputBorderborderRadius,我们创建了一个现代化的输入框设计。验证器函数在用户提交表单时被调用,确保数据的有效性。这种设计模式在Flutter表单开发中是标准做法。

模板描述输入

模板描述提供了关于模板的更详细信息。用户可以在描述中说明模板的用途、适用场景等。描述字段通常是可选的,但提供描述可以帮助其他用户更好地理解模板。

Widget _buildDescriptionInput() {
  return TextFormField(
    controller: _descriptionController,
    decoration: InputDecoration(
      labelText: 'Description',
      hintText: 'Enter template description',
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8.r),
      ),
      prefixIcon: const Icon(Icons.notes),
    ),
    maxLines: 3,
    validator: (value) {
      if (value != null && value.length > 500) {
        return 'Description must not exceed 500 characters';
      }
      return null;
    },
  );
}

描述输入使用maxLines: 3来允许用户输入多行文本。通过validator,我们限制描述的长度不超过500个字符,避免过长的描述。TextFormFieldmaxLines参数控制输入框的高度和允许的最大行数。通过设置合理的字符限制,我们可以确保数据库存储的效率。prefixIcon使用笔记图标,帮助用户快速识别这是描述字段。验证器检查字符长度,提供了数据质量的保证。这种多行输入的设计在需要用户输入较长文本的场景中非常有用。

分类选择功能

分类选择功能允许用户为模板选择一个分类。分类可以帮助用户快速定位所需的模板。应用可以预定义一些常见的分类,如商业合同、法律文件、个人协议等。

Widget _buildCategorySelector() {
  final categories = [
    'Business',
    'Legal',
    'Personal',
    'Employment',
    'Real Estate',
    'Finance',
  ];

  return DropdownButtonFormField<String>(
    value: _selectedCategory,
    decoration: InputDecoration(
      labelText: 'Category',
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8.r),
      ),
      prefixIcon: const Icon(Icons.category),
    ),
    items: categories.map((category) {
      return DropdownMenuItem(
        value: category,
        child: Text(category),
      );
    }).toList(),
    onChanged: (value) {
      setState(() {
        _selectedCategory = value ?? 'Business';
      });
    },
  );
}

分类选择使用DropdownButtonFormField来实现。用户可以从预定义的分类列表中选择一个分类。当用户选择分类时,我们使用setState来更新选中的分类。DropdownButtonFormField是Flutter中用于下拉菜单选择的标准组件,它集成了表单验证功能。通过map方法,我们可以动态生成下拉菜单项。prefixIcon提供了视觉提示,帮助用户识别这是分类选择字段。onChanged回调在用户选择新的分类时被触发,允许我们实时更新应用状态。这种设计确保了用户界面的响应性和数据的一致性。

标签选择功能

标签选择功能允许用户为模板添加多个标签。标签可以帮助用户更精细地分类和搜索模板。用户可以从预定义的标签列表中选择,也可以输入自定义标签。

标签功能的实现需要考虑以下几点:首先,我们需要提供一个标签列表供用户选择。其次,我们需要支持用户输入自定义标签。最后,我们需要显示已选择的标签,并允许用户删除不需要的标签。

Widget _buildTagSelector() {
  final availableTags = [
    'Urgent',
    'Important',
    'Review',
    'Approved',
    'Draft',
    'Final',
  ];

  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(
        'Tags',
        style: TextStyle(
          fontSize: 14.sp,
          fontWeight: FontWeight.bold,
        ),
      ),
      SizedBox(height: 8.h),
      Wrap(
        spacing: 8.w,
        children: availableTags.map((tag) {
          final isSelected = _selectedTags.contains(tag);
          return FilterChip(
            label: Text(tag),
            selected: isSelected,
            onSelected: (selected) {
              setState(() {
                if (selected) {
                  _selectedTags.add(tag);
                } else {
                  _selectedTags.remove(tag);
                }
              });
            },
          );
        }).toList(),
      ),
    ],
  );
}

标签选择使用FilterChip来实现。用户可以点击标签来选择或取消选择。已选择的标签会显示不同的样式,让用户清楚地看到哪些标签已被选中。Wrap组件用于自动换行显示标签,确保在不同屏幕尺寸上都能正确显示。FilterChip提供了一个直观的选择界面,用户可以通过点击来切换标签的选中状态。通过selected属性,我们可以控制标签的视觉状态。onSelected回调在用户点击标签时被触发,允许我们更新选中的标签列表。这种设计提供了一个灵活而用户友好的多选界面。

模板内容编辑

模板内容编辑是创建模板的核心功能。用户需要在这里输入模板的具体内容,包括合同条款、条件等。内容编辑器应该提供基本的文本编辑功能,如格式化、撤销等。

Widget _buildContentEditor() {
  return TextFormField(
    controller: _contentController,
    decoration: InputDecoration(
      labelText: 'Template Content',
      hintText: 'Enter template content here',
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8.r),
      ),
      prefixIcon: const Icon(Icons.edit),
    ),
    maxLines: 10,
    validator: (value) {
      if (value == null || value.isEmpty) {
        return 'Please enter template content';
      }
      if (value.length < 10) {
        return 'Content must be at least 10 characters';
      }
      return null;
    },
  );
}

内容编辑器使用TextFormField来实现。通过maxLines: 10,我们允许用户输入多行内容。通过validator,我们确保用户输入的内容不为空且长度足够。TextFormFieldmaxLines参数设置为10,提供了足够的空间让用户输入模板内容。验证器检查内容的最小长度,确保模板内容不会过于简洁。prefixIcon使用编辑图标,帮助用户识别这是内容编辑字段。在实际应用中,我们可以使用富文本编辑器库来提供更高级的编辑功能,如格式化、撤销等。这种多行输入的设计为用户提供了充足的空间来编辑模板内容。

模板预览功能

模板预览功能让用户在保存前能够查看模板的最终效果。这样可以避免用户保存错误的模板。预览应该显示模板的所有信息,包括名称、描述、分类、标签和内容。

预览功能的实现需要考虑以下几点:首先,我们需要收集用户输入的所有信息。其次,我们需要以清晰的格式展示这些信息。最后,我们需要提供返回编辑的选项,让用户可以继续修改模板。

void _showPreview() {
  Get.dialog(
    AlertDialog(
      title: const Text('Template Preview'),
      content: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Name: ${_nameController.text}',
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 8.h),
            Text(
              'Category: $_selectedCategory',
            ),
            SizedBox(height: 8.h),
            Text(
              'Description: ${_descriptionController.text}',
            ),
            SizedBox(height: 8.h),
            Text(
              'Tags: ${_selectedTags.join(", ")}',
            ),
            SizedBox(height: 12.h),
            Text(
              'Content:',
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 8.h),
            Text(_contentController.text),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Get.back(),
          child: const Text('Back to Edit'),
        ),
        TextButton(
          onPressed: _saveTemplate,
          child: const Text('Save Template'),
        ),
      ],
    ),
  );
}

预览功能使用Get.dialog来显示一个对话框。对话框中显示了模板的所有信息。用户可以选择返回编辑或保存模板。SingleChildScrollView用于包装预览内容,确保当内容过长时能够滚动。通过ColumnSizedBox的组合,我们创建了一个清晰的信息展示布局。预览对话框显示了模板的所有关键信息,包括名称、分类、描述、标签和内容。通过提供"返回编辑"和"保存模板"两个按钮,用户可以灵活地选择下一步操作。这种预览功能可以帮助用户在保存前确认模板的内容是否正确。

表单验证

表单验证是确保用户输入数据有效的重要步骤。我们需要验证所有必填字段都已填写,并且数据格式正确。表单验证应该在用户提交表单时进行。

bool _validateForm() {
  if (_formKey.currentState?.validate() ?? false) {
    if (_nameController.text.isEmpty) {
      _showError('Please enter template name');
      return false;
    }
    if (_contentController.text.isEmpty) {
      _showError('Please enter template content');
      return false;
    }
    return true;
  }
  return false;
}

表单验证方法检查所有必填字段。如果验证失败,会显示错误信息。只有当所有字段都有效时,才会返回true_formKey.currentState?.validate()调用所有TextFormField的验证器,确保所有字段都符合要求。通过链式检查,我们可以提供更详细的错误信息。_showError方法用于向用户显示验证错误。这种分层的验证方法确保了数据的完整性和有效性。在实际应用中,我们可以添加更复杂的验证逻辑,如检查模板名称的唯一性。

保存模板功能

保存模板功能将用户输入的模板信息保存到数据库或发送到服务器。保存前需要进行表单验证,确保数据有效。保存成功后,应该显示成功提示,并返回到模板列表页面。

Future<void> _saveTemplate() async {
  if (!_validateForm()) {
    return;
  }

  setState(() {
    _isLoading = true;
    _errorMessage = null;
  });

  try {
    final template = TemplateModel(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      name: _nameController.text,
      description: _descriptionController.text,
      category: _selectedCategory,
      content: _contentController.text,
      tags: _selectedTags,
      createdDate: DateTime.now(),
      updatedDate: DateTime.now(),
      status: 'draft',
      author: 'Current User',
      version: 1,
    );

    // 这里应该调用API来保存模板
    await Future.delayed(const Duration(seconds: 1));

    Get.back();
    Get.snackbar(
      'Success',
      'Template saved successfully',
      snackPosition: SnackPosition.BOTTOM,
    );
  } catch (e) {
    setState(() {
      _errorMessage = e.toString();
    });
    Get.snackbar(
      'Error',
      'Failed to save template: $_errorMessage',
      snackPosition: SnackPosition.BOTTOM,
    );
  } finally {
    setState(() {
      _isLoading = false;
    });
  }
}

保存模板方法首先验证表单数据。然后创建一个TemplateModel对象,包含用户输入的所有信息。最后,将模板保存到数据库或发送到服务器。_isLoading标志用于禁用保存按钮,防止用户重复提交。通过Future.delayed模拟网络请求,我们可以测试加载状态的UI表现。try-catch-finally块确保无论操作成功还是失败,都能正确处理状态。Get.snackbar用于显示成功或失败的提示信息。在实际应用中,这里应该调用真实的API来保存模板到服务器。

错误处理

错误处理是确保应用稳定性的重要部分。我们需要捕获各种可能的错误,并向用户显示有意义的错误信息。错误处理应该包括网络错误、验证错误、服务器错误等。

void _showError(String message) {
  Get.snackbar(
    'Error',
    message,
    snackPosition: SnackPosition.BOTTOM,
    backgroundColor: Colors.red,
    colorText: Colors.white,
  );
}

错误处理方法使用Get.snackbar来显示错误信息。用户可以看到清晰的错误提示,了解发生了什么问题。Get.snackbar提供了一个非侵入式的错误提示方式,不会中断用户的操作流程。通过使用不同的背景颜色(如红色表示错误),我们可以为用户提供视觉反馈。snackPosition: SnackPosition.BOTTOM将提示显示在屏幕底部,不会遮挡重要的内容。在实际应用中,我们应该记录错误日志,以便进行调试和分析。这种错误处理方法提高了应用的用户友好性。

页面的完整构建

现在让我们将所有部分组合在一起,构建完整的创建模板页面。页面应该包含所有输入字段、按钮和其他UI元素。


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Create Template'),
      centerTitle: true,
      elevation: 0,
    ),
    body: SingleChildScrollView(
      padding: EdgeInsets.all(16.w),
      child: Form(
        key: _formKey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildNameInput(),
            SizedBox(height: 16.h),
            _buildDescriptionInput(),
            SizedBox(height: 16.h),
            _buildCategorySelector(),
            SizedBox(height: 16.h),
            _buildTagSelector(),
            SizedBox(height: 16.h),
            _buildContentEditor(),
            SizedBox(height: 24.h),
            Row(
              children: [
                Expanded(
                  child: OutlinedButton(
                    onPressed: _showPreview,
                    child: const Text('Preview'),
                  ),
                ),
                SizedBox(width: 12.w),
                Expanded(
                  child: ElevatedButton(
                    onPressed: _isLoading ? null : _saveTemplate,
                    child: _isLoading
                        ? const SizedBox(
                            height: 20,
                            width: 20,
                            child: CircularProgressIndicator(
                              strokeWidth: 2,
                            ),
                          )
                        : const Text('Save Template'),
                  ),
                ),
              ],
            ),
            SizedBox(height: 16.h),
          ],
        ),
      ),
    ),
  );
}

完整的页面构建包含了所有输入字段和按钮。页面使用SingleChildScrollView来支持滚动,确保所有内容都可见。Form组件包装了所有表单字段,提供了统一的验证管理。通过ColumnSizedBox的组合,我们创建了一个清晰的布局。Row组件用于并排显示预览和保存按钮,提供了高效的空间利用。ElevatedButton在加载时显示加载动画,提供了用户反馈。OutlinedButton用于预览操作,与保存按钮形成对比。这种设计提供了一个完整而直观的模板创建界面。

总结

这个创建模板功能为用户提供了一个完整的模板创建界面。通过提供清晰的表单、完善的验证和错误处理,我们能够为用户提供良好的体验。用户可以轻松创建符合自己需求的合同模板,提高工作效率。


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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐