mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 19:29:04 +08:00
perf: perf modal and drawer
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import './src/index.less';
|
||||
export { default as BasicModal } from './src/BasicModal';
|
||||
export { default as Modal } from './src/Modal';
|
||||
import BasicModalLib from './src/BasicModal';
|
||||
import { withInstall } from '../util';
|
||||
|
||||
export { useModalContext } from './src/useModalContext';
|
||||
export { useModal, useModalInner } from './src/useModal';
|
||||
export * from './src/types';
|
||||
export const BasicModal = withInstall(BasicModalLib);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { ModalProps, ModalMethods } from './types';
|
||||
|
||||
import { defineComponent, computed, ref, watch, unref, watchEffect } from 'vue';
|
||||
import { defineComponent, computed, ref, watch, unref, watchEffect, toRef } from 'vue';
|
||||
|
||||
import Modal from './Modal';
|
||||
import { Button } from '/@/components/Button';
|
||||
@@ -11,10 +11,10 @@ import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-
|
||||
import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { deepMerge } from '/@/utils';
|
||||
import { buildUUID } from '/@/utils/uuid';
|
||||
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
|
||||
|
||||
import { basicProps } from './props';
|
||||
// import { triggerWindowResize } from '@/utils/event/triggerWindowResizeEvent';
|
||||
import { useFullScreen } from './useFullScreen';
|
||||
export default defineComponent({
|
||||
name: 'BasicModal',
|
||||
props: basicProps,
|
||||
@@ -26,31 +26,41 @@ export default defineComponent({
|
||||
// modal Bottom and top height
|
||||
const extHeightRef = ref(0);
|
||||
// Unexpanded height of the popup
|
||||
const formerHeightRef = ref(0);
|
||||
const fullScreenRef = ref(false);
|
||||
|
||||
// Custom title component: get title
|
||||
const getMergeProps = computed(() => {
|
||||
return {
|
||||
...props,
|
||||
...(unref(propsRef) as any),
|
||||
};
|
||||
const getMergeProps = computed(
|
||||
(): ModalProps => {
|
||||
return {
|
||||
...props,
|
||||
...(unref(propsRef) as any),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
|
||||
modalWrapperRef,
|
||||
extHeightRef,
|
||||
wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
|
||||
});
|
||||
|
||||
// modal component does not need title
|
||||
const getProps = computed((): any => {
|
||||
const opt = {
|
||||
...props,
|
||||
...((unref(propsRef) || {}) as any),
|
||||
visible: unref(visibleRef),
|
||||
title: undefined,
|
||||
};
|
||||
const { wrapClassName = '' } = opt;
|
||||
const className = unref(fullScreenRef) ? `${wrapClassName} fullscreen-modal` : wrapClassName;
|
||||
return {
|
||||
...opt,
|
||||
wrapClassName: className,
|
||||
};
|
||||
const getProps = computed(
|
||||
(): ModalProps => {
|
||||
const opt = {
|
||||
...unref(getMergeProps),
|
||||
visible: unref(visibleRef),
|
||||
title: undefined,
|
||||
};
|
||||
|
||||
return {
|
||||
...opt,
|
||||
wrapClassName: unref(getWrapClassName),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const getModalBindValue = computed((): any => {
|
||||
return { ...attrs, ...unref(getProps) };
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
@@ -80,7 +90,35 @@ export default defineComponent({
|
||||
);
|
||||
}
|
||||
|
||||
// 取消事件
|
||||
async function handleCancel(e: Event) {
|
||||
e?.stopPropagation();
|
||||
|
||||
if (props.closeFunc && isFunction(props.closeFunc)) {
|
||||
const isClose: boolean = await props.closeFunc();
|
||||
visibleRef.value = !isClose;
|
||||
return;
|
||||
}
|
||||
|
||||
visibleRef.value = false;
|
||||
emit('cancel');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 设置modal参数
|
||||
*/
|
||||
function setModalProps(props: Partial<ModalProps>): void {
|
||||
// Keep the last setModalProps
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, props);
|
||||
if (!Reflect.has(props, 'visible')) return;
|
||||
visibleRef.value = !!props.visible;
|
||||
}
|
||||
|
||||
function renderContent() {
|
||||
type OmitWrapperType = Omit<
|
||||
ModalProps,
|
||||
'fullScreen' | 'modalFooterHeight' | 'visible' | 'loading'
|
||||
>;
|
||||
const { useWrapper, loading, wrapperProps } = unref(getProps);
|
||||
if (!useWrapper) return getSlot(slots);
|
||||
|
||||
@@ -93,7 +131,7 @@ export default defineComponent({
|
||||
loading={loading}
|
||||
visible={unref(visibleRef)}
|
||||
modalFooterHeight={showFooter}
|
||||
{...wrapperProps}
|
||||
{...((wrapperProps as unknown) as OmitWrapperType)}
|
||||
onGetExtHeight={(height: number) => {
|
||||
extHeightRef.value = height;
|
||||
}}
|
||||
@@ -106,18 +144,6 @@ export default defineComponent({
|
||||
);
|
||||
}
|
||||
|
||||
// 取消事件
|
||||
async function handleCancel(e: Event) {
|
||||
e && e.stopPropagation();
|
||||
if (props.closeFunc && isFunction(props.closeFunc)) {
|
||||
const isClose: boolean = await props.closeFunc();
|
||||
visibleRef.value = !isClose;
|
||||
return;
|
||||
}
|
||||
visibleRef.value = false;
|
||||
emit('cancel');
|
||||
}
|
||||
|
||||
// 底部按钮自定义实现,
|
||||
function renderFooter() {
|
||||
const {
|
||||
@@ -162,64 +188,37 @@ export default defineComponent({
|
||||
*/
|
||||
function renderClose() {
|
||||
const { canFullscreen } = unref(getProps);
|
||||
if (!canFullscreen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fullScreen = unref(fullScreenRef) ? (
|
||||
<FullscreenExitOutlined role="full" onClick={handleFullScreen} />
|
||||
) : (
|
||||
<FullscreenOutlined role="close" onClick={handleFullScreen} />
|
||||
);
|
||||
|
||||
const cls = [
|
||||
'custom-close-icon',
|
||||
{
|
||||
'can-full': canFullscreen,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div class="custom-close-icon">
|
||||
{unref(fullScreenRef) ? (
|
||||
<FullscreenExitOutlined role="full" onClick={handleFullScreen} />
|
||||
) : (
|
||||
<FullscreenOutlined role="close" onClick={handleFullScreen} />
|
||||
)}
|
||||
<div class={cls}>
|
||||
{canFullscreen && fullScreen}
|
||||
<CloseOutlined onClick={handleCancel} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function handleFullScreen(e: Event) {
|
||||
e && e.stopPropagation();
|
||||
fullScreenRef.value = !unref(fullScreenRef);
|
||||
|
||||
const modalWrapper = unref(modalWrapperRef);
|
||||
if (!modalWrapper) return;
|
||||
|
||||
const wrapperEl = modalWrapper.$el as HTMLElement;
|
||||
if (!wrapperEl) return;
|
||||
|
||||
const modalWrapSpinEl = wrapperEl.querySelector('.ant-spin-nested-loading') as HTMLElement;
|
||||
if (!modalWrapSpinEl) return;
|
||||
|
||||
if (!unref(formerHeightRef) && unref(fullScreenRef)) {
|
||||
formerHeightRef.value = modalWrapSpinEl.offsetHeight;
|
||||
}
|
||||
|
||||
if (unref(fullScreenRef)) {
|
||||
modalWrapSpinEl.style.height = `${window.innerHeight - unref(extHeightRef)}px`;
|
||||
} else {
|
||||
modalWrapSpinEl.style.height = `${unref(formerHeightRef)}px`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 设置modal参数
|
||||
*/
|
||||
function setModalProps(props: Partial<ModalProps>): void {
|
||||
// Keep the last setModalProps
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, props);
|
||||
if (!Reflect.has(props, 'visible')) return;
|
||||
visibleRef.value = !!props.visible;
|
||||
}
|
||||
|
||||
const modalMethods: ModalMethods = {
|
||||
setModalProps,
|
||||
};
|
||||
|
||||
const uuid = buildUUID();
|
||||
emit('register', modalMethods, uuid);
|
||||
|
||||
tryTsxEmit((instance) => {
|
||||
emit('register', modalMethods, instance.uid);
|
||||
});
|
||||
return () => (
|
||||
<Modal onCancel={handleCancel} {...{ ...attrs, ...props, ...unref(getProps) }}>
|
||||
<Modal onCancel={handleCancel} {...unref(getModalBindValue)}>
|
||||
{{
|
||||
footer: () => renderFooter(),
|
||||
closeIcon: () => renderClose(),
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { defineComponent, watchEffect } from 'vue';
|
||||
import { defineComponent, toRefs } from 'vue';
|
||||
import { basicProps } from './props';
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
|
||||
import { useModalDragMove } from './useModalDrag';
|
||||
import { extendSlots } from '/@/utils/helper/tsxHelper';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -9,99 +9,12 @@ export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: basicProps,
|
||||
setup(props, { attrs, slots }) {
|
||||
const getStyle = (dom: any, attr: any) => {
|
||||
return getComputedStyle(dom)[attr];
|
||||
};
|
||||
const drag = (wrap: any) => {
|
||||
if (!wrap) return;
|
||||
wrap.setAttribute('data-drag', props.draggable);
|
||||
const dialogHeaderEl = wrap.querySelector('.ant-modal-header');
|
||||
const dragDom = wrap.querySelector('.ant-modal');
|
||||
const { visible, draggable, destroyOnClose } = toRefs(props);
|
||||
|
||||
if (!dialogHeaderEl || !dragDom || !props.draggable) return;
|
||||
|
||||
dialogHeaderEl.style.cursor = 'move';
|
||||
|
||||
dialogHeaderEl.onmousedown = (e: any) => {
|
||||
if (!e) return;
|
||||
// 鼠标按下,计算当前元素距离可视区的距离
|
||||
const disX = e.clientX;
|
||||
const disY = e.clientY;
|
||||
const screenWidth = document.body.clientWidth; // body当前宽度
|
||||
const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
|
||||
|
||||
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
|
||||
const dragDomheight = dragDom.offsetHeight; // 对话框高度
|
||||
|
||||
const minDragDomLeft = dragDom.offsetLeft;
|
||||
|
||||
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
|
||||
const minDragDomTop = dragDom.offsetTop;
|
||||
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
|
||||
// 获取到的值带px 正则匹配替换
|
||||
const domLeft = getStyle(dragDom, 'left');
|
||||
const domTop = getStyle(dragDom, 'top');
|
||||
let styL = +domLeft;
|
||||
let styT = +domTop;
|
||||
|
||||
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
|
||||
if (domLeft.includes('%')) {
|
||||
styL = +document.body.clientWidth * (+domLeft.replace(/%/g, '') / 100);
|
||||
styT = +document.body.clientHeight * (+domTop.replace(/%/g, '') / 100);
|
||||
} else {
|
||||
styL = +domLeft.replace(/px/g, '');
|
||||
styT = +domTop.replace(/px/g, '');
|
||||
}
|
||||
|
||||
document.onmousemove = function (e) {
|
||||
// 通过事件委托,计算移动的距离
|
||||
let left = e.clientX - disX;
|
||||
let top = e.clientY - disY;
|
||||
|
||||
// 边界处理
|
||||
if (-left > minDragDomLeft) {
|
||||
left = -minDragDomLeft;
|
||||
} else if (left > maxDragDomLeft) {
|
||||
left = maxDragDomLeft;
|
||||
}
|
||||
|
||||
if (-top > minDragDomTop) {
|
||||
top = -minDragDomTop;
|
||||
} else if (top > maxDragDomTop) {
|
||||
top = maxDragDomTop;
|
||||
}
|
||||
|
||||
// 移动当前元素
|
||||
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
|
||||
};
|
||||
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const handleDrag = () => {
|
||||
const dragWraps = document.querySelectorAll('.ant-modal-wrap');
|
||||
for (const wrap of dragWraps as any) {
|
||||
if (!wrap) continue;
|
||||
const display = getStyle(wrap, 'display');
|
||||
const draggable = wrap.getAttribute('data-drag');
|
||||
if (display !== 'none') {
|
||||
// 拖拽位置
|
||||
(draggable === null || props.destroyOnClose) && drag(wrap);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (!props.visible) {
|
||||
return;
|
||||
}
|
||||
useTimeoutFn(() => {
|
||||
handleDrag();
|
||||
}, 30);
|
||||
useModalDragMove({
|
||||
visible,
|
||||
destroyOnClose,
|
||||
draggable,
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type { PropType } from 'vue';
|
||||
import type { ModalWrapperProps } from './types';
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import {
|
||||
defineComponent,
|
||||
@@ -18,59 +18,44 @@ import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
|
||||
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { useElResize } from '/@/hooks/event/useElResize';
|
||||
import { provideModal } from './provideModal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { createModalContext } from './useModalContext';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ModalWrapper',
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
modalHeaderHeight: {
|
||||
type: Number as PropType<number>,
|
||||
default: 50,
|
||||
},
|
||||
modalFooterHeight: {
|
||||
type: Number as PropType<number>,
|
||||
default: 70,
|
||||
},
|
||||
minHeight: {
|
||||
type: Number as PropType<number>,
|
||||
default: 200,
|
||||
},
|
||||
footerOffset: {
|
||||
type: Number as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
visible: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
fullScreen: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
loading: propTypes.bool,
|
||||
modalHeaderHeight: propTypes.number.def(50),
|
||||
modalFooterHeight: propTypes.number.def(54),
|
||||
minHeight: propTypes.number.def(200),
|
||||
footerOffset: propTypes.number.def(0),
|
||||
visible: propTypes.bool,
|
||||
fullScreen: propTypes.bool,
|
||||
},
|
||||
emits: ['heightChange', 'getExtHeight'],
|
||||
setup(props: ModalWrapperProps, { slots, emit }) {
|
||||
const wrapperRef = ref<HTMLElement | null>(null);
|
||||
const wrapperRef = ref<ElRef>(null);
|
||||
const spinRef = ref<ComponentRef>(null);
|
||||
const realHeightRef = ref(0);
|
||||
// 重试次数
|
||||
// let tryCount = 0;
|
||||
|
||||
let stopElResizeFn: Fn = () => {};
|
||||
|
||||
provideModal(setModalHeight);
|
||||
useWindowSizeFn(setModalHeight);
|
||||
|
||||
const wrapStyle = computed(() => {
|
||||
return {
|
||||
minHeight: `${props.minHeight}px`,
|
||||
height: `${unref(realHeightRef)}px`,
|
||||
overflow: 'auto',
|
||||
};
|
||||
createModalContext({
|
||||
redoModalHeight: setModalHeight,
|
||||
});
|
||||
|
||||
const wrapStyle = computed(
|
||||
(): CSSProperties => {
|
||||
return {
|
||||
minHeight: `${props.minHeight}px`,
|
||||
height: `${unref(realHeightRef)}px`,
|
||||
overflow: 'auto',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
setModalHeight();
|
||||
});
|
||||
@@ -92,8 +77,6 @@ export default defineComponent({
|
||||
stopElResizeFn && stopElResizeFn();
|
||||
});
|
||||
|
||||
useWindowSizeFn(setModalHeight);
|
||||
|
||||
async function setModalHeight() {
|
||||
// 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
|
||||
// 加上这个,就必须在使用的时候传递父级的visible
|
||||
@@ -107,9 +90,8 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
|
||||
if (!modalDom) {
|
||||
return;
|
||||
}
|
||||
if (!modalDom) return;
|
||||
|
||||
const modalRect = getComputedStyle(modalDom).top;
|
||||
const modalTop = Number.parseInt(modalRect);
|
||||
let maxHeight =
|
||||
@@ -135,11 +117,12 @@ export default defineComponent({
|
||||
|
||||
if (props.fullScreen) {
|
||||
realHeightRef.value =
|
||||
window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 6;
|
||||
window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight;
|
||||
} else {
|
||||
realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30;
|
||||
}
|
||||
emit('heightChange', unref(realHeightRef));
|
||||
|
||||
nextTick(() => {
|
||||
const el = spinEl.$el;
|
||||
if (el) {
|
||||
@@ -154,8 +137,10 @@ export default defineComponent({
|
||||
function listenElResize() {
|
||||
const wrapper = unref(wrapperRef);
|
||||
if (!wrapper) return;
|
||||
|
||||
const container = wrapper.querySelector('.ant-spin-container');
|
||||
if (!container) return;
|
||||
|
||||
const [start, stop] = useElResize(container, () => {
|
||||
setModalHeight();
|
||||
});
|
||||
|
@@ -9,6 +9,11 @@
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
|
||||
&-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +40,23 @@
|
||||
height: 95%;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
margin-left: 12px;
|
||||
> span {
|
||||
margin-left: 48px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&.can-full {
|
||||
> span {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.can-full) {
|
||||
> span:nth-child(1) {
|
||||
&:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& span:nth-child(1) {
|
||||
@@ -76,7 +96,7 @@
|
||||
}
|
||||
|
||||
&-footer {
|
||||
padding: 10px 26px 26px 16px;
|
||||
// padding: 10px 26px 26px 16px;
|
||||
|
||||
button + button {
|
||||
margin-left: 10px;
|
||||
|
@@ -2,66 +2,38 @@ import type { PropType } from 'vue';
|
||||
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
const { t } = useI18n('component.modal');
|
||||
|
||||
export const modalProps = {
|
||||
visible: Boolean as PropType<boolean>,
|
||||
visible: propTypes.bool,
|
||||
// open drag
|
||||
draggable: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
centered: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
cancelText: {
|
||||
type: String as PropType<string>,
|
||||
default: t('cancelText'),
|
||||
},
|
||||
okText: {
|
||||
type: String as PropType<string>,
|
||||
default: t('okText'),
|
||||
},
|
||||
draggable: propTypes.bool.def(true),
|
||||
centered: propTypes.bool,
|
||||
cancelText: propTypes.string.def(t('cancelText')),
|
||||
okText: propTypes.string.def(t('okText')),
|
||||
|
||||
closeFunc: Function as PropType<() => Promise<boolean>>,
|
||||
};
|
||||
|
||||
export const basicProps = Object.assign({}, modalProps, {
|
||||
// Can it be full screen
|
||||
canFullscreen: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
canFullscreen: propTypes.bool.def(true),
|
||||
// After enabling the wrapper, the bottom can be increased in height
|
||||
wrapperFooterOffset: {
|
||||
type: Number as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
wrapperFooterOffset: propTypes.number.def(0),
|
||||
// Warm reminder message
|
||||
helpMessage: [String, Array] as PropType<string | string[]>,
|
||||
// Whether to setting wrapper
|
||||
useWrapper: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
useWrapper: propTypes.bool.def(true),
|
||||
loading: propTypes.bool,
|
||||
/**
|
||||
* @description: Show close button
|
||||
*/
|
||||
showCancelBtn: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
showCancelBtn: propTypes.bool.def(true),
|
||||
/**
|
||||
* @description: Show confirmation button
|
||||
*/
|
||||
showOkBtn: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
showOkBtn: propTypes.bool.def(true),
|
||||
|
||||
wrapperProps: Object as PropType<any>,
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
import { provide, inject } from 'vue';
|
||||
|
||||
const key = Symbol('basic-modal');
|
||||
|
||||
export function provideModal(redoHeight: Fn) {
|
||||
provide(key, redoHeight);
|
||||
}
|
||||
|
||||
export function injectModal(): Fn {
|
||||
return inject(key, () => {}) as Fn;
|
||||
}
|
@@ -8,9 +8,11 @@ export interface ModalMethods {
|
||||
}
|
||||
|
||||
export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void;
|
||||
|
||||
export interface ReturnMethods extends ModalMethods {
|
||||
openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void;
|
||||
}
|
||||
|
||||
export type UseModalReturnType = [RegisterFn, ReturnMethods];
|
||||
|
||||
export interface ReturnInnerMethods extends ModalMethods {
|
||||
@@ -18,6 +20,7 @@ export interface ReturnInnerMethods extends ModalMethods {
|
||||
changeLoading: (loading: boolean) => void;
|
||||
changeOkLoading: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods];
|
||||
|
||||
export interface ModalProps {
|
||||
|
44
src/components/Modal/src/useFullScreen.ts
Normal file
44
src/components/Modal/src/useFullScreen.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { computed, Ref, ref, unref } from 'vue';
|
||||
|
||||
export interface UseFullScreenContext {
|
||||
wrapClassName: Ref<string | undefined>;
|
||||
modalWrapperRef: Ref<ComponentRef>;
|
||||
extHeightRef: Ref<number>;
|
||||
}
|
||||
|
||||
export function useFullScreen(context: UseFullScreenContext) {
|
||||
const formerHeightRef = ref(0);
|
||||
const fullScreenRef = ref(false);
|
||||
|
||||
const getWrapClassName = computed(() => {
|
||||
const clsName = unref(context.wrapClassName) || '';
|
||||
|
||||
return unref(fullScreenRef) ? `fullscreen-modal ${clsName} ` : unref(clsName);
|
||||
});
|
||||
|
||||
function handleFullScreen(e: Event) {
|
||||
e && e.stopPropagation();
|
||||
fullScreenRef.value = !unref(fullScreenRef);
|
||||
|
||||
const modalWrapper = unref(context.modalWrapperRef);
|
||||
|
||||
if (!modalWrapper) return;
|
||||
|
||||
const wrapperEl = modalWrapper.$el as HTMLElement;
|
||||
if (!wrapperEl) return;
|
||||
const modalWrapSpinEl = wrapperEl.querySelector('.ant-spin-nested-loading') as HTMLElement;
|
||||
|
||||
if (!modalWrapSpinEl) return;
|
||||
|
||||
if (!unref(formerHeightRef) && unref(fullScreenRef)) {
|
||||
formerHeightRef.value = modalWrapSpinEl.offsetHeight;
|
||||
}
|
||||
|
||||
if (unref(fullScreenRef)) {
|
||||
modalWrapSpinEl.style.height = `${window.innerHeight - unref(context.extHeightRef)}px`;
|
||||
} else {
|
||||
modalWrapSpinEl.style.height = `${unref(formerHeightRef)}px`;
|
||||
}
|
||||
}
|
||||
return { getWrapClassName, handleFullScreen, fullScreenRef };
|
||||
}
|
@@ -5,9 +5,21 @@ import type {
|
||||
ReturnMethods,
|
||||
UseModalInnerReturnType,
|
||||
} from './types';
|
||||
import { ref, onUnmounted, unref, getCurrentInstance, reactive, watchEffect, nextTick } from 'vue';
|
||||
|
||||
import {
|
||||
ref,
|
||||
onUnmounted,
|
||||
unref,
|
||||
getCurrentInstance,
|
||||
reactive,
|
||||
watchEffect,
|
||||
nextTick,
|
||||
toRaw,
|
||||
} from 'vue';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
|
||||
const dataTransferRef = reactive<any>({});
|
||||
|
||||
/**
|
||||
@@ -20,6 +32,7 @@ export function useModal(): UseModalReturnType {
|
||||
const modalRef = ref<Nullable<ModalMethods>>(null);
|
||||
const loadedRef = ref<Nullable<boolean>>(false);
|
||||
const uidRef = ref<string>('');
|
||||
|
||||
function register(modalMethod: ModalMethods, uuid: string) {
|
||||
uidRef.value = uuid;
|
||||
|
||||
@@ -52,13 +65,16 @@ export function useModal(): UseModalReturnType {
|
||||
visible: visible,
|
||||
});
|
||||
|
||||
if (data) {
|
||||
dataTransferRef[unref(uidRef)] = openOnSet
|
||||
? {
|
||||
...data,
|
||||
__t__: Date.now(),
|
||||
}
|
||||
: data;
|
||||
if (!data) return;
|
||||
|
||||
if (openOnSet) {
|
||||
dataTransferRef[unref(uidRef)] = null;
|
||||
dataTransferRef[unref(uidRef)] = data;
|
||||
return;
|
||||
}
|
||||
const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), data);
|
||||
if (!equal) {
|
||||
dataTransferRef[unref(uidRef)] = data;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -66,7 +82,7 @@ export function useModal(): UseModalReturnType {
|
||||
}
|
||||
|
||||
export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
|
||||
const modalInstanceRef = ref<ModalMethods | null>(null);
|
||||
const modalInstanceRef = ref<Nullable<ModalMethods>>(null);
|
||||
const currentInstall = getCurrentInstance();
|
||||
const uidRef = ref<string>('');
|
||||
|
||||
@@ -83,6 +99,11 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
|
||||
};
|
||||
|
||||
const register = (modalInstance: ModalMethods, uuid: string) => {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
modalInstanceRef.value = null;
|
||||
});
|
||||
|
||||
uidRef.value = uuid;
|
||||
modalInstanceRef.value = modalInstance;
|
||||
currentInstall.emit('register', modalInstance);
|
||||
|
16
src/components/Modal/src/useModalContext.ts
Normal file
16
src/components/Modal/src/useModalContext.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { InjectionKey } from 'vue';
|
||||
import { createContext, useContext } from '/@/hooks/core/useContext';
|
||||
|
||||
export interface ModalContextProps {
|
||||
redoModalHeight: () => void;
|
||||
}
|
||||
|
||||
const modalContextInjectKey: InjectionKey<ModalContextProps> = Symbol();
|
||||
|
||||
export function createModalContext(context: ModalContextProps) {
|
||||
return createContext<ModalContextProps>(context, modalContextInjectKey);
|
||||
}
|
||||
|
||||
export function useModalContext() {
|
||||
return useContext<ModalContextProps>(modalContextInjectKey);
|
||||
}
|
107
src/components/Modal/src/useModalDrag.ts
Normal file
107
src/components/Modal/src/useModalDrag.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Ref, unref, watchEffect } from 'vue';
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
|
||||
|
||||
export interface UseModalDragMoveContext {
|
||||
draggable: Ref<boolean>;
|
||||
destroyOnClose: Ref<boolean | undefined> | undefined;
|
||||
visible: Ref<boolean>;
|
||||
}
|
||||
|
||||
export function useModalDragMove(context: UseModalDragMoveContext) {
|
||||
const getStyle = (dom: any, attr: any) => {
|
||||
return getComputedStyle(dom)[attr];
|
||||
};
|
||||
const drag = (wrap: any) => {
|
||||
if (!wrap) return;
|
||||
wrap.setAttribute('data-drag', unref(context.draggable));
|
||||
const dialogHeaderEl = wrap.querySelector('.ant-modal-header');
|
||||
const dragDom = wrap.querySelector('.ant-modal');
|
||||
|
||||
if (!dialogHeaderEl || !dragDom || !unref(context.draggable)) return;
|
||||
|
||||
dialogHeaderEl.style.cursor = 'move';
|
||||
|
||||
dialogHeaderEl.onmousedown = (e: any) => {
|
||||
if (!e) return;
|
||||
// 鼠标按下,计算当前元素距离可视区的距离
|
||||
const disX = e.clientX;
|
||||
const disY = e.clientY;
|
||||
const screenWidth = document.body.clientWidth; // body当前宽度
|
||||
const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
|
||||
|
||||
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
|
||||
const dragDomheight = dragDom.offsetHeight; // 对话框高度
|
||||
|
||||
const minDragDomLeft = dragDom.offsetLeft;
|
||||
|
||||
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
|
||||
const minDragDomTop = dragDom.offsetTop;
|
||||
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
|
||||
// 获取到的值带px 正则匹配替换
|
||||
const domLeft = getStyle(dragDom, 'left');
|
||||
const domTop = getStyle(dragDom, 'top');
|
||||
let styL = +domLeft;
|
||||
let styT = +domTop;
|
||||
|
||||
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
|
||||
if (domLeft.includes('%')) {
|
||||
styL = +document.body.clientWidth * (+domLeft.replace(/%/g, '') / 100);
|
||||
styT = +document.body.clientHeight * (+domTop.replace(/%/g, '') / 100);
|
||||
} else {
|
||||
styL = +domLeft.replace(/px/g, '');
|
||||
styT = +domTop.replace(/px/g, '');
|
||||
}
|
||||
|
||||
document.onmousemove = function (e) {
|
||||
// 通过事件委托,计算移动的距离
|
||||
let left = e.clientX - disX;
|
||||
let top = e.clientY - disY;
|
||||
|
||||
// 边界处理
|
||||
if (-left > minDragDomLeft) {
|
||||
left = -minDragDomLeft;
|
||||
} else if (left > maxDragDomLeft) {
|
||||
left = maxDragDomLeft;
|
||||
}
|
||||
|
||||
if (-top > minDragDomTop) {
|
||||
top = -minDragDomTop;
|
||||
} else if (top > maxDragDomTop) {
|
||||
top = maxDragDomTop;
|
||||
}
|
||||
|
||||
// 移动当前元素
|
||||
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
|
||||
};
|
||||
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const handleDrag = () => {
|
||||
const dragWraps = document.querySelectorAll('.ant-modal-wrap');
|
||||
for (const wrap of Array.from(dragWraps)) {
|
||||
if (!wrap) continue;
|
||||
const display = getStyle(wrap, 'display');
|
||||
const draggable = wrap.getAttribute('data-drag');
|
||||
if (display !== 'none') {
|
||||
// 拖拽位置
|
||||
if (draggable === null || unref(context.destroyOnClose)) {
|
||||
drag(wrap);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (!unref(context.visible) || !unref(context.draggable)) {
|
||||
return;
|
||||
}
|
||||
useTimeoutFn(() => {
|
||||
handleDrag();
|
||||
}, 30);
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user