Skip to content
On this page

defineFatForm 定义器

defineFatTable 类似, 我们也提供了 defineFatForm 用于快速定义表单。相比直接使用 <template>, defineFatForm 可以提供更好的类型提示和检查。




Hello world

查看代码
tsx
import { defineFatForm } from '@wakeadmin/components';
import { ElMessageBox } from 'element-plus';

export default defineFatForm<{
  // 🔴 这里的泛型变量可以定义表单数据结构
  name: string;
  nickName: string;
}>(({ item, form, consumer, group }) => {
  // 🔴 这里可以放置 Hooks

  // 🔴 form 为 FatForm 实例引用
  console.log(form);

  // 返回表单定义
  return () => ({
    // FatForm props 定义
    initialValue: {
      name: 'ivan',
      nickName: '狗蛋',
    },

    submit: async values => {
      await ElMessageBox.confirm('确认保存');
      console.log('保存成功', values);
    },

    // 🔴 子节点
    children: [
      item({ prop: 'name', label: '账号名' }),
      item({
        prop: 'nickName',
        label: '昵称',
      }),

      // 🔴 这里甚至可以放 JSX
      <div>JSX hello</div>,

      // 🔴 不过,如果你想要监听 表单数据,还是建议使用 FatFormConsumer, 否则会导致整个表单的重新渲染
      // 不信,你可以打开 Vue 开发者工具的 Highlight Updates 试一下
      consumer(({ values }) => {
        return group({
          label: '表单状态',
          children: (
            <pre>
              <code>{JSON.stringify(values, null, 2)}</code>
            </pre>
          ),
        });
      }),
    ],
  });
});

defineFatForm 使用方法类似于 Vue 的 defineComponent, 只不过返回一个 DSL, 这个 DSL 定义了表单的结构和配置信息。





定义结构

defineFatForm 的 DSL 需要满足类似 <template> 的灵活结构的同时,保持一定约束性。为此我们提供了一些 helpers 来辅助你定义这个 DSL,并且能关联类型上下文。



底层方法

  • renderChild 可以将上述辅助转换为 JSX 节点
  • renderChildren 和 renderChild 类似,只不过支持接收一个数组

上述 helper 接收对应组件的 Props,除了 item , groupsection 还支持传入一个 children 字段, 用于定义下级节点:



tsx
defineFatForm(({ item, group, section, consumer }) => {
  return () => ({
    // 这里放置 FatForm 的 Props
    // ...

    // 定义下级节点
    children: [
      group({
        children: [
          // group 支持下级节点
        ],
      }),
      section({
        children: [
          // section 支持下级节点
          group({
            // 可以逐级嵌套
            children: [],
          }),
        ],
      }),
      consumer(() => {
        // consumer 回调的返回值等价于 children
        return [];
      }),
    ],
  });
});



children 支持传入 itemgroupsectionconsumer, 以及 JSX:


tsx
defineFatForm(({ item, group, section, consumer }) => {
  return () => ({
    // 定义下级节点
    children: [
      group({
        /*..*/
      }),
      item({
        /*..*/
      }),
      section({
        /*..*/
      }),
      consumer(() => {
        /*..*/
      }),
      <div>JSX</div>,
      someCondition && <div>JSX</div>,
    ],
  });
});



为什么推荐使用 consumer 而不是直接写 JSX

如果想要实现表单联动等, 依赖于表单数据的渲染,我们推荐使用 consumer, 因为它能够实现精确渲染。下面对比两个示例(打开 Vue 开发者工具的 Highlight Updates 查看重新渲染的范围):

查看代码
tsx
import { defineFatForm } from '@wakeadmin/components';

export const UseConsumer = defineFatForm(({ item, consumer }) => {
  return () => ({
    children: [
      item({ label: 'a', prop: 'a' }),
      item({ label: 'b', prop: 'b' }),
      item({ label: 'c', prop: 'c' }),
      consumer(({ values }) => {
        return <div>{JSON.stringify(values)}</div>;
      }),
    ],
  });
});

/**
 * 未使用 consumer, 将导致全量渲染
 */
export const NotUseConsumer = defineFatForm(({ item, form }) => {
  return () => ({
    children: [
      item({ label: 'a', prop: 'a' }),
      item({ label: 'b', prop: 'b' }),
      item({ label: 'c', prop: 'c' }),
      <div>{JSON.stringify(form.value?.values)}</div>,
    ],
  });
});




示例

来看一个相对复杂的案例,这个案例来源于惟客云(会员中心/积分倍率活动)。这个例子会展示 checkboxs 原件,FatFormGroup 的妙用:

查看代码
tsx
import { defineFatForm } from '@wakeadmin/components';

enum Type {
  Or,
  And,
  None,
}

interface Values {
  type: Type;
  conditions: string[];
}

export default defineFatForm<Values>(({ group, renderChild, item }) => {
  return () => {
    return {
      initialValue: {
        type: Type.Or,
        conditions: [],
      },
      layout: 'vertical',
      children: [
        group({
          label: '触发条件',
          gutter: 'sm',
          vertical: true,
          children: [
            item({
              prop: 'type',
              valueType: 'radio',
              valueProps: {
                options: [
                  {
                    label: '满足一项选中条件即可',
                    value: Type.Or,
                  },
                  {
                    label: '满足全部选中条件',
                    value: Type.And,
                  },
                  {
                    label: '无限制',
                    value: Type.None,
                  },
                ],
              },
              // 依赖于 conditions, 即 conditions 变化时会触发它重新验证
              dependencies: 'conditions',
              rules: values => ({
                validator(_rule, value, callback) {
                  if (value !== Type.None && !values.conditions?.length) {
                    callback(new Error('请选择至少一条触发时间条件'));
                  } else {
                    callback();
                  }
                },
              }),
            }),
            // group 可以控制下级的一些状态,比如 disabled
            group({
              disabled: f => f.values.type === Type.None,
              children: [
                item({
                  prop: 'conditions',
                  valueType: 'checkboxs',
                  valueProps: {
                    // 垂直布局
                    vertical: true,
                    options: [
                      {
                        label: active =>
                          renderChild(
                            group({
                              // 当选项未选中时禁用表单, 如果选中,传入 undefined, 让 group 从父级继承 disabled 状态
                              disabled: !active ? true : undefined,
                              children: [
                                '每年',
                                item({
                                  prop: 'year.dateRange',
                                  valueType: 'date-range',
                                  required: true,
                                  width: 'small',
                                }),
                                '时间段',
                                item({
                                  prop: 'year.timeRange',
                                  valueType: 'time-range',
                                  required: true,
                                  width: 'small',
                                }),
                              ],
                            })
                          ),
                        value: 'year',
                      },
                      {
                        label: active =>
                          renderChild(
                            group({
                              // 当选项未选中时禁用表单
                              disabled: !active ? true : undefined,
                              children: [
                                '每月',
                                item({
                                  prop: 'month.date',
                                  valueType: 'select',
                                  valueProps: {
                                    options: [{ value: 1, label: '1' }],
                                  },
                                  width: 'mini',
                                  required: true,
                                }),
                                '时间段',
                                item({
                                  prop: 'month.timeRange',
                                  width: 'small',
                                  valueType: 'time-range',
                                  required: true,
                                }),
                              ],
                            })
                          ),
                        value: 'month',
                      },
                    ],
                  },
                }),
              ],
            }),
          ],
        }),
      ],
    };
  };
});



要点:

  • FatFormGroup 配置的状态可以被下级继承。这些状态包含 mode, size, disabled, hidden, hideMessageOnPreview, clearable, col 等。如果下级显式配置了这些属性,那么将以下级的优先。
  • renderChild 用于将 item, group, consumer, section 等 helper 的输出转换为 JSX。这是一个底层方法,用于一些复杂的场景。
  • FatFormItem 的状态为 hidden,disabled 或者 mode 为 'preview' 时,针对该字段的验证规则也会自动移除。
  • rules 支持传入一个函数,用于依赖上下文的一些验证。 另外一个配合 dependencies 配置依赖的其他字段,当这些字段变化时重新验证。
  • 最佳实践是在 FatFormItem 上配置 rule,而不是在 FatForm 上。这样更加灵活,尤其是在动态表单的场景。