Appearance
Form 表单
用于统一管理表单字段、声明式校验规则、错误提示和无障碍属性。
命令行安装
bash
npx @loongship-kit/ui add form input textarea file-upload select combobox checkbox radio-group calendar date-picker date-range-picker time-field组件结构
| 角色 | 组件 | 职责 | 使用条件 |
|---|---|---|---|
| 根组件 | Form | 管理数据模型、规则和整体验证 | 必需 |
| 字段容器 | FormItem | 关联字段、标签、控件和错误信息 | 需要字段验证时使用 |
完整表单校验
Form 接收整个数据模型与规则,FormItem 通过 prop 关联字段。下面的示例集中展示所有表单字段组件的必填、长度和格式校验。提交失败时会定位并聚焦第一个错误字段。
查看代码
vue
<script setup lang="ts">
import { CalendarDate, Time } from '@internationalized/date'
import { reactive, ref } from 'vue'
import { Calendar } from '@/components/loongship/calendar'
import { Checkbox } from '@/components/loongship/checkbox'
import {
Combobox,
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxViewport
} from '@/components/loongship/combobox'
import { DatePicker } from '@/components/loongship/date-picker'
import { DateRangePicker, type DateRangeValue } from '@/components/loongship/date-range-picker'
import { FileUpload, type FileUploadItem } from '@/components/loongship/file-upload'
import { Form, FormItem, type FormExpose, type FormRules } from '@/components/loongship/form'
import { Input } from '@/components/loongship/input'
import { RadioGroup, RadioGroupItem } from '@/components/loongship/radio-group'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/loongship/select'
import { Textarea } from '@/components/loongship/textarea'
import { TimeField } from '@/components/loongship/time-field'
interface CompleteFormModel {
vesselName: string
notes: string
attachments: FileUploadItem[]
port: string
searchPort: string
accepted: boolean
route: string
calendarDate?: CalendarDate
arrivalDate?: CalendarDate
voyageRange?: DateRangeValue
departureTime?: Time
}
const formRef = ref<FormExpose>()
const submitted = ref(false)
const ports = ['Shanghai', 'Singapore', 'Yokohama']
const datePlaceholder = new CalendarDate(2026, 8, 1)
const model = reactive<CompleteFormModel>({
vesselName: '',
notes: '',
attachments: [],
port: '',
searchPort: '',
accepted: false,
route: '',
calendarDate: undefined,
arrivalDate: undefined,
voyageRange: undefined,
departureTime: undefined
})
const rules: FormRules = {
vesselName: [
{ required: true, message: '请输入船舶名称', trigger: 'blur' },
{ min: 2, message: '船舶名称至少需要 2 个字符', trigger: ['blur', 'change'] }
],
notes: [
{ required: true, message: '请输入操作说明' },
{ min: 10, message: '至少输入 10 个字符' }
],
attachments: { required: true, message: '请选择附件' },
port: { required: true, message: '请选择港口' },
searchPort: { required: true, message: '请搜索并选择港口' },
accepted: { required: true, message: '请确认申报信息' },
route: { required: true, message: '请选择航向' },
calendarDate: { required: true, message: '请选择日历日期' },
arrivalDate: { required: true, message: '请选择抵达日期' },
voyageRange: { required: true, message: '请选择完整航次日期' },
departureTime: { required: true, message: '请选择离港时间' }
}
async function submitForm() {
submitted.value = (await formRef.value?.validate()) ?? false
}
</script>
<template>
<Form ref="formRef" :model="model" :rules="rules" @submit.prevent="submitForm">
<FormItem label="Vessel name" prop="vesselName">
<Input v-model="model.vesselName" placeholder="EVER GIVEN" />
</FormItem>
<FormItem label="Operation notes" prop="notes">
<Textarea v-model="model.notes" placeholder="At least 10 characters" />
</FormItem>
<FormItem label="Attachment" prop="attachments">
<FileUpload v-model:file-list="model.attachments" />
</FormItem>
<FormItem label="Port" prop="port">
<Select v-model="model.port">
<SelectTrigger><SelectValue placeholder="Select a port" /></SelectTrigger>
<SelectContent>
<SelectItem value="cnsgh">Shanghai</SelectItem>
<SelectItem value="sgsin">Singapore</SelectItem>
</SelectContent>
</Select>
</FormItem>
<FormItem label="Search port" prop="searchPort">
<Combobox v-model="model.searchPort">
<ComboboxAnchor><ComboboxInput placeholder="Search port" /></ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport>
<ComboboxEmpty>No port found.</ComboboxEmpty>
<ComboboxItem v-for="port in ports" :key="port" :value="port">{{ port }}</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</Combobox>
</FormItem>
<FormItem label="Declaration" prop="accepted">
<label><Checkbox v-model="model.accepted" /> I confirm the declaration</label>
</FormItem>
<FormItem label="Route" prop="route">
<RadioGroup v-model="model.route" orientation="horizontal">
<label><RadioGroupItem value="east" /> Eastbound</label>
<label><RadioGroupItem value="west" /> Westbound</label>
</RadioGroup>
</FormItem>
<FormItem label="Calendar date" prop="calendarDate">
<Calendar v-model="model.calendarDate" :default-placeholder="datePlaceholder" />
</FormItem>
<FormItem label="Arrival date" prop="arrivalDate">
<DatePicker v-model="model.arrivalDate" :default-placeholder="datePlaceholder" />
</FormItem>
<FormItem label="Voyage range" prop="voyageRange">
<DateRangePicker v-model="model.voyageRange" :default-placeholder="datePlaceholder" />
</FormItem>
<FormItem label="Departure time" prop="departureTime">
<TimeField v-model="model.departureTime" />
</FormItem>
<button type="submit">提交</button>
<span v-if="submitted">校验通过</span>
</Form>
</template>嵌套路径与自定义校验
字段路径与校验返回值
prop 支持点路径或路径数组,例如 contacts.0.phone 和 ['contacts', '0', 'phone']。自定义 validator 可返回错误文本、false 或 Promise;返回空字符串、true 或无返回值表示通过。
ts
const rules: FormRules = {
'contacts.0.phone': {
trigger: 'change',
validator: async (_rule, value) => {
const available = await checkPhone(String(value))
return available ? '' : '该号码已被使用'
}
}
}标签布局
统一标签宽度
label-position="top" 为默认的顶部标签。设置 label-position="left" 后,可通过 label-width 统一左侧标签宽度,也可在单个 FormItem 上覆盖。
API
Form(主组件)
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
model | Record<string, unknown> | - | 表单数据模型,必填 |
rules | FormRules | {} | 以字段路径为 key 的校验规则 |
labelPosition | 'top' | 'left' | 'top' | 标签位置 |
labelWidth | string | number | - | 左侧标签宽度 |
disabled | boolean | false | 禁用表单内的原生表单控件 |
validateOnRuleChange | boolean | true | 规则改变后重新校验已触发字段 |
事件
| 事件 | 参数 | 说明 |
|---|---|---|
validate | (prop, valid: boolean, message: string) | 字段校验完成后触发 |
插槽
| 插槽 | 参数 | 说明 |
|---|---|---|
default | — | 表单内容 |
暴露方法
| 方法 | 返回值 | 说明 |
|---|---|---|
validate() | Promise<boolean> | 校验全部字段 |
validateField(prop) | Promise<boolean> | 校验指定字段 |
resetFields(prop?) | void | 恢复字段注册时的值并清除错误 |
clearValidate(prop?) | void | 清除校验状态,不修改数据 |
scrollToField(prop) | void | 将指定字段滚入视图 |
子组件
FormItem — 字段、标签与校验状态容器
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | - | 字段标签 |
prop | string | string[] | - | 模型字段路径 |
rules | FormRule | FormRule[] | - | 字段局部规则 |
required | boolean | false | 添加必填约束与标记 |
error | string | - | 外部错误文本 |
showMessage | boolean | true | 是否显示字段下方的错误文本 |
labelWidth | string | number | - | 覆盖本字段的标签宽度 |
插槽
| 插槽 | 参数 | 说明 |
|---|---|---|
default | { error: string, validateState: ValidateState } | 字段控件 |
label | { label: string } | 自定义字段标签 |
error | { error: string } | 自定义校验错误内容 |
暴露方法
| 方法 | 返回值 | 说明 |
|---|---|---|
validate(trigger?) | Promise<boolean> | 校验当前字段 |
resetField() | void | 恢复初值并清除错误 |
clearValidate() | void | 清除校验状态 |
类型
FormRule
| 属性 | 类型 | 说明 |
|---|---|---|
required | boolean | 非空校验 |
min / max | number | 字符串或数组校验长度,数字校验数值范围 |
pattern | RegExp | 字符串格式校验 |
message | string | 校验失败文案 |
trigger | 'blur' | 'change' | Array | 触发时机;未设置时响应两种触发 |
validator | (rule, value, model) => result | Promise<result> | 自定义同步或异步校验 |