Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow
This commit is contained in:
@@ -80,6 +80,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
||||
"enable": true,
|
||||
"height": 38,
|
||||
"keepAlive": true,
|
||||
"middleClickToClose": false,
|
||||
"persist": true,
|
||||
"showIcon": true,
|
||||
"showMaximize": true,
|
||||
|
@@ -80,6 +80,7 @@ const defaultPreferences: Preferences = {
|
||||
enable: true,
|
||||
height: 38,
|
||||
keepAlive: true,
|
||||
middleClickToClose: false,
|
||||
persist: true,
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
|
@@ -168,6 +168,8 @@ interface TabbarPreferences {
|
||||
height: number;
|
||||
/** 开启标签页缓存功能 */
|
||||
keepAlive: boolean;
|
||||
/** 是否点击中键时关闭标签 */
|
||||
middleClickToClose: boolean;
|
||||
/** 是否持久化标签 */
|
||||
persist: boolean;
|
||||
/** 是否开启多标签页图标 */
|
||||
|
@@ -4,6 +4,12 @@ import { Store } from '@vben-core/shared/store';
|
||||
import { bindMethods, isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
export class DrawerApi {
|
||||
// 共享数据
|
||||
public sharedData: Record<'payload', any> = {
|
||||
payload: {},
|
||||
};
|
||||
public store: Store<DrawerState>;
|
||||
|
||||
private api: Pick<
|
||||
DrawerApiOptions,
|
||||
| 'onBeforeClose'
|
||||
@@ -13,16 +19,10 @@ export class DrawerApi {
|
||||
| 'onOpenChange'
|
||||
| 'onOpened'
|
||||
>;
|
||||
|
||||
// private prevState!: DrawerState;
|
||||
private state!: DrawerState;
|
||||
|
||||
// 共享数据
|
||||
public sharedData: Record<'payload', any> = {
|
||||
payload: {},
|
||||
};
|
||||
|
||||
public store: Store<DrawerState>;
|
||||
|
||||
constructor(options: DrawerApiOptions = {}) {
|
||||
const {
|
||||
connectedComponent: _,
|
||||
@@ -149,6 +149,7 @@ export class DrawerApi {
|
||||
|
||||
setData<T>(payload: T) {
|
||||
this.sharedData.payload = payload;
|
||||
return this;
|
||||
}
|
||||
|
||||
setState(
|
||||
@@ -161,5 +162,6 @@ export class DrawerApi {
|
||||
} else {
|
||||
this.store.setState((prev) => ({ ...prev, ...stateOrFn }));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type { Component, Ref } from 'vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import type { DrawerApi } from './drawer-api';
|
||||
|
||||
export type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';
|
||||
@@ -84,12 +85,16 @@ export interface DrawerProps {
|
||||
* 是否自动聚焦
|
||||
*/
|
||||
openAutoFocus?: boolean;
|
||||
/**
|
||||
* 弹窗遮罩模糊效果
|
||||
*/
|
||||
overlayBlur?: number;
|
||||
|
||||
/**
|
||||
* 抽屉位置
|
||||
* @default right
|
||||
*/
|
||||
placement?: DrawerPlacement;
|
||||
|
||||
/**
|
||||
* 是否显示取消按钮
|
||||
* @default true
|
||||
@@ -123,11 +128,11 @@ export interface DrawerState extends DrawerProps {
|
||||
sharedData?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type ExtendedDrawerApi = {
|
||||
export type ExtendedDrawerApi = DrawerApi & {
|
||||
useStore: <T = NoInfer<DrawerState>>(
|
||||
selector?: (state: NoInfer<DrawerState>) => T,
|
||||
) => Readonly<Ref<T>>;
|
||||
} & DrawerApi;
|
||||
};
|
||||
|
||||
export interface DrawerApiOptions extends DrawerState {
|
||||
/**
|
||||
|
@@ -68,6 +68,7 @@ const {
|
||||
loading: showLoading,
|
||||
modal,
|
||||
openAutoFocus,
|
||||
overlayBlur,
|
||||
placement,
|
||||
showCancelButton,
|
||||
showConfirmButton,
|
||||
@@ -140,6 +141,7 @@ const getAppendTo = computed(() => {
|
||||
:open="state?.isOpen"
|
||||
:side="placement"
|
||||
:z-index="zIndex"
|
||||
:overlay-blur="overlayBlur"
|
||||
@close-auto-focus="handleFocusOutside"
|
||||
@closed="() => drawerApi?.onClosed()"
|
||||
@escape-key-down="escapeKeyDown"
|
||||
|
@@ -4,6 +4,12 @@ import { Store } from '@vben-core/shared/store';
|
||||
import { bindMethods, isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
export class ModalApi {
|
||||
// 共享数据
|
||||
public sharedData: Record<'payload', any> = {
|
||||
payload: {},
|
||||
};
|
||||
public store: Store<ModalState>;
|
||||
|
||||
private api: Pick<
|
||||
ModalApiOptions,
|
||||
| 'onBeforeClose'
|
||||
@@ -13,16 +19,10 @@ export class ModalApi {
|
||||
| 'onOpenChange'
|
||||
| 'onOpened'
|
||||
>;
|
||||
|
||||
// private prevState!: ModalState;
|
||||
private state!: ModalState;
|
||||
|
||||
// 共享数据
|
||||
public sharedData: Record<'payload', any> = {
|
||||
payload: {},
|
||||
};
|
||||
|
||||
public store: Store<ModalState>;
|
||||
|
||||
constructor(options: ModalApiOptions = {}) {
|
||||
const {
|
||||
connectedComponent: _,
|
||||
@@ -159,6 +159,7 @@ export class ModalApi {
|
||||
|
||||
setData<T>(payload: T) {
|
||||
this.sharedData.payload = payload;
|
||||
return this;
|
||||
}
|
||||
|
||||
setState(
|
||||
@@ -171,5 +172,6 @@ export class ModalApi {
|
||||
} else {
|
||||
this.store.setState((prev) => ({ ...prev, ...stateOrFn }));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@@ -99,6 +99,10 @@ export interface ModalProps {
|
||||
* 是否自动聚焦
|
||||
*/
|
||||
openAutoFocus?: boolean;
|
||||
/**
|
||||
* 弹窗遮罩模糊效果
|
||||
*/
|
||||
overlayBlur?: number;
|
||||
/**
|
||||
* 是否显示取消按钮
|
||||
* @default true
|
||||
@@ -132,11 +136,11 @@ export interface ModalState extends ModalProps {
|
||||
sharedData?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type ExtendedModalApi = {
|
||||
export type ExtendedModalApi = ModalApi & {
|
||||
useStore: <T = NoInfer<ModalState>>(
|
||||
selector?: (state: NoInfer<ModalState>) => T,
|
||||
) => Readonly<Ref<T>>;
|
||||
} & ModalApi;
|
||||
};
|
||||
|
||||
export interface ModalApiOptions extends ModalState {
|
||||
/**
|
||||
|
@@ -77,6 +77,7 @@ const {
|
||||
loading: showLoading,
|
||||
modal,
|
||||
openAutoFocus,
|
||||
overlayBlur,
|
||||
showCancelButton,
|
||||
showConfirmButton,
|
||||
title,
|
||||
@@ -196,6 +197,7 @@ const getAppendTo = computed(() => {
|
||||
:open="state?.isOpen"
|
||||
:show-close="closable"
|
||||
:z-index="zIndex"
|
||||
:overlay-blur="overlayBlur"
|
||||
close-class="top-3"
|
||||
@close-auto-focus="handleFocusOutside"
|
||||
@closed="() => modalApi?.onClosed()"
|
||||
|
@@ -1,8 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type { DialogContentEmits, DialogContentProps } from 'radix-vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { X } from 'lucide-vue-next';
|
||||
import {
|
||||
DialogClose,
|
||||
@@ -10,26 +14,26 @@ import {
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import DialogOverlay from './DialogOverlay.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
{
|
||||
DialogContentProps & {
|
||||
appendTo?: HTMLElement | string;
|
||||
class?: ClassType;
|
||||
closeClass?: ClassType;
|
||||
modal?: boolean;
|
||||
open?: boolean;
|
||||
overlayBlur?: number;
|
||||
showClose?: boolean;
|
||||
zIndex?: number;
|
||||
} & DialogContentProps
|
||||
}
|
||||
>(),
|
||||
{ appendTo: 'body', showClose: true, zIndex: 1000 },
|
||||
);
|
||||
const emits = defineEmits<
|
||||
{ close: []; closed: []; opened: [] } & DialogContentEmits
|
||||
DialogContentEmits & { close: []; closed: []; opened: [] }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
@@ -79,7 +83,12 @@ defineExpose({
|
||||
<Transition name="fade">
|
||||
<DialogOverlay
|
||||
v-if="open && modal"
|
||||
:style="{ zIndex, position }"
|
||||
:style="{
|
||||
zIndex,
|
||||
position,
|
||||
backdropFilter:
|
||||
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||
}"
|
||||
@click="() => emits('close')"
|
||||
/>
|
||||
</Transition>
|
||||
|
@@ -3,10 +3,12 @@ import type { DialogContentEmits, DialogContentProps } from 'radix-vue';
|
||||
|
||||
import type { SheetVariants } from './sheet';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
import { DialogContent, DialogPortal, useForwardPropsEmits } from 'radix-vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { DialogContent, DialogPortal, useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
import { sheetVariants } from './sheet';
|
||||
import SheetOverlay from './SheetOverlay.vue';
|
||||
|
||||
@@ -15,6 +17,7 @@ interface SheetContentProps extends DialogContentProps {
|
||||
class?: any;
|
||||
modal?: boolean;
|
||||
open?: boolean;
|
||||
overlayBlur?: number;
|
||||
side?: SheetVariants['side'];
|
||||
zIndex?: number;
|
||||
}
|
||||
@@ -29,7 +32,7 @@ const props = withDefaults(defineProps<SheetContentProps>(), {
|
||||
});
|
||||
|
||||
const emits = defineEmits<
|
||||
{ close: []; closed: []; opened: [] } & DialogContentEmits
|
||||
DialogContentEmits & { close: []; closed: []; opened: [] }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
@@ -73,12 +76,23 @@ function onAnimationEnd(event: AnimationEvent) {
|
||||
<template>
|
||||
<DialogPortal :to="appendTo">
|
||||
<Transition name="fade">
|
||||
<SheetOverlay v-if="open && modal" :style="{ zIndex, position }" />
|
||||
<SheetOverlay
|
||||
v-if="open && modal"
|
||||
:style="{
|
||||
zIndex,
|
||||
position,
|
||||
backdropFilter:
|
||||
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||
}"
|
||||
/>
|
||||
</Transition>
|
||||
<DialogContent
|
||||
ref="contentRef"
|
||||
:class="cn(sheetVariants({ side }), props.class)"
|
||||
:style="{ zIndex, position }"
|
||||
:style="{
|
||||
zIndex,
|
||||
position,
|
||||
}"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
>
|
||||
|
@@ -56,6 +56,20 @@ const tabsView = computed(() => {
|
||||
} as TabConfig;
|
||||
});
|
||||
});
|
||||
|
||||
function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
if (
|
||||
e.button === 1 &&
|
||||
tab.closable &&
|
||||
!tab.affixTab &&
|
||||
tabsView.value.length > 1 &&
|
||||
props.middleClickToClose
|
||||
) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
emit('close', tab.key);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -82,6 +96,7 @@ const tabsView = computed(() => {
|
||||
class="tabs-chrome__item draggable translate-all group relative -mr-3 flex h-full select-none items-center"
|
||||
data-tab-item="true"
|
||||
@click="active = tab.key"
|
||||
@mousedown="onMouseDown($event, tab)"
|
||||
>
|
||||
<VbenContextMenu
|
||||
:handler-data="tab"
|
||||
|
@@ -62,6 +62,20 @@ const tabsView = computed(() => {
|
||||
} as TabConfig;
|
||||
});
|
||||
});
|
||||
|
||||
function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
||||
if (
|
||||
e.button === 1 &&
|
||||
tab.closable &&
|
||||
!tab.affixTab &&
|
||||
tabsView.value.length > 1 &&
|
||||
props.middleClickToClose
|
||||
) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
emit('close', tab.key);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -85,6 +99,7 @@ const tabsView = computed(() => {
|
||||
class="tab-item [&:not(.is-active)]:hover:bg-accent translate-all group relative flex cursor-pointer select-none"
|
||||
data-tab-item="true"
|
||||
@click="active = tab.key"
|
||||
@mousedown="onMouseDown($event, tab)"
|
||||
>
|
||||
<VbenContextMenu
|
||||
:handler-data="tab"
|
||||
|
@@ -33,6 +33,11 @@ export interface TabsProps {
|
||||
* 仅限 tabs-chrome
|
||||
*/
|
||||
maxWidth?: number;
|
||||
/**
|
||||
* @zh_CN 点击中键时关闭Tab
|
||||
*/
|
||||
middleClickToClose?: boolean;
|
||||
|
||||
/**
|
||||
* @zh_CN tab最小宽度
|
||||
* 仅限 tabs-chrome
|
||||
@@ -43,11 +48,11 @@ export interface TabsProps {
|
||||
* @zh_CN 是否显示图标
|
||||
*/
|
||||
showIcon?: boolean;
|
||||
|
||||
/**
|
||||
* @zh_CN 标签页风格
|
||||
*/
|
||||
styleType?: TabsStyleType;
|
||||
|
||||
/**
|
||||
* @zh_CN 选项卡数据
|
||||
*/
|
||||
|
Reference in New Issue
Block a user