refactor(@vben-core/form-ui): migrate to Zod 4 and TanStack Form (#8176)

* build(@vben-core/form-ui): update form validation dependencies

* refactor(@vben-core/form-ui): replace vee-validate with TanStack Form

* test(@vben-core/form-ui): cover TanStack Form migration

* docs(@vben/docs): document Zod 4 form migration

* refactor(project): update form adapters

* refactor(project): migrate application form consumers

* refactor(@vben/playground): migrate form examples

* docs(@vben/docs): fix validate return type and required rule empty check in form docs

* fix(@vben-core/form-ui): resolve oxlint and eslint errors

- rewrite nested ternary expressions as if/else in form-field-array.vue and form-runtime.ts
- sort @tanstack/vue-form before @vben-core/composables in package.json

* chore: fix merge artifacts and formatting

- move dependencies before devDependencies in root package.json
- update preferences snapshot for widget positioning fields
- format form-array demo README

* refactor(@vben-core/form-ui): optimize form runtime and value flow

- add fine-grained field selectors and atomic dependency resolution
- expose raw and formatted value snapshots with focused regression coverage

* fix(@vben/layouts): dispose sortable instance on unmount

* docs(@vben/docs): document form runtime API changes

- list added, changed, removed, and deprecated form APIs
- document atomic dependencies and raw versus formatted values
This commit is contained in:
dream-weave
2026-07-22 19:47:07 +08:00
committed by GitHub
parent 6b6708bcf2
commit 28757fb9c8
81 changed files with 4683 additions and 1435 deletions

View File

@@ -285,11 +285,16 @@ const [BaseForm, baseFormApi] = useVbenForm({
},
{
component: 'DatePicker',
dependencies: {
resolve: ({ values }) => ({
help: () =>
[`这是一个可输出其他字段值的帮助信息${values.rate}`].map((value) =>
h('p', value),
),
}),
triggerFields: ['rate'],
},
fieldName: 'datePicker',
help: (values) =>
[`这是一个可输出其他字段值的帮助信息${values?.rate}`].map((v) =>
h('p', v),
),
label: '日期选择框',
},
{

View File

@@ -22,8 +22,8 @@ const layout = ref<FormLayout>('vertical');
function getNumberValidator(key: string, limit?: [number?, number?]) {
let validator = z.number({
required_error: `${key} 值不能为空`,
invalid_type_error: `${key} 值只能为数字`,
error: (issue) =>
issue.input === undefined ? `${key} 值不能为空` : `${key} 值只能为数字`,
});
if (limit) {
@@ -257,14 +257,14 @@ function handleSetFormValue() {
}
function handleResetFormValue() {
baseFormApi.resetForm(undefined, { force: true });
baseFormApi.reset(undefined, { force: true });
}
async function handleSubmitFormValue() {
const { valid } = await baseFormApi.validate();
if (valid) {
baseFormApi.submitForm();
baseFormApi.submit();
}
}
</script>

View File

@@ -57,7 +57,7 @@ const [Form] = useVbenForm({
{
component: markRaw(TwoFields),
defaultValue: [undefined, ''],
disabledOnChangeListener: false,
changeEventFallback: true,
fieldName: 'field4',
formItemClass: 'col-span-1',
label: '组合字段',

View File

@@ -70,7 +70,7 @@ const [Form, formApi] = useVbenForm({
fieldName: 'field4',
// 界面显示的label
label: '邮箱',
rules: z.string().email('请输入正确的邮箱'),
rules: z.email('请输入正确的邮箱'),
},
{
component: 'InputNumber',
@@ -184,8 +184,7 @@ const [Form, formApi] = useVbenForm({
},
fieldName: 'input-blur',
formFieldProps: {
validateOnChange: false,
validateOnModelUpdate: false,
validateOn: ['blur'],
},
help: 'blur时才会触发校验',
label: 'blur触发',
@@ -235,7 +234,7 @@ function onSubmit(values: Record<string, any>) {
<Card title="基础组件校验示例">
<template #extra>
<Button @click="() => formApi.validate()">校验表单</Button>
<Button class="mx-2" @click="() => formApi.resetValidate()">
<Button class="mx-2" @click="() => formApi.clearValidation()">
清空校验信息
</Button>
</template>

View File

@@ -98,7 +98,7 @@ const [Form, formApi] = useVbenForm({
// 测试 validateAndSubmitForm(验证并提交)
async function testValidateAndSubmit() {
await formApi.validateAndSubmitForm();
await formApi.validateAndSubmit();
}
// 测试 validate(手动验证整个表单)
@@ -112,13 +112,13 @@ async function testValidateField() {
}
// 切换滚动功能
function toggleScrollToError() {
formApi.setState({ scrollToFirstError: scrollEnabled.value });
function toggleScrollToError(checked: boolean) {
formApi.setState({ scrollToFirstError: checked });
}
// 填充部分数据测试
async function fillPartialData() {
await formApi.resetForm();
await formApi.reset();
await formApi.setFieldValue('username', '测试用户');
await formApi.setFieldValue('email', 'test@example.com');
}
@@ -153,13 +153,13 @@ async function fillPartialData() {
<h4 class="mb-3 font-medium">验证方法测试:</h4>
<div class="flex flex-wrap gap-2">
<Button type="primary" @click="testValidateAndSubmit">
测试 validateAndSubmitForm()
测试 validateAndSubmit()
</Button>
<Button @click="testValidate"> 测试 validate() </Button>
<Button @click="testValidateField"> 测试 validateField() </Button>
</div>
<div class="mt-2 text-xs text-gray-500">
<p>• validateAndSubmitForm(): 验证表单并提交</p>
<p>• validateAndSubmit(): 验证表单并提交</p>
<p>• validate(): 手动验证整个表单</p>
<p>• validateField(): 验证单个字段(这里测试用户名字段)</p>
</div>
@@ -169,7 +169,7 @@ async function fillPartialData() {
<h4 class="mb-3 font-medium">数据填充测试:</h4>
<div class="flex flex-wrap gap-2">
<Button @click="fillPartialData"> 填充部分数据 </Button>
<Button @click="() => formApi.resetForm()"> 清空表单 </Button>
<Button @click="() => formApi.reset()"> 清空表单 </Button>
</div>
<div class="mt-2 text-xs text-gray-500">
<p>• 填充部分数据后验证,会滚动到第一个错误字段</p>