Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into antdv-next

This commit is contained in:
dap
2026-01-26 18:50:23 +08:00
68 changed files with 1850 additions and 286 deletions

View File

@@ -18,9 +18,9 @@
font-size: var(--font-size-base, 16px);
font-variation-settings: normal;
font-synthesis-weight: none;
line-height: 1.15;
text-size-adjust: 100%;
font-synthesis-weight: none;
scroll-behavior: smooth;
text-rendering: optimizelegibility;
-webkit-tap-highlight-color: transparent;

View File

@@ -93,9 +93,6 @@
},
"devDependencies": {
"@types/lodash.clonedeep": "catalog:",
"@types/lodash.get": "catalog:",
"@types/lodash.isequal": "catalog:",
"@types/lodash.set": "catalog:",
"@types/nprogress": "catalog:"
}
}

View File

@@ -116,11 +116,11 @@ describe('getElementVisibleRect', () => {
} as HTMLElement;
expect(getElementVisibleRect(element)).toEqual({
bottom: 800,
bottom: 0,
height: 0,
left: 1100,
right: 1000,
top: 900,
left: 0,
right: 0,
top: 0,
width: 0,
});
});

View File

@@ -41,6 +41,18 @@ export function getElementVisibleRect(
const left = Math.max(rect.left, 0);
const right = Math.min(rect.right, viewWidth);
// 如果元素完全不可见,则返回一个空的矩形
if (top >= viewHeight || bottom <= 0 || left >= viewWidth || right <= 0) {
return {
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
};
}
return {
bottom,
height: Math.max(0, bottom - top),

View File

@@ -29,9 +29,9 @@ describe('useSortable', () => {
await initializeSortable();
// Import sortablejs to access the mocked create function
const Sortable = await import(
'sortablejs/modular/sortable.complete.esm.js'
);
const Sortable =
// @ts-expect-error - This is a dynamic import
await import('sortablejs/modular/sortable.complete.esm.js');
// Verify that Sortable.create was called with the correct parameters
expect(Sortable.default.create).toHaveBeenCalledTimes(1);

View File

@@ -63,8 +63,9 @@ class PreferenceManager {
/**
* 初始化偏好设置
* @param namespace - 命名空间,用于隔离不同应用的配置
* @param overrides - 要覆盖的偏好设
* @param options - 初始化配置
* @param options.namespace - 命名空间,用于隔离不同应用的配
* @param options.overrides - 要覆盖的偏好设置
*/
initPreferences = async ({ namespace, overrides }: InitialOptions) => {
// 防止重复初始化

View File

@@ -135,7 +135,7 @@ function usePreferences() {
});
/**
* @zh_CN 登录注册页面布局是否为
* @zh_CN 登录注册页面布局是否为
*/
const authPanelRight = computed(() => {
return appPreferences.value.authPageLayout === 'panel-right';

View File

@@ -82,10 +82,8 @@ export default function useDependencies(
// 2. 判断show如果show为false则隐藏
if (isFunction(show)) {
isShow.value = !!(await show(formValues, formApi));
if (!isShow.value) return;
} else if (isBoolean(show)) {
isShow.value = show;
if (!isShow.value) return;
}
if (isFunction(componentProps)) {

View File

@@ -53,7 +53,11 @@ const wrapperClass = computed(() => {
provideFormRenderProps(props);
const { isCalculated, keepFormItemIndex, wrapperRef } = useExpandable(props);
const {
isCalculated,
keepFormItemIndex,
wrapperRef: _wrapperRef,
} = useExpandable(props);
const shapes = computed(() => {
const resultShapes: FormShape[] = [];
@@ -170,7 +174,7 @@ const computedSchema = computed(
<template>
<component :is="formComponent" v-bind="formComponentProps">
<div ref="wrapperRef" :class="wrapperClass">
<div ref="_wrapperRef" :class="wrapperClass">
<template v-for="cSchema in computedSchema" :key="cSchema.fieldName">
<!-- <div v-if="$slots[cSchema.fieldName]" :class="cSchema.formItemClass">
<slot :definition="cSchema" :name="cSchema.fieldName"> </slot>

View File

@@ -350,9 +350,9 @@ export interface ActionButtonOptions extends VbenButtonProps {
export interface VbenFormProps<
T extends BaseFormComponentType = BaseFormComponentType,
> extends Omit<
FormRenderProps<T>,
'componentBindEventMap' | 'componentMap' | 'form'
> {
FormRenderProps<T>,
'componentBindEventMap' | 'componentMap' | 'form'
> {
/**
* 操作按钮是否反转(提交按钮前置)
*/

View File

@@ -26,7 +26,8 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {});
const { contentElement, overlayStyle } = useLayoutContentStyle();
const { contentElement: _contentElement, overlayStyle } =
useLayoutContentStyle();
const style = computed((): CSSProperties => {
const {
@@ -55,7 +56,11 @@ const style = computed((): CSSProperties => {
</script>
<template>
<main ref="contentElement" :style="style" class="relative bg-background-deep">
<main
ref="_contentElement"
:style="style"
class="relative bg-background-deep"
>
<Slot :style="overlayStyle">
<slot name="overlay"></slot>
</Slot>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { computed, shallowRef, useSlots, watchEffect } from 'vue';
import { computed, useSlots, watchEffect } from 'vue';
import { VbenScrollbar } from '@vben-core/shadcn-ui';
@@ -114,7 +114,7 @@ const extraVisible = defineModel<boolean>('extraVisible');
const isLocked = useScrollLock(document.body);
const slots = useSlots();
const asideRef = shallowRef<HTMLDivElement | null>();
// const asideRef = shallowRef<HTMLDivElement | null>();
const hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true));
@@ -290,7 +290,6 @@ function handleMouseleave() {
/>
<div
v-if="isSidebarMixed"
ref="asideRef"
:class="{
'border-l': extraVisible,
}"

View File

@@ -403,13 +403,10 @@ watch(
);
{
const mouseMove = () => {
mouseY.value > headerWrapperHeight.value
? (headerIsHidden.value = true)
: (headerIsHidden.value = false);
};
const HEADER_TRIGGER_DISTANCE = 12;
watch(
[() => props.headerMode, () => mouseY.value],
[() => props.headerMode, () => mouseY.value, () => headerIsHidden.value],
() => {
if (!isHeaderAutoMode.value || isMixedNav.value || isFullContent.value) {
if (props.headerMode !== 'auto-scroll') {
@@ -417,8 +414,12 @@ watch(
}
return;
}
headerIsHidden.value = true;
mouseMove();
const isInTriggerZone = mouseY.value <= HEADER_TRIGGER_DISTANCE;
const isInHeaderZone =
!headerIsHidden.value && mouseY.value <= headerWrapperHeight.value;
headerIsHidden.value = !(isInTriggerZone || isInHeaderZone);
},
{
immediate: true,

View File

@@ -351,14 +351,14 @@ function getActivePaths() {
role="menu"
>
<template v-if="mode === 'horizontal' && getSlot.showSlotMore">
<template v-for="item in getSlot.slotDefault" :key="item.key">
<template v-for="(item, index) in getSlot.slotDefault" :key="index">
<component :is="item" />
</template>
<SubMenu is-sub-menu-more path="sub-menu-more">
<template #title>
<Ellipsis class="size-4" />
</template>
<template v-for="item in getSlot.slotMore" :key="item.key">
<template v-for="(item, index) in getSlot.slotMore" :key="index">
<component :is="item" />
</template>
</SubMenu>

View File

@@ -54,7 +54,7 @@ const components = globalShareState.getComponents();
const id = useId();
provide('DISMISSABLE_DRAWER_ID', id);
const wrapperRef = ref<HTMLElement>();
// const wrapperRef = ref<HTMLElement>();
const { $t } = useSimpleLocale();
const { isMobile } = useIsMobile();
@@ -282,7 +282,6 @@ const getForceMount = computed(() => {
</VisuallyHidden>
</template>
<div
ref="wrapperRef"
:class="
cn('relative flex-1 overflow-y-auto p-3', contentClass, {
'pointer-events-none': showLoading || submitting,

View File

@@ -50,10 +50,10 @@ const props = withDefaults(defineProps<Props>(), {
const components = globalShareState.getComponents();
const contentRef = ref();
const wrapperRef = ref<HTMLElement>();
// const wrapperRef = ref<HTMLElement>();
const dialogRef = ref();
const headerRef = ref();
const footerRef = ref();
// const footerRef = ref();
const id = useId();
@@ -306,7 +306,6 @@ function handleClosed() {
</VisuallyHidden>
</DialogHeader>
<div
ref="wrapperRef"
:class="
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
'pointer-events-none': showLoading || submitting,
@@ -327,7 +326,6 @@ function handleClosed() {
<DialogFooter
v-if="showFooter"
ref="footerRef"
:class="
cn(
'flex-row items-center justify-end p-2',

View File

@@ -41,6 +41,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
// 不能用 Object.assign,会丢失 api 的原型函数
Object.setPrototypeOf(extendedApi, api);
},
consumed: false,
options,
async reCreateModal() {
isModalReady.value = false;
@@ -73,7 +74,13 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
return [Modal, extendedApi as ExtendedModalApi] as const;
}
const injectData = inject<any>(USER_MODAL_INJECT_KEY, {});
let injectData = inject<any>(USER_MODAL_INJECT_KEY, {});
// 这个数据已经被使用了说明这个弹窗是嵌套的弹窗不应该merge上层的配置
if (injectData.consumed) {
injectData = {};
} else {
injectData.consumed = true;
}
const mergedOptions = {
...DEFAULT_MODAL_PROPS,

View File

@@ -27,8 +27,10 @@ export type CustomRenderType = (() => Component | string) | string;
export type ValueType = boolean | number | string;
export interface VbenButtonGroupProps
extends Pick<VbenButtonProps, 'disabled'> {
export interface VbenButtonGroupProps extends Pick<
VbenButtonProps,
'disabled'
> {
/** 单选模式下允许清除选中 */
allowClear?: boolean;
/** 值改变前的回调 */

View File

@@ -42,14 +42,17 @@ export default defineComponent({
return props.content;
}
}
return h(props.content as never, {
...attrs,
props: {
...props,
return h(
props.content as never,
{
...attrs,
props: {
...props,
...attrs,
},
},
slots,
});
);
};
},
});

View File

@@ -32,19 +32,19 @@ const props = withDefaults(defineProps<Props>(), {
// const startTime = ref(0);
const showSpinner = ref(false);
const renderSpinner = ref(false);
const timer = ref<ReturnType<typeof setTimeout>>();
let timer: ReturnType<typeof setTimeout> | undefined;
watch(
() => props.spinning,
(show) => {
if (!show) {
showSpinner.value = false;
clearTimeout(timer.value);
timer && clearTimeout(timer);
return;
}
// startTime.value = performance.now();
timer.value = setTimeout(() => {
timer = setTimeout(() => {
// const loadingTime = performance.now() - startTime.value;
showSpinner.value = true;

View File

@@ -3,7 +3,7 @@ import type { TabDefinition } from '@vben-core/typings';
import type { TabConfig, TabsProps } from '../../types';
import { computed, ref } from 'vue';
import { computed } from 'vue';
import { Pin, X } from '@vben-core/icons';
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
@@ -28,8 +28,8 @@ const emit = defineEmits<{
}>();
const active = defineModel<string>('active');
const contentRef = ref();
const tabRef = ref();
// const contentRef = ref();
// const tabRef = ref();
const style = computed(() => {
const { gap } = props;
@@ -73,7 +73,6 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
<template>
<div
ref="contentRef"
:class="contentClass"
:style="style"
class="tabs-chrome !flex h-full w-max overflow-y-hidden pr-6"
@@ -82,7 +81,6 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
<div
v-for="(tab, i) in tabsView"
:key="tab.key"
ref="tabRef"
:class="[
{
'is-active': tab.key === active,

View File

@@ -29,7 +29,7 @@ const forward = useForwardPropsEmits(props, emit);
const {
handleScrollAt,
handleWheel,
scrollbarRef,
scrollbarRef: _scrollbarRef,
scrollDirection,
scrollIsAtLeft,
scrollIsAtRight,
@@ -69,7 +69,7 @@ useTabsDrag(props, emit);
class="size-full flex-1 overflow-hidden"
>
<VbenScrollbar
ref="scrollbarRef"
ref="_scrollbarRef"
:shadow-bottom="false"
:shadow-top="false"
class="h-full"