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" >
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]; diff --git a/packages/effects/plugins/src/vxe-table/api.ts b/packages/effects/plugins/src/vxe-table/api.ts index 133dbb33..2b60d602 100644 --- a/packages/effects/plugins/src/vxe-table/api.ts +++ b/packages/effects/plugins/src/vxe-table/api.ts @@ -1,8 +1,11 @@ -import type { ExtendedFormApi } from '@vben-core/form-ui'; import type { VxeGridInstance } from 'vxe-table'; +import type { ExtendedFormApi } from '@vben-core/form-ui'; + import type { VxeGridProps } from './types'; +import { toRaw } from 'vue'; + import { Store } from '@vben-core/shared/store'; import { bindMethods, @@ -11,7 +14,6 @@ import { mergeWithArrayOverride, StateHandler, } from '@vben-core/shared/utils'; -import { toRaw } from 'vue'; function getDefaultState(): VxeGridProps { return { @@ -24,18 +26,18 @@ function getDefaultState(): VxeGridProps { }; } -export class VxeGridApi { - private isMounted = false; - - private stateHandler: StateHandler; +export class VxeGridApi = any> { public formApi = {} as ExtendedFormApi; // private prevState: null | VxeGridProps = null; - public grid = {} as VxeGridInstance; + public grid = {} as VxeGridInstance; + public state: null | VxeGridProps = null; - public state: null | VxeGridProps = null; + public store: Store>; - public store: Store; + private isMounted = false; + + private stateHandler: StateHandler; constructor(options: VxeGridProps = {}) { const storeState = { ...options }; @@ -97,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 09c2a8ee..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?: Partial; + 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/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); } }); diff --git a/playground/src/adapter/vxe-table.ts b/playground/src/adapter/vxe-table.ts index 71a0ecb4..24dfd4cd 100644 --- a/playground/src/adapter/vxe-table.ts +++ b/playground/src/adapter/vxe-table.ts @@ -1,10 +1,16 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; 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'; @@ -42,7 +48,7 @@ setupVbenVxeTable({ round: true, showOverflow: true, size: 'small', - }, + } as VxeTableGridOptions, }); /** @@ -277,7 +283,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); 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'], },