实战:构建可配置化表单系统

文摘   2024-12-30 09:02   湖北  

 

实战:构建可配置化表单系统

在企业级应用开发中,表单是最常见也是最复杂的交互界面之一。构建一个可配置化的表单系统可以大大提高开发效率,减少重复代码。本文将带领大家实现一个完整的可配置化表单系统。

1. 核心架构设计

1.1 表单配置数据结构

首先定义表单配置的核心数据结构:

// types/form.ts
exportinterfaceFieldConfig {
type'input' | 'select' | 'radio' | 'checkbox' | 'date' | 'upload';
namestring;
labelstring;
  defaultValue?: any;
  placeholder?: string;
  rules?: ValidationRule[];
  options?: Array<{
    labelstring;
    valueany;
  }>;
  props?: Record<stringany>;
  dependencies?: string[];  // 字段依赖
  visible?: boolean | ((valuesany) =>boolean);
  disabled?: boolean | ((valuesany) =>boolean);
}

exportinterfaceFormConfig {
namestring;
fieldsFieldConfig[];
  layout?: 'horizontal' | 'vertical';
  labelWidth?: number | string;
  submitText?: string;
  onSubmit?: (valuesany) =>void | Promise<void>;
}

exportinterfaceValidationRule {
  required?: boolean;
  message?: string;
  pattern?: RegExp;
  validator?: (valueany) =>boolean | Promise<boolean>;
}

1.2 核心表单组件

// components/ConfigurableForm.tsx
importReactfrom'react';
import { Form, message } from'antd';
import { FormConfig } from'../types/form';
import { useFormFields } from'../hooks/useFormFields';
import { createValidator } from'../utils/validator';

interfaceConfigurableFormProps {
configFormConfig;
  initialValues?: Record<stringany>;
  onSubmit?: (valuesany) =>void | Promise<void>;
}

exportconstConfigurableFormReact.FC<ConfigurableFormProps> = ({
  config,
  initialValues,
  onSubmit
}
) =>
 {
const [form] = Form.useForm();
const fields = useFormFields(config.fields, form);

consthandleSubmit = async (valuesany) => {
    try {
      if (config.onSubmit) {
        await config.onSubmit(values);
      }
      if (onSubmit) {
        awaitonSubmit(values);
      }
      message.success('提交成功');
    } catch (error) {
      message.error('提交失败:' + error.message);
    }
  };

return (
    <Form
      form={form}
      layout={config.layout || 'horizontal'}
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 14 }}
      initialValues={initialValues}
      onFinish={handleSubmit}
    >

      {fields}
      <Form.Item wrapperCol={{ offset: 6span: 14 }}>
        <Button type="primary" htmlType="submit">
          {config.submitText || '提交'}
        </Button>
      </Form.Item>
    </Form>

  );
};

2. 动态表单字段实现

2.1 字段渲染钩子

// hooks/useFormFields.ts
import { useEffect } from'react';
import { Form } from'antd';
import { FieldConfig } from'../types/form';
import { renderField } from'../utils/fieldRenderer';

exportfunctionuseFormFields(fieldsFieldConfig[], formFormInstance) {
useEffect(() => {
    // 监听字段值变化,处理联动逻辑
    const subscription = form.subscribe(
      ({ values, forms }) => {
        fields.forEach(field => {
          if (field.dependencies) {
            const visible = evaluateFieldVisibility(field, values);
            const disabled = evaluateFieldDisabled(field, values);
            
            form.setFields([
              {
                name: field.name,
                hidden: !visible,
                disabled: disabled
              }
            ]);
          }
        });
      },
      ['values']
    );
    
    return() => subscription.unsubscribe();
  }, [fields, form]);

return fields.map(field =>renderField(field, form));
}

2.2 字段渲染器

// utils/fieldRenderer.tsx
importReactfrom'react';
import { FormInputSelectRadioCheckboxDatePickerUpload } from'antd';
import { FieldConfig } from'../types/form';
import { createValidator } from'./validator';

exportfunctionrenderField(fieldFieldConfigformFormInstance) {
const {
    type,
    name,
    label,
    rules = [],
    placeholder,
    options,
    props = {},
    visible = true,
    disabled = false
  } = field;

const baseProps = {
    placeholder,
    disabledtypeof disabled === 'function' ? disabled(form.getFieldsValue()) : disabled,
    ...props
  };

letfieldComponentReact.ReactNode;

switch (type) {
    case'input':
      fieldComponent = <Input {...baseProps} />;
      break;
    
    case'select':
      fieldComponent = (
        <Select {...baseProps}>
          {options?.map(opt => (
            <Select.Option key={opt.value} value={opt.value}>
              {opt.label}
            </Select.Option>
          ))}
        </Select>

      );
      break;
    
    case'radio':
      fieldComponent = (
        <Radio.Group {...baseProps}>
          {options?.map(opt => (
            <Radio key={opt.value} value={opt.value}>
              {opt.label}
            </Radio>
          ))}
        </Radio.Group>

      );
      break;
    
    // ... 其他字段类型的渲染逻辑
  }

return (
    <Form.Item
      key={name}
      name={name}
      label={label}
      rules={rules.map(createValidator)}
      hidden={typeof visible === 'function' ? !visible(form.getFieldsValue()) : !visible}
    >

      {fieldComponent}
    </Form.Item>

  );
}

3. 表单验证系统

3.1 验证规则生成器

// utils/validator.ts
import { Rule } from'antd/lib/form';
import { ValidationRule } from'../types/form';

exportfunctioncreateValidator(ruleValidationRule): Rule {
constbaseRuleRule = {};

if (rule.required) {
    baseRule.required = true;
    baseRule.message = rule.message || '此字段为必填项';
  }

if (rule.pattern) {
    baseRule.pattern = rule.pattern;
    baseRule.message = rule.message || '格式不正确';
  }

if (rule.validator) {
    baseRule.validator = async (_, value) => {
      const result = await rule.validator(value);
      if (!result) {
        thrownewError(rule.message || '验证失败');
      }
    };
  }

return baseRule;
}

3.2 自定义验证规则

// utils/customValidators.ts
exportconst customValidators = {
// 手机号验证
phone: {
    pattern/^1[3-9]\d{9}$/,
    message'请输入正确的手机号'
  },

// 邮箱验证
email: {
    pattern/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/,
    message'请输入正确的邮箱地址'
  },

// 异步验证示例
asyncuniqueUsername(valuestring) {
    const response = awaitfetch(`/api/check-username?username=${value}`);
    const result = await response.json();
    return !result.exists;
  }
};

4. 表单联动实现

4.1 字段依赖处理

// utils/dependencyHandler.ts
exportfunctionevaluateFieldVisibility(
  fieldFieldConfig,
  formValuesRecord<stringany>
): boolean {
if (!field.dependencies || !field.visible) {
    returntrue;
  }

if (typeof field.visible === 'function') {
    return field.visible(formValues);
  }

return field.visible;
}

exportfunctionevaluateFieldDisabled(
  fieldFieldConfig,
  formValuesRecord<stringany>
): boolean {
if (!field.dependencies || !field.disabled) {
    returnfalse;
  }

if (typeof field.disabled === 'function') {
    return field.disabled(formValues);
  }

return field.disabled;
}

4.2 联动配置示例

// examples/formConfig.ts
exportconstformConfigFormConfig = {
name'userProfile',
fields: [
    {
      type'select',
      name'userType',
      label'用户类型',
      options: [
        { label'个人'value'personal' },
        { label'企业'value'business' }
      ]
    },
    {
      type'input',
      name'companyName',
      label'公司名称',
      dependencies: ['userType'],
      visible(values) => values.userType === 'business',
      rules: [{ requiredtruemessage'请输入公司名称' }]
    },
    {
      type'checkbox',
      name'needInvoice',
      label'是否需要发票',
      defaultValuefalse
    },
    {
      type'input',
      name'taxNumber',
      label'税号',
      dependencies: ['needInvoice'],
      visible(values) => values.needInvoice,
      rules: [{ requiredtruemessage'请输入税号' }]
    }
  ]
};

5. 实际应用示例

5.1 完整表单示例

// examples/UserProfileForm.tsx
importReactfrom'react';
import { ConfigurableForm } from'../components/ConfigurableForm';
import { message } from'antd';

constuserProfileConfigFormConfig = {
name'userProfile',
layout'horizontal',
labelWidth'120px',
fields: [
    {
      type'input',
      name'username',
      label'用户名',
      rules: [
        { requiredtruemessage'请输入用户名' },
        { validator: customValidators.uniqueUsernamemessage'用户名已存在' }
      ]
    },
    {
      type'input',
      name'phone',
      label'手机号',
      rules: [customValidators.phone]
    },
    {
      type'select',
      name'region',
      label'所在地区',
      options: [
        { label'中国'value'CN' },
        { label'美国'value'US' },
        { label'日本'value'JP' }
      ]
    }
  ],
submitText'保存信息'
};

exportfunctionUserProfilePage() {
consthandleSubmit = async (valuesany) => {
    try {
      awaitsaveUserProfile(values);
      message.success('保存成功');
    } catch (error) {
      message.error('保存失败:' + error.message);
    }
  };

return (
    <div className="user-profile-page">
      <h1>个人信息设置</h1>
      <ConfigurableForm
        config={userProfileConfig}
        onSubmit={handleSubmit}
      />

    </div>

  );
}

5.2 表单数据处理

// services/formDataService.ts
exportclassFormDataService {
staticasyncvalidateFormData(configFormConfigvaluesany) {
    consterrorsRecord<stringstring> = {};
    
    for (const field of config.fields) {
      if (!field.rulescontinue;
      
      for (const rule of field.rules) {
        try {
          const validator = createValidator(rule);
          await validator.validator(null, values[field.name]);
        } catch (error) {
          errors[field.name] = error.message;
          break;
        }
      }
    }
    
    return {
      validObject.keys(errors).length === 0,
      errors
    };
  }

statictransformFormData(configFormConfigvaluesany) {
    constresultRecord<stringany> = {};
    
    for (const field of config.fields) {
      if (values[field.name] !== undefined) {
        result[field.name] = values[field.name];
      }
    }
    
    return result;
  }
}

总结

构建可配置化表单系统需要考虑以下几个关键点:

  1. 1. 清晰的配置数据结构设计
  2. 2. 灵活的字段渲染机制
  3. 3. 强大的表单验证系统
  4. 4. 完善的字段联动处理
  5. 5. 友好的错误提示
  6. 6. 可扩展的接口设计

通过以上实现,我们获得了一个功能完备的可配置化表单系统,它能够:

  • • 通过配置快速生成复杂表单
  • • 支持多种表单字段类型
  • • 实现灵活的字段联动
  • • 提供完善的验证机制
  • • 易于扩展和维护

在实际项目中,可以基于这个基础框架,根据具体需求进行扩展和定制,以满足不同场景的表单需求。

 


前端道萌
魔界如,佛界如,一如,无二如。
 最新文章