Skip to content

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>

表单校验 ​

14/80
点击提交查看校验结果
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>

状态与反馈 ​

开启 feedback 后,支持的控件会展示反馈图标
Section One
设计研发
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是否禁用整个表单booleanundefined
rules表单校验规则Record<string, FieldRule | FieldRule[]>-
autoLabelWidth自动计算标签宽度,仅水平布局生效booleanfalse
id表单 id 属性和表单控件 id 前缀string-
scrollToFirstError校验失败后滚动到第一个错误字段,可传滚动配置boolean | ScrollIntoViewOptionsfalse

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是否显示冒号booleanfalse
noStyle是否去除布局样式booleanfalse
disabled是否禁用当前项booleanundefined
help帮助文案string-
extra额外文案string-
required是否必填booleanfalse
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是否隐藏标签booleanfalse
hideAsterisk是否隐藏星号booleanfalse
labelColStyle标签列样式object继承 Form
wrapperColStyle控件列样式object继承 Form
rowProps当前项 Row 配置object-
rowClass当前项 Row classstring | array | object-
contentClass控件包裹层 classstring | array | object-
contentFlex内容层是否启用 flexbooleantrue
labelColFlex标签列 flex 宽度number | string-
feedback是否显示反馈图标booleanfalse
labelComponent标签渲染元素string'label'
labelAttrs标签元素属性object-

FormItem Slots ​

插槽名说明
default表单控件内容
label自定义标签
help自定义帮助信息
extra自定义额外内容