This commit is contained in:
dap
2024-08-29 19:55:11 +08:00
74 changed files with 1238 additions and 453 deletions

View File

@@ -77,7 +77,7 @@
/* ============= custom ============= */
/* 遮罩颜色 */
--overlay: 0deg 0% 0% / 30%;
--overlay: 0 0% 0% / 30%;
/* 基本文字大小 */
--font-size-base: 16px;

View File

@@ -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();
}
}
/**

View File

@@ -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,
})
"
>

View File

@@ -38,10 +38,10 @@ export class ModalApi {
footer: true,
fullscreen: false,
fullscreenButton: true,
header: true,
isOpen: false,
loading: false,
modal: true,
sharedData: {},
title: '',
};

View File

@@ -60,12 +60,16 @@ export interface ModalProps {
* @default true
*/
fullscreenButton?: boolean;
/**
* 是否显示顶栏
* @default true
*/
header?: boolean;
/**
* 弹窗是否显示
* @default false
*/
loading?: boolean;
/**
* 是否显示遮罩
* @default true

View File

@@ -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>

View File

@@ -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 };
// }

View File

@@ -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,
},

View File

@@ -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')"
/>