@delon/form动态模式表单技能 delon-form-dynamic-schema-forms

这是一种使用@delon/form库的SF组件创建动态模式表单的技能,支持JSON模式定义、类型安全、内置验证、动态渲染、异步数据加载、多步骤表单、响应式布局和国际化。

前端开发 0 次安装 0 次浏览 更新于 2/28/2026

@delon/form 动态模式表单技能

这个技能帮助使用 @delon/form 的 SF(模式表单)组件创建动态表单。

核心原则

模式驱动表单

  • JSON模式:使用JSON模式声明式定义表单
  • 类型安全:模式定义的完整TypeScript支持
  • 验证:内置验证和自定义验证器
  • 动态渲染:基于表单状态的条件字段

关键特性

  • 根据模式自动生成表单
  • 用于特殊输入的自定义组件
  • 异步数据加载(下拉列表、自动完成)
  • 多步骤表单(向导)
  • 响应式网格布局
  • 国际化支持

基本模式表单

import { Component, signal, output } from '@angular/core';
import { SFSchema, SFComponent, SFUISchema } from '@delon/form';
import { SHARED_IMPORTS } from '@shared';

@Component({
  selector: 'app-user-form',
  standalone: true,
  imports: [SHARED_IMPORTS, SFComponent],
  template: `
    <sf 
      [schema]="schema"
      [ui]="ui"
      [formData]="initialData()"
      [loading]="loading()"
      (formSubmit)="handleSubmit($event)"
      (formChange)="handleChange($event)"
      (formError)="handleError($event)"
    />
  `
})
export class UserFormComponent {
  loading = signal(false);
  initialData = signal<any>({});
  formSubmit = output<any>();
  
  schema: SFSchema = {
    properties: {
      name: {
        type: 'string',
        title: '全名',
        maxLength: 100
      },
      email: {
        type: 'string',
        title: '电子邮件',
        format: 'email'
      },
      age: {
        type: 'number',
        title: '年龄',
        minimum: 18,
        maximum: 120
      },
      role: {
        type: 'string',
        title: '角色',
        enum: ['admin', 'member', 'viewer'],
        default: 'member'
      }
    },
    required: ['name', 'email', 'role']
  };
  
  ui: SFUISchema = {
    '*': {
      spanLabel: 6,
      spanControl: 18,
      grid: { span: 24 }
    },
    $name: {
      placeholder: '输入全名',
      widget: 'string'
    },
    $email: {
      placeholder: 'user@example.com',
      widget: 'string'
    },
    $age: {
      widget: 'number'
    },
    $role: {
      widget: 'select',
      placeholder: '选择角色'
    }
  };
  
  handleSubmit(value: any): void {
    console.log('表单提交:', value);
    this.formSubmit.emit(value);
  }
  
  handleChange(value: any): void {
    console.log('表单改变:', value);
  }
  
  handleError(errors: any): void {
    console.error('表单错误:', errors);
  }
}

常见组件

字符串输入

{
  name: {
    type: 'string',
    title: '姓名',
    ui: {
      widget: 'string',
      placeholder: '输入姓名',
      prefix: 'User',
      suffix: '@',
      maxLength: 100
    }
  }
}

文本区域

{
  description: {
    type: 'string',
    title: '描述',
    ui: {
      widget: 'textarea',
      autosize: { minRows: 3, maxRows: 6 },
      placeholder: '输入描述'
    }
  }
}

数字输入

{
  amount: {
    type: 'number',
    title: '金额',
    minimum: 0,
    maximum: 1000000,
    ui: {
      widget: 'number',
      precision: 2,
      prefix: '$',
      formatter: (value: number) => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
    }
  }
}

日期选择器

{
  birthDate: {
    type: 'string',
    title: '出生日期',
    format: 'date',
    ui: {
      widget: 'date',
      mode: 'date',
      displayFormat: 'yyyy-MM-dd',
      end: 'today' // 不能选择未来日期
    }
  }
}

日期范围

{
  dateRange: {
    type: 'string',
    title: '日期范围',
    ui: {
      widget: 'date',
      mode: 'range',
      displayFormat: 'yyyy-MM-dd'
    }
  }
}

选择下拉列表

{
  category: {
    type: 'string',
    title: '类别',
    enum: [
      { label: '技术', value: 'tech' },
      { label: '商业', value: 'business' },
      { label: '科学', value: 'science' }
    ],
    ui: {
      widget: 'select',
      placeholder: '选择类别',
      allowClear: true,
      showSearch: true
    }
  }
}

多选

{
  tags: {
    type: 'array',
    title: '标签',
    items: {
      type: 'string',
      enum: ['angular', 'react', 'vue', 'typescript']
    },
    ui: {
      widget: 'select',
      mode: 'multiple',
      placeholder: '选择标签'
    }
  }
}

自动完成

{
  city: {
    type: 'string',
    title: '城市',
    ui: {
      widget: 'autocomplete',
      asyncData: () => this.loadCities(),
      debounceTime: 300,
      placeholder: '搜索城市'
    }
  }
}

private async loadCities(): Promise<any[]> {
  return [
    { label: '纽约', value: 'ny' },
    { label: '洛杉矶', value: 'la' },
    { label: '芝加哥', value: 'chi' }
  ];
}

单选按钮

{
  priority: {
    type: 'string',
    title: '优先级',
    enum: [
      { label: '低', value: 'low' },
      { label: '中', value: 'medium' },
      { label: '高', value: 'high' }
    ],
    default: 'medium',
    ui: {
      widget: 'radio',
      styleType: 'button' // 或 'default'
    }
  }
}

复选框

{
  agree: {
    type: 'boolean',
    title: '同意条款',
    ui: {
      widget: 'checkbox'
    }
  }
}

开关

{
  isActive: {
    type: 'boolean',
    title: '激活状态',
    ui: {
      widget: 'switch',
      checkedChildren: 'On',
      unCheckedChildren: 'Off'
    }
  }
}

滑块

{
  rating: {
    type: 'number',
    title: '评分',
    minimum: 0,
    maximum: 100,
    ui: {
      widget: 'slider',
      marks: {
        0: '0',
        50: '50',
        100: '100'
      }
    }
  }
}

文件上传

{
  avatar: {
    type: 'string',
    title: '头像',
    ui: {
      widget: 'upload',
      action: '/api/upload',
      accept: 'image/*',
      limit: 1,
      listType: 'picture-card'
    }
  }
}

异步数据加载

动态下拉选项

{
  assignee: {
    type: 'string',
    title: '被分配人',
    ui: {
      widget: 'select',
      asyncData: () => this.loadUsers(),
      placeholder: '选择用户'
    }
  }
}

private async loadUsers(): Promise<any[]> {
  const users = await this.userService.getUsers();
  return users.map(u => ({
    label: u.name,
    value: u.id
  }));
}

级联选择

{
  country: {
    type: 'string',
    title: '国家',
    ui: {
      widget: 'select',
      asyncData: () => this.loadCountries(),
      change: (value: string) => this.onCountryChange(value)
    }
  },
  city: {
    type: 'string',
    title: '城市',
    ui: {
      widget: 'select',
      asyncData: () => this.loadCities(this.selectedCountry())
    }
  }
}

private selectedCountry = signal<string>('');

onCountryChange(value: string): void {
  this.selectedCountry.set(value);
}

条件字段

基于值显示/隐藏

schema: SFSchema = {
  properties: {
    userType: {
      type: 'string',
      title: '用户类型',
      enum: ['individual', 'company']
    },
    // 仅对公司显示
    companyName: {
      type: 'string',
      title: '公司名称',
      ui: {
        visibleIf: {
          userType: ['company']
        }
      }
    },
    // 仅对个人显示
    firstName: {
      type: 'string',
      title: '名字',
      ui: {
        visibleIf: {
          userType: ['individual']
        }
      }
    }
  }
};

自定义验证器

import { SFSchema } from '@delon/form';

schema: SFSchema = {
  properties: {
    password: {
      type: 'string',
      title: '密码',
      ui: {
        type: 'password',
        validator: (value: string, formProperty: any, form: any) => {
          if (value.length < 8) {
            return [{ keyword: 'minLength', message: '密码至少8个字符' }];
          }
          if (!/[A-Z]/.test(value)) {
            return [{ keyword: 'uppercase', message: '密码必须包含大写字母' }];
          }
          return [];
        }
      }
    },
    confirmPassword: {
      type: 'string',
      title: '确认密码',
      ui: {
        type: 'password',
        validator: (value: string, formProperty: any, form: any) => {
          if (value !== form.value.password) {
            return [{ keyword: 'match', message: '密码必须匹配' }];
          }
          return [];
        }
      }
    }
  }
};

多步骤表单(向导)

import { Component, signal } from '@angular/core';
import { SFSchema } from '@delon/form';

@Component({
  selector: 'app-wizard-form',
  template: `
    <nz-steps [nzCurrent]="currentStep()">
      <nz-step nzTitle="基本信息" />
      <nz-step nzTitle="地址" />
      <nz-step nzTitle="确认" />
    </nz-steps>
    
    @switch (currentStep()) {
      @case (0) {
        <sf [schema]="basicInfoSchema" (formSubmit)="nextStep($event)" />
      }
      @case (1) {
        <sf [schema]="addressSchema" (formSubmit)="nextStep($event)" />
      }
      @case (2) {
        <div class="confirmation">
          <h3>审核您的信息</h3>
          <pre>{{ formData() | json }}</pre>
          <button nz-button nzType="primary" (click)="submit()">提交</button>
        </div>
      }
    }
  `
})
export class WizardFormComponent {
  currentStep = signal(0);
  formData = signal<any>({});
  
  basicInfoSchema: SFSchema = {
    properties: {
      name: { type: 'string', title: '姓名' },
      email: { type: 'string', title: '电子邮件', format: 'email' }
    },
    required: ['name', 'email']
  };
  
  addressSchema: SFSchema = {
    properties: {
      street: { type: 'string', title: '街道' },
      city: { type: 'string', title: '城市' },
      zipCode: { type: 'string', title: '邮政编码' }
    },
    required: ['street', 'city']
  };
  
  nextStep(value: any): void {
    this.formData.update(data => ({ ...data, ...value }));
    this.currentStep.update(step => step + 1);
  }
  
  submit(): void {
    console.log('最终数据:', this.formData());
  }
}

网格布局

ui: SFUISchema = {
  '*': {
    spanLabel: 4,
    spanControl: 20,
    grid: { span: 12 } // 2列(24 / 12 = 2)
  },
  $description: {
    grid: { span: 24 } // 满宽度
  }
};

响应式布局

ui: SFUISchema = {
  '*': {
    grid: {
      xs: 24,  // 移动:满宽度
      sm: 12,  // 平板:2列
      md: 8,   // 桌面:3列
      lg: 6    // 大屏:4列
    }
  }
};

表单操作

@Component({
  template: `
    <sf [schema]="schema" [button]="button">
      <ng-template sf-template="button" let-btn>
        <button 
          nz-button 
          [nzType]="btn.submit ? 'primary' : 'default'"
          (click)="btn.submit ? handleSubmit() : handleReset()"
        >
          {{ btn.submit ? '提交' : '重置' }}
        </button>
      </ng-template>
    </sf>
  `
})
export class CustomButtonFormComponent {
  button = {
    submit_text: '提交',
    reset_text: '重置',
    submit_type: 'primary' as const,
    reset_type: 'default' as const
  };
}

清单

创建SF表单时:

  • [ ] 使用适当的JSON模式类型
  • [ ] 定义必填字段
  • [ ] 设置验证规则(最小/最大,格式,模式)
  • [ ] 使用适当的组件处理数据类型
  • [ ] 处理异步数据加载
  • [ ] 实现条件字段可见性
  • [ ] 需要时添加自定义验证器
  • [ ] 配置响应式网格布局
  • [ ] 处理表单提交和错误
  • [ ] 提供加载状态
  • [ ] 测试表单验证
  • [ ] 确保可访问性(标签,ARIA)

参考资料