Appearance
Form 表单
承载数据录入、校验和提交,是业务系统里最核心的交互容器。
何时使用
- 需要把多个输入控件组成一次提交,并统一尺寸、禁用和校验反馈。
- 需要必填、格式、数值范围或异步业务校验。
- 需要动态字段、嵌套数据、重置初始值或展示服务端错误。
validate() / validateField() 成功返回 undefined,失败返回字段错误对象。通过返回值判断结果,不使用 try/catch 判断业务校验失败。resetFields() 恢复字段挂载时的初始快照,clearValidate() 只清理状态。
基础用法
等待提交
vue
<template>
<x-form :model="form" @submit="handleSubmit">
<x-form-item field="name" tooltip="用于展示在业务列表中" label="名称" required>
<x-input v-model="form.name" placeholder="请输入名称" />
</x-form-item>
<x-form-item field="post" label="职位">
<x-input v-model="form.post" placeholder="请输入职位" />
</x-form-item>
<x-form-item field="isRead">
<x-checkbox v-model="form.isRead">我已阅读并同意</x-checkbox>
</x-form-item>
<x-form-item>
<x-button type="primary" html-type="submit">提交</x-button>
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const form = reactive({ name: '', post: '', isRead: false });
const handleSubmit = ({ values, errors }) => {
console.log(values, errors);
};
</script>布局
inline 布局的控件列默认宽度为 200px,并在空间不足时换行;可通过 wrapperColStyle 调整宽度。
vue
<template>
<x-radio-group v-model="layout" type="button">
<x-radio value="horizontal">horizontal</x-radio>
<x-radio value="vertical">vertical</x-radio>
<x-radio value="inline">inline</x-radio>
</x-radio-group>
<x-form :model="form" :layout="layout">
<x-form-item field="name" label="用户名">
<x-input v-model="form.name" />
</x-form-item>
<x-form-item field="post" label="岗位">
<x-input v-model="form.post" />
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
const layout = ref('horizontal');
const form = reactive({ name: '', post: '' });
</script>表单校验
点击提交查看校验结果
vue
<template>
<x-form ref="formRef" :model="form" :rules="rules" scroll-to-first-error @submit="handleSubmit">
<x-form-item field="name" label="用户名" :validate-trigger="['change', 'input']">
<x-input v-model="form.name" placeholder="至少 3 个字符" />
</x-form-item>
<x-form-item field="age" label="年龄">
<x-input-number v-model="form.age" :min="0" :max="200" />
</x-form-item>
<x-form-item field="section" label="部门">
<x-select v-model="form.section" :options="sectionOptions" allow-clear />
</x-form-item>
<x-form-item>
<x-button type="primary" html-type="submit">提交校验</x-button>
<x-button @click="formRef?.resetFields()">重置</x-button>
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
const formRef = ref();
const form = reactive({ name: '', age: 18, section: '' });
const rules = {
name: [
{ required: true, message: '请输入用户名' },
{ minLength: 3, message: '用户名至少 3 个字符' },
],
age: [{ type: 'number', max: 120, message: '年龄不能超过 120' }],
section: [{ match: /section one/, message: '请选择 Section One' }],
};
const sectionOptions = [
{ label: 'Section One', value: 'section one' },
{ label: 'Section Two', value: 'section two' },
];
const handleSubmit = ({ values, errors }) => {
console.log(values, errors);
};
</script>表单方法
可手动触发表单方法
vue
<template>
<x-form ref="formRef" :model="form" :rules="rules">
<x-form-item field="username" label="用户名">
<x-input v-model="form.username" />
</x-form-item>
<x-form-item field="email" label="邮箱">
<x-input v-model="form.email" />
</x-form-item>
<x-form-item>
<x-button @click="formRef?.validate()">validate</x-button>
<x-button @click="setServerError">setFields</x-button>
<x-button @click="formRef?.clearValidate()">clearValidate</x-button>
<x-button @click="formRef?.resetFields()">resetFields</x-button>
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
const formRef = ref();
const form = reactive({ username: '', email: '' });
const rules = {
username: [{ required: true, message: '请输入用户名' }],
email: [{ type: 'email', message: '邮箱格式不正确' }],
};
const setServerError = () => {
formRef.value?.setFields({
email: { status: 'error', message: '服务端返回:邮箱已被占用' },
});
};
</script>状态与反馈
vue
<template>
<x-form :model="form" :size="size">
<x-form-item
field="name"
label="名称"
help="当前状态由 validateStatus 手动控制"
extra="开启 feedback 后展示反馈图标"
:validate-status="status"
feedback
>
<x-input v-model="form.name" />
</x-form-item>
<x-form-item field="tags" label="标签" :validate-status="status" feedback>
<x-input-tag v-model="form.tags" />
</x-form-item>
<x-form-item field="date" label="日期" :validate-status="status" feedback>
<x-date-picker v-model="form.date" allow-clear />
</x-form-item>
<x-form-item field="time" label="时间" :validate-status="status" feedback>
<x-time-picker v-model="form.time" allow-clear />
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
const status = ref('success');
const size = ref('medium');
const form = reactive({ name: '', tags: [], date: '2026-09-30', time: '09:30:00' });
</script>校验状态的视觉支持范围
校验失败时,所有控件都会在 FormItem 上展示错误文案与错误色(x-form--item-error);feedback 开关控制是否额外展示反馈图标。
控件自身的错误描边支持范围如下:
| 控件 | 控件级错误描边 | 反馈图标 |
|---|---|---|
| Input | 支持 | 支持 |
| Select / InputTag / InputNumber | 支持 | 支持 |
| DatePicker / TimePicker(含范围形态) | 支持 | 支持 |
| Textarea | 支持 | 不支持(右下角为字数统计,无图标位) |
| Radio / RadioGroup / Checkbox / CheckboxGroup | 不支持(仅 FormItem 文案与错误色) | 不支持 |
| Slider / Switch | 不支持(仅 FormItem 文案与错误色) | 不支持 |
Radio、Checkbox、Slider、Switch 属于无输入边框的控件,参考 Arco Design Vue 的行为,不额外添加控件级错误描边,避免与"选中态"视觉混淆。Textarea 的右下角用于字数统计,同样不放置反馈图标。
全局禁用
vue
<template>
<x-form :model="form" disabled>
<x-form-item field="name" label="用户名">
<x-input v-model="form.name" />
</x-form-item>
<x-form-item field="isRead">
<x-checkbox v-model="form.isRead">已确认</x-checkbox>
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const form = reactive({ name: 'Locked user', isRead: true });
</script>帮助信息和插槽
vue
<template>
<x-form :model="form">
<x-form-item field="name" required validate-trigger="input">
<template #label>
<span>登录名</span>
</template>
<x-input v-model="form.name" />
<template #extra>
<span>用于后台账号展示</span>
</template>
</x-form-item>
<x-form-item field="post" label="岗位" required>
<x-input v-model="form.post" />
<template #help>
<span>自定义帮助信息会展示在控件下方</span>
</template>
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const form = reactive({ name: '', post: '' });
</script>嵌套字段
支持 user.name、members[0] 这类路径
vue
<template>
<x-form ref="formRef" :model="form" :rules="rules" @submit="handleSubmit">
<x-form-item field="user.name" label="姓名">
<x-input v-model="form.user.name" />
</x-form-item>
<x-form-item field="user.email" label="邮箱">
<x-input v-model="form.user.email" />
</x-form-item>
<x-form-item field="members" label="成员">
<x-select v-model="form.members" :options="memberOptions" multiple />
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
const formRef = ref();
const form = reactive({
user: { name: '', email: '' },
members: ['pm'],
});
const rules = {
'user.name': [{ required: true, message: '请输入姓名' }],
'user.email': [{ type: 'email', message: '邮箱格式不正确' }],
members: [{ type: 'array', minLength: 2, message: '至少选择 2 个成员' }],
};
const memberOptions = [
{ label: '产品', value: 'pm' },
{ label: '设计', value: 'design' },
{ label: '前端', value: 'frontend' },
];
const handleSubmit = ({ values, errors }) => {
console.log(values, errors);
};
</script>异步校验与过期结果
输入 admin 后失焦会得到占用提示;等待时继续修改并失焦,旧请求不会覆盖最新字段状态。也可以在请求完成前重置。
当前值:空
vue
<template>
<x-form ref="formRef" :model="form" :rules="rules">
<x-form-item field="username" label="用户名" validate-trigger="blur" feedback>
<x-input v-model="form.username" placeholder="尝试 admin" allow-clear />
</x-form-item>
<x-form-item>
<x-button @click="formRef.validateField('username')">校验当前字段</x-button>
<x-button @click="formRef.resetFields()">重置</x-button>
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import type { FieldRule, FormInstance } from 'x-next';
const formRef = ref<FormInstance>();
const form = reactive({ username: '' });
const rules: Record<string, FieldRule[]> = {
username: [
{ required: true, message: '请输入用户名' },
{
validator: async (value, addError) => {
await new Promise((resolve) => setTimeout(resolve, 600));
if (value === 'admin') addError('admin 已被占用,请换一个名称');
},
},
],
};
</script>异步 validator 返回 Promise,通过 addError(message) 报告错误,再正常完成 Promise。不要把 reject 当作字段错误。validating 在等待期间显示;清理状态、重置、切换字段或卸载后,旧请求不会重新写入错误。每次 validate() 返回该次校验的结果;等待提交期间修改模型或字段结构会取消旧的提交事件,可重新提交新值。
noStyle 与动态字段
展开邮箱字段,校验后可以隐藏它
vue
<template>
<x-checkbox v-model="visible" @change="result = visible ? '字段已展开' : '字段已隐藏'">显示邮箱字段</x-checkbox>
<x-form ref="formRef" :model="form">
<x-form-item label="联系方式">
<x-form-item v-if="visible" field="email" no-style required>
<x-input v-model="form.email" placeholder="先校验,再隐藏字段" />
</x-form-item>
<span v-else>当前没有邮箱字段</span>
</x-form-item>
<x-form-item><x-button @click="validate">校验</x-button></x-form-item>
</x-form>
<p role="status">{{ result }}</p>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
const visible = ref(true);
const formRef = ref();
const form = reactive({ email: '' });
const result = ref('等待校验');
const validate = async () => {
const errors = await formRef.value.validate();
result.value = errors ? '邮箱未填写' : '校验通过';
};
</script>noStyle 子项参与校验,错误展示在外层 FormItem。通过 v-if 卸载会注销字段并清理其父级错误;模型中的值仍由业务保存。v-show 只隐藏 DOM,字段仍参与校验。
自动标签、帮助与错误关联
点击姓名标签可直接聚焦输入框
vue
<template>
<x-form ref="formRef" :model="form">
<x-form-item field="name" label="姓名" required help="填写真实姓名" extra="用于联系和通知">
<x-input v-model="form.name" />
</x-form-item>
<x-form-item field="note" label="备注" help="可选,支持多行">
<x-textarea v-model="form.note" />
</x-form-item>
<x-button @click="validate">校验姓名</x-button>
<p>{{ result }}</p>
</x-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import type { FormInstance } from 'x-next';
const form = reactive({ name: '', note: '' });
const formRef = ref<FormInstance>();
const result = ref('点击姓名标签可聚焦输入框');
const validate = async () => {
result.value = (await formRef.value!.validate()) ? '请补全必填项' : '校验通过';
};
</script>单个原生输入自动获得稳定 ID,并与标签的 for 关联。多个输入、选择组、评分、滑块和时间区间通过 aria-labelledby 共享标签;noStyle 字段继承外层标签。帮助、额外说明和当前错误通过 aria-describedby 关联,校验失败时同步 aria-invalid 和 aria-errormessage,清除校验后恢复帮助说明。
隐藏标签的字段应显式提供 aria-label。自定义控件需要自行绑定原生 ID 和 ARIA 属性;自动关联覆盖 x-next 内置表单控件。
手动标签与原生输入关联
vue
<template>
<x-form :model="form">
<x-form-item field="name" label="姓名" :label-attrs="{ for: 'profile-name' }" required>
<x-input id="profile-name" v-model="form.name" aria-required="true" aria-describedby="profile-name-hint" />
<template #extra><span id="profile-name-hint">请填写用于展示的姓名。</span></template>
</x-form-item>
</x-form>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const form = reactive({ name: '' });
</script>显式 labelAttrs.for、原生 id 和 ARIA 名称会保留;额外 aria-describedby 与 Form 描述合并。重复渲染时请给自定义 ID 使用独立值。Form 的 id 前缀用于字段包装层及 scrollToField,与原生输入 ID 分开。
按需导入
ts
import { Form, FormItem } from 'x-next';样式按需引入(base.css 为共享基础层,多个组件只需引入一次):
ts
import 'x-next/style/base.css';
import 'x-next/style/form.css';Form Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
model | 表单数据对象 | Record<string, any> | 必填 |
layout | 表单布局 | 'horizontal' | 'vertical' | 'inline' | 'horizontal' |
size | 表单控件尺寸 | 'mini' | 'small' | 'medium' | 'large' | 跟随全局配置 |
labelColProps | 标签栅格配置 | object | { span: 5, offset: 0 } |
wrapperColProps | 控件栅格配置 | object | { span: 19, offset: 0 } |
labelColStyle | 标签列样式 | object | - |
wrapperColStyle | 控件列样式 | object | - |
labelAlign | 标签对齐方式 | 'left' | 'right' | 'right' |
disabled | 是否禁用整个表单 | boolean | undefined |
rules | 表单校验规则 | Record<string, FieldRule | FieldRule[]> | - |
autoLabelWidth | 自动计算标签宽度,仅水平布局生效 | boolean | false |
id | 表单 id 属性和表单控件 id 前缀 | string | - |
scrollToFirstError | 校验失败后滚动到第一个错误字段,可传滚动配置 | boolean | ScrollIntoViewOptions | false |
Form Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
submit | 表单提交时触发 | { values, errors }, event |
submit-success | 校验成功时触发 | values, event |
submit-failed | 校验失败时触发 | { values, errors }, event |
Form Methods
| 方法 | 说明 | 参数 |
|---|---|---|
validate | 校验全部表单数据 | (callback?) |
validateField | 校验指定字段 | (field, callback?) |
resetFields | 重置字段值和校验状态 | (field?) |
clearValidate | 清除字段校验状态 | (field?) |
setFields | 设置表单项值与状态 | (data) |
scrollToField | 滚动到指定字段 | (field, options?) |
FormItem Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
field | 字段路径 | string | '' |
label | 标签文本 | string | - |
tooltip | 标签提示 | string | - |
showColon | 是否显示冒号 | boolean | false |
noStyle | 是否去除布局样式 | boolean | false |
disabled | 是否禁用当前项 | boolean | undefined |
help | 帮助文案 | string | - |
extra | 额外文案 | string | - |
required | 是否必填 | boolean | false |
asteriskPosition | 星号位置 | 'start' | 'end' | 'start' |
rules | 当前项校验规则 | FieldRule | FieldRule[] | - |
validateStatus | 手动指定校验状态 | 'success' | 'warning' | 'error' | 'validating' | - |
validateTrigger | 触发校验的事件 | 'change' | 'input' | 'focus' | 'blur' | string[] | 'change' |
labelColProps | 当前项标签列配置 | object | 继承 Form |
wrapperColProps | 当前项控件列配置 | object | 继承 Form |
hideLabel | 是否隐藏标签 | boolean | false |
hideAsterisk | 是否隐藏星号 | boolean | false |
labelColStyle | 标签列样式 | object | 继承 Form |
wrapperColStyle | 控件列样式 | object | 继承 Form |
rowProps | 当前项 Row 配置 | object | - |
rowClass | 当前项 Row class | string | array | object | - |
contentClass | 控件包裹层 class | string | array | object | - |
contentFlex | 内容层是否启用 flex | boolean | true |
labelColFlex | 标签列 flex 宽度 | number | string | - |
feedback | 是否显示反馈图标 | boolean | false |
labelComponent | 标签渲染元素 | string | 'label' |
labelAttrs | 标签元素属性 | object | - |
FormItem Slots
| 插槽名 | 说明 |
|---|---|
default | 表单控件内容 |
label | 自定义标签 |
help | 自定义帮助信息 |
extra | 自定义额外内容 |