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();
|
||||
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 {
|
||||
useScrollLock as _useScrollLock,
|
||||
|
@ -13,6 +13,9 @@ export function useScrollLock() {
|
|||
const scrollbarWidth = getScrollbarWidth();
|
||||
|
||||
tryOnBeforeMount(() => {
|
||||
if (!needsScrollbar()) {
|
||||
return;
|
||||
}
|
||||
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
||||
|
||||
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
|
||||
|
@ -30,6 +33,9 @@ export function useScrollLock() {
|
|||
});
|
||||
|
||||
tryOnBeforeUnmount(() => {
|
||||
if (!needsScrollbar()) {
|
||||
return;
|
||||
}
|
||||
isLocked.value = false;
|
||||
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
|
||||
`.${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 { defaultPreferences } from './config';
|
||||
import { PreferenceManager } from './preferences';
|
||||
import { isDarkTheme } from './update-css-variables';
|
||||
import { defaultPreferences } from '../src/config';
|
||||
import { PreferenceManager } from '../src/preferences';
|
||||
import { isDarkTheme } from '../src/update-css-variables';
|
||||
|
||||
describe('preferences', () => {
|
||||
let preferenceManager: PreferenceManager;
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"include": ["src"],
|
||||
"include": ["src", "__tests__"],
|
||||
"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 {
|
||||
// private prevState!: ModalState;
|
||||
private state: null | VbenFormProps = null;
|
||||
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
|
||||
public form = {} as FormActions;
|
||||
|
||||
isMounted = false;
|
||||
|
||||
// private prevState!: ModalState;
|
||||
public state: null | VbenFormProps = null;
|
||||
|
||||
stateHandler: StateHandler;
|
||||
|
||||
public store: Store<VbenFormProps>;
|
||||
|
@ -92,6 +92,10 @@ export class FormApi {
|
|||
this.store.batch(cb);
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
async getValues() {
|
||||
const form = await this.getForm();
|
||||
return form.values;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"include": ["src"],
|
||||
"include": ["src", "__tests__"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
@ -78,7 +78,8 @@
|
|||
"query": "Query Form",
|
||||
"rules": "Form Rules",
|
||||
"dynamic": "Dynamic Form",
|
||||
"custom": "Custom Component"
|
||||
"custom": "Custom Component",
|
||||
"api": "Api"
|
||||
},
|
||||
"captcha": {
|
||||
"title": "Captcha",
|
||||
|
|
|
@ -78,7 +78,8 @@
|
|||
"query": "查询表单",
|
||||
"rules": "表单校验",
|
||||
"dynamic": "动态表单",
|
||||
"custom": "自定义组件"
|
||||
"custom": "自定义组件",
|
||||
"api": "Api"
|
||||
},
|
||||
"captcha": {
|
||||
"title": "验证码",
|
||||
|
|
|
@ -99,6 +99,14 @@ const routes: RouteRecordRaw[] = [
|
|||
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',
|
||||
},
|
||||
},
|
||||
// 使用 tailwindcss grid布局
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
layout: 'horizontal',
|
||||
// 水平布局,label和input在同一行
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
|
|
|
@ -16,12 +16,11 @@ const [BaseForm] = useVbenForm({
|
|||
},
|
||||
labelClass: 'w-2/6',
|
||||
},
|
||||
// 使用 tailwindcss grid布局
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
layout: 'horizontal',
|
||||
// 水平布局,label和input在同一行
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
|
@ -31,7 +30,6 @@ const [BaseForm] = useVbenForm({
|
|||
suffix: () => h('span', { class: 'text-red-600' }, '元'),
|
||||
},
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
component: 'Input',
|
||||
fieldName: 'field1',
|
||||
label: '自定义组件slot',
|
||||
|
@ -41,14 +39,12 @@ const [BaseForm] = useVbenForm({
|
|||
}),
|
||||
},
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
component: h(Input, { placeholder: '请输入' }),
|
||||
fieldName: 'field2',
|
||||
label: '自定义组件',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
component: 'Input',
|
||||
fieldName: 'field3',
|
||||
label: '自定义组件(slot)',
|
||||
|
|
|
@ -6,10 +6,8 @@ import { Button, Card, message } from 'ant-design-vue';
|
|||
import { useVbenForm } from '#/adapter';
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
// 使用 tailwindcss grid布局
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 水平布局,label和input在同一行
|
||||
schema: [
|
||||
{
|
||||
component: 'Switch',
|
||||
|
@ -55,12 +53,9 @@ const [Form, formApi] = useVbenForm({
|
|||
show(values) {
|
||||
return !!values.field2Switch;
|
||||
},
|
||||
// 只有指定的字段改变时,才会触发
|
||||
triggerFields: ['field2Switch'],
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field2',
|
||||
// 界面显示的label
|
||||
label: '字段2',
|
||||
},
|
||||
{
|
||||
|
@ -69,12 +64,9 @@ const [Form, formApi] = useVbenForm({
|
|||
disabled(values) {
|
||||
return !!values.field3Switch;
|
||||
},
|
||||
// 只有指定的字段改变时,才会触发
|
||||
triggerFields: ['field3Switch'],
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field3',
|
||||
// 界面显示的label
|
||||
label: '字段3',
|
||||
},
|
||||
{
|
||||
|
@ -83,12 +75,9 @@ const [Form, formApi] = useVbenForm({
|
|||
required(values) {
|
||||
return !!values.field4Switch;
|
||||
},
|
||||
// 只有指定的字段改变时,才会触发
|
||||
triggerFields: ['field4Switch'],
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field4',
|
||||
// 界面显示的label
|
||||
label: '字段4',
|
||||
},
|
||||
{
|
||||
|
@ -100,13 +89,10 @@ const [Form, formApi] = useVbenForm({
|
|||
}
|
||||
return null;
|
||||
},
|
||||
// 只有指定的字段改变时,才会触发
|
||||
triggerFields: ['field1'],
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field5',
|
||||
help: '当字段1的值为`123`时,必填',
|
||||
// 界面显示的label
|
||||
label: '动态rules',
|
||||
},
|
||||
{
|
||||
|
@ -150,13 +136,10 @@ const [Form, formApi] = useVbenForm({
|
|||
}
|
||||
return {};
|
||||
},
|
||||
// 只有指定的字段改变时,才会触发
|
||||
triggerFields: ['field2'],
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field6',
|
||||
help: '当字段2的值为`123`时,更改下拉选项',
|
||||
// 界面显示的label
|
||||
label: '动态配置',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -18,9 +18,8 @@ const [QueryForm] = useVbenForm({
|
|||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
layout: 'horizontal',
|
||||
// 使用 tailwindcss grid布局
|
||||
// 水平布局,label和input在同一行
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
|
@ -101,9 +100,8 @@ const [QueryForm1] = useVbenForm({
|
|||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
layout: 'horizontal',
|
||||
// 使用 tailwindcss grid布局
|
||||
// 水平布局,label和input在同一行
|
||||
layout: 'horizontal',
|
||||
schema: (() => {
|
||||
const schema = [];
|
||||
for (let index = 0; index < 14; index++) {
|
||||
|
|
|
@ -13,12 +13,11 @@ const [Form, formApi] = useVbenForm({
|
|||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
// 使用 tailwindcss grid布局
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
layout: 'horizontal',
|
||||
// 水平布局,label和input在同一行
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
|
@ -80,7 +79,6 @@ const [Form, formApi] = useVbenForm({
|
|||
},
|
||||
fieldName: 'number',
|
||||
label: '数字',
|
||||
// 预处理函数,将空字符串或null转换为undefined
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue