Form
表单包含 输入框, 单选框, 下拉选择, 多选框 等用户输入的组件。 使用表单,您可以收集、验证和提交数据。
TIP
Form组件为Flex 布局。
基础表单
<template>
<cl-form :model="form" label-width="50px">
<cl-form-item label="name">
<cl-input v-model="form.name" />
</cl-form-item>
<cl-form-item label="desc">
<cl-input v-model="form.desc" type="textarea" />
</cl-form-item>
<cl-form-item label="count">
<cl-input-number v-model="form.count" />
</cl-form-item>
<cl-form-item label="color">
<cl-color-picker v-model="form.color" />
</cl-form-item>
<cl-form-item label="">
<cl-button type="primary" @click="onSubmit">Create</cl-button>
<cl-button>Cancel</cl-button>
</cl-form-item>
</cl-form>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
// do not use same name with ref
const form = reactive({
name: "",
count: "",
color: "",
desc: ""
});
const onSubmit = () => {
console.log("submit!");
};
</script>
TIP
W3C 标准定义:
当一个表单中只有一个单行文本输入字段时, 浏览器应当将在此字段中按下 Enter (回车键)的行为视为提交表单的请求。 如果希望阻止这一默认行为,可以在 <cl-form> 标签上添加 @submit.prevent。
对齐方式
<template>
<cl-button-group label="label position">
<cl-button @click="labelPosition='left'">Left</cl-button>
<cl-button @click="labelPosition='right'">Right</cl-button>
<cl-button @click="labelPosition='top'">Top</cl-button>
</cl-button-group>
<div style="margin: 20px" />
<cl-form
:label-position="labelPosition"
label-width="100px"
:model="formLabelAlign"
style="max-width: 460px"
>
<cl-form-item label="Name">
<cl-input v-model="formLabelAlign.name" />
</cl-form-item>
<cl-form-item label="Activity zone">
<cl-input v-model="formLabelAlign.region" />
</cl-form-item>
<cl-form-item label="Activity form">
<cl-input v-model="formLabelAlign.type" />
</cl-form-item>
</cl-form>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
const labelPosition = ref('right')
const formLabelAlign = reactive({
name: '',
region: '',
type: '',
})
</script>
表单校验
TIP
使用scroll-to-error 参数控制是否在验证失败后滚动至第一个错误表单 使用scroll-into-view-options 进行scrollIntoView 的配置
<template>
<cl-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="120px"
class="demo-ruleForm"
:size="formSize"
status-icon
scroll-to-error
:scroll-into-view-options="{
block:'center'
}"
>
<cl-form-item label="Activity name" prop="name">
<cl-input v-model="ruleForm.name" />
</cl-form-item>
<cl-form-item label="Activity form" prop="desc">
<cl-input v-model="ruleForm.desc" type="textarea" />
</cl-form-item>
<cl-form-item>
<cl-button type="primary" @click="submitForm(ruleFormRef)"> Create </cl-button>
<cl-button @click="resetForm(ruleFormRef)">Reset</cl-button>
</cl-form-item>
</cl-form>
</template>
<script lang="ts" setup>
import { FormInstance, FormRules } from "@kirkw/carol-ui";
import { reactive, ref } from "vue";
interface RuleForm {
name: string;
region: string;
count: string;
date1: string;
date2: string;
delivery: boolean;
type: string[];
resource: string;
desc: string;
}
const formSize = ref("medium");
const ruleFormRef = ref<FormInstance>();
const ruleForm = reactive<RuleForm>({
name: "Hello",
region: "",
count: "",
date1: "",
date2: "",
delivery: false,
type: [],
resource: "",
desc: ""
});
const rules = reactive<FormRules<RuleForm>>({
name: [
{ required: true, message: "Please input Activity name", trigger: "blur" },
{ min: 3, max: 5, message: "Length should be 3 to 5", trigger: "blur" }
],
region: [
{
required: true,
message: "Please select Activity zone",
trigger: "change"
}
],
count: [
{
required: true,
message: "Please select Activity count",
trigger: "change"
}
],
date1: [
{
type: "date",
required: true,
message: "Please pick a date",
trigger: "change"
}
],
date2: [
{
type: "date",
required: true,
message: "Please pick a time",
trigger: "change"
}
],
type: [
{
type: "array",
required: true,
message: "Please select at least one activity type",
trigger: "change"
}
],
resource: [
{
required: true,
message: "Please select activity resource",
trigger: "change"
}
],
desc: [{ required: true, message: "Please input activity form", trigger: "blur" }]
});
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
console.log("submit!");
} else {
console.log("error submit!", fields);
}
});
};
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
const options = Array.from({ length: 10000 }).map((_, idx) => ({
value: `${idx + 1}`,
label: `${idx + 1}`
}));
</script>
自定义校验规则
<template>
<cl-form ref="ruleFormRef" :model="ruleForm" status-icon :rules="rules" label-width="80" class="demo-ruleForm">
<cl-form-item label="Password" prop="pass">
<cl-input v-model="ruleForm.pass" type="password" autocomplete="off" />
</cl-form-item>
<cl-form-item label="Confirm" prop="checkPass">
<cl-input v-model="ruleForm.checkPass" type="password" autocomplete="off" />
</cl-form-item>
<cl-form-item label="Age" prop="age">
<cl-input v-model.number="ruleForm.age" />
</cl-form-item>
<cl-form-item>
<cl-button type="primary" @click="submitForm(ruleFormRef)">Submit</cl-button>
<cl-button @click="resetForm(ruleFormRef)">Reset</cl-button>
</cl-form-item>
</cl-form>
</template>
<script lang="ts" setup>
import type { FormInstance, FormRules } from "@kirkw/carol-ui";
import { reactive, ref } from "vue";
const ruleFormRef = ref<FormInstance>();
const checkAge = (rule: any, value: any, callback: any) => {
if (!value) {
return callback(new Error("Please input the age"));
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error("Please input digits"));
} else {
if (value < 18) {
callback(new Error("Age must be greater than 18"));
} else {
callback();
}
}
}, 1000);
};
const validatePass = (rule: any, value: any, callback: any) => {
if (value === "") {
callback(new Error("Please input the password"));
} else {
if (ruleForm.checkPass !== "") {
if (!ruleFormRef.value) return;
ruleFormRef.value.validateField("checkPass", () => null);
}
callback();
}
};
const validatePass2 = (rule: any, value: any, callback: any) => {
if (value === "") {
callback(new Error("Please input the password again"));
} else if (value !== ruleForm.pass) {
callback(new Error("Two inputs don't match!"));
} else {
callback();
}
};
const ruleForm = reactive({
pass: "",
checkPass: "",
age: ""
});
const rules = reactive<FormRules<typeof ruleForm>>({
pass: [{ validator: validatePass, trigger: "blur" }],
checkPass: [{ validator: validatePass2, trigger: "blur" }],
age: [{ validator: checkAge, trigger: "blur" }]
});
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(valid => {
if (valid) {
console.log("submit!");
} else {
console.log("error submit!");
return false;
}
});
};
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>
TIP
自定义的校验回调函数必须被调用。 更多高级用法可参考 async-validator。
添加/删除表单项
<template>
<cl-form
ref="formRef"
:model="dynamicValidateForm"
label-width="80px"
class="demo-dynamic"
>
<cl-form-item
prop="email"
label="Email"
:rules="[
{
required: true,
message: 'Please input email address',
trigger: 'blur',
},
{
type: 'email',
message: 'Please input correct email address',
trigger: ['blur', 'change'],
},
]"
>
<cl-input v-model="dynamicValidateForm.email" />
</cl-form-item>
<cl-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
:label="'Domain' + index"
:prop="'domains.' + index + '.value'"
:rules="{
required: true,
message: 'domain can not be null',
trigger: 'blur',
}"
>
<cl-input v-model="domain.value" />
<cl-button class="mt-2" @click.prevent="removeDomain(domain)"
>Delete</cl-button
>
</cl-form-item>
<cl-form-item>
<cl-button type="primary" @click="submitForm(formRef)">Submit</cl-button>
<cl-button @click="addDomain">New domain</cl-button>
<cl-button @click="resetForm(formRef)">Reset</cl-button>
</cl-form-item>
</cl-form>
</template>
<script lang="ts" setup>
import { FormInstance } from '@kirkw/carol-ui';
import { reactive, ref } from 'vue'
const formRef = ref<FormInstance>()
const dynamicValidateForm = reactive<{
domains: DomainItem[]
email: string
}>({
domains: [
{
key: 1,
value: '',
},
],
email: '',
})
interface DomainItem {
key: number
value: string
}
const removeDomain = (item: DomainItem) => {
const index = dynamicValidateForm.domains.indexOf(item)
if (index !== -1) {
dynamicValidateForm.domains.splice(index, 1)
}
}
const addDomain = () => {
dynamicValidateForm.domains.push({
key: Date.now(),
value: '',
})
}
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
console.log('submit!')
} else {
console.log('error submit!')
return false
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
}
</script>
数字类型验证
<template>
<cl-form
ref="formRef"
:model="numberValidateForm"
label-width="auto"
class="demo-ruleForm"
>
<cl-form-item
label="age"
prop="age"
:rules="[
{ required: true, message: 'age is required' },
{ type: 'number', message: 'age must be a number' },
]"
>
<cl-input
v-model.number="numberValidateForm.age"
type="text"
autocomplete="off"
/>
</cl-form-item>
<cl-form-item>
<cl-button type="primary" @click="submitForm(formRef)">Submit</cl-button>
<cl-button @click="resetForm(formRef)">Reset</cl-button>
</cl-form-item>
</cl-form>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import type { FormInstance } from '@kirkw/carol-ui'
const formRef = ref<FormInstance>()
const numberValidateForm = reactive({
age: '',
})
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
console.log('submit!')
} else {
console.log('error submit!')
return false
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
}
</script>
TIP
When an cl-form-item
is nested in another cl-form-item
, its label width will be 0
. You can set label-width
on that cl-form-item
if needed.
尺寸控制
<template>
<div>
<cl-button-group v-model="size" label="size control">
<cl-button label="large" @click="size = 'large'">large</cl-button>
<cl-button label="medium" @click="size = 'medium'">medium</cl-button>
<cl-button label="small" @click="size = 'small'">small</cl-button>
</cl-button-group>
<cl-button-group style="margin-left: 10px" v-model="labelPosition" label="position control">
<cl-button label="left" @click="labelPosition = 'left'">Left</cl-button>
<cl-button label="right" @click="labelPosition = 'right'">Right</cl-button>
<cl-button label="top" @click="labelPosition = 'top'">Top</cl-button>
</cl-button-group>
</div>
<br />
<cl-form ref="form" :model="sizeForm" label-width="140px" :label-position="labelPosition" :size="size">
<cl-form-item label="Activity name">
<cl-input v-model="sizeForm.name" />
</cl-form-item>
<cl-form-item>
<cl-button type="primary" @click="onSubmit">Create</cl-button>
<cl-button>Cancel</cl-button>
</cl-form-item>
</cl-form>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
const size = ref("medium");
const labelPosition = ref("right");
const sizeForm = reactive({
name: "",
region: "",
date1: "",
date2: "",
delivery: false,
type: [],
resource: "",
desc: ""
});
function onSubmit() {
console.log("submit!");
}
</script>
<style>
.cl-radio-group {
margin-right: 12px;
}
</style>
:::
Form API
Form Attributes
属性 | 描述 | 类型 | 默认值 |
---|---|---|---|
model | 表单数据对象 | object Record<string, any> | — |
rules | 表单验证规则. | object FormRules | — |
label-position | 表单域标签的位置, 当设置为 left 或 right 时,则也需要设置 label-width 属性 | enum 'left' | 'right' | 'top' | right |
label-width | 标签的长度,例如 '50px'。 作为 Form 直接子元素的 form-item 会继承该值。 可以使用 auto。 | string / number | '' |
label-suffix | 表单域标签的后缀 | string | '' |
hide-required-asterisk | 是否隐藏必填字段标签旁边的红色星号。 | boolean | false |
require-asterisk-position | 星号的位置。 | enum 'left' | 'right' | left |
show-message | 是否显示校验错误信息 | boolean | true |
validate-on-rule-change | 是否在 rules 属性改变后立即触发一次验证 | boolean | true |
size | 用于控制该表单内组件的尺寸form. | enum '' | 'large' | 'default' | 'small' | — |
disabled | 是否禁用该表单内的所有组件。 如果设置为 true, 它将覆盖内部组件的 disabled 属性 | boolean | false |
scroll-to-error | 当校验失败时,滚动到第一个错误表单项 | boolean | false |
scroll-into-view-options | 当校验有失败结果时,滚动到第一个失败的表单项目 可通过 scrollIntoView 配置 | object | boolean | {block:'center'} |
Form Events
属性 | 描述 | 类型 |
---|---|---|
validate | 任一表单项被校验后触发 | Function (prop: FormItemProp, isValid: boolean, message: string) => void |
Form Slots
属性 | 描述 | Subtags |
---|---|---|
default | 自定义默认内容 | FormItem |
Form Exposes
属性 | 描述 | 类型 |
---|---|---|
validate | 对整个表单的内容进行验证。 接收一个回调函数,或返回 Promise。 | Function (callback?: FormValidateCallback) => Promise<void> |
validateField | 验证具体的某个字段。 | Function (props?: Arrayable<FormItemProp> | undefined, callback?: FormValidateCallback | undefined) => FormValidationResult |
resetFields | 重置该表单项,将其值重置为初始值,并移除校验结果 | |
Function (props?: Arrayable<FormItemProp> | undefined) => void | ||
clearValidate | 清理某个字段的表单验证信息。 | Function (props?: Arrayable<FormItemProp> | undefined) => void |
FormItem API
FormItem Attributes
| 属性 | 描述 | 类型 | 默认值 | | ------------ | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------- | --- | --- | | prop | model 的键名。 它可以是一个属性的值(如 a.b.0 或 ['a', 'b', '0'])。 在使用了 validate、resetFields 的方法时,该属性是必填的 | string / string[] | — | | label | 标签文本 | string | — | | label-width | 标签宽度,例如 '50px'。 可以使用 auto。 | string / number | '' | | required | 是否为必填项,如不设置,则会根据校验规则确认 | boolean | — | | rules | 表单验证规则, 具体配置见下表, 更多内容可以参考async-validator | object Arrayable<FormItemRule>
| — | | error | 表单域验证错误时的提示信息。设置该值会导致表单验证状态变为 error,并显示该错误信息。 | string | — | | show-message | 是否显示校验错误信息 | boolean | true | | size | 用于控制该表单域下组件的默认尺寸 | enum '' \| 'large' \| 'default' \| 'small'
| — | | |
FormItemRule
属性 | 描述 | 类型 | 默认值 |
---|---|---|---|
trigger | 验证逻辑的触发方式 | enum 'blur' | 'change' | — |
TIP
如果您不想根据输入事件触发验证器, 在相应的输入类型组件上设置 validate-event 属性为 false (<cl-input>, <cl-radio>, <cl-select>, . ……).
FormItem Slots
属性 | 描述 | 类型 |
---|---|---|
default | 表单的内容。 | — |
label | 标签位置显示的内容 | object { label: string } |
error | 验证错误信息的显示内容 | object { error: string } |
FormItem Exposes
属性 | 描述 | 类型 |
---|---|---|
size | 表单项大小 | object ComputedRef<'' | 'large' | 'default' | 'small'> |
validateMessage | 校验消息 | object Ref<string> |
validateState | 校验状态 | object Ref<'' | 'error' | 'validating' | 'success'> |
validate | 验证表单项 | Function (trigger: string, callback?: FormValidateCallback | undefined) => FormValidationResult |
resetField | 对该表单项进行重置,将其值重置为初始值并移除校验结果 | Function () => void |
clearValidate | 移除该表单项的校验结果 | Function () => void |
类型声明
展示类型声明
type Arrayable<T> = T | T[];
type FormValidationResult = Promise<boolean>;
// ValidateFieldsError: see [async-validator](https://github.com/yiminghe/async-validator/blob/master/src/interface.ts)
type FormValidateCallback = (isValid: boolean, invalidFields?: ValidateFieldsError) => void;
// RuleItem: see [async-validator](https://github.com/yiminghe/async-validator/blob/master/src/interface.ts)
interface FormItemRule extends RuleItem {
trigger?: Arrayable<string>;
}
type FormRules = Partial<Record<string, Arrayable<FormItemRule>>>;