fix: improve the scroll bar flashing when the modal box is opened (#4438)
parent
56bdb8f606
commit
161820dbc1
|
@ -69,3 +69,19 @@ export function getScrollbarWidth() {
|
||||||
scrollDiv.remove();
|
scrollDiv.remove();
|
||||||
return scrollbarWidth;
|
return scrollbarWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function needsScrollbar() {
|
||||||
|
const doc = document.documentElement;
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
// 检查 body 的 overflow-y 样式
|
||||||
|
const overflowY = window.getComputedStyle(body).overflowY;
|
||||||
|
|
||||||
|
// 如果明确设置了需要滚动条的样式
|
||||||
|
if (overflowY === 'scroll' || overflowY === 'auto') {
|
||||||
|
return doc.scrollHeight > window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在其他情况下,根据 scrollHeight 和 innerHeight 比较判断
|
||||||
|
return doc.scrollHeight > window.innerHeight;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getScrollbarWidth } from '@vben-core/shared/utils';
|
import { getScrollbarWidth, needsScrollbar } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useScrollLock as _useScrollLock,
|
useScrollLock as _useScrollLock,
|
||||||
|
@ -13,6 +13,9 @@ export function useScrollLock() {
|
||||||
const scrollbarWidth = getScrollbarWidth();
|
const scrollbarWidth = getScrollbarWidth();
|
||||||
|
|
||||||
tryOnBeforeMount(() => {
|
tryOnBeforeMount(() => {
|
||||||
|
if (!needsScrollbar()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
||||||
|
|
||||||
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
|
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
|
||||||
|
@ -30,6 +33,9 @@ export function useScrollLock() {
|
||||||
});
|
});
|
||||||
|
|
||||||
tryOnBeforeUnmount(() => {
|
tryOnBeforeUnmount(() => {
|
||||||
|
if (!needsScrollbar()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
isLocked.value = false;
|
isLocked.value = false;
|
||||||
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
|
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
|
||||||
`.${SCROLL_FIXED_CLASS}`,
|
`.${SCROLL_FIXED_CLASS}`,
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`defaultPreferences immutability test > should not modify the config object 1`] = `
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"accessMode": "frontend",
|
||||||
|
"authPageLayout": "panel-right",
|
||||||
|
"checkUpdatesInterval": 1,
|
||||||
|
"colorGrayMode": false,
|
||||||
|
"colorWeakMode": false,
|
||||||
|
"compact": false,
|
||||||
|
"contentCompact": "wide",
|
||||||
|
"defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.6/source/avatar-v1.webp",
|
||||||
|
"dynamicTitle": true,
|
||||||
|
"enableCheckUpdates": true,
|
||||||
|
"enablePreferences": true,
|
||||||
|
"enableRefreshToken": false,
|
||||||
|
"isMobile": false,
|
||||||
|
"layout": "sidebar-nav",
|
||||||
|
"locale": "zh-CN",
|
||||||
|
"loginExpiredMode": "page",
|
||||||
|
"name": "Vben Admin",
|
||||||
|
"preferencesButtonPosition": "auto",
|
||||||
|
"watermark": false,
|
||||||
|
},
|
||||||
|
"breadcrumb": {
|
||||||
|
"enable": true,
|
||||||
|
"hideOnlyOne": false,
|
||||||
|
"showHome": false,
|
||||||
|
"showIcon": true,
|
||||||
|
"styleType": "normal",
|
||||||
|
},
|
||||||
|
"copyright": {
|
||||||
|
"companyName": "Vben",
|
||||||
|
"companySiteLink": "https://www.vben.pro",
|
||||||
|
"date": "2024",
|
||||||
|
"enable": true,
|
||||||
|
"icp": "",
|
||||||
|
"icpLink": "",
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"enable": true,
|
||||||
|
"fixed": false,
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"enable": true,
|
||||||
|
"hidden": false,
|
||||||
|
"mode": "fixed",
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"enable": true,
|
||||||
|
"source": "https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp",
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"accordion": true,
|
||||||
|
"split": true,
|
||||||
|
"styleType": "rounded",
|
||||||
|
},
|
||||||
|
"shortcutKeys": {
|
||||||
|
"enable": true,
|
||||||
|
"globalLockScreen": true,
|
||||||
|
"globalLogout": true,
|
||||||
|
"globalPreferences": true,
|
||||||
|
"globalSearch": true,
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"collapsed": false,
|
||||||
|
"collapsedShowTitle": false,
|
||||||
|
"enable": true,
|
||||||
|
"expandOnHover": true,
|
||||||
|
"extraCollapse": true,
|
||||||
|
"hidden": false,
|
||||||
|
"width": 224,
|
||||||
|
},
|
||||||
|
"tabbar": {
|
||||||
|
"dragable": true,
|
||||||
|
"enable": true,
|
||||||
|
"height": 38,
|
||||||
|
"keepAlive": true,
|
||||||
|
"persist": true,
|
||||||
|
"showIcon": true,
|
||||||
|
"showMaximize": true,
|
||||||
|
"showMore": true,
|
||||||
|
"showRefresh": true,
|
||||||
|
"styleType": "chrome",
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"builtinType": "default",
|
||||||
|
"colorDestructive": "hsl(348 100% 61%)",
|
||||||
|
"colorPrimary": "hsl(212 100% 45%)",
|
||||||
|
"colorSuccess": "hsl(144 57% 58%)",
|
||||||
|
"colorWarning": "hsl(42 84% 61%)",
|
||||||
|
"mode": "dark",
|
||||||
|
"radius": "0.5",
|
||||||
|
"semiDarkHeader": false,
|
||||||
|
"semiDarkSidebar": true,
|
||||||
|
},
|
||||||
|
"transition": {
|
||||||
|
"enable": true,
|
||||||
|
"loading": true,
|
||||||
|
"name": "fade-slide",
|
||||||
|
"progress": true,
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"fullscreen": true,
|
||||||
|
"globalSearch": true,
|
||||||
|
"languageToggle": true,
|
||||||
|
"lockScreen": true,
|
||||||
|
"notification": true,
|
||||||
|
"sidebarToggle": true,
|
||||||
|
"themeToggle": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { defaultPreferences } from '../src/config';
|
||||||
|
|
||||||
|
describe('defaultPreferences immutability test', () => {
|
||||||
|
// 创建快照,确保默认配置对象不被修改
|
||||||
|
it('should not modify the config object', () => {
|
||||||
|
expect(defaultPreferences).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,8 +1,8 @@
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { defaultPreferences } from './config';
|
import { defaultPreferences } from '../src/config';
|
||||||
import { PreferenceManager } from './preferences';
|
import { PreferenceManager } from '../src/preferences';
|
||||||
import { isDarkTheme } from './update-css-variables';
|
import { isDarkTheme } from '../src/update-css-variables';
|
||||||
|
|
||||||
describe('preferences', () => {
|
describe('preferences', () => {
|
||||||
let preferenceManager: PreferenceManager;
|
let preferenceManager: PreferenceManager;
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"extends": "@vben/tsconfig/web.json",
|
"extends": "@vben/tsconfig/web.json",
|
||||||
"include": ["src"],
|
"include": ["src", "__tests__"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
// 假设这个文件为 FormApi.ts
|
||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { FormApi } from '../src/form-api';
|
||||||
|
|
||||||
|
vi.mock('@vben-core/shared/utils', () => ({
|
||||||
|
bindMethods: vi.fn(),
|
||||||
|
createMerge: vi.fn((mergeFn) => {
|
||||||
|
return (stateOrFn, prev) => {
|
||||||
|
mergeFn(prev, 'key', stateOrFn);
|
||||||
|
return { ...prev, ...stateOrFn };
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
isFunction: (fn: any) => typeof fn === 'function',
|
||||||
|
StateHandler: vi.fn().mockImplementation(() => ({
|
||||||
|
reset: vi.fn(),
|
||||||
|
setConditionTrue: vi.fn(),
|
||||||
|
waitForCondition: vi.fn().mockResolvedValue(true),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('formApi', () => {
|
||||||
|
let formApi: FormApi;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
formApi = new FormApi();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize with default state', () => {
|
||||||
|
expect(formApi.state).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
actionWrapperClass: '',
|
||||||
|
collapsed: false,
|
||||||
|
collapsedRows: 1,
|
||||||
|
commonConfig: {},
|
||||||
|
handleReset: undefined,
|
||||||
|
handleSubmit: undefined,
|
||||||
|
layout: 'horizontal',
|
||||||
|
resetButtonOptions: {},
|
||||||
|
schema: [],
|
||||||
|
showCollapseButton: false,
|
||||||
|
showDefaultActions: true,
|
||||||
|
submitButtonOptions: {},
|
||||||
|
wrapperClass: 'grid-cols-1',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(formApi.isMounted).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mount form actions', async () => {
|
||||||
|
const formActions: any = {
|
||||||
|
meta: {},
|
||||||
|
resetForm: vi.fn(),
|
||||||
|
setFieldValue: vi.fn(),
|
||||||
|
setValues: vi.fn(),
|
||||||
|
submitForm: vi.fn(),
|
||||||
|
validate: vi.fn(),
|
||||||
|
values: { name: 'test' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await formApi.mount(formActions);
|
||||||
|
expect(formApi.isMounted).toBe(true);
|
||||||
|
expect(formApi.form).toEqual(formActions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get values from form', async () => {
|
||||||
|
const formActions: any = {
|
||||||
|
meta: {},
|
||||||
|
values: { name: 'test' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await formApi.mount(formActions);
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
expect(values).toEqual({ name: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set field value', async () => {
|
||||||
|
const setFieldValueMock = vi.fn();
|
||||||
|
const formActions: any = {
|
||||||
|
meta: {},
|
||||||
|
setFieldValue: setFieldValueMock,
|
||||||
|
values: { name: 'test' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await formApi.mount(formActions);
|
||||||
|
await formApi.setFieldValue('name', 'new value');
|
||||||
|
expect(setFieldValueMock).toHaveBeenCalledWith(
|
||||||
|
'name',
|
||||||
|
'new value',
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset form', async () => {
|
||||||
|
const resetFormMock = vi.fn();
|
||||||
|
const formActions: any = {
|
||||||
|
meta: {},
|
||||||
|
resetForm: resetFormMock,
|
||||||
|
values: { name: 'test' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await formApi.mount(formActions);
|
||||||
|
await formApi.resetForm();
|
||||||
|
expect(resetFormMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call handleSubmit on submit', async () => {
|
||||||
|
const handleSubmitMock = vi.fn();
|
||||||
|
const formActions: any = {
|
||||||
|
meta: {},
|
||||||
|
submitForm: vi.fn().mockResolvedValue(true),
|
||||||
|
values: { name: 'test' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
handleSubmit: handleSubmitMock,
|
||||||
|
};
|
||||||
|
|
||||||
|
formApi.setState(state);
|
||||||
|
await formApi.mount(formActions);
|
||||||
|
|
||||||
|
const result = await formApi.submitForm();
|
||||||
|
expect(formActions.submitForm).toHaveBeenCalled();
|
||||||
|
expect(handleSubmitMock).toHaveBeenCalledWith({ name: 'test' });
|
||||||
|
expect(result).toEqual({ name: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unmount form and reset state', () => {
|
||||||
|
formApi.unmounted();
|
||||||
|
expect(formApi.isMounted).toBe(false);
|
||||||
|
expect(formApi.stateHandler.reset).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate form', async () => {
|
||||||
|
const validateMock = vi.fn().mockResolvedValue(true);
|
||||||
|
const formActions: any = {
|
||||||
|
meta: {},
|
||||||
|
validate: validateMock,
|
||||||
|
};
|
||||||
|
|
||||||
|
await formApi.mount(formActions);
|
||||||
|
const isValid = await formApi.validate();
|
||||||
|
expect(validateMock).toHaveBeenCalled();
|
||||||
|
expect(isValid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -43,13 +43,13 @@ function getDefaultState(): VbenFormProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FormApi {
|
export class FormApi {
|
||||||
// private prevState!: ModalState;
|
|
||||||
private state: null | VbenFormProps = null;
|
|
||||||
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
|
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
|
||||||
public form = {} as FormActions;
|
public form = {} as FormActions;
|
||||||
|
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
|
|
||||||
|
// private prevState!: ModalState;
|
||||||
|
public state: null | VbenFormProps = null;
|
||||||
|
|
||||||
stateHandler: StateHandler;
|
stateHandler: StateHandler;
|
||||||
|
|
||||||
public store: Store<VbenFormProps>;
|
public store: Store<VbenFormProps>;
|
||||||
|
@ -92,6 +92,10 @@ export class FormApi {
|
||||||
this.store.batch(cb);
|
this.store.batch(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
async getValues() {
|
async getValues() {
|
||||||
const form = await this.getForm();
|
const form = await this.getForm();
|
||||||
return form.values;
|
return form.values;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"extends": "@vben/tsconfig/web.json",
|
"extends": "@vben/tsconfig/web.json",
|
||||||
"include": ["src"],
|
"include": ["src", "__tests__"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,8 @@
|
||||||
"query": "Query Form",
|
"query": "Query Form",
|
||||||
"rules": "Form Rules",
|
"rules": "Form Rules",
|
||||||
"dynamic": "Dynamic Form",
|
"dynamic": "Dynamic Form",
|
||||||
"custom": "Custom Component"
|
"custom": "Custom Component",
|
||||||
|
"api": "Api"
|
||||||
},
|
},
|
||||||
"captcha": {
|
"captcha": {
|
||||||
"title": "Captcha",
|
"title": "Captcha",
|
||||||
|
|
|
@ -78,7 +78,8 @@
|
||||||
"query": "查询表单",
|
"query": "查询表单",
|
||||||
"rules": "表单校验",
|
"rules": "表单校验",
|
||||||
"dynamic": "动态表单",
|
"dynamic": "动态表单",
|
||||||
"custom": "自定义组件"
|
"custom": "自定义组件",
|
||||||
|
"api": "Api"
|
||||||
},
|
},
|
||||||
"captcha": {
|
"captcha": {
|
||||||
"title": "验证码",
|
"title": "验证码",
|
||||||
|
|
|
@ -99,6 +99,14 @@ const routes: RouteRecordRaw[] = [
|
||||||
title: $t('page.examples.form.custom'),
|
title: $t('page.examples.form.custom'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'FormApiExample',
|
||||||
|
path: '/examples/form/api',
|
||||||
|
component: () => import('#/views/examples/form/api.vue'),
|
||||||
|
meta: {
|
||||||
|
title: $t('page.examples.form.api'),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Card, message, Space } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter';
|
||||||
|
|
||||||
|
const [BaseForm, formApi] = useVbenForm({
|
||||||
|
// 所有表单项共用,可单独在表单内覆盖
|
||||||
|
commonConfig: {
|
||||||
|
// 所有表单项
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 使用 tailwindcss grid布局
|
||||||
|
// 提交函数
|
||||||
|
handleSubmit: onSubmit,
|
||||||
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
|
layout: 'horizontal',
|
||||||
|
// 水平布局,label和input在同一行
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
component: 'Input',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入用户名',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'field1',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'field1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入',
|
||||||
|
},
|
||||||
|
fieldName: 'field2',
|
||||||
|
label: 'field2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入',
|
||||||
|
},
|
||||||
|
fieldName: 'field3',
|
||||||
|
label: 'field3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSubmit(values: Record<string, any>) {
|
||||||
|
message.success({
|
||||||
|
content: `form values: ${JSON.stringify(values)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(
|
||||||
|
action:
|
||||||
|
| 'batchAddSchema'
|
||||||
|
| 'batchDeleteSchema'
|
||||||
|
| 'disabled'
|
||||||
|
| 'hiddenAction'
|
||||||
|
| 'hiddenResetButton'
|
||||||
|
| 'hiddenSubmitButton'
|
||||||
|
| 'labelWidth'
|
||||||
|
| 'resetDisabled'
|
||||||
|
| 'resetLabelWidth'
|
||||||
|
| 'showAction'
|
||||||
|
| 'showResetButton'
|
||||||
|
| 'showSubmitButton'
|
||||||
|
| 'updateActionAlign'
|
||||||
|
| 'updateResetButton'
|
||||||
|
| 'updateSubmitButton',
|
||||||
|
) {
|
||||||
|
switch (action) {
|
||||||
|
case 'labelWidth': {
|
||||||
|
formApi.setState({
|
||||||
|
commonConfig: {
|
||||||
|
labelWidth: 150,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'resetLabelWidth': {
|
||||||
|
formApi.setState({
|
||||||
|
commonConfig: {
|
||||||
|
labelWidth: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'disabled': {
|
||||||
|
formApi.setState({ commonConfig: { disabled: true } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'resetDisabled': {
|
||||||
|
formApi.setState({ commonConfig: { disabled: false } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'hiddenAction': {
|
||||||
|
formApi.setState({ showDefaultActions: false });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'showAction': {
|
||||||
|
formApi.setState({ showDefaultActions: true });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'hiddenResetButton': {
|
||||||
|
formApi.setState({ resetButtonOptions: { show: false } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'showResetButton': {
|
||||||
|
formApi.setState({ resetButtonOptions: { show: true } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'hiddenSubmitButton': {
|
||||||
|
formApi.setState({ submitButtonOptions: { show: false } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'showSubmitButton': {
|
||||||
|
formApi.setState({ submitButtonOptions: { show: true } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'updateResetButton': {
|
||||||
|
formApi.setState({
|
||||||
|
resetButtonOptions: { disabled: true },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'updateSubmitButton': {
|
||||||
|
formApi.setState({
|
||||||
|
submitButtonOptions: { loading: true },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'updateActionAlign': {
|
||||||
|
formApi.setState({
|
||||||
|
// 可以自行调整class
|
||||||
|
actionWrapperClass: 'text-center',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'batchAddSchema': {
|
||||||
|
formApi.setState((prev) => {
|
||||||
|
const currentSchema = prev?.schema ?? [];
|
||||||
|
const newSchema = [];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
newSchema.push({
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入',
|
||||||
|
},
|
||||||
|
fieldName: `field${i}${Date.now()}`,
|
||||||
|
label: `field+`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
schema: [...currentSchema, ...newSchema],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'batchDeleteSchema': {
|
||||||
|
formApi.setState((prev) => {
|
||||||
|
const currentSchema = prev?.schema ?? [];
|
||||||
|
return {
|
||||||
|
schema: currentSchema.slice(0, -3),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page description="表单组件api操作示例。" title="表单组件">
|
||||||
|
<Space class="mb-5 flex-wrap">
|
||||||
|
<Button @click="handleClick('labelWidth')">更改labelWidth</Button>
|
||||||
|
<Button @click="handleClick('resetLabelWidth')">还原labelWidth</Button>
|
||||||
|
<Button @click="handleClick('disabled')">禁用表单</Button>
|
||||||
|
<Button @click="handleClick('resetDisabled')">解除禁用</Button>
|
||||||
|
<Button @click="handleClick('hiddenAction')">隐藏操作按钮</Button>
|
||||||
|
<Button @click="handleClick('showAction')">显示操作按钮</Button>
|
||||||
|
<Button @click="handleClick('hiddenResetButton')">隐藏重置按钮</Button>
|
||||||
|
<Button @click="handleClick('showResetButton')">显示重置按钮</Button>
|
||||||
|
<Button @click="handleClick('hiddenSubmitButton')">隐藏提交按钮</Button>
|
||||||
|
<Button @click="handleClick('showSubmitButton')">显示提交按钮</Button>
|
||||||
|
<Button @click="handleClick('updateResetButton')">修改重置按钮</Button>
|
||||||
|
<Button @click="handleClick('updateSubmitButton')">修改提交按钮</Button>
|
||||||
|
<Button @click="handleClick('updateActionAlign')">
|
||||||
|
调整操作按钮位置
|
||||||
|
</Button>
|
||||||
|
<Button @click="handleClick('batchAddSchema')"> 批量添加表单项 </Button>
|
||||||
|
<Button @click="handleClick('batchDeleteSchema')">
|
||||||
|
批量删除表单项
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<Card title="操作示例">
|
||||||
|
<BaseForm />
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
|
@ -14,12 +14,11 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 使用 tailwindcss grid布局
|
|
||||||
// 提交函数
|
// 提交函数
|
||||||
handleSubmit: onSubmit,
|
handleSubmit: onSubmit,
|
||||||
// 垂直布局,label和input在不同行,值为vertical
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
layout: 'horizontal',
|
|
||||||
// 水平布局,label和input在同一行
|
// 水平布局,label和input在同一行
|
||||||
|
layout: 'horizontal',
|
||||||
schema: [
|
schema: [
|
||||||
{
|
{
|
||||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
|
|
@ -16,12 +16,11 @@ const [BaseForm] = useVbenForm({
|
||||||
},
|
},
|
||||||
labelClass: 'w-2/6',
|
labelClass: 'w-2/6',
|
||||||
},
|
},
|
||||||
// 使用 tailwindcss grid布局
|
|
||||||
// 提交函数
|
// 提交函数
|
||||||
handleSubmit: onSubmit,
|
handleSubmit: onSubmit,
|
||||||
// 垂直布局,label和input在不同行,值为vertical
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
layout: 'horizontal',
|
|
||||||
// 水平布局,label和input在同一行
|
// 水平布局,label和input在同一行
|
||||||
|
layout: 'horizontal',
|
||||||
schema: [
|
schema: [
|
||||||
{
|
{
|
||||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
@ -31,7 +30,6 @@ const [BaseForm] = useVbenForm({
|
||||||
suffix: () => h('span', { class: 'text-red-600' }, '元'),
|
suffix: () => h('span', { class: 'text-red-600' }, '元'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
fieldName: 'field1',
|
fieldName: 'field1',
|
||||||
label: '自定义组件slot',
|
label: '自定义组件slot',
|
||||||
|
@ -41,14 +39,12 @@ const [BaseForm] = useVbenForm({
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
|
||||||
component: h(Input, { placeholder: '请输入' }),
|
component: h(Input, { placeholder: '请输入' }),
|
||||||
fieldName: 'field2',
|
fieldName: 'field2',
|
||||||
label: '自定义组件',
|
label: '自定义组件',
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
fieldName: 'field3',
|
fieldName: 'field3',
|
||||||
label: '自定义组件(slot)',
|
label: '自定义组件(slot)',
|
||||||
|
|
|
@ -6,10 +6,8 @@ import { Button, Card, message } from 'ant-design-vue';
|
||||||
import { useVbenForm } from '#/adapter';
|
import { useVbenForm } from '#/adapter';
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
// 使用 tailwindcss grid布局
|
|
||||||
// 提交函数
|
// 提交函数
|
||||||
handleSubmit: onSubmit,
|
handleSubmit: onSubmit,
|
||||||
// 水平布局,label和input在同一行
|
|
||||||
schema: [
|
schema: [
|
||||||
{
|
{
|
||||||
component: 'Switch',
|
component: 'Switch',
|
||||||
|
@ -55,12 +53,9 @@ const [Form, formApi] = useVbenForm({
|
||||||
show(values) {
|
show(values) {
|
||||||
return !!values.field2Switch;
|
return !!values.field2Switch;
|
||||||
},
|
},
|
||||||
// 只有指定的字段改变时,才会触发
|
|
||||||
triggerFields: ['field2Switch'],
|
triggerFields: ['field2Switch'],
|
||||||
},
|
},
|
||||||
// 字段名
|
|
||||||
fieldName: 'field2',
|
fieldName: 'field2',
|
||||||
// 界面显示的label
|
|
||||||
label: '字段2',
|
label: '字段2',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -69,12 +64,9 @@ const [Form, formApi] = useVbenForm({
|
||||||
disabled(values) {
|
disabled(values) {
|
||||||
return !!values.field3Switch;
|
return !!values.field3Switch;
|
||||||
},
|
},
|
||||||
// 只有指定的字段改变时,才会触发
|
|
||||||
triggerFields: ['field3Switch'],
|
triggerFields: ['field3Switch'],
|
||||||
},
|
},
|
||||||
// 字段名
|
|
||||||
fieldName: 'field3',
|
fieldName: 'field3',
|
||||||
// 界面显示的label
|
|
||||||
label: '字段3',
|
label: '字段3',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -83,12 +75,9 @@ const [Form, formApi] = useVbenForm({
|
||||||
required(values) {
|
required(values) {
|
||||||
return !!values.field4Switch;
|
return !!values.field4Switch;
|
||||||
},
|
},
|
||||||
// 只有指定的字段改变时,才会触发
|
|
||||||
triggerFields: ['field4Switch'],
|
triggerFields: ['field4Switch'],
|
||||||
},
|
},
|
||||||
// 字段名
|
|
||||||
fieldName: 'field4',
|
fieldName: 'field4',
|
||||||
// 界面显示的label
|
|
||||||
label: '字段4',
|
label: '字段4',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -100,13 +89,10 @@ const [Form, formApi] = useVbenForm({
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
// 只有指定的字段改变时,才会触发
|
|
||||||
triggerFields: ['field1'],
|
triggerFields: ['field1'],
|
||||||
},
|
},
|
||||||
// 字段名
|
|
||||||
fieldName: 'field5',
|
fieldName: 'field5',
|
||||||
help: '当字段1的值为`123`时,必填',
|
help: '当字段1的值为`123`时,必填',
|
||||||
// 界面显示的label
|
|
||||||
label: '动态rules',
|
label: '动态rules',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -150,13 +136,10 @@ const [Form, formApi] = useVbenForm({
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
// 只有指定的字段改变时,才会触发
|
|
||||||
triggerFields: ['field2'],
|
triggerFields: ['field2'],
|
||||||
},
|
},
|
||||||
// 字段名
|
|
||||||
fieldName: 'field6',
|
fieldName: 'field6',
|
||||||
help: '当字段2的值为`123`时,更改下拉选项',
|
help: '当字段2的值为`123`时,更改下拉选项',
|
||||||
// 界面显示的label
|
|
||||||
label: '动态配置',
|
label: '动态配置',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,9 +18,8 @@ const [QueryForm] = useVbenForm({
|
||||||
// 提交函数
|
// 提交函数
|
||||||
handleSubmit: onSubmit,
|
handleSubmit: onSubmit,
|
||||||
// 垂直布局,label和input在不同行,值为vertical
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
layout: 'horizontal',
|
|
||||||
// 使用 tailwindcss grid布局
|
|
||||||
// 水平布局,label和input在同一行
|
// 水平布局,label和input在同一行
|
||||||
|
layout: 'horizontal',
|
||||||
schema: [
|
schema: [
|
||||||
{
|
{
|
||||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
@ -101,9 +100,8 @@ const [QueryForm1] = useVbenForm({
|
||||||
// 提交函数
|
// 提交函数
|
||||||
handleSubmit: onSubmit,
|
handleSubmit: onSubmit,
|
||||||
// 垂直布局,label和input在不同行,值为vertical
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
layout: 'horizontal',
|
|
||||||
// 使用 tailwindcss grid布局
|
|
||||||
// 水平布局,label和input在同一行
|
// 水平布局,label和input在同一行
|
||||||
|
layout: 'horizontal',
|
||||||
schema: (() => {
|
schema: (() => {
|
||||||
const schema = [];
|
const schema = [];
|
||||||
for (let index = 0; index < 14; index++) {
|
for (let index = 0; index < 14; index++) {
|
||||||
|
|
|
@ -13,12 +13,11 @@ const [Form, formApi] = useVbenForm({
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 使用 tailwindcss grid布局
|
|
||||||
// 提交函数
|
// 提交函数
|
||||||
handleSubmit: onSubmit,
|
handleSubmit: onSubmit,
|
||||||
// 垂直布局,label和input在不同行,值为vertical
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
layout: 'horizontal',
|
|
||||||
// 水平布局,label和input在同一行
|
// 水平布局,label和input在同一行
|
||||||
|
layout: 'horizontal',
|
||||||
schema: [
|
schema: [
|
||||||
{
|
{
|
||||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
@ -80,7 +79,6 @@ const [Form, formApi] = useVbenForm({
|
||||||
},
|
},
|
||||||
fieldName: 'number',
|
fieldName: 'number',
|
||||||
label: '数字',
|
label: '数字',
|
||||||
// 预处理函数,将空字符串或null转换为undefined
|
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue