Preview Updated 2026-05-10

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 组合就能完成”声明 + 校验 + 提交”全流程。

背景 视口
src/App.vue
<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

字段类型说明
namestring字段 key
labelstring显示标题
type'text' | 'password' | 'textarea' | 'number' | 'select' | 'checkbox' | 'radio' | 'switch'控件类型
hint / placeholderstring提示 / 占位文案
required / disabledboolean必填 / 禁用
options{ label, value }[]select / radio 用
min / max / stepnumbernumber 用
spannumberCfFormGrid 中的跨列数

API

属性类型默认说明
fieldsFormFieldDef[]字段定义
modelValue (Vue) / value (React)Record<string, unknown>当前值(受控)
@update:modelValue (Vue) / onChange (React)(v) => void值变更回调
errorsRecord<string, string>字段错误信息
layout'vertical' | 'horizontal''vertical'字段 label 位置
size'sm' | 'md' | 'lg''md'控件尺寸
disabledbooleanfalse整表禁用
onFieldChange(name, v) => void单字段变化通知

反馈与讨论

FormSchema Schema 表单 · Discussion

0
0 / 600
正在加载评论...