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:12JunnyFormGlobalConfig.instance.init(...),可添加:
      • filterEnableChunkedDefault: true
      • filterChunkInitialCountDefault: 2
      • filterChunkStepCountDefault: 2
      • filterChunkIntervalMillisDefault: 0
      • filterCacheFiltersDefault: 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>() 方法,能够根据字段类型(如 DateFieldTextField 等)动态生成已注册的表单字段组件,简化字段组件的创建过程。

  • JunnyFormFieldConfig:用于定义表单字段的配置类。

    JunnyFormFieldConfig 类用于存储字段的基本属性(如名称、标题等),并支持扩展自定义参数。通过该类,开发者可以灵活配置字段的展示和行为。

License

该库遵循 MIT License 开源协议。

Libraries

junny_form