Skip to content

Form 表单

用于数据录入、校验,支持输入框、单选框、复选框、文件上传等类型,需要与 Field 输入框 组件搭配使用。

导入

js
import Form from '@/components/form/Form.vue';

使用方法

基础用法

表单组件结合 Field 组件使用,可以实现数据录入和表单验证。

vue
<template>
  <Form
    ref="formRef1"
    :model="model1"
    :rules="{
      userName: { required: true, message: '请输入用户名' },
      password: [
        { required: true, message: '请输入密码' },
        { min: 6, message: '密码长度必须大于等于6位' },
      ],
      passwordRepeat: [
        { required: true, message: '请再输入一次密码' },
        { validator(rule, value, callback, source) {
            if (value !== source.password) {
              callback('两次输入密码不一致,请检查');
              return;
            }
            callback();
          },
        },
      ],
    }"
    :labelFlex="1"
    :inputFlex="4"
    @submit="handleSubmit"
  >
    <Field name="userName" label="用户名" placeholder="请输入用户名" />
    <Field name="password" label="密码" placeholder="请输入密码"  type="password" />
    <Field name="passwordRepeat" label="密码" placeholder="再输入一次"  type="password" />
  </Form>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Form from '@/components/form/Form.vue';
import Field from '@/components/form/Field.vue';
import type { FormInstance, FormValues } from '@/components/form/Form.vue';
import Button from '@/components/basic/Button.vue';
import FlexCol from '@/components/layout/FlexCol.vue';

const formRef1 = ref<FormInstance>();
const model1 = ref({
  userName: '',
  password: '',
  passwordRepeat: '',
});

function handleSubmit(values: FormValues) {
  console.log('表单数据', values);
}
</script>

校验规则

表单使用 async-validator 进行表单校验,支持多种校验规则。

vue
<template>
  <Form
    ref="formRef2"
    :model="model2"
    :rules="form2Rules"
    :labelFlex="1"
    :inputFlex="4"
    @submit="handleSubmit"
  >
    <Field name="pattern" label="文本" placeholder="正则校验(输入数字)" />
    <Field name="validator" label="文本" placeholder="自定义校验(输入1正确,其他错误)" />
    <Field name="async" label="文本" placeholder="异步函数校验(输入1正确,其他错误)" />
  </Form>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Form from '@/components/form/Form.vue';
import Field from '@/components/form/Field.vue';
import type { FormInstance, FormValues } from '@/components/form/Form.vue';
import type { Rules } from 'async-validator';

const formRef2 = ref<FormInstance>();
const model2 = ref({
  pattern: '',
  validator: '',
  async: '',
});

const form2Rules: Rules = {
  pattern: { required: true, pattern: /\d/, message: '请输入数字' },
  validator: { 
    required: true, 
    validator: (rule, value) => {
      if (value !== '1') {
        return new Error('请输入1');
      }
    } 
  },
  async: { 
    required: true, 
    validator: (rule, value, callback) => {
      setTimeout(() => {
        callback(value === '1' ? undefined : '请输入1');
      }, 200);
    },
  },
};

function handleSubmit(values: FormValues) {
  console.log('表单数据', values);
}
</script>

自定义样式

可以通过 fieldProps 属性设置自定义样式。

vue
<template>
  <Form
    ref="formRef4"
    :model="model4"
    :rules="{
      userName: { required: true, message: '请输入用户名' },
      password: [
        { required: true, message: '请输入密码' },
        { min: 6, message: '密码长度必须大于等于6位' },
      ],
      passwordRepeat: [
        { required: true, message: '请再输入一次密码' },
        {
          validator(rule, value, callback, source) {
            if (value !== source.password) {
              callback('两次输入密码不一致,请检查');
              return;
            }
            callback();
          },
        },
      ],
    }"
    :showLabel="false"
    :labelFlex="1"
    :inputFlex="4"
    :fieldProps="{
      fieldStyle: { backgroundColor: 'transparent' },
      activeFieldStyle: { borderBottomColor: '#1989fa' },
      errorFieldStyle: { borderBottomColor: '#ee0a24' },
    }"
    @submit="handleSubmit"
  >
    <Field name="userName" label="用户名" placeholder="请输入用户名" />
    <Field name="password" label="密码" placeholder="请输入密码"  type="password" />
    <Field name="passwordRepeat" label="密码" placeholder="再输入一次"  type="password" />
  </Form>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Form from '@/components/form/Form.vue';
import Field from '@/components/form/Field.vue';
import type { FormInstance, FormValues } from '@/components/form/Form.vue';

const formRef4 = ref<FormInstance>();
const model4 = ref({
  userName: '',
  password: '',
  passwordRepeat: '',
});

function handleSubmit(values: FormValues) {
  console.log('表单数据', values);
}
</script>

不同表单类型

支持多种表单类型组合使用。

vue
<template>
  <Form
    ref="formRef"
    :model="model"
    :labelFlex="3"
    :inputFlex="6"
    @submit="handleSubmit"
  >
    <Field name="text" label="文本" placeholder="文本框" />
    <Field name="text" label="数字输入" placeholder="请输入数字" type="number" />
    <Field name="text" label="密码输入" placeholder="请输入密码" type="password" />
    <Field name="switch" label="开关">
      <Switch />
    </Field>
    <Field name="check" label="复选框" center>
      <CheckBox shape="square" text="我是复选框" />
    </Field>
    <Field name="checkgroup" label="复选框组" center>
      <CheckBoxGroup>
        <CheckBox name="a" text="复选框1" />
        <CheckBox name="b" text="复选框2" />
      </CheckBoxGroup>
    </Field>
    <Field name="radio" label="单选框" center>
      <RadioGroup>
        <Radio name="a" text="单选框1" />
        <Radio name="b" text="单选框2" />
      </RadioGroup>
    </Field>
    <Field name="stepper" label="步进器"><Stepper /></Field>
    <Field name="rate" label="评分"><Rate /></Field>
    <Field name="sider" label="滑块"><Slider /></Field>
    <Field name="file" label="上传文件">
      <UploaderField :upload="(action) => {
        //测试完成上传,实际这里需要调用地址上传
        action.onFinish({
          uploadedUrl: action.item.filePath,
        });
      }" />
    </Field>
    <Field name="date" label="选择日期" showRightArrow><DatePickerField /></Field>
    <Field name="time" label="选择时间" showRightArrow><TimePickerField /></Field>
    <Field name="datetime" label="选择日期+时间" showRightArrow><DateTimePickerField /></Field>
    <Field name="day" label="选择日历" showRightArrow>
      <CalendarField />
    </Field>
    <Field name="day" label="选择日历(多选)" showRightArrow>
      <CalendarField pickType="range" />
    </Field>
    <Field name="options" label="选择选项" showRightArrow> 
      <PickerField :columns="[[
        { text: '苹果', value: '1'},
        { text: '香蕉', value: '2'},
        { text: '橘子', value: '3'},
        { text: '葡萄', value: '4'},
        { text: '菠萝', value: '5'},
      ]]" />
    </Field>
    <Field name="cascadeoptions" label="选择联动选项" showRightArrow>
      <CascadePickerField :columnsCount="2" :columns="[
        { text: '水果', value: '1', children: [
          { text: '苹果', value: '1-1' },
          { text: '香蕉', value: '1-2' },
          { text: '橘子', value: '1-3' },
          { text: '葡萄', value: '1-4' },
          { text: '菠萝', value: '1-5' },
        ] },
        { text: '食品', value: '2', children: [
          { text: '披萨', value: '2-1' },
          { text: '汉堡', value: '2-2' },
          { text: '薯条', value: '2-3' },
          { text: '爆米花', value: '2-4' },
        ]  },
        { text: '厨具', value: '3', children: [
          { text: '炒锅', value: '3-1' },
          { text: '锅铲', value: '3-2' },
          { text: '菜刀', value: '3-3' },
        ]  },
        { text: '家具', value: '4', children: [
          { text: '沙发', value: '4-1' },
          { text: '椅子', value: '4-2' },
          { text: '衣柜', value: '4-3' },
          { text: '茶几', value: '4-4' },
          { text: '书桌', value: '4-5' },
        ] },
      ]" />
    </Field>
  </Form>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Form from '@/components/form/Form.vue';
import Field from '@/components/form/Field.vue';
import Switch from '@/components/form/Switch.vue';
import CheckBoxGroup from '@/components/form/CheckBoxGroup.vue';
import CheckBox from '@/components/form/CheckBox.vue';
import RadioGroup from '@/components/form/RadioGroup.vue';
import Radio from '@/components/form/Radio.vue';
import type { FormInstance, FormValues } from '@/components/form/Form.vue';

const formRef3 = ref<FormInstance>();
const model3 = ref({
  text: '',
  switch: false,
  check: false,
  checkgroup: ['a'],
  radio: 'a',
});

function handleSubmit(values: FormValues) {
  console.log('表单数据', values);
}
</script>

API 参考

Props

名称说明类型必填默认值
model表单的值FormState-
rules表单验证规则Rules-
name表单名称string-
labelFlex标签flex值number-
labelWidth标签宽度string | number-
labelPosition标签位置'top' | 'left'-
labelAlign标签对齐方式"left" | "center" | "right"-
inputFlex输入区域flex值number-
colon添加冒号booleantrue
validateTrigger校验时机'blur' | 'change' | 'submit''submit'
showLabel是否显示标签booleantrue
readonly是否只读booleanfalse
disabled是否禁用booleanfalse
clearValidResult提交时是否清除之前的错误验证结果booleantrue
addRequireMark是否根据校验规则自动添加必填星号booleantrue
fieldProps传入Field组件的属性FieldProps-
innerStyle内部容器样式ViewStyle-

Events

名称说明参数
reset表单重置回调
submit表单提交成功回调FormValues
submitFail表单提交失败回调ValidateError[]

Methods

方法名说明参数
blur取消表单内部的输入框激活
clearValidate清除验证错误信息name?: string | string[]
resetFields重置表单字段到初始值name?: string | string[]
validate验证表单name?: string | string[]
submit提交表单valid?: boolean

Slots

名称说明
default表单内容,通常包含Field组件

类型定义

typescript
// 表单值类型
export type FormValueType = Date|null|number|string|boolean|number[]|string[]|boolean[]|null[]|FormValueType[];

// 表单值对象
export interface FormValues {
  [index: string]: FormValueType;
}

// 表单状态
export interface FormState {
  [index: string]: any;
}

// 表单实例
export interface FormInstance {
  blur(): void;
  clearValidate: (name?: string | string[]) => void;
  resetFields: (name?: string | string[]) => void;
  validate(name?: string | string[]): Promise<void>;
  submit(valid?: boolean): Promise<void>;
}

校验规则

本库校验规则基于async-validator,支持的校验规则请参考async-validator的文档。

FormContext

FormContext 是表单组件的核心上下文系统,提供了表单与表单项之间的通信机制,以及表单项子组件与表单项之间的数据交换能力。

表单项上下文(FormItemContext)

表单项上下文为表单内部的输入组件提供了与表单项交互的接口。

ts
import { useInjectFormItemContext } from '@/components/form/FormContext'

const formItemContext = useInjectFormItemContext()

方法

方法名说明参数返回值
getFieldName获取字段名称ref: anystring
onFieldFocus触发表单条目获得焦点事件
onFieldBlur触发表单条目失去焦点事件
onFieldChange触发表单条目值改变事件newValue: unknown
clearValidate清除表单条目校验状态
setOnClickListener设置表单条目点击事件监听器listener: (() => void)|undefined
getFormModelValue获取表单组件中的当前值any

表单上下文(FormContext)

表单上下文由表单组件提供,用于管理所有表单项。

ts
import { useInjectFormContext } from '@/components/form/FormContext'

const formContext = useInjectFormContext()

方法

方法由表单项组件调用,一般不需要手动调用。

属性

允许获取表单的相关配置。

属性名说明类型
validateTrigger校验触发时机Ref<ValidTrigger|undefined>
addRequireMark是否添加必填标记Ref<boolean|undefined>
colon是否添加冒号Ref<boolean|undefined>
labelWidth标签宽度Ref<string|number|undefined>
labelAlign标签对齐方式Ref<"left"|"center"|"right"|undefined>
labelPosition标签位置Ref<'top'|'left'|undefined>
labelFlex标签弹性布局Ref<number|undefined>
inputFlex输入框弹性布局Ref<number|undefined>
showLabel是否显示标签Ref<boolean|undefined>
name表单名称Ref<string|undefined>
fieldProps字段属性Ref<FieldProps|undefined>

校验触发时机(ValidTrigger)

ts
export type ValidTrigger = "blur" | "change" | "submit";
说明
blur失去焦点时触发校验
change值改变时触发校验
submit提交表单时触发校验

实用工具函数

useFieldChildValueInjector

用于注入表单项子组件值,实现表单项值的双向绑定。

ts
import { useFieldChildValueInjector } from '@/components/form/FormContext'

const { value, updateValue } = useFieldChildValueInjector(
  toRef(props, 'modelValue'),
  (v) => emit('update:modelValue', v)
)
参数
参数说明类型必填默认值
propsModelValue组件外部传入的modelValueRef<T>-
emit组件外部的emit(v: T) => void-
secondParentContext二级父组件上下文{ getValue: () => T, updateValue: (v: T) => void }-
fieldClick表单项点击事件监听器() => void-
initialValue初始值T-
返回值
属性说明类型
value临时值ComputedRef<T>
updateValue更新表单项值的方法(newValue: T) => void
context表单项上下文FormItemContext

propGetFormContext

用于在Props默认值回调中获取表单上下文。

ts
import { propGetFormContext } from '@/components/form/FormContext'

const props = defineProps({
  labelWidth: {
    type: [String, Number],
    default: () => {
      const formContext = propGetFormContext();
      return formContext?.labelWidth.value;
    }
  }
})

useInjectFormItemContext

用于注入表单项上下文。

ts
import { useInjectFormItemContext } from '@/components/form/FormContext'

const formItemContext = useInjectFormItemContext()

useInjectFormContext

用于注入表单上下文。

ts
import { useInjectFormContext } from '@/components/form/FormContext'

const formContext = useInjectFormContext()

使用示例

自定义输入组件

vue
<script setup lang="ts">
import { ref, toRef } from 'vue'
import { useFieldChildValueInjector } from '@/components/form/FormContext'

const props = defineProps<{
  modelValue?: string
  placeholder?: string
}>()

const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void
}>()

// 使用useFieldChildValueInjector实现值的双向绑定和表单交互
const { value, updateValue } = useFieldChildValueInjector(
  toRef(props, 'modelValue'),
  (v) => emit('update:modelValue', v)
)

// 处理输入事件
const handleInput = (e: Event) => {
  const target = e.target as HTMLInputElement
  updateValue(target.value)
}
</script>

<template>
  <input
    :value="value || ''"
    :placeholder="placeholder"
    @input="handleInput"
  />
</template>

处理表单项交互事件

vue
<script setup lang="ts">
import { useInjectFormItemContext } from '@/components/form/FormContext'

const formItemContext = useInjectFormItemContext()

// 处理聚焦事件
const handleFocus = () => {
  formItemContext.onFieldFocus()
}

// 处理失焦事件
const handleBlur = () => {
  formItemContext.onFieldBlur()
}

// 清除校验状态
const handleClear = () => {
  formItemContext.clearValidate()
}
</script>

<template>
  <input
    @focus="handleFocus"
    @blur="handleBlur"
  />
  <button @click="handleClear">清除校验</button>
</template>