FormSchema Schema 表单
用字段数组声明式生成表单,搭配 useFormValidation 完成校验、提交、重置。
English translation pending This page hasn't been translated yet — falling back to Chinese. PRs welcome on GitHub.
基础用法
CfFormSchema 接收 fields: FormFieldDef[],按声明顺序渲染。内部每个字段被 CfFieldRow 包装,支持 text / textarea / number / select / checkbox / radio / switch / password。和 useFormValidation 组合就能完成”声明 + 校验 + 提交”全流程。
背景 视口
<script setup lang="ts">
import { ref } from 'vue';
import { CfFormSchema, CfButton, useFormValidation, type FormFieldDef } from '@chufix-design/vue';
const fields: FormFieldDef[] = [
{ name: 'name', label: '姓名', type: 'text', required: true, placeholder: '请输入' },
{ name: 'email', label: '邮箱', type: 'text', placeholder: '[email protected]' },
{ name: 'role', label: '角色', type: 'select', options: [
{ label: '管理员', value: 'admin' },
{ label: '编辑者', value: 'editor' },
{ label: '访客', value: 'viewer' },
] },
{ name: 'bio', label: '简介', type: 'textarea' },
{ name: 'subscribe', label: '订阅', type: 'switch', placeholder: '接收产品更新邮件' },
];
const form = useFormValidation({
initialValues: { name: '', email: '', role: 'viewer', bio: '', subscribe: false } as Record<string, unknown>,
validateOn: 'change',
schema: {
name: (v) => (!v ? '姓名必填' : (v as string).length < 2 ? '至少 2 字' : undefined),
email: (v) => (v && !/@/.test(v as string) ? '邮箱格式不正确' : undefined),
},
});
const submitted = ref<unknown>(null);
function onModelUpdate(next: Record<string, unknown>) {
for (const k of Object.keys(next)) {
if (form.values[k] !== next[k]) form.setValue(k, next[k]);
}
}
async function onSubmit() {
await form.submit(async (values) => {
submitted.value = { ...values };
});
}
</script>
<template>
<div class="demo-scope">
<CfFormSchema
:fields="fields"
:modelValue="form.values"
:errors="form.errors"
@update:modelValue="onModelUpdate"
/>
<div class="demo-actions">
<CfButton variant="primary" :disabled="form.isSubmitting" @click="onSubmit">提交</CfButton>
<CfButton variant="tertiary" @click="() => form.reset()">重置</CfButton>
</div>
<pre v-if="submitted" class="demo-pre">{{ submitted }}</pre>
</div>
</template>
<style scoped>
.demo-scope { max-width: 560px; }
.demo-actions { display: flex; gap: 8px; margin-top: 12px; }
.demo-pre {
margin-top: 12px;
padding: 12px;
background: var(--bg-2);
border-radius: var(--r-3);
font-size: var(--t-12);
}
</style> <script setup>
import { ref } from 'vue';
import { CfFormSchema, CfButton, useFormValidation } from '@chufix-design/vue';
const fields= [
{ name: 'name', label: '姓名', type: 'text', required, placeholder: '请输入' },
{ name: 'email', label: '邮箱', type: 'text', placeholder: '[email protected]' },
{ name: 'role', label: '角色', type: 'select', options: [
{ label: '管理员', value: 'admin' },
{ label: '编辑者', value: 'editor' },
{ label: '访客', value: 'viewer' },
] },
{ name: 'bio', label: '简介', type: 'textarea' },
{ name: 'subscribe', label: '订阅', type: 'switch', placeholder: '接收产品更新邮件' },
];
const form = useFormValidation({
initialValues: { name: '', email: '', role: 'viewer', bio: '', subscribe: false }
validateOn: 'change',
schema: {
name: (v) => (!v ? '姓名必填' : (v).length < 2 ? '至少 2 字' : undefined),
email: (v) => (v && !/@/.test(v) ? '邮箱格式不正确' : undefined),
},
});
const submitted = ref<unknown>(null);
function onModelUpdate(next) {
for (const k of Object.keys(next)) {
if (form.values[k] !== next[k]) form.setValue(k, next[k]);
}
}
async function onSubmit() {
await form.submit(async (values) => {
submitted.value = { ...values };
});
}
</script>
<template>
<div class="demo-scope">
<CfFormSchema
:fields="fields"
:modelValue="form.values"
:errors="form.errors"
@update:modelValue="onModelUpdate"
/>
<div class="demo-actions">
<CfButton variant="primary" :disabled="form.isSubmitting" @click="onSubmit">提交</CfButton>
<CfButton variant="tertiary" @click="() => form.reset()">重置</CfButton>
</div>
<pre v-if="submitted" class="demo-pre">{{ submitted }}</pre>
</div>
</template>
<style scoped>
.demo-scope { max-width: 560px; }
.demo-actions { display: flex; gap: 8px; margin-top: 12px; }
.demo-pre {
margin-top: 12px;
padding: 12px;
background: var(--bg-2);
border-radius: var(--r-3);
font-size: var(--t-12);
}
</style> import { useState } from 'react';
import { CfButton, CfFormSchema } from '@chufix-design/react';
export default function Demo() {
const fields: FormFieldDef[] = [
{ name: 'name', label: '姓名', type: 'text', required: true, placeholder: '请输入' },
{ name: 'email', label: '邮箱', type: 'text', placeholder: '[email protected]' },
{ name: 'role', label: '角色', type: 'select', options: [
{ label: '管理员', value: 'admin' },
{ label: '编辑者', value: 'editor' },
{ label: '访客', value: 'viewer' },
] },
{ name: 'bio', label: '简介', type: 'textarea' },
{ name: 'subscribe', label: '订阅', type: 'switch', placeholder: '接收产品更新邮件' },
];
const form = useFormValidation({
initialValues: { name: '', email: '', role: 'viewer', bio: '', subscribe: false } as Record<string, unknown>,
validateOn: 'change',
schema: {
name: (v) => (!v ? '姓名必填' : (v as string).length < 2 ? '至少 2 字' : undefined),
email: (v) => (v && !/@/.test(v as string) ? '邮箱格式不正确' : undefined),
},
});
const [submitted, setSubmitted] = useState<unknown>(null);
function onModelUpdate(next: Record<string, unknown>) {
for (const k of Object.keys(next)) {
if (form.values[k] !== next[k]) form.setValue(k, next[k]);
}
}
async function onSubmit() {
await form.submit(async (values) => {
setSubmitted({ ...values });
});
}
return (
<>
<div className="demo-scope">
<CfFormSchema fields={fields} modelValue={form.values} errors={form.errors} onModelValueChange={onModelUpdate} />
<div className="demo-actions">
<CfButton variant="primary" disabled={form.isSubmitting} onClick={onSubmit}>提交</CfButton>
<CfButton variant="tertiary" onClick={() => form.reset()}>重置</CfButton>
</div>
<pre v-if="submitted" className="demo-pre">{submitted}</pre>
</div>
</>
);
} import { useState } from 'react';
import { CfButton, CfFormSchema } from '@chufix-design/react';
export default function Demo() {
const fields= [
{ name: 'name', label: '姓名', type: 'text', required, placeholder: '请输入' },
{ name: 'email', label: '邮箱', type: 'text', placeholder: '[email protected]' },
{ name: 'role', label: '角色', type: 'select', options: [
{ label: '管理员', value: 'admin' },
{ label: '编辑者', value: 'editor' },
{ label: '访客', value: 'viewer' },
] },
{ name: 'bio', label: '简介', type: 'textarea' },
{ name: 'subscribe', label: '订阅', type: 'switch', placeholder: '接收产品更新邮件' },
];
const form = useFormValidation({
initialValues: { name: '', email: '', role: 'viewer', bio: '', subscribe: false }
validateOn: 'change',
schema: {
name: (v) => (!v ? '姓名必填' : (v).length < 2 ? '至少 2 字' : undefined),
email: (v) => (v && !/@/.test(v) ? '邮箱格式不正确' : undefined),
},
});
const [submitted, setSubmitted] = useState<unknown>(null);
function onModelUpdate(next) {
for (const k of Object.keys(next)) {
if (form.values[k] !== next[k]) form.setValue(k, next[k]);
}
}
async function onSubmit() {
await form.submit(async (values) => {
setSubmitted({ ...values });
});
}
return (
<>
<div className="demo-scope">
<CfFormSchema fields={fields} modelValue={form.values} errors={form.errors} onModelValueChange={onModelUpdate} />
<div className="demo-actions">
<CfButton variant="primary" disabled={form.isSubmitting} onClick={onSubmit}>提交</CfButton>
<CfButton variant="tertiary" onClick={() => form.reset()}>重置</CfButton>
</div>
<pre v-if="submitted" className="demo-pre">{submitted}</pre>
</div>
</>
);
} FormFieldDef
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 字段 key |
label | string | 显示标题 |
type | 'text' | 'password' | 'textarea' | 'number' | 'select' | 'checkbox' | 'radio' | 'switch' | 控件类型 |
hint / placeholder | string | 提示 / 占位文案 |
required / disabled | boolean | 必填 / 禁用 |
options | { label, value }[] | select / radio 用 |
min / max / step | number | number 用 |
span | number | 在 CfFormGrid 中的跨列数 |
API
| 属性 | 类型 | 默认 | 说明 |
|---|---|---|---|
fields | FormFieldDef[] | — | 字段定义 |
modelValue (Vue) / value (React) | Record<string, unknown> | — | 当前值(受控) |
@update:modelValue (Vue) / onChange (React) | (v) => void | — | 值变更回调 |
errors | Record<string, string> | — | 字段错误信息 |
layout | 'vertical' | 'horizontal' | 'vertical' | 字段 label 位置 |
size | 'sm' | 'md' | 'lg' | 'md' | 控件尺寸 |
disabled | boolean | false | 整表禁用 |
onFieldChange | (name, v) => void | — | 单字段变化通知 |
反馈与讨论
FormSchema Schema 表单 · Discussion