Ant Design Pro动态表单生成:基于JSON Schema的表单配置

Ant Design Pro动态表单生成:基于JSON Schema的表单配置

【免费下载链接】ant-design-pro 👨🏻‍💻👩🏻‍💻 Use Ant Design like a Pro! 【免费下载链接】ant-design-pro 项目地址: https://gitcode.com/gh_mirrors/an/ant-design-pro

引言:告别重复编码的表单开发困境

你是否还在为每个业务场景编写重复的表单代码?是否经历过表单字段频繁变更导致的大量重构工作?本文将系统介绍如何利用JSON Schema(JSON模式)在Ant Design Pro中实现动态表单生成,通过一份配置文件驱动多种表单场景,从根本上解决传统表单开发效率低下、维护成本高的问题。

读完本文后,你将掌握:

  • JSON Schema核心规范与表单配置映射关系
  • Ant Design Pro中动态表单渲染的完整实现方案
  • 复杂业务场景下的表单交互逻辑处理技巧
  • 表单配置的最佳实践与性能优化策略

一、JSON Schema与动态表单基础

1.1 JSON Schema核心概念

JSON Schema是一种用于描述JSON数据结构的规范,它允许你定义数据的类型、格式、约束条件等元数据。在动态表单场景中,JSON Schema扮演着"表单描述语言"的角色,通过声明式配置替代命令式编码。

{
  "type": "object",
  "properties": {
    "username": {
      "type": "string",
      "title": "用户名",
      "minLength": 3,
      "maxLength": 20
    },
    "email": {
      "type": "string",
      "title": "电子邮箱",
      "format": "email"
    },
    "age": {
      "type": "integer",
      "title": "年龄",
      "minimum": 18,
      "maximum": 120
    }
  },
  "required": ["username", "email"]
}

1.2 Ant Design Pro表单组件映射

Ant Design Pro提供了丰富的表单组件,我们需要建立JSON Schema类型与UI组件的映射关系:

Schema类型表单组件常用配置
stringInputmaxLength, minLength, pattern
number/integerInputNumberminimum, maximum, step
booleanSwitchcheckedChildren, unCheckedChildren
string(format: "date")DatePickerformat, disabledDate
string(format: "email")Input[InputType=email]-
arraySelect[mode=multiple]items, minItems, maxItems
object嵌套表单properties, required

1.3 动态表单工作原理

动态表单的核心工作流程可以概括为:

mermaid

  1. Schema解析:将JSON Schema转换为内部表单配置对象
  2. 组件映射:根据Schema类型匹配对应的Ant Design组件
  3. 动态渲染:递归遍历Schema结构,生成表单DOM树
  4. 状态管理:维护表单数据的双向绑定与状态更新
  5. 数据验证:基于Schema约束进行实时数据校验
  6. 交互处理:处理表单项之间的依赖关系与条件渲染

二、Ant Design Pro动态表单实现

2.1 项目环境准备

首先确保你的Ant Design Pro项目已正确配置:

# 克隆项目仓库
git clone https://gitcode.com/gh_mirrors/an/ant-design-pro.git
cd ant-design-pro

# 安装依赖
npm install

# 启动开发服务器
npm start

2.2 核心组件封装

创建DynamicForm核心组件,作为动态表单渲染的入口:

// src/components/DynamicForm/index.tsx
import React, { useState } from 'react';
import { Form, Button, Space, message } from 'antd';
import type { FormProps } from 'antd';
import SchemaFormItem from './SchemaFormItem';
import type { JsonSchema } from './types';

interface DynamicFormProps extends FormProps {
  schema: JsonSchema;
  onFinish: (values: any) => void;
  initialValues?: Record<string, any>;
}

const DynamicForm: React.FC<DynamicFormProps> = ({
  schema,
  onFinish,
  initialValues,
  ...props
}) => {
  const [form] = Form.useForm();
  const [loading, setLoading] = useState(false);

  const handleSubmit = async () => {
    try {
      setLoading(true);
      const values = await form.validateFields();
      onFinish(values);
      message.success('提交成功');
    } catch (error) {
      message.error('提交失败,请检查表单数据');
    } finally {
      setLoading(false);
    }
  };

  return (
    <Form
      form={form}
      layout="vertical"
      initialValues={initialValues}
      {...props}
    >
      <SchemaFormItem schema={schema} />
      
      <Form.Item>
        <Space>
          <Button type="primary" htmlType="button" loading={loading} onClick={handleSubmit}>
            提交
          </Button>
          <Button htmlType="button" onClick={() => form.resetFields()}>
            重置
          </Button>
        </Space>
      </Form.Item>
    </Form>
  );
};

export default DynamicForm;

2.3 Schema表单项组件

创建SchemaFormItem组件,负责将Schema属性转换为具体的表单项:

// src/components/DynamicForm/SchemaFormItem.tsx
import React from 'react';
import { Form } from 'antd';
import type { JsonSchema, SchemaProperty } from './types';
import SchemaField from './SchemaField';

interface SchemaFormItemProps {
  schema: JsonSchema;
  fieldKey?: string;
}

const SchemaFormItem: React.FC<SchemaFormItemProps> = ({ schema, fieldKey = '' }) => {
  const { properties = {}, required = [] } = schema;
  
  return (
    <>
      {Object.entries(properties).map(([key, property]) => {
        const isRequired = required.includes(key);
        const fieldPath = fieldKey ? `${fieldKey}.${key}` : key;
        
        return (
          <Form.Item
            key={fieldPath}
            name={fieldPath}
            label={property.title}
            rules={getValidationRules(property, isRequired)}
            hidden={property.hidden}
          >
            <SchemaField property={property} />
          </Form.Item>
        );
      })}
    </>
  );
};

// 生成表单验证规则
const getValidationRules = (property: SchemaProperty, isRequired: boolean) => {
  const rules: any[] = [];
  
  if (isRequired) {
    rules.push({
      required: true,
      message: `${property.title}不能为空`,
    });
  }
  
  if (property.minLength !== undefined) {
    rules.push({
      min: property.minLength,
      message: `${property.title}至少需要${property.minLength}个字符`,
    });
  }
  
  if (property.maxLength !== undefined) {
    rules.push({
      max: property.maxLength,
      message: `${property.title}最多允许${property.maxLength}个字符`,
    });
  }
  
  if (property.pattern) {
    rules.push({
      pattern: new RegExp(property.pattern),
      message: property.patternMessage || `${property.title}格式不正确`,
    });
  }
  
  return rules;
};

export default SchemaFormItem;

2.4 字段类型渲染器

创建SchemaField组件,根据Schema属性类型渲染不同的表单控件:

// src/components/DynamicForm/SchemaField.tsx
import React from 'react';
import { Input, InputNumber, Select, Switch, DatePicker, Radio, Checkbox } from 'antd';
import type { SchemaProperty } from './types';

interface SchemaFieldProps {
  property: SchemaProperty;
}

const SchemaField: React.FC<SchemaFieldProps> = ({ property }) => {
  const { type, format, enum: enums, items, options } = property;
  
  switch (type) {
    case 'string':
      if (format === 'date') {
        return <DatePicker style={{ width: '100%' }} />;
      }
      if (enums && enums.length > 0) {
        return (
          <Select style={{ width: '100%' }}>
            {enums.map((value) => (
              <Select.Option key={value} value={value}>
                {options?.[value] || value}
              </Select.Option>
            ))}
          </Select>
        );
      }
      return <Input placeholder={property.placeholder} disabled={property.disabled} />;
      
    case 'number':
    case 'integer':
      return (
        <InputNumber
          min={property.minimum}
          max={property.maximum}
          step={property.step}
          style={{ width: '100%' }}
          disabled={property.disabled}
        />
      );
      
    case 'boolean':
      return <Switch checkedChildren="是" unCheckedChildren="否" />;
      
    case 'array':
      if (items?.enum) {
        return (
          <Select
            mode="multiple"
            style={{ width: '100%' }}
            placeholder="请选择"
          >
            {items.enum.map((value) => (
              <Select.Option key={value} value={value}>
                {items.options?.[value] || value}
              </Select.Option>
            ))}
          </Select>
        );
      }
      return null;
      
    case 'object':
      return <SchemaFormItem schema={property} fieldKey={property.fieldKey} />;
      
    default:
      return <Input placeholder={property.placeholder} />;
  }
};

export default SchemaField;

2.5 类型定义

创建类型定义文件,规范Schema结构:

// src/components/DynamicForm/types.ts
export interface JsonSchema {
  type: 'object';
  properties?: Record<string, SchemaProperty>;
  required?: string[];
  title?: string;
  description?: string;
}

export interface SchemaProperty {
  type: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
  title?: string;
  description?: string;
  default?: any;
  placeholder?: string;
  hidden?: boolean;
  disabled?: boolean;
  format?: 'date' | 'email' | 'url' | string;
  
  // string类型特有
  minLength?: number;
  maxLength?: number;
  pattern?: string;
  patternMessage?: string;
  
  // number/integer类型特有
  minimum?: number;
  maximum?: number;
  step?: number;
  
  // array类型特有
  items?: SchemaProperty;
  minItems?: number;
  maxItems?: number;
  
  // object类型特有
  properties?: Record<string, SchemaProperty>;
  required?: string[];
  fieldKey?: string;
  
  // enum相关
  enum?: any[];
  options?: Record<string, string>;
  
  // 自定义扩展属性
  ui?: {
    widget?: string;
    props?: Record<string, any>;
    [key: string]: any;
  };
}

三、高级特性实现

3.1 条件渲染与依赖逻辑

实现表单项之间的依赖关系,根据某个字段的值动态显示/隐藏其他字段:

// 修改SchemaFormItem组件,添加条件渲染逻辑
const SchemaFormItem: React.FC<SchemaFormItemProps> = ({ schema, fieldKey = '' }) => {
  const { properties = {}, required = [] } = schema;
  const [form] = Form.useFormInstance();
  
  return (
    <>
      {Object.entries(properties).map(([key, property]) => {
        // ... 省略其他代码
        
        // 处理条件渲染
        const shouldShow = useMemo(() => {
          if (!property.condition) return true;
          
          const { field, value } = property.condition;
          const fieldValue = form.getFieldValue(field);
          
          if (Array.isArray(value)) {
            return value.includes(fieldValue);
          }
          
          return fieldValue === value;
        }, [form.getFieldsValue()]);
        
        return shouldShow ? (
          <Form.Item
            key={fieldPath}
            // ... 省略其他属性
          >
            <SchemaField property={property} />
          </Form.Item>
        ) : null;
      })}
    </>
  );
};

使用示例:

{
  "type": "object",
  "properties": {
    "userType": {
      "type": "string",
      "title": "用户类型",
      "enum": ["personal", "company"],
      "options": {
        "personal": "个人用户",
        "company": "企业用户"
      }
    },
    "companyName": {
      "type": "string",
      "title": "企业名称",
      "condition": {
        "field": "userType",
        "value": "company"
      }
    },
    "idType": {
      "type": "string",
      "title": "证件类型",
      "enum": ["idCard", "passport"],
      "options": {
        "idCard": "身份证",
        "passport": "护照"
      },
      "condition": {
        "field": "userType",
        "value": "personal"
      }
    }
  }
}

3.2 动态增删列表

实现数组类型字段的动态增删功能:

// src/components/DynamicForm/widgets/ArrayField.tsx
import React from 'react';
import { Button, Space, List, Card } from 'antd';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
import SchemaFormItem from '../SchemaFormItem';

interface ArrayFieldProps {
  property: SchemaProperty;
  value: any[];
  onChange: (value: any[]) => void;
}

const ArrayField: React.FC<ArrayFieldProps> = ({ property, value = [], onChange }) => {
  const { items, title } = property;
  const [list, setList] = useState(value);
  
  const handleAdd = () => {
    const newItem = items?.default || getDefaultValue(items?.type);
    setList([...list, newItem]);
    onChange([...list, newItem]);
  };
  
  const handleRemove = (index: number) => {
    const newList = [...list];
    newList.splice(index, 1);
    setList(newList);
    onChange(newList);
  };
  
  const handleItemChange = (index: number, newItem: any) => {
    const newList = [...list];
    newList[index] = newItem;
    setList(newList);
    onChange(newList);
  };
  
  return (
    <div className="array-field">
      <Space style={{ marginBottom: 16 }}>
        <h4 style={{ margin: 0 }}>{title}</h4>
        <Button 
          icon={<PlusOutlined />} 
          onClick={handleAdd}
          disabled={list.length >= (property.maxItems || Infinity)}
        >
          添加
        </Button>
      </Space>
      
      <List
        dataSource={list}
        renderItem={(item, index) => (
          <Card
            style={{ marginBottom: 16 }}
            actions={[
              <Button 
                icon={<MinusOutlined />} 
                onClick={() => handleRemove(index)}
                danger
              />
            ]}
          >
            <SchemaFormItem 
              schema={{ 
                type: 'object', 
                properties: items?.properties,
                required: items?.required
              }} 
              fieldKey={index}
            />
          </Card>
        )}
      />
    </div>
  );
};

// 获取默认值
const getDefaultValue = (type?: string) => {
  switch (type) {
    case 'string':
      return '';
    case 'number':
    case 'integer':
      return 0;
    case 'boolean':
      return false;
    case 'object':
      return {};
    case 'array':
      return [];
    default:
      return null;
  }
};

export default ArrayField;

3.3 自定义组件扩展

实现自定义表单组件的注册与使用机制:

// src/components/DynamicForm/WidgetRegistry.ts
import React from 'react';
import InputWidget from './widgets/InputWidget';
import SelectWidget from './widgets/SelectWidget';
import DatePickerWidget from './widgets/DatePickerWidget';
import ArrayWidget from './widgets/ArrayWidget';
import type { SchemaProperty } from './types';

// 定义Widget类型
export interface WidgetComponentProps {
  property: SchemaProperty;
  value?: any;
  onChange?: (value: any) => void;
}

export type WidgetComponent = React.ComponentType<WidgetComponentProps>;

// Widget注册表
const WidgetRegistry = {
  // 默认widget
  default: InputWidget,
  input: InputWidget,
  select: SelectWidget,
  date: DatePickerWidget,
  array: ArrayWidget,
  
  // 注册自定义widget
  register(name: string, component: WidgetComponent) {
    this[name] = component;
  },
  
  // 获取widget
  getWidget(property: SchemaProperty): WidgetComponent {
    const widgetName = property.ui?.widget || getDefaultWidget(property);
    return this[widgetName] || this.default;
  }
};

// 根据属性类型获取默认widget
function getDefaultWidget(property: SchemaProperty): string {
  if (property.enum && !property.type === 'array') return 'select';
  if (property.format === 'date') return 'date';
  if (property.type === 'array') return 'array';
  
  return property.type;
}

export default WidgetRegistry;

四、实战应用案例

4.1 用户信息表单

// src/pages/user-info/index.tsx
import React from 'react';
import { PageContainer } from '@ant-design/pro-components';
import DynamicForm from '@/components/DynamicForm';

const UserInfoPage: React.FC = () => {
  // 用户信息表单配置
  const userInfoSchema = {
    type: 'object',
    title: '用户信息表单',
    description: '请填写并提交用户基本信息',
    properties: {
      username: {
        type: 'string',
        title: '用户名',
        minLength: 3,
        maxLength: 20,
        placeholder: '请输入用户名'
      },
      email: {
        type: 'string',
        title: '电子邮箱',
        format: 'email',
        placeholder: '请输入电子邮箱'
      },
      phone: {
        type: 'string',
        title: '手机号码',
        pattern: '^1[3-9]\\d{9}$',
        patternMessage: '请输入有效的手机号码',
        placeholder: '请输入手机号码'
      },
      gender: {
        type: 'string',
        title: '性别',
        enum: ['male', 'female', 'other'],
        options: {
          'male': '男',
          'female': '女',
          'other': '其他'
        }
      },
      age: {
        type: 'integer',
        title: '年龄',
        minimum: 18,
        maximum: 120,
        placeholder: '请输入年龄'
      },
      hobbies: {
        type: 'array',
        title: '兴趣爱好',
        items: {
          type: 'string',
          enum: ['reading', 'sports', 'music', 'travel', 'coding'],
          options: {
            'reading': '阅读',
            'sports': '运动',
            'music': '音乐',
            'travel': '旅行',
            'coding': '编程'
          }
        },
        minItems: 1,
        description: '至少选择一项兴趣爱好'
      },
      address: {
        type: 'object',
        title: '详细地址',
        properties: {
          province: {
            type: 'string',
            title: '省份',
            placeholder: '请输入省份'
          },
          city: {
            type: 'string',
            title: '城市',
            placeholder: '请输入城市'
          },
          detail: {
            type: 'string',
            title: '详细地址',
            minLength: 5,
            placeholder: '请输入详细地址'
          }
        },
        required: ['province', 'city', 'detail']
      }
    },
    required: ['username', 'email', 'gender']
  };
  
  // 处理表单提交
  const handleFormFinish = (values: any) => {
    console.log('表单提交数据:', values);
    // 这里可以添加API调用逻辑
  };
  
  return (
    <PageContainer title="用户信息管理">
      <div style={{ background: '#fff', padding: 24 }}>
        <DynamicForm
          schema={userInfoSchema}
          onFinish={handleFormFinish}
          initialValues={{
            gender: 'male',
            hobbies: ['coding']
          }}
        />
      </div>
    </PageContainer>
  );
};

export default UserInfoPage;

4.2 产品配置表单

// 产品配置表单Schema示例
const productConfigSchema = {
  type: 'object',
  title: '产品配置表单',
  properties: {
    productName: {
      type: 'string',
      title: '产品名称',
      required: true,
      maxLength: 50
    },
    productType: {
      type: 'string',
      title: '产品类型',
      enum: ['physical', 'digital', 'service'],
      options: {
        'physical': '实体产品',
        'digital': '数字产品',
        'service': '服务产品'
      }
    },
    price: {
      type: 'number',
      title: '产品价格',
      minimum: 0,
      step: 0.01
    },
    stock: {
      type: 'integer',
      title: '库存数量',
      minimum: 0,
      condition: {
        field: 'productType',
        value: 'physical'
      }
    },
    downloadUrl: {
      type: 'string',
      title: '下载地址',
      format: 'url',
      condition: {
        field: 'productType',
        value: 'digital'
      }
    },
    attributes: {
      type: 'array',
      title: '产品属性',
      items: {
        type: 'object',
        properties: {
          name: {
            type: 'string',
            title: '属性名称'
          },
          value: {
            type: 'string',
            title: '属性值'
          }
        },
        required: ['name', 'value']
      }
    },
    isActive: {
      type: 'boolean',
      title: '是否启用',
      default: true
    }
  },
  required: ['productName', 'productType', 'price']
};

五、性能优化与最佳实践

5.1 性能优化策略

  1. 表单分片加载

    • 将大型表单拆分为多个步骤或标签页
    • 只渲染当前可见区域的表单项
    • 利用React.lazy和Suspense实现组件懒加载
  2. 数据缓存与复用

    • 缓存已解析的Schema配置
    • 复用表单组件实例
    • 避免不必要的重渲染
// 使用React.memo优化组件性能
const SchemaField = React.memo(({ property }: SchemaFieldProps) => {
  // 组件实现...
}, (prevProps, nextProps) => {
  // 自定义比较逻辑,只有当关键属性变化时才重渲染
  return isEqual(prevProps.property, nextProps.property);
});
  1. 虚拟滚动
    • 对于超长列表类型的表单,使用虚拟滚动技术

5.2 最佳实践

  1. Schema组织

    • 按业务领域拆分Schema
    • 抽取公共Schema片段进行复用
    • 使用$ref引用外部Schema文件
  2. 错误处理

    • 全局错误处理机制
    • 表单提交失败自动恢复
    • 详细的错误提示信息
  3. 可维护性

    • 编写详细的注释
    • 建立Schema文档
    • 单元测试覆盖核心逻辑
// 表单Schema单元测试示例
describe('UserInfoSchema', () => {
  it('should validate required fields', () => {
    const schema = userInfoSchema;
    const validator = new Ajv().compile(schema);
    
    const invalidData = {
      email: 'test@example.com'
    };
    
    expect(validator(invalidData)).toBe(false);
    expect(validator.errors).toContainEqual(
      expect.objectContaining({
        instancePath: '/username',
        keyword: 'required'
      })
    );
  });
});

六、总结与展望

本文详细介绍了基于JSON Schema的Ant Design Pro动态表单生成方案,从基础概念到高级特性,再到实战应用,全面覆盖了动态表单开发的各个方面。通过这种方式,我们可以:

  • 显著减少重复代码,提高开发效率
  • 实现表单配置的集中管理,降低维护成本
  • 支持灵活的业务需求变更,快速响应变化
  • 统一表单风格与行为,提升用户体验

未来,我们可以进一步探索:

  • 基于可视化界面的Schema编辑器
  • AI辅助的表单配置生成
  • 更智能的表单验证与错误提示
  • 表单配置版本控制与协作

希望本文能够帮助你在Ant Design Pro项目中更好地实践动态表单开发,如果你有任何问题或建议,欢迎在评论区留言讨论。

附录:常用Schema配置参考

配置项类型说明
typestring数据类型,可选值:string, number, integer, boolean, array, object
titlestring字段标题
descriptionstring字段描述
defaultany默认值
requiredboolean是否必填
minLengthnumber字符串最小长度
maxLengthnumber字符串最大长度
patternstring正则表达式验证
minimumnumber数字最小值
maximumnumber数字最大值
stepnumber数字步长
enumarray枚举值列表
optionsobject枚举值标签映射
formatstring数据格式,如date, email, url
itemsobject数组元素配置
propertiesobject对象属性配置
conditionobject条件渲染配置
uiobjectUI相关配置

如果觉得本文对你有帮助,请点赞、收藏并关注,以便获取更多Ant Design Pro实战教程。下期我们将介绍"动态表单的可视化配置平台"实现方案,敬请期待!

【免费下载链接】ant-design-pro 👨🏻‍💻👩🏻‍💻 Use Ant Design like a Pro! 【免费下载链接】ant-design-pro 项目地址: https://gitcode.com/gh_mirrors/an/ant-design-pro

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值