Junny Form
Junny Form 是一个高度灵活的 Flutter 表单管理库,支持表单字段的集中管理、异步验证、配置扩展,并允许开发者定义和注册自定义的表单字段类型。通过该库,可以快速构建自定义表单组件,支持更复杂的业务逻辑。
功能概述
- 集中管理表单字段和值:通过
JunnyFormDataController管理字段的值、状态和验证结果。 - 支持同步与异步验证:集成了异步验证功能,支持基于防抖的异步验证机制。
- 灵活扩展的字段组件注册机制:开发者可自定义表单字段并注册至
junny_form,从而在表单中灵活调用。 - 类型安全的参数配置:通过代码生成支持类型安全的参数配置,避免手动编写参数 Map。
安装
1. 添加依赖:
dependencies:
junny_form: ^0.1.0
dev_dependencies:
build_runner: any
2. 导入依赖:
import 'package:junny_form/junny_form.dart';
快速上手
1. 初始化表单控制器
首先,使用 JunnyFormDataController 创建并管理表单数据和验证状态。
final JunnyFormDataController controller = JunnyFormDataController(
onFieldError: (FormFieldState<dynamic> state) {
print('Field ${state.widget.key} has an error.');
},
);
2. 注册字段类型
在使用前,您可以注册自定义的字段类型。以下为 DateField 示例。
JunnyFormFieldRegistry.registerField<String, JunnyTextField>(
(JunnyFormFieldConfig<String?> config) => JunnyTextField(
key: key,
config: config,
),
);
3. 使用 JunnyFormFieldFactory.builder 动态创建字段组件
在 UI 中,可以通过 JunnyFormFieldFactory.builder 方法根据类型参数生成字段组件:
JunnyFormFieldFactory.builder<String, JunnyTextField>(
config: JunnyFormFieldConfig<String?>(
basicConfig: const JunnyFormFieldBasicConfig(
name: 'factoryField',
title: 'Factory Builder',
),
decorationConfig: const JunnyFormFieldDecorationConfig(
decoration: InputDecoration(
hintText: 'Created using factory builder',
),
),
),
);
4. 控制器方法和字段管理
JunnyFormDataController 提供多种方法用于操作表单字段、设置值、验证状态:
- 注册字段:
controller.registerField('name', nameFieldState); - 批量设置值:
controller.patchValue({'name': 'John', 'age': 30}); - 表单验证:
await controller.validate();
5. 使用约定的额外组件参数
每个组件都可以在 JunnyFormFieldConfig.basicConfig.extraParams 中传入通过约定参数类生成的参数 Map。
final params = SubtableParams(
actionsBuilder: (context, controller) => [
MenuItemButtonParams(
title: '添加',
onPressed: () {
controller.add({'name': '新条目'});
},
),
],
errorPredict: (index, item) => item['error'] == true,
);
JunnyFormFieldFactory.builder<Iterable<Map<String, dynamic>>, JunnySubtableField>(
config: JunnyFormFieldConfig<Iterable<Map<String, dynamic>>>(
basicConfig: JunnyFormFieldBasicConfig(
name: 'details',
title: '子表示例',
extraParams: SubtableParamsBuilder(params).toMap(), // 使用规定参数的构建器
),
),
),
附件字段 (JunnyAttachmentField)
基本使用
JunnyFormFieldFactory.builder<Iterable<Map<String, dynamic>>, JunnyAttachmentField>(
config: JunnyFormFieldConfig<Iterable<Map<String, dynamic>>?>(
basicConfig: JunnyFormFieldBasicConfig(
name: 'attachments',
title: '附件选择',
extraParams: JunnyFormAttachmentParams(
systemCode: 'SYSTEM_A',
maxCount: 5,
).toMap(),
),
),
)
自定义字段名支持
对于特殊页面中使用非标准字段名的情况,可以自定义附件数据来源字段名称:
JunnyFormFieldFactory.builder<Iterable<Map<String, dynamic>>, JunnyAttachmentField>(
config: JunnyFormFieldConfig<Iterable<Map<String, dynamic>>?>(
basicConfig: JunnyFormFieldBasicConfig(
name: 'contractAttachment',
title: '合同附件',
extraParams: JunnyFormAttachmentParams(
systemCode: 'CONTRACT_SYSTEM',
maxCount: 3,
attachmentsFieldName: 'testAttachments', // 自定义附件字段名
deleteAttachmentsFieldName: 'testDeleteAttachments', // 自定义删除字段名
).toMap(),
),
),
)
这样配置后,附件数据将存储在 testAttachments.contractAttachment 中,删除的附件ID将存储在 testDeleteAttachments.contractAttachment 中。
数据结构
使用自定义字段名时的表单数据结构:
{
"testAttachments": {
"contractAttachment": [
{
"id": 679361612329221,
"sysFileMetadataId": 679361504485509,
"originalName": "合同文件.pdf",
"fileSize": 19968,
// ... 其他附件信息
}
]
},
"testDeleteAttachments": {
"contractAttachment": [
"679361504485509" // 删除的附件ID
]
}
}
Filter 分批渲染(防卡顿)
- 背景:当筛选项较多时,筛选弹窗一次性构建会导致首帧卡顿。已在
FilterDialog内置"分批渲染/逐帧追加",并支持全局默认参数。 - 全局默认配置(放在应用启动处):
- 参考
example/lib/main.dart:12的JunnyFormGlobalConfig.instance.init(...),可添加:filterEnableChunkedDefault: truefilterChunkInitialCountDefault: 2filterChunkStepCountDefault: 2filterChunkIntervalMillisDefault: 0filterCacheFiltersDefault: true
- 参考
- 实例覆盖:如需针对某个筛选弹窗临时调整,可在
FilterDialog构造时传入同名参数(例如enableChunked: false临时关闭)。 - 值持久性:
FilterDialog不创建/销毁外部JunnyFormController;关闭后再次打开,内部值保持不变,仅在"重置"时恢复初始值。
重要:Filter 重建机制
由于 FilterDialog 通过 Navigator.push() 打开,每次打开都会触发完整的 Widget 重建,Filter 中所有 form 组件都会重新执行 initState。
对于异步获取数据的组件(如远程下拉选项),需要注意:
- 避免在
initState中无条件发起网络请求,防止重复请求 - 建议在组件外部缓存异步数据,或通过
JunnyFormController持久化状态 - 当前架构通过快照机制保证
formData的恢复,但组件内部状态(如异步加载的选项列表)需要开发者自行管理
示例
以下是完整的表单示例代码:
class MyFormPage extends StatelessWidget {
final JunnyFormDataController controller = JunnyFormDataController();
MyFormPage() {
// 注册自定义字段
JunnyFormFieldRegistry.registerField<String, JunnyTextField>(
(JunnyFormFieldConfig<String?> config) => JunnyTextField(
key: key,
config: config,
),
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// 方法 1: 直接调用构造函数
JunnyTextField(
config: JunnyFormFieldConfig<String?>(
basicConfig: const JunnyFormFieldBasicConfig(
name: 'directField',
title: 'Direct Constructor',
),
decorationConfig: const JunnyFormFieldDecorationConfig(
decoration: InputDecoration(
hintText: 'Directly created JunnyTextField',
),
),
),
),
const SizedBox(height: 20),
// 方法 2: 使用工厂模式
JunnyFormFieldFactory.builder<String, JunnyTextField>(
config: JunnyFormFieldConfig<String?>(
basicConfig: const JunnyFormFieldBasicConfig(
name: 'factoryField',
title: 'Factory Builder',
),
decorationConfig: const JunnyFormFieldDecorationConfig(
decoration: InputDecoration(
hintText: 'Created using factory builder',
),
),
),
),
ElevatedButton(
onPressed: () async {
final bool isValid = await controller.validate();
if (isValid) {
// 提交表单逻辑
}
},
child: Text('提交'),
),
],
);
}
}
主要类说明
-
JunnyFormDataController:负责管理表单字段的状态、值和验证结果。
JunnyFormDataController作为表单的核心管理类,负责管理表单的数据、字段的状态、验证和错误信息。它提供了注册字段、设置字段值、批量设置值、执行表单验证、重置表单等功能。 -
JunnyFormField:表单字段的抽象基类。
JunnyFormField是所有自定义表单字段的基类。通过继承该类并实现buildContent()方法,开发者可以创建自定义的表单字段组件。 -
JunnyFormFieldRegistry:用于注册和获取自定义字段类型及其构造函数。
JunnyFormFieldRegistry提供字段类型的注册和查找功能。开发者可以注册自定义字段类型,并通过该类获取相应的构造函数,方便灵活地扩展和使用表单字段。 -
JunnyFormFieldFactory:用于动态构建已注册的表单字段实例。
JunnyFormFieldFactory提供了builder<T>()方法,能够根据字段类型(如DateField、TextField等)动态生成已注册的表单字段组件,简化字段组件的创建过程。 -
JunnyFormFieldConfig:用于定义表单字段的配置类。
JunnyFormFieldConfig类用于存储字段的基本属性(如名称、标题等),并支持扩展自定义参数。通过该类,开发者可以灵活配置字段的展示和行为。
License
该库遵循 MIT License 开源协议。