diff --git a/internal/lint-configs/eslint-config/src/configs/javascript.ts b/internal/lint-configs/eslint-config/src/configs/javascript.ts index cae21b81..0d87c1ba 100644 --- a/internal/lint-configs/eslint-config/src/configs/javascript.ts +++ b/internal/lint-configs/eslint-config/src/configs/javascript.ts @@ -147,7 +147,7 @@ export async function javascript(): Promise { 'no-template-curly-in-string': 'error', 'no-this-before-super': 'error', 'no-throw-literal': 'error', - 'no-undef': 'error', + 'no-undef': 'off', 'no-undef-init': 'error', 'no-unexpected-multiline': 'error', 'no-unmodified-loop-condition': 'error', diff --git a/internal/lint-configs/eslint-config/src/configs/vue.ts b/internal/lint-configs/eslint-config/src/configs/vue.ts index 8c45e8ad..27cc3cf2 100644 --- a/internal/lint-configs/eslint-config/src/configs/vue.ts +++ b/internal/lint-configs/eslint-config/src/configs/vue.ts @@ -15,6 +15,22 @@ export async function vue(): Promise { { files: ['**/*.vue'], languageOptions: { + // globals: { + // computed: 'readonly', + // defineEmits: 'readonly', + // defineExpose: 'readonly', + // defineProps: 'readonly', + // onMounted: 'readonly', + // onUnmounted: 'readonly', + // reactive: 'readonly', + // ref: 'readonly', + // shallowReactive: 'readonly', + // shallowRef: 'readonly', + // toRef: 'readonly', + // toRefs: 'readonly', + // watch: 'readonly', + // watchEffect: 'readonly', + // }, parser: parserVue, parserOptions: { ecmaFeatures: { diff --git a/internal/vite-config/src/typing.ts b/internal/vite-config/src/typing.ts index 7505ab3e..37da46d2 100644 --- a/internal/vite-config/src/typing.ts +++ b/internal/vite-config/src/typing.ts @@ -137,12 +137,12 @@ type ApplicationOptions = ApplicationPluginOptions; type LibraryOptions = LibraryPluginOptions; -type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{ +type DefineApplicationOptions = (config: ConfigEnv) => Promise<{ application?: ApplicationOptions; vite?: UserConfig; }>; -type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{ +type DefineLibraryOptions = (config: ConfigEnv) => Promise<{ library?: LibraryOptions; vite?: UserConfig; }>; diff --git a/packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts b/packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts index 79734d7c..d61627d9 100644 --- a/packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts +++ b/packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts @@ -6,7 +6,7 @@ import { FormApi } from '../src/form-api'; vi.mock('@vben-core/shared/utils', () => ({ bindMethods: vi.fn(), createMerge: vi.fn((mergeFn) => { - return (stateOrFn, prev) => { + return (stateOrFn: any, prev: any) => { mergeFn(prev, 'key', stateOrFn); return { ...prev, ...stateOrFn }; }; @@ -144,3 +144,64 @@ describe('formApi', () => { expect(isValid).toBe(true); }); }); + +describe('updateSchema', () => { + let instance: FormApi; + + beforeEach(() => { + instance = new FormApi(); + instance.state = { + schema: [ + { component: 'text', fieldName: 'name' }, + { component: 'number', fieldName: 'age', label: 'Age' }, + ], + }; + }); + + it('should update the schema correctly when fieldName matches', () => { + const newSchema = [ + { component: 'text', fieldName: 'name' }, + { component: 'number', fieldName: 'age', label: 'Age' }, + ]; + + instance.updateSchema(newSchema); + + expect(instance.state?.schema?.[0]?.component).toBe('text'); + expect(instance.state?.schema?.[1]?.label).toBe('Age'); + }); + + it('should log an error if fieldName is missing in some items', () => { + const newSchema: any[] = [ + { component: 'textarea', fieldName: 'name' }, + { component: 'number' }, + ]; + + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + instance.updateSchema(newSchema); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'All items in the schema array must have a valid `fieldName` property to be updated', + ); + }); + + it('should not update schema if fieldName does not match', () => { + const newSchema = [{ component: 'textarea', fieldName: 'unknown' }]; + + instance.updateSchema(newSchema); + + expect(instance.state?.schema?.[0]?.component).toBe('text'); + expect(instance.state?.schema?.[1]?.component).toBe('number'); + }); + + it('should not update schema if updatedMap is empty', () => { + const newSchema: any[] = [{ component: 'textarea' }]; + + instance.updateSchema(newSchema); + + expect(instance.state?.schema?.[0]?.component).toBe('text'); + expect(instance.state?.schema?.[1]?.component).toBe('number'); + }); +}); diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index 191f4233..1476778f 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -5,7 +5,7 @@ import type { ValidationOptions, } from 'vee-validate'; -import type { FormActions, VbenFormProps } from './types'; +import type { FormActions, FormSchema, VbenFormProps } from './types'; import { toRaw } from 'vue'; @@ -186,6 +186,37 @@ export class FormApi { this.stateHandler.reset(); } + updateSchema(schema: Partial[]) { + const updated: Partial[] = [...schema]; + const hasField = updated.every( + (item) => Reflect.has(item, 'fieldName') && item.fieldName, + ); + + if (!hasField) { + console.error( + 'All items in the schema array must have a valid `fieldName` property to be updated', + ); + return; + } + const currentSchema = [...(this.state?.schema ?? [])]; + + const updatedMap: Record = {}; + + updated.forEach((item) => { + if (item.fieldName) { + updatedMap[item.fieldName] = item; + } + }); + + currentSchema.forEach((schema, index) => { + const updatedData = updatedMap[schema.fieldName]; + if (updatedData) { + currentSchema[index] = merge(updatedData, schema) as FormSchema; + } + }); + this.setState({ schema: currentSchema }); + } + async validate(opts?: Partial) { const form = await this.getForm(); return await form.validate(opts); diff --git a/packages/effects/common-ui/src/ui/about/about.vue b/packages/effects/common-ui/src/ui/about/about.vue index b2ad0667..41189c17 100644 --- a/packages/effects/common-ui/src/ui/about/about.vue +++ b/packages/effects/common-ui/src/ui/about/about.vue @@ -52,7 +52,6 @@ const { license, version, // vite inject-metadata 插件注入的全局变量 - // eslint-disable-next-line no-undef } = __VBEN_ADMIN_METADATA__ || {}; const vbenDescriptionItems: DescriptionItem[] = [ diff --git a/packages/effects/layouts/src/basic/layout.vue b/packages/effects/layouts/src/basic/layout.vue index fb58ba6c..28bd28a4 100644 --- a/packages/effects/layouts/src/basic/layout.vue +++ b/packages/effects/layouts/src/basic/layout.vue @@ -143,6 +143,19 @@ watch( }, ); +watch( + () => preferences.app.layout, + async (val) => { + if (val === 'sidebar-mixed-nav' && preferences.sidebar.hidden) { + updatePreferences({ + sidebar: { + hidden: false, + }, + }); + } + }, +); + const slots = useSlots(); const headerSlots = computed(() => { return Object.keys(slots).filter((key) => key.startsWith('header-')); diff --git a/packages/styles/src/antd/index.css b/packages/styles/src/antd/index.css index b6246ea4..c41716bc 100644 --- a/packages/styles/src/antd/index.css +++ b/packages/styles/src/antd/index.css @@ -12,6 +12,6 @@ @apply dark:border-border/60 dark:border; } -.ant-app .form-valid-error .ant-select-selector { +.form-valid-error .ant-select-selector { border-color: hsl(var(--destructive)); } diff --git a/playground/src/views/examples/form/api.vue b/playground/src/views/examples/form/api.vue index 32f12225..8efcaaa9 100644 --- a/playground/src/views/examples/form/api.vue +++ b/playground/src/views/examples/form/api.vue @@ -41,12 +41,25 @@ const [BaseForm, formApi] = useVbenForm({ label: 'field2', }, { - component: 'Input', + component: 'Select', componentProps: { - placeholder: '请输入', + allowClear: true, + filterOption: true, + options: [ + { + label: '选项1', + value: '1', + }, + { + label: '选项2', + value: '2', + }, + ], + placeholder: '请选择', + showSearch: true, }, - fieldName: 'field3', - label: 'field3', + fieldName: 'fieldOptions', + label: '下拉选', }, ], // 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个 @@ -75,9 +88,35 @@ function handleClick( | 'showSubmitButton' | 'updateActionAlign' | 'updateResetButton' + | 'updateSchema' | 'updateSubmitButton', ) { switch (action) { + case 'updateSchema': { + formApi.updateSchema([ + { + componentProps: { + options: [ + { + label: '选项1', + value: '1', + }, + { + label: '选项2', + value: '2', + }, + { + label: '选项3', + value: '3', + }, + ], + }, + fieldName: 'fieldOptions', + }, + ]); + break; + } + case 'labelWidth': { formApi.setState({ commonConfig: { @@ -181,6 +220,7 @@ function handleClick(