From 017ed1a9e1b9390389cd8802c14e32bed2ba13fe Mon Sep 17 00:00:00 2001 From: RanMaoting <62940878+RanMaoting@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:43:02 +0800 Subject: [PATCH 1/6] =?UTF-8?q?types:=20=E4=B8=BAuseVbenVxeGrid=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B3=9B=E5=9E=8B=E5=A3=B0=E6=98=8E,=E4=BD=BFgrid?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E4=B8=8A=E8=83=BD=E6=AD=A3=E7=A1=AE=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=88=B0=E8=A1=8C=E6=95=B0=E6=8D=AE=E7=9A=84=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=20(#5653)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com> --- packages/effects/plugins/src/vxe-table/api.ts | 12 +++++----- .../effects/plugins/src/vxe-table/types.ts | 23 ++++++++++++------- .../plugins/src/vxe-table/use-vxe-grid.ts | 11 ++++++--- playground/src/adapter/vxe-table.ts | 12 ++++++++-- .../src/views/examples/vxe-table/basic.vue | 17 +++++++++++++- 5 files changed, 55 insertions(+), 20 deletions(-) diff --git a/packages/effects/plugins/src/vxe-table/api.ts b/packages/effects/plugins/src/vxe-table/api.ts index 50879b67..2b60d602 100644 --- a/packages/effects/plugins/src/vxe-table/api.ts +++ b/packages/effects/plugins/src/vxe-table/api.ts @@ -26,14 +26,14 @@ function getDefaultState(): VxeGridProps { }; } -export class VxeGridApi { +export class VxeGridApi = any> { public formApi = {} as ExtendedFormApi; // private prevState: null | VxeGridProps = null; - public grid = {} as VxeGridInstance; - public state: null | VxeGridProps = null; + public grid = {} as VxeGridInstance; + public state: null | VxeGridProps = null; - public store: Store; + public store: Store>; private isMounted = false; @@ -99,8 +99,8 @@ export class VxeGridApi { setState( stateOrFn: - | ((prev: VxeGridProps) => Partial) - | Partial, + | ((prev: VxeGridProps) => Partial>) + | Partial>, ) { if (isFunction(stateOrFn)) { this.store.setState((prev) => { diff --git a/packages/effects/plugins/src/vxe-table/types.ts b/packages/effects/plugins/src/vxe-table/types.ts index da8a014c..8b9aea47 100644 --- a/packages/effects/plugins/src/vxe-table/types.ts +++ b/packages/effects/plugins/src/vxe-table/types.ts @@ -9,7 +9,7 @@ import type { Ref } from 'vue'; import type { ClassType, DeepPartial } from '@vben/types'; -import type { VbenFormProps } from '@vben-core/form-ui'; +import type { BaseFormComponentType, VbenFormProps } from '@vben-core/form-ui'; import type { VxeGridApi } from './api'; @@ -35,7 +35,11 @@ export interface SeparatorOptions { show?: boolean; backgroundColor?: string; } -export interface VxeGridProps { + +export interface VxeGridProps< + T extends Record = any, + D extends BaseFormComponentType = BaseFormComponentType, +> { /** * 标题 */ @@ -55,15 +59,15 @@ export interface VxeGridProps { /** * vxe-grid 配置 */ - gridOptions?: DeepPartial; + gridOptions?: DeepPartial>; /** * vxe-grid 事件 */ - gridEvents?: DeepPartial; + gridEvents?: DeepPartial>; /** * 表单配置 */ - formOptions?: VbenFormProps; + formOptions?: VbenFormProps; /** * 显示搜索表单 */ @@ -74,9 +78,12 @@ export interface VxeGridProps { separator?: boolean | SeparatorOptions; } -export type ExtendedVxeGridApi = VxeGridApi & { - useStore: >( - selector?: (state: NoInfer) => T, +export type ExtendedVxeGridApi< + D extends Record = any, + F extends BaseFormComponentType = BaseFormComponentType, +> = VxeGridApi & { + useStore: >>( + selector?: (state: NoInfer>) => T, ) => Readonly>; }; diff --git a/packages/effects/plugins/src/vxe-table/use-vxe-grid.ts b/packages/effects/plugins/src/vxe-table/use-vxe-grid.ts index b15435ed..e69ae35b 100644 --- a/packages/effects/plugins/src/vxe-table/use-vxe-grid.ts +++ b/packages/effects/plugins/src/vxe-table/use-vxe-grid.ts @@ -1,3 +1,5 @@ +import type { BaseFormComponentType } from '@vben-core/form-ui'; + import type { ExtendedVxeGridApi, VxeGridProps } from './types'; import { defineComponent, h, onBeforeUnmount } from 'vue'; @@ -7,16 +9,19 @@ import { useStore } from '@vben-core/shared/store'; import { VxeGridApi } from './api'; import VxeGrid from './use-vxe-grid.vue'; -export function useVbenVxeGrid(options: VxeGridProps) { +export function useVbenVxeGrid< + T extends Record = any, + D extends BaseFormComponentType = BaseFormComponentType, +>(options: VxeGridProps) { // const IS_REACTIVE = isReactive(options); const api = new VxeGridApi(options); - const extendedApi: ExtendedVxeGridApi = api as ExtendedVxeGridApi; + const extendedApi: ExtendedVxeGridApi = api as ExtendedVxeGridApi; extendedApi.useStore = (selector) => { return useStore(api.store, selector); }; const Grid = defineComponent( - (props: VxeGridProps, { attrs, slots }) => { + (props: VxeGridProps, { attrs, slots }) => { onBeforeUnmount(() => { api.unmount(); }); diff --git a/playground/src/adapter/vxe-table.ts b/playground/src/adapter/vxe-table.ts index 71a0ecb4..06af5e05 100644 --- a/playground/src/adapter/vxe-table.ts +++ b/playground/src/adapter/vxe-table.ts @@ -1,10 +1,15 @@ import type { Recordable } from '@vben/types'; +import type { ComponentType } from './component'; + import { h } from 'vue'; import { IconifyIcon } from '@vben/icons'; import { $te } from '@vben/locales'; -import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; +import { + setupVbenVxeTable, + useVbenVxeGrid as useGrid, +} from '@vben/plugins/vxe-table'; import { get, isFunction, isString } from '@vben/utils'; import { objectOmit } from '@vueuse/core'; @@ -277,7 +282,10 @@ setupVbenVxeTable({ useVbenForm, }); -export { useVbenVxeGrid }; +export const useVbenVxeGrid = >( + ...rest: Parameters> +) => useGrid(...rest); + export type OnActionClickParams> = { code: string; row: T; diff --git a/playground/src/views/examples/vxe-table/basic.vue b/playground/src/views/examples/vxe-table/basic.vue index 1216468d..d2047246 100644 --- a/playground/src/views/examples/vxe-table/basic.vue +++ b/playground/src/views/examples/vxe-table/basic.vue @@ -43,7 +43,22 @@ const gridEvents: VxeGridListeners = { }, }; -const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions }); +const [Grid, gridApi] = useVbenVxeGrid({ + // 放开注释查看表单组件的类型 + // formOptions: { + // schema: [ + // { + // component: 'Switch', + // fieldName: 'name', + // }, + // ], + // }, + gridEvents, + gridOptions, +}); + +// 放开注释查看当前表格实例的类型 +// gridApi.grid const showBorder = gridApi.useStore((state) => state.gridOptions?.border); const showStripe = gridApi.useStore((state) => state.gridOptions?.stripe); From c0e601c020f2272a1d32b31409dc42ac09bb52fd Mon Sep 17 00:00:00 2001 From: ali-pay Date: Sun, 8 Jun 2025 17:50:44 +0800 Subject: [PATCH 2/6] fix: menu type is not 'button' (#6277) Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com> --- .../src/views/system/menu/modules/form.vue | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/playground/src/views/system/menu/modules/form.vue b/playground/src/views/system/menu/modules/form.vue index 6701a2e5..3cf40e35 100644 --- a/playground/src/views/system/menu/modules/form.vue +++ b/playground/src/views/system/menu/modules/form.vue @@ -241,10 +241,10 @@ const schema: VbenFormSchema[] = [ component: 'Input', dependencies: { rules: (values) => { - return values.type === 'action' ? 'required' : null; + return values.type === 'button' ? 'required' : null; }, show: (values) => { - return ['action', 'catalog', 'embedded', 'menu'].includes(values.type); + return ['button', 'catalog', 'embedded', 'menu'].includes(values.type); }, triggerFields: ['type'], }, @@ -277,7 +277,7 @@ const schema: VbenFormSchema[] = [ }, dependencies: { show: (values) => { - return values.type !== 'action'; + return values.type !== 'button'; }, triggerFields: ['type'], }, @@ -295,7 +295,7 @@ const schema: VbenFormSchema[] = [ }, dependencies: { show: (values) => { - return values.type !== 'action'; + return values.type !== 'button'; }, triggerFields: ['type'], }, @@ -314,7 +314,7 @@ const schema: VbenFormSchema[] = [ }, dependencies: { show: (values) => { - return values.type !== 'action'; + return values.type !== 'button'; }, triggerFields: ['type'], }, @@ -325,7 +325,7 @@ const schema: VbenFormSchema[] = [ component: 'Divider', dependencies: { show: (values) => { - return !['action', 'link'].includes(values.type); + return !['button', 'link'].includes(values.type); }, triggerFields: ['type'], }, @@ -372,7 +372,7 @@ const schema: VbenFormSchema[] = [ component: 'Checkbox', dependencies: { show: (values) => { - return !['action'].includes(values.type); + return !['button'].includes(values.type); }, triggerFields: ['type'], }, @@ -402,7 +402,7 @@ const schema: VbenFormSchema[] = [ component: 'Checkbox', dependencies: { show: (values) => { - return !['action', 'link'].includes(values.type); + return !['button', 'link'].includes(values.type); }, triggerFields: ['type'], }, @@ -417,7 +417,7 @@ const schema: VbenFormSchema[] = [ component: 'Checkbox', dependencies: { show: (values) => { - return !['action', 'link'].includes(values.type); + return !['button', 'link'].includes(values.type); }, triggerFields: ['type'], }, From dcccc213ce71afdfc0324c43434d287ae0cedcc0 Mon Sep 17 00:00:00 2001 From: zhang Date: Sun, 8 Jun 2025 17:51:16 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20requestClient.upload=E4=BC=9A?= =?UTF-8?q?=E5=B0=86vbenform=E4=B8=ADvalue=E4=B8=BAundefined=E7=9A=84?= =?UTF-8?q?=E5=80=BC=E8=BD=AC=E4=B8=BA=E5=AD=97=E7=AC=A6=E4=B8=B2undefined?= =?UTF-8?q?=E2=80=99=E6=8F=90=E2=80=A6=20(#6300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: requestClient.upload会将vbenform中value为undefined的值转为字符串undefined’提交给后台保存 * fix: requestClient.upload会将vbenform中value为undefined的值转为字符串'undefined’提交给后台保存 --- .../effects/request/src/request-client/modules/uploader.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/effects/request/src/request-client/modules/uploader.ts b/packages/effects/request/src/request-client/modules/uploader.ts index de251ca8..1353222a 100644 --- a/packages/effects/request/src/request-client/modules/uploader.ts +++ b/packages/effects/request/src/request-client/modules/uploader.ts @@ -1,6 +1,8 @@ import type { RequestClient } from '../request-client'; import type { RequestClientConfig } from '../types'; +import { isUndefined } from '@vben/utils'; + class FileUploader { private client: RequestClient; @@ -18,10 +20,10 @@ class FileUploader { Object.entries(data).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach((item, index) => { - formData.append(`${key}[${index}]`, item); + !isUndefined(item) && formData.append(`${key}[${index}]`, item); }); } else { - formData.append(key, value); + !isUndefined(value) && formData.append(key, value); } }); From b69320c0707e416286660ce2e402713e5e894f96 Mon Sep 17 00:00:00 2001 From: broBinChen <139344558+broBinChen@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:53:29 +0800 Subject: [PATCH 4/6] feat(hooks): support separate enter/leave delays in useHoverToggle (#6325) Co-authored-by: xiaobin --- .../effects/hooks/src/use-hover-toggle.ts | 77 ++++++++++++++++--- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/packages/effects/hooks/src/use-hover-toggle.ts b/packages/effects/hooks/src/use-hover-toggle.ts index 491b1f58..8b1addeb 100644 --- a/packages/effects/hooks/src/use-hover-toggle.ts +++ b/packages/effects/hooks/src/use-hover-toggle.ts @@ -8,19 +8,40 @@ import { isFunction } from '@vben/utils'; import { useElementHover } from '@vueuse/core'; +interface HoverDelayOptions { + /** 鼠标进入延迟时间 */ + enterDelay?: (() => number) | number; + /** 鼠标离开延迟时间 */ + leaveDelay?: (() => number) | number; +} + +const DEFAULT_LEAVE_DELAY = 500; // 鼠标离开延迟时间,默认为 500ms +const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0(立即响应) + /** * 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false * @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true - * @param delay 延迟更新状态的时间 + * @param delay 延迟更新状态的时间,可以是数字或包含进入/离开延迟的配置对象 * @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用 */ export function useHoverToggle( refElement: Arrayable, - delay: (() => number) | number = 500, + delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY, ) { + // 兼容旧版本API + const normalizedOptions: HoverDelayOptions = + typeof delay === 'number' || isFunction(delay) + ? { enterDelay: DEFAULT_ENTER_DELAY, leaveDelay: delay } + : { + enterDelay: DEFAULT_ENTER_DELAY, + leaveDelay: DEFAULT_LEAVE_DELAY, + ...delay, + }; + const isHovers: Array> = []; const value = ref(false); - const timer = ref | undefined>(); + const enterTimer = ref | undefined>(); + const leaveTimer = ref | undefined>(); const refs = Array.isArray(refElement) ? refElement : [refElement]; refs.forEach((refEle) => { const eleRef = computed(() => { @@ -32,15 +53,47 @@ export function useHoverToggle( }); const isOutsideAll = computed(() => isHovers.every((v) => !v.value)); + function clearTimers() { + if (enterTimer.value) { + clearTimeout(enterTimer.value); + enterTimer.value = undefined; + } + if (leaveTimer.value) { + clearTimeout(leaveTimer.value); + leaveTimer.value = undefined; + } + } + function setValueDelay(val: boolean) { - timer.value && clearTimeout(timer.value); - timer.value = setTimeout( - () => { - value.value = val; - timer.value = undefined; - }, - isFunction(delay) ? delay() : delay, - ); + clearTimers(); + + if (val) { + // 鼠标进入 + const enterDelay = normalizedOptions.enterDelay ?? DEFAULT_ENTER_DELAY; + const delayTime = isFunction(enterDelay) ? enterDelay() : enterDelay; + + if (delayTime <= 0) { + value.value = true; + } else { + enterTimer.value = setTimeout(() => { + value.value = true; + enterTimer.value = undefined; + }, delayTime); + } + } else { + // 鼠标离开 + const leaveDelay = normalizedOptions.leaveDelay ?? DEFAULT_LEAVE_DELAY; + const delayTime = isFunction(leaveDelay) ? leaveDelay() : leaveDelay; + + if (delayTime <= 0) { + value.value = false; + } else { + leaveTimer.value = setTimeout(() => { + value.value = false; + leaveTimer.value = undefined; + }, delayTime); + } + } } const watcher = watch( @@ -61,7 +114,7 @@ export function useHoverToggle( }; onUnmounted(() => { - timer.value && clearTimeout(timer.value); + clearTimers(); }); return [value, controller] as [typeof value, typeof controller]; From b015fbc9fcd644ecabcc7b3ffa010b337afb171d Mon Sep 17 00:00:00 2001 From: zyy <532612154@qq.com> Date: Sun, 8 Jun 2025 17:53:55 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20[adapter]=20=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=B1=BB=E5=9E=8B=E6=8A=A5=E9=94=99=20(#6327?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 配置toolbarConfig中的search时会有类型报错 --- apps/web-antd/src/adapter/vxe-table.ts | 4 +++- apps/web-ele/src/adapter/vxe-table.ts | 4 +++- apps/web-naive/src/adapter/vxe-table.ts | 4 +++- playground/src/adapter/vxe-table.ts | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index d296b205..7de2859d 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -1,3 +1,5 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + import { h } from 'vue'; import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; @@ -33,7 +35,7 @@ setupVbenVxeTable({ round: true, showOverflow: true, size: 'small', - }, + } as VxeTableGridOptions, }); // 表格配置项可以用 cellRender: { name: 'CellImage' }, diff --git a/apps/web-ele/src/adapter/vxe-table.ts b/apps/web-ele/src/adapter/vxe-table.ts index 44b31eae..40b8179d 100644 --- a/apps/web-ele/src/adapter/vxe-table.ts +++ b/apps/web-ele/src/adapter/vxe-table.ts @@ -1,3 +1,5 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + import { h } from 'vue'; import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; @@ -33,7 +35,7 @@ setupVbenVxeTable({ round: true, showOverflow: true, size: 'small', - }, + } as VxeTableGridOptions, }); // 表格配置项可以用 cellRender: { name: 'CellImage' }, diff --git a/apps/web-naive/src/adapter/vxe-table.ts b/apps/web-naive/src/adapter/vxe-table.ts index 081cfb29..3bad067c 100644 --- a/apps/web-naive/src/adapter/vxe-table.ts +++ b/apps/web-naive/src/adapter/vxe-table.ts @@ -1,3 +1,5 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + import { h } from 'vue'; import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; @@ -33,7 +35,7 @@ setupVbenVxeTable({ round: true, showOverflow: true, size: 'small', - }, + } as VxeTableGridOptions, }); // 表格配置项可以用 cellRender: { name: 'CellImage' }, diff --git a/playground/src/adapter/vxe-table.ts b/playground/src/adapter/vxe-table.ts index 06af5e05..24dfd4cd 100644 --- a/playground/src/adapter/vxe-table.ts +++ b/playground/src/adapter/vxe-table.ts @@ -1,3 +1,4 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; import type { Recordable } from '@vben/types'; import type { ComponentType } from './component'; @@ -47,7 +48,7 @@ setupVbenVxeTable({ round: true, showOverflow: true, size: 'small', - }, + } as VxeTableGridOptions, }); /** From 8ba7bdf2bdc3dc23ed174b8cbac34b1b1b429208 Mon Sep 17 00:00:00 2001 From: XiaoHetitu <38452227+XiaoHeitu@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:56:24 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix(button):=20=E4=B8=BA=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0type=E5=B1=9E=E6=80=A7=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=E6=8F=90=E4=BA=A4=E6=84=8F=E5=A4=96=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E8=A1=A8=E5=8D=95=E9=AA=8C=E8=AF=81=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=20(#6340)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在按钮组件中,按钮元素缺少type="button"属性可能导致在表单中意外提交。添加此属性以确保按钮行为符合预期。 Co-authored-by: yuanwj --- .../shadcn-ui/src/components/button/check-button-group.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/button/check-button-group.vue b/packages/@core/ui-kit/shadcn-ui/src/components/button/check-button-group.vue index fd87396e..4172d82b 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/button/check-button-group.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/button/check-button-group.vue @@ -122,6 +122,7 @@ async function onBtnClick(value: ValueType) { v-bind="btnDefaultProps" :variant="innerValue.includes(btn.value) ? 'default' : 'outline'" @click="onBtnClick(btn.value)" + type="button" >