TypeScript装饰器之我们是这么做表单和校验的

2,012 阅读2分钟

banner.jpg

一、 先贴实现效果图

image.png

该表单有如下验证规则:

  • 昵称只能输入中文,必填
  • 年龄 18-50 必填
  • 性别默认女 从字典选择
  • 手机号格式验证 选填
  • 简介选填,有输入限制

二、 实现方案

1. 声明 @FormField() 装饰器, 用户表单配置和一些通用验证

声明装饰器

export function FormField(fieldConfig: IFormFieldConfig = {}): Function {
  return (target: any, key: string) => {
    fieldConfig.key = key
    return AirDecorator.setFieldConfig(target, key, FIELD_CONFIG_KEY, fieldConfig, FIELD_LIST_KEY)
  }
}

装饰器的配置 IFormFieldConfig 接口(也就是上篇文章提到的,我们为数不多允许使用 interface 定义结构体的地方之一)

export interface IFormFieldConfig extends IFieldConfig {
  placeholder?: string;
  
  isTextarea?: boolean;
  
  isPassword?: boolean;
  
  dictionary?: AirDictionaryArray<IDictionary>;
  
  isSwitch?: boolean;
  
  isRadioButton?: boolean;
  
  defaultValue?: boolean | string | number;

  isChinese?: boolean | string

  isMobilePhone?: boolean | string

  isTelPhone?: boolean | string

  isEmail?: boolean | string

  isPhone?: boolean | string

  isRequiredString?: boolean | string

  isRequiredNumber?: boolean | string
  
  regExp?: RegExp
}

当然,其实上面还有很多配置项,也有很多的注释,为了篇幅我删掉了。

2. 声明实体用于数据结构声明和配置负载

export class UserEntity extends BaseEntity {
  @FormField({
    isRequiredString: true,
    isChinese: true,
    placeholder: '起个牛逼的昵称...',
  })
  @FieldName('昵称') nickname!: string

  @Dictionary(SexDictionary)
  @FormField({
    defaultValue: Sex.FEMALE,
  })
  @FieldName('性别') sex!: Sex

  @FormField({
    isRequiredNumber: true,
    min: 18,
    max: 50,
    isNumber: true,
  })
  @FieldName('年龄') age!: number

  @FormField({
    isMobilePhone: true,
  })
  @FieldName('手机') phone!: string

  @FormField({
    isTextarea: true,
    maxLength: 250,
  })
  @FieldName('简介') desc!: string
}

3. 封装输入组件 AInput

AInput 组件封装文件的篇幅太长, 简单截个图吧

image.png

大概的实现逻辑是:)

  • 读取传入的 entity 类上的所有 @FormField() 配置信息, 渲染页面
  • 将输入结果 emits 给父组件

4. View层调用

<template>
  <ADialog
    :title="title"
    :form-ref="formRef"
    :loading="isLoading"
    confirm-text="保存"
    :fullable="false"
    @on-confirm="onSubmit()"
    @on-cancel="onCancel()"
  >
    <el-form
      ref="formRef"
      :model="formData"
      label-width="120px"
      :rules="rules"
      @submit.prevent
    >
      <el-form-item
        :label="UserEntity.getFormFieldLabel('email')"
        prop="email"
      >
        <AInput
          v-model.email="formData.email"
          :entity="UserEntity"
        />
      </el-form-item>
      <el-form-item
        :label="UserEntity.getFormFieldLabel('nickname')"
        prop="nickname"
      >
        <AInput
          v-model.nickname="formData.nickname"
          :entity="UserEntity"
        />
      </el-form-item>
      <el-form-item
        :label="UserEntity.getFormFieldLabel('remark')"
        prop="remark"
      >
        <AInput
          v-model.remark="formData.remark"
          :entity="UserEntity"
        />
      </el-form-item>
    </el-form>
  </ADialog>
</template>

<script lang="ts" setup>
import {
  ADialog, AInput,
} from '@/airpower/component'
import { airPropsParam } from '@/airpower/config/AirProps'
import { UserService } from '@/model/user/UserService'
import { UserEntity } from '@/model/user/UserEntity'
import { useAirEditor } from '@/airpower/hook/useAirEditor'

const props = defineProps(airPropsParam(new UserEntity()))

const {
  isLoading, formData, formRef, title, rules,
  onSubmit,
} = useAirEditor(props, UserEntity, UserService, {

})
</script>

视图层的部分代码已经删除,不影响查看实现逻辑。

三、 That's all

今天就这么多吧, 实现方式是不是跟你的不太一样呢?

是的,我还是那个 Java仔/运维仔/前端仔

是,也不仅仅。

2023-07-26 17:31:14 更新

项目已经在Github开源: github.com/HammCn/AirP…

四、更多的文章可以查看这个专栏

juejin.cn/column/7249…