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 | 添加冒号 | boolean | 否 | true |
| validateTrigger | 校验时机 | 'blur' | 'change' | 'submit' | 否 | 'submit' |
| showLabel | 是否显示标签 | boolean | 否 | true |
| readonly | 是否只读 | boolean | 否 | false |
| disabled | 是否禁用 | boolean | 否 | false |
| clearValidResult | 提交时是否清除之前的错误验证结果 | boolean | 否 | true |
| addRequireMark | 是否根据校验规则自动添加必填星号 | boolean | 否 | true |
| 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: any | string |
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 | 组件外部传入的modelValue | Ref<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>