Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin
This commit is contained in:
@@ -77,7 +77,7 @@
|
||||
/* ============= custom ============= */
|
||||
|
||||
/* 遮罩颜色 */
|
||||
--overlay: 0deg 0% 0% / 30%;
|
||||
--overlay: 0 0% 0% / 30%;
|
||||
|
||||
/* 基本文字大小 */
|
||||
--font-size-base: 16px;
|
||||
|
@@ -38,7 +38,6 @@ export class DrawerApi {
|
||||
isOpen: false,
|
||||
loading: false,
|
||||
modal: true,
|
||||
sharedData: {},
|
||||
title: '',
|
||||
};
|
||||
|
||||
@@ -93,7 +92,11 @@ export class DrawerApi {
|
||||
* 取消操作
|
||||
*/
|
||||
onCancel() {
|
||||
this.api.onCancel?.();
|
||||
if (this.api.onCancel) {
|
||||
this.api.onCancel?.();
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DrawerProps, ExtendedDrawerApi } from './drawer';
|
||||
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { useIsMobile, usePriorityValue } from '@vben-core/composables';
|
||||
import { Info, X } from '@vben-core/icons';
|
||||
import {
|
||||
@@ -31,6 +33,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
drawerApi: undefined,
|
||||
});
|
||||
|
||||
const wrapperRef = ref<HTMLElement>();
|
||||
|
||||
const { isMobile } = useIsMobile();
|
||||
const state = props.drawerApi?.useStore?.();
|
||||
|
||||
@@ -47,6 +51,18 @@ const confirmText = usePriorityValue('confirmText', props, state);
|
||||
const closeOnClickModal = usePriorityValue('closeOnClickModal', props, state);
|
||||
const closeOnPressEscape = usePriorityValue('closeOnPressEscape', props, state);
|
||||
|
||||
watch(
|
||||
() => showLoading.value,
|
||||
(v) => {
|
||||
if (v && wrapperRef.value) {
|
||||
wrapperRef.value.scrollTo({
|
||||
// behavior: 'smooth',
|
||||
top: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function interactOutside(e: Event) {
|
||||
if (!closeOnClickModal.value) {
|
||||
e.preventDefault();
|
||||
@@ -129,9 +145,10 @@ function pointerDownOutside(e: Event) {
|
||||
</SheetHeader>
|
||||
|
||||
<div
|
||||
ref="wrapperRef"
|
||||
:class="
|
||||
cn('relative flex-1 p-3', contentClass, {
|
||||
'overflow-y-auto': !showLoading,
|
||||
cn('relative flex-1 overflow-y-auto p-3', contentClass, {
|
||||
'overflow-hidden': showLoading,
|
||||
})
|
||||
"
|
||||
>
|
||||
|
@@ -38,10 +38,10 @@ export class ModalApi {
|
||||
footer: true,
|
||||
fullscreen: false,
|
||||
fullscreenButton: true,
|
||||
header: true,
|
||||
isOpen: false,
|
||||
loading: false,
|
||||
modal: true,
|
||||
sharedData: {},
|
||||
title: '',
|
||||
};
|
||||
|
||||
|
@@ -60,12 +60,16 @@ export interface ModalProps {
|
||||
* @default true
|
||||
*/
|
||||
fullscreenButton?: boolean;
|
||||
/**
|
||||
* 是否显示顶栏
|
||||
* @default true
|
||||
*/
|
||||
header?: boolean;
|
||||
/**
|
||||
* 弹窗是否显示
|
||||
* @default false
|
||||
*/
|
||||
loading?: boolean;
|
||||
|
||||
/**
|
||||
* 是否显示遮罩
|
||||
* @default true
|
||||
|
@@ -12,7 +12,6 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
VbenButton,
|
||||
VbenIconButton,
|
||||
VbenLoading,
|
||||
@@ -21,8 +20,6 @@ import {
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
// import { useElementSize } from '@vueuse/core';
|
||||
|
||||
import { useModalDraggable } from './use-modal-draggable';
|
||||
|
||||
interface Props extends ModalProps {
|
||||
@@ -42,15 +39,15 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const contentRef = ref();
|
||||
const wrapperRef = ref<HTMLElement>();
|
||||
const dialogRef = ref();
|
||||
const headerRef = ref();
|
||||
const footerRef = ref();
|
||||
|
||||
const { isMobile } = useIsMobile();
|
||||
// const { height: headerHeight } = useElementSize(headerRef);
|
||||
// const { height: footerHeight } = useElementSize(footerRef);
|
||||
const state = props.modalApi?.useStore?.();
|
||||
|
||||
const header = usePriorityValue('header', props, state);
|
||||
const title = usePriorityValue('title', props, state);
|
||||
const fullscreen = usePriorityValue('fullscreen', props, state);
|
||||
const description = usePriorityValue('description', props, state);
|
||||
@@ -68,35 +65,43 @@ const fullscreenButton = usePriorityValue('fullscreenButton', props, state);
|
||||
const closeOnClickModal = usePriorityValue('closeOnClickModal', props, state);
|
||||
const closeOnPressEscape = usePriorityValue('closeOnPressEscape', props, state);
|
||||
|
||||
const shouldFullscreen = computed(() => fullscreen.value || isMobile.value);
|
||||
const shouldDraggable = computed(
|
||||
() => draggable.value && !shouldFullscreen.value,
|
||||
const shouldFullscreen = computed(
|
||||
() => (fullscreen.value && header.value) || isMobile.value,
|
||||
);
|
||||
|
||||
const { dragging } = useModalDraggable(dialogRef, headerRef, shouldDraggable);
|
||||
const shouldDraggable = computed(
|
||||
() => draggable.value && !shouldFullscreen.value && header.value,
|
||||
);
|
||||
|
||||
// const loadingStyle = computed(() => {
|
||||
// // py-5 4px*5*2
|
||||
// const headerPadding = 40;
|
||||
// // p-2 4px*2*2
|
||||
// const footerPadding = 16;
|
||||
|
||||
// return {
|
||||
// bottom: `${footerHeight.value + footerPadding}px`,
|
||||
// height: `calc(100% - ${footerHeight.value + headerHeight.value + headerPadding + footerPadding}px)`,
|
||||
// top: `${headerHeight.value + headerPadding}px`,
|
||||
// };
|
||||
// });
|
||||
const { dragging, transform } = useModalDraggable(
|
||||
dialogRef,
|
||||
headerRef,
|
||||
shouldDraggable,
|
||||
);
|
||||
|
||||
watch(
|
||||
() => state?.value?.isOpen,
|
||||
async (v) => {
|
||||
if (v) {
|
||||
await nextTick();
|
||||
if (contentRef.value) {
|
||||
const innerContentRef = contentRef.value.getContentRef();
|
||||
dialogRef.value = innerContentRef.$el;
|
||||
}
|
||||
if (!contentRef.value) return;
|
||||
const innerContentRef = contentRef.value.getContentRef();
|
||||
dialogRef.value = innerContentRef.$el;
|
||||
// reopen modal reassign value
|
||||
const { offsetX, offsetY } = transform;
|
||||
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => showLoading.value,
|
||||
(v) => {
|
||||
if (v && wrapperRef.value) {
|
||||
wrapperRef.value.scrollTo({
|
||||
// behavior: 'smooth',
|
||||
top: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -134,15 +139,11 @@ function pointerDownOutside(e: Event) {
|
||||
:open="state?.isOpen"
|
||||
@update:open="() => modalApi?.close()"
|
||||
>
|
||||
<DialogTrigger v-if="$slots.trigger" as-child>
|
||||
<slot name="trigger"> </slot>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
ref="contentRef"
|
||||
:class="
|
||||
cn(
|
||||
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0',
|
||||
'border-border left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col border p-0',
|
||||
props.class,
|
||||
{
|
||||
'left-0 top-0 size-full max-h-full !translate-x-0 !translate-y-0':
|
||||
@@ -162,8 +163,9 @@ function pointerDownOutside(e: Event) {
|
||||
ref="headerRef"
|
||||
:class="
|
||||
cn(
|
||||
'border-b px-6 py-5',
|
||||
'border-b px-5 py-4',
|
||||
{
|
||||
hidden: !header,
|
||||
'cursor-move select-none': shouldDraggable,
|
||||
},
|
||||
props.headerClass,
|
||||
@@ -174,12 +176,14 @@ function pointerDownOutside(e: Event) {
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
|
||||
<VbenTooltip v-if="titleTooltip" side="right">
|
||||
<template #trigger>
|
||||
<Info class="inline-flex size-5 cursor-pointer pb-1" />
|
||||
</template>
|
||||
{{ titleTooltip }}
|
||||
</VbenTooltip>
|
||||
<slot v-if="titleTooltip" name="titleTooltip">
|
||||
<VbenTooltip side="right">
|
||||
<template #trigger>
|
||||
<Info class="inline-flex size-5 cursor-pointer pb-1" />
|
||||
</template>
|
||||
{{ titleTooltip }}
|
||||
</VbenTooltip>
|
||||
</slot>
|
||||
</slot>
|
||||
</DialogTitle>
|
||||
<DialogDescription v-if="description">
|
||||
@@ -193,13 +197,18 @@ function pointerDownOutside(e: Event) {
|
||||
</VisuallyHidden>
|
||||
</DialogHeader>
|
||||
<div
|
||||
ref="wrapperRef"
|
||||
:class="
|
||||
cn('relative min-h-40 flex-1 p-3', contentClass, {
|
||||
'overflow-y-auto': !showLoading,
|
||||
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
|
||||
'overflow-hidden': showLoading,
|
||||
})
|
||||
"
|
||||
>
|
||||
<VbenLoading v-if="showLoading" class="size-full" spinning />
|
||||
<VbenLoading
|
||||
v-if="showLoading"
|
||||
class="size-full h-auto min-h-full"
|
||||
spinning
|
||||
/>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
* 调整部分细节
|
||||
*/
|
||||
|
||||
import { onBeforeUnmount, onMounted, ref, watchEffect } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, watchEffect } from 'vue';
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
|
||||
import { unrefElement } from '@vueuse/core';
|
||||
@@ -13,16 +13,13 @@ export function useModalDraggable(
|
||||
dragRef: Ref<HTMLElement | undefined>,
|
||||
draggable: ComputedRef<boolean>,
|
||||
) {
|
||||
let transform = {
|
||||
const transform = reactive({
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
};
|
||||
});
|
||||
|
||||
const dragging = ref(false);
|
||||
|
||||
// let isFirstDrag = true;
|
||||
// let initialX = 0;
|
||||
// let initialY = 0;
|
||||
const onMousedown = (e: MouseEvent) => {
|
||||
const downX = e.clientX;
|
||||
const downY = e.clientY;
|
||||
@@ -31,12 +28,6 @@ export function useModalDraggable(
|
||||
return;
|
||||
}
|
||||
|
||||
// if (isFirstDrag) {
|
||||
// const { x, y } = getInitialTransform(targetRef.value);
|
||||
// initialX = x;
|
||||
// initialY = y;
|
||||
// }
|
||||
|
||||
const targetRect = targetRef.value.getBoundingClientRect();
|
||||
|
||||
const { offsetX, offsetY } = transform;
|
||||
@@ -56,17 +47,12 @@ export function useModalDraggable(
|
||||
const onMousemove = (e: MouseEvent) => {
|
||||
let moveX = offsetX + e.clientX - downX;
|
||||
let moveY = offsetY + e.clientY - downY;
|
||||
// const x = isFirstDrag ? initialX : 0;
|
||||
// const y = isFirstDrag ? initialY : 0;
|
||||
moveX = Math.min(Math.max(moveX, minLeft), maxLeft);
|
||||
// + x;
|
||||
moveY = Math.min(Math.max(moveY, minTop), maxTop);
|
||||
// + y;
|
||||
|
||||
transform = {
|
||||
offsetX: moveX,
|
||||
offsetY: moveY,
|
||||
};
|
||||
moveX = Math.min(Math.max(moveX, minLeft), maxLeft);
|
||||
moveY = Math.min(Math.max(moveY, minTop), maxTop);
|
||||
|
||||
transform.offsetX = moveX;
|
||||
transform.offsetY = moveY;
|
||||
|
||||
if (targetRef.value) {
|
||||
targetRef.value.style.transform = `translate(${moveX}px, ${moveY}px)`;
|
||||
@@ -75,7 +61,6 @@ export function useModalDraggable(
|
||||
};
|
||||
|
||||
const onMouseup = () => {
|
||||
// isFirstDrag = false;
|
||||
dragging.value = false;
|
||||
document.removeEventListener('mousemove', onMousemove);
|
||||
document.removeEventListener('mouseup', onMouseup);
|
||||
@@ -100,10 +85,9 @@ export function useModalDraggable(
|
||||
};
|
||||
|
||||
const resetPosition = () => {
|
||||
transform = {
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
};
|
||||
transform.offsetX = 0;
|
||||
transform.offsetY = 0;
|
||||
|
||||
const target = unrefElement(targetRef);
|
||||
if (target) {
|
||||
target.style.transform = 'none';
|
||||
@@ -127,22 +111,6 @@ export function useModalDraggable(
|
||||
return {
|
||||
dragging,
|
||||
resetPosition,
|
||||
transform,
|
||||
};
|
||||
}
|
||||
|
||||
// function getInitialTransform(target: HTMLElement) {
|
||||
// let x = 0;
|
||||
// let y = 0;
|
||||
// const transformValue = window.getComputedStyle(target)?.transform;
|
||||
// if (transformValue) {
|
||||
// const match = transformValue.match(/matrix\(([^)]+)\)/);
|
||||
// if (match) {
|
||||
// const values = match[1]?.split(', ') ?? [];
|
||||
// // 获取 translateX 值
|
||||
// x = Number.parseFloat(`${values[4]}`);
|
||||
// // 获取 translateY 值
|
||||
// y = Number.parseFloat(`${values[5]}`);
|
||||
// }
|
||||
// }
|
||||
// return { x, y };
|
||||
// }
|
||||
|
@@ -69,7 +69,7 @@ function onTransitionEnd() {
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'bg-overlay z-100 pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center backdrop-blur-sm transition-all duration-500',
|
||||
'bg-overlay z-100 pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center transition-all duration-500',
|
||||
{
|
||||
'invisible opacity-0': !showSpinner,
|
||||
},
|
||||
|
@@ -44,7 +44,7 @@ defineExpose({
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay
|
||||
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-overlay fixed inset-0 z-[1000] backdrop-blur-sm"
|
||||
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-overlay fixed inset-0 z-[1000]"
|
||||
data-dismissable-modal="true"
|
||||
@click="() => emits('close')"
|
||||
/>
|
||||
|
Reference in New Issue
Block a user