Flutter for OpenHarmony 电子合同签署App实战 - 创建模板实现
摘要 本文介绍了电子合同应用中创建模板功能的设计与实现。该功能允许用户自定义合同模板,包括基本信息输入、内容编辑、分类设置等核心模块。文章详细阐述了模板数据模型的设计,包含id、名称、描述、分类、内容等关键字段,并提供了JSON序列化方法。创建模板页面采用StatefulWidget管理表单状态,通过TextFormField实现模板名称和描述的输入验证,确保数据有效性。整体设计注重用户体验,提供

创建模板功能是电子合同应用的核心功能之一。这个功能允许用户根据自己的需求创建自定义的合同模板,包括模板的基本信息、内容编辑、分类设置等。通过创建模板功能,用户可以快速生成符合自己业务需求的合同模板,提高工作效率。在这篇文章中,我们将详细讲解如何实现一个功能完整、用户友好的创建模板页面。
创建模板功能的设计目标
创建模板功能需要实现以下设计目标:
- 模板基本信息输入:提供清晰的表单界面,让用户能够输入模板名称、描述、分类等基本信息。这些信息是模板的元数据,用于标识和分类模板。
- 模板内容编辑:提供富文本编辑器或简单的文本编辑功能,让用户能够编辑模板的具体内容。用户可以添加合同条款、条件等内容。
- 分类和标签管理:支持为模板添加分类和标签,方便用户后续查找和管理模板。分类可以帮助用户快速定位所需的模板。
- 模板预览:提供模板预览功能,让用户在保存前能够查看模板的最终效果。这样可以避免用户保存错误的模板。
- 保存和发布:支持保存模板为草稿或直接发布模板。用户可以先保存为草稿,后续继续编辑。
- 错误处理和验证:完善的表单验证和错误处理机制,确保用户输入的数据有效。
模板数据模型的设计
首先,我们需要定义模板的数据模型。这个模型会包含模板的所有必要信息,包括基本信息、内容、分类等。数据模型是应用与数据之间的桥梁,通过定义清晰的模型,我们可以确保数据的一致性和类型安全。
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,
};
}
}
这个数据模型包含了模板的所有必要信息。通过这个模型,我们可以统一管理模板数据,避免数据散落在各个地方。模型中的每个字段都有明确的含义,使得代码更加易于理解和维护。通过提供fromJson和toJson方法,我们可以轻松地进行数据序列化和反序列化,这是处理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提供了视觉提示,帮助用户识别这是一个模板名称字段。通过OutlineInputBorder和borderRadius,我们创建了一个现代化的输入框设计。验证器函数在用户提交表单时被调用,确保数据的有效性。这种设计模式在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个字符,避免过长的描述。TextFormField的maxLines参数控制输入框的高度和允许的最大行数。通过设置合理的字符限制,我们可以确保数据库存储的效率。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,我们确保用户输入的内容不为空且长度足够。TextFormField的maxLines参数设置为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用于包装预览内容,确保当内容过长时能够滚动。通过Column和SizedBox的组合,我们创建了一个清晰的信息展示布局。预览对话框显示了模板的所有关键信息,包括名称、分类、描述、标签和内容。通过提供"返回编辑"和"保存模板"两个按钮,用户可以灵活地选择下一步操作。这种预览功能可以帮助用户在保存前确认模板的内容是否正确。
表单验证
表单验证是确保用户输入数据有效的重要步骤。我们需要验证所有必填字段都已填写,并且数据格式正确。表单验证应该在用户提交表单时进行。
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组件包装了所有表单字段,提供了统一的验证管理。通过Column和SizedBox的组合,我们创建了一个清晰的布局。Row组件用于并排显示预览和保存按钮,提供了高效的空间利用。ElevatedButton在加载时显示加载动画,提供了用户反馈。OutlinedButton用于预览操作,与保存按钮形成对比。这种设计提供了一个完整而直观的模板创建界面。
总结
这个创建模板功能为用户提供了一个完整的模板创建界面。通过提供清晰的表单、完善的验证和错误处理,我们能够为用户提供良好的体验。用户可以轻松创建符合自己需求的合同模板,提高工作效率。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)