Skip to content

JsonForm: 表单

介绍

JsonForm 是基于 ant-design-vue 的表单组件,通过 json 配置实现表单的渲染,组件内置的表单都是基于ant-design-vue 的表单组件,有Input、TextArea、InputNumber、Select、Radio、RadioGroup、Checkbox、CheckboxGroup、DatePicker、RangePicker、Switch、TreeSelect、Cascader及详情的Text跟Time组件,通过registerForm进行全局组件注册。

js
import { 
  Input, 
  Textarea, 
  Switch, 
  Checkbox, 
  InputNumber, 
  Radio, 
  DatePicker, 
  Select, 
  TreeSelect,
  Cascader
} from 'ant-design-vue';
import Text from './components/text.vue';
import Time from './components/time.vue';
const { RangePicker } = DatePicker;
const RadioGroup = Radio.Group;
const CheckboxGroup = Checkbox.Group;

import {
  defineComponent,
  h,
  useModel,
  reactive,
  computed,
  onMounted,
  watch,
  type Component,
} from 'vue'

// 处理 v-model:checked 绑定
export const transformBinding = (Component: Component) => {
  return defineComponent({
    name: Component.name,
    props: {
      value: {
        type: Boolean,
        default: false,
      },
    },
    setup(props, { attrs, slots, emit }) {
      const model = useModel(props, 'value');

      watch(
        () => props.value,
        newValue => {
          model.value = !!newValue;
        },
      );

      return () =>
        h(
          Component,
          {
            ...attrs,
            checked: !!model.value,
            onChange: (e: { target: { checked: boolean } }) => {
              const checked = typeof e === 'object' ? e.target.checked : e;
              model.value = !!checked;
              emit('update:value', !!checked);
            },
          },
          slots,
        );
    },
  });
};

// 扩展组件,支持异步属性getOptions获取options
const extendComponentsOptions = (Component: Component, config?: any) => {
  return defineComponent({
    name: Component.name,
    props: {
      getOptions: {
        type: Function,
        default: undefined,
      },
      options: {
        type: Array,
        default: () => [],
      },
    },
    setup(props, { attrs, slots }) {
      const state = reactive({
        options: props.options || [], // 默认使用传入的 options
      });

      // 过滤掉扩展属性getOptions,只保留独有的props
      const selectProps = computed(() => {
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const { getOptions, ...rest } = props;
        return rest;
      });

      // 获取选项数据
      const fetchOptions = async (...args: any) => {
        if (!props.getOptions) return; // 如果没有提供 getOptions,直接返回
        try {
          const result = await props.getOptions(...args);
          // 格式化选项数据,自己在接口里map处理label跟value的对应值
          state.options = result;
        } catch (error) {
          state.options = [];
        }
      };

      onMounted(() => {
        fetchOptions();
      });

      watch(
        () => props.options,
        newOptions => {
          if (newOptions && !props.getOptions) {
            state.options = newOptions;
          }
        },
      );
      return () =>
        h(
          Component,
          {
            ...attrs,
            // treeSelect需要treeData, 需要特殊处理,正常配置options就可以
            // 如有嵌套childern的话,也需要深层递归满足label跟value格式
            ...(Component.name === 'ATreeSelect' ? { treeData: state.options } : { options: state.options }),
            ...config,
            ...selectProps,
          },
          slots,
        );
    },
  });
};

export const componentsMap = {
  Text,
  Time,
  Textarea,
  InputNumber,
  DatePicker,
  Input,
  RangePicker,
  Cascader: extendComponentsOptions(Cascader, {
    allowClear: true,
    showSearch: true,
    getPopupContainer: (triggerNode: HTMLElement) => triggerNode.parentNode,
  }),
  TreeSelect: extendComponentsOptions(TreeSelect, {
    allowClear: true,
    showSearch: true,
    getPopupContainer: (triggerNode: HTMLElement) => triggerNode.parentNode,
    filterTreeNode: (inputValue: string, { label }: any) => label.indexOf(inputValue) !== -1,
  }),
  Select: extendComponentsOptions(Select, {
    allowClear: true,
    showSearch: true,
    getPopupContainer: (triggerNode: HTMLElement) => triggerNode.parentNode,
  }),
  CheckboxGroup: extendComponentsOptions(CheckboxGroup),
  RadioGroup: extendComponentsOptions(RadioGroup),
  Checkbox: transformBinding(Checkbox), // 处理 v-model:checked 绑定
  Switch: transformBinding(Switch), // 处理 v-model:checked 绑定
  Radio: transformBinding(Radio), // 处理 v-model:checked 绑定
};

基础用法

通过注入已有的表单组件,即可实现表单的渲染,如需增加其他表单组件,可在registerForm文件中进行增加

查看代码
vue
<template>
  <JsonForm
    :columns="columns"
    :labelCol="{ style: { width: '100px' } }"
    :rules="rules"
    v-model="formData"
    ref="formRef"
  >
  </JsonForm>
  <contextHolder />
</template>
<script setup>
  import { h, ref, useTemplateRef, computed } from 'vue';
  import { Button, Modal } from 'ant-design-vue';

  const [modal, contextHolder] = Modal.useModal();
  const formRef = useTemplateRef('formRef');

  const formData = ref({
    projectNum: 1,
    toPdt: 'beijing',
    switch: true,
    projectStatus: 'doing',
  });

  const rules = {
    projectName: [{ required: true, message: '请输入项目名称' }],
    toPdt: [{ required: true, message: '请选择地区' }],
  };

  const columns = ref([
    {
      label: '项目名称',
      field: 'projectName',
      el: 'Input',
      placeholder: '输入项目名称',
    },
    {
      label: '项目描述',
      field: 'projectDesc',
      el: 'Textarea',
      placeholder: '输入项目描述',
    },
    {
      label: '项目数量',
      field: 'projectNum',
      el: 'InputNumber',
      placeholder: '输入项目数量',
      min: 1,
      max: 100,
      style: { width: '30%' },
    },
    {
      label: '地区',
      field: 'toPdt',
      el: 'Select',
      placeholder: '请选择地区',
      options: [
        { label: '北京', value: 'beijing' },
        { label: '上海', value: 'shanghai' },
        { label: '广州', value: 'guangzhou' },
      ],
    },
    {
      label: '级联',
      field: 'cascader',
      el: 'Cascader',
      placeholder: '请选择级联',
      options: [
        {
          value: 'zhejiang',
          label: '浙江',
          children: [
            {
              value: 'hangzhou',
              label: '杭州',
              children: [
                {
                  value: 'xihu',
                  label: '西湖',
                },
              ],
            },
          ],
        },
        {
          value: 'jiangsu',
          label: '江苏',
          children: [
            {
              value: 'nanjing',
              label: '南京',
              children: [
                {
                  value: 'zhonghuamen',
                  label: '中华门',
                },
              ],
            },
          ],
        },
      ],
    },
    {
      label: '节点',
      field: 'node',
      el: 'TreeSelect',
      placeholder: '请选择节点',
      treeDefaultExpandAll: true,
      treeNodeFilterProp: 'label',
      options: [
        {
          label: 'root 1',
          value: 'root 1',
          children: [
            {
              label: 'parent 1',
              value: 'parent 1',
              children: [
                {
                  label: 'parent 1-0',
                  value: 'parent 1-0',
                  children: [
                    {
                      label: 'my leaf',
                      value: 'leaf1',
                    },
                    {
                      label: 'your leaf',
                      value: 'leaf2',
                    },
                  ],
                },
                {
                  label: 'parent 1-1',
                  value: 'parent 1-1',
                },
              ],
            },
            {
              label: 'parent 2',
              value: 'parent 2',
            },
          ],
        },
      ],
    },
    {
      label: '项目状态',
      field: 'projectStatus',
      el: 'RadioGroup',
      options: [
        { label: '进行中', value: 'doing' },
        { label: '已完成', value: 'done' },
        { label: '已取消', value: 'cancel' },
      ],
    },
    {
      label: '项目类型',
      field: 'projectType',
      el: 'CheckboxGroup',
      options: [
        { label: '服务', value: 'service' },
        { label: '咨询', value: 'consulting' },
      ],
    },
    {
      label: '时间',
      field: 'date',
      el: 'RangePicker',
    },
    {
      label: '开关',
      field: 'switch',
      el: 'Switch',
    },
    {
      label: '同意条款',
      field: 'checkbox',
      el: 'Checkbox',
    },
    {
      label: 'radio',
      field: 'radio',
      el: 'Radio',
    },
    {
      label: '',
      field: '',
      style: { textAlign: 'center' },
      el: h('div', [
        h(
          Button,
          {
            type: 'primary',
            onClick: async () => {
              await formRef.value?.validateFields();
              modal.success({
                title: '提交参数',
                content: h(
                  'div',
                  Object.entries(formData.value).map(([key, value]) => h('div', `${key}: ${value}`)),
                ),
              });
            },
          },
          '提交',
        ),
        h(
          Button,
          {
            style: { marginLeft: '10px' },
            onClick: () => {
              formRef.value?.resetFields();
            },
          },
          '重置',
        ),
      ]),
    },
  ]);
</script>

自定义表单

支持自定义表单,通过h函数来创建表单节点

查看代码
vue
<template>
  <JsonForm :columns="columns" :labelCol="{ style: { width: '100px' } }" v-model="formData" ref="formRef"> </JsonForm>
  <contextHolder />
</template>
<script setup>
  import { h, useTemplateRef, ref, computed } from 'vue';
  import { Input, Modal, Tooltip, Button } from 'ant-design-vue';
  import { QuestionCircleOutlined } from '@ant-design/icons-vue';
  const [modal, contextHolder] = Modal.useModal();
  const formRef = useTemplateRef('formRef');

  const formData = ref({});

  const columns = [
    {
      label: '项目名称',
      field: 'projectName',
      el: h(Input),
      placeholder: '输入项目名称',
    },
    {
      label: h('span', [
        h('span', '版本号'),
        h(
          Tooltip,
          {
            title: '输入多个版本号',
            getPopupContainer: () => document.body,
            color: 'blue',
          },
          h(QuestionCircleOutlined, {
            style: { color: 'blue' },
          }),
        ),
      ]),
      field: 'toVersion',
      el: h('div', [
        h(Input, {
          style: { width: '100%' },
          placeholder: '请输入版本号',
          value: computed(() => formData.value.toVersion),
          onChange: e => {
            formData.value.toVersion = e.target.value || undefined;
          },
        }),
      ]),
    },
    {
      label: '',
      field: '',
      style: { textAlign: 'center' },
      el: h(
        'div',
        h(
          Button,
          {
            type: 'primary',
            onClick: async () => {
              await formRef.value?.validateFields();
              modal.success({
                title: '提交参数',
                content: h(
                  'div',
                  Object.entries(formData.value).map(([key, value]) => h('div', `${key}: ${value}`)),
                ),
              });
            },
          },
          '提交',
        ),
      ),
    },
  ];
</script>

异步获取表单数据

表单组件(如 Select、TreeSelect、CheckboxGroup、RadioGroup、Cascader)支持通options属性直接配置渲染数据;新增getOptions异步方法,可动态获取并加载表单数据,灵活适配需要远程请求的场景。需返回一个 Promise 对象,格式为:{ label: string, value: string | number }[]

查看代码
vue
<template>
  <JsonForm :columns="columns" :labelCol="{ style: { width: '100px' } }" v-model="formData"> </JsonForm>
</template>

<script setup>
  import { computed, reactive, ref } from 'vue';

  const formData = ref({});

  const columns = reactive([
    {
      label: '项目地址',
      field: 'name',
      el: 'Select',
      placeholder: '请选择项目名称',
      getOptions: () =>
        Promise.resolve([
          { label: '项目1', value: 'project1' },
          { label: '项目2', value: 'project2' },
          { label: '项目3', value: 'project3' },
        ]),
    },
    {
      label: '爱好',
      field: 'hobby',
      el: 'CheckboxGroup',
      getOptions: () =>
        Promise.resolve([
          { name: '吃饭', key: 'eat' },
          { name: '睡觉', key: 'sleep' },
          { name: '打游戏', key: 'game' },
        ]).then(options => options.map(item => ({ label: item.name, value: item.key }))),
    },
    {
      label: '性别',
      field: 'sex',
      el: 'RadioGroup',
      value: computed(() => formData.value.sex || 'female'),
      onChange: e => {
        formData.value.sex = e.target.value;
      },
      getOptions: () =>
        Promise.resolve([
          { name: '男', value: 'male' },
          { name: '女', value: 'female' },
        ]).then(options => options.map(item => ({ ...item, label: item.name }))),
    },
    {
      label: '节点',
      field: 'node',
      placeholder: '请选择节点',
      el: 'TreeSelect',
      treeDefaultExpandAll: true,
      treeNodeFilterProp: 'label',
      getOptions: () =>
        Promise.resolve([
          {
            name: 'root 1',
            value: 'root 1',
            children: [
              {
                name: 'parent 1',
                value: 'parent 1',
                children: [
                  {
                    name: 'parent 1-0',
                    value: 'parent 1-0',
                    children: [
                      {
                        name: 'my leaf',
                        value: 'leaf1',
                      },
                      {
                        name: 'your leaf',
                        value: 'leaf2',
                      },
                    ],
                  },
                  {
                    name: 'parent 1-1',
                    value: 'parent 1-1',
                  },
                ],
              },
              {
                name: 'parent 2',
                value: 'parent 2',
              },
            ],
          },
        ]).then(options =>
          options.map(function deep(item) {
            if (item.children) {
              item.children = item.children.map(deep);
            }
            return { label: item.name, value: item.value, children: item.children };
          }),
        ),
    },
    {
      label: '级联',
      field: 'cascader',
      el: 'Cascader',
      placeholder: '请选择级联',
      getOptions: () =>
        Promise.resolve([
          { label: '浙江', value: 'zhejiang', children: [{ label: '杭州', value: 'hangzhou' }] },
          { label: '江苏', value: 'jiangsu', children: [{ label: '南京', value: 'nanjing' }] },
        ])
    },
  ]);
</script>

内置详情Text及日期Time组件

内置详情Text组件,可以使表单支持编辑跟详情使用一套代码逻辑,时间格式表单详情为Time,极大简化代码量

查看代码
vue
<template>
  <div style="margin-bottom: 40px">
    <a-switch v-model:checked="checked" checked-children="编辑" un-checked-children="详情" />
  </div>
  <JsonForm :columns="columns" :labelCol="{ style: { width: '120px' } }" v-model="formData"> </JsonForm>
</template>
<script setup>
  import { computed, reactive, ref } from 'vue';
  import dayjs from 'dayjs';

  const checked = ref(true);

  const formData = ref({
    projectName: '项目名称',
    projectDesc: '项目描述',
    projectNums: 1,
    toPdt: ['beijing', 'shanghai'],
    node: 'root 1',
    projectStatus: 'cancel',
    projectType: ['service'],
    switch: true,
    checkbox: true,
    date: dayjs('2022-01-01', 'YYYY-MM-DD'),
    time: [dayjs('2022-02-02', 'YYYY-MM-DD'), dayjs('2022-03-03', 'YYYY-MM-DD')],
  });

  const columns = reactive([
    {
      label: 'Input',
      field: 'projectName',
      el: computed(() => (checked.value ? 'Input' : 'Text')),
      placeholder: '输入项目名称',
    },
    {
      label: 'Textarea',
      field: 'projectDesc',
      el: computed(() => (checked.value ? 'Textarea' : 'Text')),
      placeholder: '输入项目描述',
    },
    {
      label: 'InputNumber',
      field: 'projectNums',
      el: computed(() => (checked.value ? 'InputNumber' : 'Text')),
      placeholder: '输入项目描述',
    },
    {
      label: 'Select',
      field: 'toPdt',
      el: computed(() => (checked.value ? 'Select' : 'Text')),
      placeholder: '请选择地区',
      mode: 'multiple',
      options: [
        { label: '北京', value: 'beijing' },
        { label: '上海', value: 'shanghai' },
        { label: '广州', value: 'guangzhou' },
      ],
    },
    {
      label: 'TreeSelect',
      field: 'node',
      el: computed(() => (checked.value ? 'TreeSelect' : 'Text')),
      placeholder: '请选择节点',
      treeDefaultExpandAll: true,
      treeNodeFilterProp: 'label',
      options: [
        {
          label: 'root 1',
          value: 'root 1',
          children: [
            {
              label: 'parent 1',
              value: 'parent 1',
              children: [
                {
                  label: 'parent 1-1',
                  value: 'parent 1-1',
                },
              ],
            },
            {
              label: 'parent 2',
              value: 'parent 2',
            },
          ],
        },
      ],
    },
    {
      label: 'RadioGroup',
      field: 'projectStatus',
      el: computed(() => (checked.value ? 'RadioGroup' : 'Text')),
      options: [
        { label: '进行中', value: 'doing' },
        { label: '已完成', value: 'done' },
        { label: '已取消', value: 'cancel' },
      ],
    },
    {
      label: 'CheckboxGroup',
      field: 'projectType',
      el: computed(() => (checked.value ? 'CheckboxGroup' : 'Text')),
      options: [
        { label: '服务', value: 'service' },
        { label: '咨询', value: 'consulting' },
      ],
    },
    {
      label: 'Switch',
      field: 'switch',
      el: computed(() => (checked.value ? 'Switch' : 'Text')),
    },
    {
      label: 'Checkbox',
      field: 'checkbox',
      el: computed(() => (checked.value ? 'Checkbox' : 'Text')),
    },
    {
      label: 'DatePicker',
      field: 'date',
      el: computed(() => (checked.value ? 'DatePicker' : 'Time')),
    },
    {
      label: 'RangePicker',
      field: 'time',
      el: computed(() => (checked.value ? 'RangePicker' : 'Time')),
    },
  ]);
</script>

动态增减自定义表格

地址手机号操作
删除
查看代码
vue
<template>
  <div style="margin-bottom: 40px">
    <a-switch v-model:checked="checked" checked-children="编辑" un-checked-children="详情" />
  </div>
  <JsonForm :columns="columns" :labelCol="{ style: { width: '70px' } }" v-model="formData" ref="formRef"> </JsonForm>
  <contextHolder />
</template>
<script setup>
  import { h, useTemplateRef, ref, reactive, computed } from 'vue';
  import { Modal, Button } from 'ant-design-vue';
  import Table from './components/table.vue';
  const [modal, contextHolder] = Modal.useModal();
  const formRef = useTemplateRef('formRef');

  const checked = ref(true);

  const formData = reactive({
    address: [
      {
        city: '中国',
        phone: '12345678901',
      },
    ],
  });

  const columns = reactive([
    {
      label: '姓名',
      field: 'projectName',
      el: computed(() => (checked.value ? 'Input' : 'Text')),
      placeholder: '请输入姓名',
      span: 12,
    },
    {
      label: '手机号',
      field: 'projectNo',
      el: computed(() => (checked.value ? 'Input' : 'Text')),
      placeholder: '请输入手机号',
      span: 12,
    },
    {
      label: '地址',
      field: 'address',
      el: h(Table),
      isEdit: checked,
    },
    {
      label: '',
      field: '',
      isShow: checked,
      style: { textAlign: 'center' },
      el: h(
        'div',
        h(
          Button,
          {
            type: 'primary',
            onClick: async () => {
              await formRef.value?.validateFields();
              modal.success({
                title: '提交参数',
                content: h(
                  'div',
                  Object.entries(formData).map(([key, value]) => h('div', `${key}: ${JSON.stringify(value)}`)),
                ),
              });
            },
          },
          '提交',
        ),
      ),
    },
  ]);
</script>

vue
<template>
  <a-table :columns="columns" :data-source="data" :pagination="false" />
  <a-button danger style="width: 100%" @click="addRow" v-if="checked">添加一行</a-button>
</template>

<script setup>
  import { Input } from 'ant-design-vue';
  import { h, reactive, useModel, watch, ref } from 'vue';
  import { Form } from 'ant-design-vue';

  const formItemContext = Form.useInjectFormItemContext();
  const props = defineProps({
    value: {
      type: Array,
      default: () => [],
    },
    isEdit: {
      type: Boolean,
      default: true,
    },
  });

  const model = useModel(props, 'value');
  const data = reactive([]);
  const checked = ref(true);

  watch(
    () => props.value,
    newValue => {
      data.length = 0;
      (newValue || []).forEach((item, index) => {
        data.push({
          key: `${index + 1}`,
          city: item.city || '',
          phone: item.phone || '',
        });
      });
    },
    { immediate: true, deep: true },
  );

  watch(
    () => props.isEdit,
    newValue => {
      checked.value = newValue;
    },
  );

  const addRow = () => {
    data.push({
      key: `${data.length + 1}`,
      city: '',
      phone: '',
    });
    model.value = JSON.parse(JSON.stringify(data));
    formItemContext.onFieldChange();
  };

  const handleChange = (name, value, index) => {
    if (!data[index]) return;
    data[index][name] = value;
    model.value = JSON.parse(JSON.stringify(data));
    formItemContext.onFieldChange();
  };

  const columns = reactive([
    {
      title: '地址',
      dataIndex: 'city',
      width: 190,
      customRender: ({ _, record, index }) =>
        checked.value
          ? h(
              Form.Item,
              {
                name: ['address', index, 'city'],
                rules: [{ required: true, message: '请输入地址' }],
              },
              h(Input, {
                placeholder: '请输入地址',
                value: data[index]?.city || '',
                onChange: e => handleChange('city', e.target.value, index),
              }),
            )
          : h('span', data[index]?.city || ''),
    },
    {
      title: '手机号',
      dataIndex: 'phone',
      width: 190,
      customRender: ({ _, record, index }) =>
        checked.value
          ? h(
              Form.Item,
              {
                name: ['address', index, 'phone'],
                rules: [{ required: true, message: '请输入手机号' }],
              },
              h(Input, {
                placeholder: '请输入手机号',
                value: data[index]?.phone || '',
                onChange: e => handleChange('phone', e.target.value, index),
              }),
            )
          : h('span', data[index]?.phone || ''),
    },
    {
      title: '操作',
      dataIndex: 'action',
      width: 190,
      customRender: ({ _, record, index }) =>
        checked.value
          ? h(
              'span',
              {
                style: { color: '#1890ff', cursor: 'pointer' },
                onClick: () => {
                  data.splice(index, 1).forEach((_, i) => (data[i].key = `${i + 1}`));
                  model.value = JSON.parse(JSON.stringify(data));
                  formItemContext.onFieldChange();
                },
              },
              '删除',
            )
          : h('span', '--'),
    },
  ]);
</script>

高级搜索

高级搜索
查看代码
vue
<template>
  <JsonForm
    :columns="columns.slice(0, expand ? columns.length : 6)"
    :labelCol="{ style: { width: '40px' } }"
    :wrapperCol="{ style: { width: '100px' } }"
    layout="inline"
    class="search-form"
    :span="8"
  >
    <div style="margin-top: 15px">
      <a-button type="primary">查询</a-button>
      <a-button style="margin: 0 8px">重置</a-button>
      <a style="font-size: 12px" @click="expand = !expand">
        <template v-if="expand">
          <UpOutlined />
        </template>
        <template v-else>
          <DownOutlined />
        </template>
        高级搜索
      </a>
    </div>
  </JsonForm>
</template>
<script setup>
  import { reactive, ref } from 'vue';
  import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';

  const expand = ref(false);

  const columns = reactive([
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
    {
      label: '姓名',
      field: 'projectName',
      el: 'Input',
      placeholder: '请输入姓名',
    },
  ]);
</script>
<style scoped>
  .search-form {
    justify-content: flex-end;
  }
</style>

布局

禁用:
查看代码
vue
<template>
  <div style="margin-bottom: 40px">
    <a-radio-group v-model:value="layout" button-style="solid">
      <a-radio-button value="horizontal">Horizontal</a-radio-button>
      <a-radio-button value="vertical">Vertical</a-radio-button>
      <a-radio-button value="inline">Inline</a-radio-button>
    </a-radio-group>
    <span style="margin: 0 20px">禁用: <a-switch v-model:checked="disabled" /></span>
    <a-radio-group v-model:value="col" button-style="solid">
      <a-radio-button :value="1">1列</a-radio-button>
      <a-radio-button :value="2">2列</a-radio-button>
      <a-radio-button :value="3">3列</a-radio-button>
    </a-radio-group>
  </div>
  <JsonForm
    :columns="columns"
    :labelCol="{ style: { width: '100px' } }"
    :disabled="disabled"
    :layout="layout"
    :span="span"
  >
  </JsonForm>
</template>
<script setup>
  import { ref, computed } from 'vue';
  const layout = ref('horizontal');
  const disabled = ref(false);
  const col = ref(1);
  const span = computed(() => {
    return Number(24 / col.value);
  });

  const columns = [
    {
      label: '项目名称',
      field: 'projectName',
      el: 'Input',
      placeholder: '输入项目名称',
    },
    {
      label: '项目描述',
      field: 'projectDesc',
      el: 'Textarea',
      placeholder: '输入项目描述',
    },
    {
      label: '项目状态',
      field: 'projectStatus',
      el: 'Select',
      placeholder: '选择项目状态',
      options: [
        { label: '进行中', value: '进行中' },
        { label: '已完成', value: '已完成' },
        { label: '已取消', value: '已取消' },
      ],
    },
    {
      label: '项目负责人',
      field: 'projectLeader',
      el: 'Select',
      placeholder: '选择项目负责人',
      options: [
        { label: '张三', value: '张三' },
        { label: '李四', value: '李四' },
        { label: '王五', value: '王五' },
      ],
    },
    {
      label: '开始日期',
      field: 'startDate',
      el: 'DatePicker',
      placeholder: '选择开始日期',
    },
    {
      label: '结束日期',
      field: 'endDate',
      el: 'DatePicker',
      placeholder: '选择结束日期',
    },
  ];
</script>

API

参数名类型默认值说明
columnsFormItem[]表单列配置数组
spannumbera-col 组件的 span 属性,用于控制表单项的布局个数
其余参数同 antd form 一样------参考 antd form

columns 参数

参数名类型说明
elstring | Function表单项使用的组件名称(在 registerForm 文件中查看枚举)或自定义组件函数或 h 函数生成组件
labelstring表单项的标签文本
fieldstring同表单绑定的 v-model:value 或者 v-model:checked
valueany表单项的默认值
isShowboolean表单项的显示隐藏
getOptionsPromise.resolve表单项有 options 配置的可以通过 getOptions 函数异步拿取 options 并将数据 return 为 Array<{label:'显示标题', value:'值'}>
剩余表单 propsany表单项各自的属性的配置可直接作用到表单项上

引入组件可以通过 import 来导入,或者内部的 hook 方式引入组件,抛出的实例可根据业务进行自己增加或者修改

js
<template>
  <JsonForm />
</template>
  import { useForm } from '@/components/Form/useForm';
  import { JsonForm, validateFields } = useForm()

registerForm 可根据需要进行增加全局通用的表单组件进行统一配置,或单独自定义组件来导入

Released under the MIT License.