fix(@vben-core/form-ui): restore lock screen form submission (#8195)

* fix(@vben-core/form-ui): avoid stale form context access

* fix(@vben/playground): constrain date range codec fields

* test(@vben-core/form-ui): harden benchmark fixtures

* docs(@vben/docs): document form runtime and benchmarks

* fix(@vben/docs): 明确 getValues/submit 返回值及 submit 参数类型
This commit is contained in:
dream-weave
2026-07-24 10:05:27 +08:00
committed by GitHub
parent 0542b509ca
commit dcdbfe5dfc
13 changed files with 195 additions and 86 deletions

View File

@@ -0,0 +1,21 @@
import { defineComponent, h } from 'vue';
export const TestInput = defineComponent({
inheritAttrs: false,
emits: ['update:modelValue'],
setup(_props, { attrs, emit }) {
function handleInput(event: Event) {
const target = event.target;
if (target instanceof HTMLInputElement) {
emit('update:modelValue', target.value);
}
}
return () =>
h('input', {
...attrs,
onInput: handleInput,
value: attrs.modelValue ?? '',
});
},
});

View File

@@ -61,6 +61,20 @@ describe('formApi', () => {
expect(values).toEqual({ name: 'test' });
});
it('should set a field error through the public api', async () => {
const setFieldError = vi.fn();
const formActions: any = {
meta: {},
setFieldError,
values: {},
};
formApi.mount(formActions, new Map());
await formApi.setFieldError('password', 'Invalid password');
expect(setFieldError).toHaveBeenCalledWith('password', 'Invalid password');
});
it('should format schema values when getting values', async () => {
formApi.setState({
schema: [

View File

@@ -1,38 +1,19 @@
import type { FormSchema } from '../src/types';
import { flushPromises, mount } from '@vue/test-utils';
import { defineComponent, h, nextTick } from 'vue';
import { nextTick } from 'vue';
import { afterAll, bench, describe } from 'vitest';
import { z } from 'zod';
import { setupVbenForm } from '../src/config';
import { useVbenForm } from '../src/use-vben-form';
import { TestInput } from './benchmark-fixtures';
const BENCHMARK_OPTIONS = { time: 750, warmupTime: 150 } as const;
const FIELD_COUNT = 100;
const MOUNT_FIELD_COUNT = 50;
const TestInput = defineComponent({
inheritAttrs: false,
emits: ['update:modelValue'],
setup(_props, { attrs, emit }) {
function handleInput(event: Event) {
const target = event.target;
if (target instanceof HTMLInputElement) {
emit('update:modelValue', target.value);
}
}
return () =>
h('input', {
...attrs,
onInput: handleInput,
value: attrs.modelValue ?? '',
});
},
});
function createFlatSchema(
fieldCount: number,
withRules: boolean = false,

View File

@@ -1,12 +1,12 @@
import { flushPromises, mount } from '@vue/test-utils';
import { defineComponent, h, nextTick } from 'vue';
import { nextTick } from 'vue';
import { afterAll, bench, describe } from 'vitest';
import { setupVbenForm } from '../src/config';
import { FormApi } from '../src/form-api';
import { encodeFormValues } from '../src/form-codec';
import { useVbenForm } from '../src/use-vben-form';
import { TestInput } from './benchmark-fixtures';
interface ContactValues {
enabled: boolean;
@@ -30,26 +30,6 @@ interface PerformanceFormValues extends Record<string, unknown> {
const ROW_COUNT = 100;
const TestInput = defineComponent({
inheritAttrs: false,
emits: ['update:modelValue'],
setup(_props, { attrs, emit }) {
function handleInput(event: Event) {
const target = event.target;
if (target instanceof HTMLInputElement) {
emit('update:modelValue', target.value);
}
}
return () =>
h('input', {
...attrs,
onInput: handleInput,
value: attrs.modelValue ?? '',
});
},
});
function createFormValues(): PerformanceFormValues {
return {
contacts: Array.from({ length: ROW_COUNT }, (_, index) => ({
@@ -82,10 +62,24 @@ const codec = {
};
const formValues = createFormValues();
const codecFormApi = new FormApi<PerformanceFormValues>({ codec });
codecFormApi.mount({ meta: {}, values: formValues } as never, new Map());
setupVbenForm({ config: {}, rules: {} });
const [CodecForm, codecFormApi] = useVbenForm<PerformanceFormValues>({
codec,
schema: [
{
component: TestInput,
defaultValue: formValues.contacts,
fieldName: 'contacts',
},
{
component: TestInput,
defaultValue: formValues.settings,
fieldName: 'settings',
},
],
showDefaultActions: false,
});
const codecWrapper = mount(CodecForm);
const [ArrayForm, arrayFormApi] = useVbenForm({
schema: [
{
@@ -115,6 +109,7 @@ let arraySchemaIteration = 0;
afterAll(() => {
arrayWrapper.unmount();
codecWrapper.unmount();
});
describe('form codec performance', () => {

View File

@@ -404,6 +404,11 @@ export class FormApi<
}
}
async setFieldError(fieldName: FormFieldName<TFormValues>, error?: string) {
const form = await this.getForm();
form.setFieldError(fieldName, error);
}
async setFieldValue<TFieldName extends FormFieldName<TFormValues>>(
field: TFieldName,
value: FormFieldValue<TFormValues, NoInfer<TFieldName>>,

View File

@@ -37,38 +37,38 @@ const date = useDateFormat(now, 'YYYY-MM-DD dddd', { locales: locale.value });
const showUnlockForm = ref(false);
const { lockScreenPassword } = storeToRefs(accessStore);
const [Form, { form, validate, getFieldComponentRef }] = useVbenForm(
reactive({
commonConfig: {
hideLabel: true,
hideRequiredMark: true,
},
schema: computed(() => [
{
component: 'VbenInputPassword' as const,
componentProps: {
placeholder: $t('ui.widgets.lockScreen.placeholder'),
},
fieldName: 'password',
label: $t('authentication.password'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
const [Form, { getFieldComponentRef, getRawValues, setFieldError, validate }] =
useVbenForm(
reactive({
commonConfig: {
hideLabel: true,
hideRequiredMark: true,
},
]),
showDefaultActions: false,
}),
);
const validPass = computed(
() => lockScreenPassword?.value === form?.values?.password,
);
schema: computed(() => [
{
component: 'VbenInputPassword' as const,
componentProps: {
placeholder: $t('ui.widgets.lockScreen.placeholder'),
},
fieldName: 'password',
label: $t('authentication.password'),
rules: z
.string()
.min(1, { message: $t('authentication.passwordTip') }),
},
]),
showDefaultActions: false,
}),
);
async function handleSubmit() {
const { valid } = await validate();
if (valid) {
if (validPass.value) {
const { password } = await getRawValues();
if (lockScreenPassword?.value === password) {
accessStore.unlockScreen();
} else {
form.setFieldError('password', $t('authentication.passwordErrorTip'));
await setFieldError('password', $t('authentication.passwordErrorTip'));
}
}
}

View File

@@ -339,7 +339,6 @@ async function init() {
'query',
formOptions.value ? ((await formApi.getValues()) ?? {}) : {},
);
// props.api.reload(formApi.form?.values ?? {});
}
// form 由 vben-form代替所以不适配formConfig这里给出警告