mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 16:46:19 +08:00
perf: perf modal and drawer
This commit is contained in:
@@ -18,12 +18,18 @@
|
||||
|
||||
- 缓存可以配置是否加密,默认生产环境开启 Aes 加密
|
||||
- 新增标签页拖拽排序
|
||||
- 新增 LayoutFooter.默认显示,可以在配置内关闭
|
||||
|
||||
### ⚡ Performance Improvements
|
||||
|
||||
- 优化`Modal`组件全屏动画不流畅问题
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- 修复 tree 文本超出挡住操作按钮问题
|
||||
- 修复通过 useRedo 刷新页面参数丢失问题
|
||||
- 修复表单校验先设置在校验及控制台错误信息问题
|
||||
- 修复`modal`与`drawer`组件传递数组参数问题
|
||||
|
||||
### 🎫 Chores
|
||||
|
||||
|
@@ -1,4 +1,6 @@
|
||||
export { default as BasicDrawer } from './src/BasicDrawer';
|
||||
import BasicDrawerLib from './src/BasicDrawer';
|
||||
import { withInstall } from '../util';
|
||||
|
||||
export { useDrawer, useDrawerInner } from './src/useDrawer';
|
||||
export * from './src/types';
|
||||
export { useDrawer, useDrawerInner } from './src/useDrawer';
|
||||
export const BasicDrawer = withInstall(BasicDrawerLib);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import './index.less';
|
||||
|
||||
import type { DrawerInstance, DrawerProps } from './types';
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw } from 'vue';
|
||||
import { Drawer, Row, Col, Button } from 'ant-design-vue';
|
||||
@@ -9,53 +10,96 @@ import { BasicTitle } from '/@/components/Basic';
|
||||
import { FullLoading } from '/@/components/Loading/index';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import { basicProps } from './props';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { isFunction, isNumber } from '/@/utils/is';
|
||||
import { buildUUID } from '/@/utils/uuid';
|
||||
import { deepMerge } from '/@/utils';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
|
||||
|
||||
import { basicProps } from './props';
|
||||
|
||||
const prefixCls = 'basic-drawer';
|
||||
export default defineComponent({
|
||||
// inheritAttrs: false,
|
||||
inheritAttrs: false,
|
||||
props: basicProps,
|
||||
emits: ['visible-change', 'ok', 'close', 'register'],
|
||||
setup(props, { slots, emit, attrs }) {
|
||||
const scrollRef = ref<ElRef>(null);
|
||||
|
||||
const visibleRef = ref(false);
|
||||
const propsRef = ref<Partial<DrawerProps> | null>(null);
|
||||
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
|
||||
|
||||
const { t } = useI18n('component.drawer');
|
||||
|
||||
const getMergeProps = computed((): any => {
|
||||
const getMergeProps = computed(
|
||||
(): DrawerProps => {
|
||||
return deepMerge(toRaw(props), unref(propsRef));
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const getProps = computed(() => {
|
||||
const opt: any = {
|
||||
const getProps = computed(
|
||||
(): DrawerProps => {
|
||||
const opt = {
|
||||
placement: 'right',
|
||||
...attrs,
|
||||
...props,
|
||||
...(unref(propsRef) as any),
|
||||
...unref(getMergeProps),
|
||||
visible: unref(visibleRef),
|
||||
};
|
||||
opt.title = undefined;
|
||||
|
||||
if (opt.isDetail) {
|
||||
if (!opt.width) {
|
||||
const { isDetail, width, wrapClassName, getContainer } = opt;
|
||||
if (isDetail) {
|
||||
if (!width) {
|
||||
opt.width = '100%';
|
||||
}
|
||||
opt.wrapClassName = opt.wrapClassName
|
||||
? `${opt.wrapClassName} ${prefixCls}__detail`
|
||||
: `${prefixCls}__detail`;
|
||||
if (!opt.getContainer) {
|
||||
opt.getContainer = '.layout-content';
|
||||
const detailCls = `${prefixCls}__detail`;
|
||||
|
||||
opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
|
||||
|
||||
if (!getContainer) {
|
||||
// TODO type error?
|
||||
opt.getContainer = '.layout-content' as any;
|
||||
}
|
||||
}
|
||||
return opt;
|
||||
return opt as DrawerProps;
|
||||
}
|
||||
);
|
||||
|
||||
const getBindValues = computed(
|
||||
(): DrawerProps => {
|
||||
return {
|
||||
...attrs,
|
||||
...unref(getProps),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Custom implementation of the bottom button,
|
||||
const getFooterHeight = computed(() => {
|
||||
const { footerHeight, showFooter } = unref(getProps);
|
||||
|
||||
if (showFooter && footerHeight) {
|
||||
return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
|
||||
}
|
||||
return `0px`;
|
||||
});
|
||||
|
||||
const getScrollContentStyle = computed(
|
||||
(): CSSProperties => {
|
||||
const footerHeight = unref(getFooterHeight);
|
||||
return {
|
||||
position: 'relative',
|
||||
height: `calc(100% - ${footerHeight})`,
|
||||
overflow: 'auto',
|
||||
padding: '16px',
|
||||
paddingBottom: '30px',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const getLoading = computed(() => {
|
||||
return {
|
||||
hidden: !unref(getProps).loading,
|
||||
};
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
@@ -74,22 +118,13 @@ export default defineComponent({
|
||||
}
|
||||
);
|
||||
|
||||
// Custom implementation of the bottom button,
|
||||
const getFooterHeight = computed(() => {
|
||||
const { footerHeight, showFooter }: DrawerProps = unref(getProps);
|
||||
if (showFooter && footerHeight) {
|
||||
return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
|
||||
}
|
||||
return `0px`;
|
||||
});
|
||||
|
||||
// Cancel event
|
||||
async function onClose(e: any) {
|
||||
async function onClose(e: ChangeEvent) {
|
||||
const { closeFunc } = unref(getProps);
|
||||
emit('close', e);
|
||||
if (closeFunc && isFunction(closeFunc)) {
|
||||
const res = await closeFunc();
|
||||
res && (visibleRef.value = false);
|
||||
visibleRef.value = !res;
|
||||
return;
|
||||
}
|
||||
visibleRef.value = false;
|
||||
@@ -98,12 +133,16 @@ export default defineComponent({
|
||||
function setDrawerProps(props: Partial<DrawerProps>): void {
|
||||
// Keep the last setDrawerProps
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, props);
|
||||
|
||||
if (Reflect.has(props, 'visible')) {
|
||||
visibleRef.value = !!props.visible;
|
||||
}
|
||||
}
|
||||
|
||||
function renderFooter() {
|
||||
if (slots?.footer) {
|
||||
return getSlot(slots, 'footer');
|
||||
}
|
||||
const {
|
||||
showCancelBtn,
|
||||
cancelButtonProps,
|
||||
@@ -114,14 +153,14 @@ export default defineComponent({
|
||||
okButtonProps,
|
||||
confirmLoading,
|
||||
showFooter,
|
||||
}: DrawerProps = unref(getProps);
|
||||
} = unref(getProps);
|
||||
if (!showFooter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
getSlot(slots, 'footer') ||
|
||||
(showFooter && (
|
||||
<div class={`${prefixCls}__footer`}>
|
||||
{getSlot(slots, 'insertFooter')}
|
||||
|
||||
{showCancelBtn && (
|
||||
<Button {...cancelButtonProps} onClick={onClose} class="mr-2">
|
||||
{() => cancelText}
|
||||
@@ -140,17 +179,21 @@ export default defineComponent({
|
||||
{() => okText}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{getSlot(slots, 'appendFooter')}
|
||||
</div>
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
function renderHeader() {
|
||||
if (slots?.title) {
|
||||
return getSlot(slots, 'title');
|
||||
}
|
||||
const { title } = unref(getMergeProps);
|
||||
return props.isDetail ? (
|
||||
getSlot(slots, 'title') || (
|
||||
|
||||
if (!props.isDetail) {
|
||||
return <BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>;
|
||||
}
|
||||
return (
|
||||
<Row type="flex" align="middle" class={`${prefixCls}__detail-header`}>
|
||||
{() => (
|
||||
<>
|
||||
@@ -159,20 +202,15 @@ export default defineComponent({
|
||||
{() => <LeftOutlined />}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{title && (
|
||||
<Col style="flex:1" class={[`${prefixCls}__detail-title`, 'ellipsis', 'px-2']}>
|
||||
{() => title}
|
||||
</Col>
|
||||
)}
|
||||
|
||||
{getSlot(slots, 'titleToolbar')}
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
)
|
||||
) : (
|
||||
<BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,41 +218,20 @@ export default defineComponent({
|
||||
setDrawerProps: setDrawerProps,
|
||||
};
|
||||
|
||||
const uuid = buildUUID();
|
||||
emit('register', drawerInstance, uuid);
|
||||
tryTsxEmit((instance) => {
|
||||
emit('register', drawerInstance, instance.uid);
|
||||
});
|
||||
|
||||
return () => {
|
||||
const footerHeight = unref(getFooterHeight);
|
||||
return (
|
||||
<Drawer
|
||||
class={prefixCls}
|
||||
onClose={onClose}
|
||||
{...{
|
||||
...attrs,
|
||||
...unref(getProps),
|
||||
}}
|
||||
>
|
||||
<Drawer class={prefixCls} onClose={onClose} {...unref(getBindValues)}>
|
||||
{{
|
||||
title: () => renderHeader(),
|
||||
default: () => (
|
||||
<>
|
||||
<div
|
||||
ref={scrollRef}
|
||||
{...attrs}
|
||||
style={{
|
||||
position: 'relative',
|
||||
height: `calc(100% - ${footerHeight})`,
|
||||
overflow: 'auto',
|
||||
padding: '16px',
|
||||
paddingBottom: '30px',
|
||||
}}
|
||||
>
|
||||
<FullLoading
|
||||
absolute
|
||||
tip={t('loadingText')}
|
||||
class={[!unref(getProps).loading ? 'hidden' : '']}
|
||||
/>
|
||||
{getSlot(slots, 'default')}
|
||||
<div ref={scrollRef} style={unref(getScrollContentStyle)}>
|
||||
<FullLoading absolute tip={t('loadingText')} class={unref(getLoading)} />
|
||||
{getSlot(slots)}
|
||||
</div>
|
||||
{renderFooter()}
|
||||
</>
|
||||
|
@@ -1,72 +1,37 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
const { t } = useI18n('component.drawer');
|
||||
|
||||
export const footerProps = {
|
||||
confirmLoading: Boolean as PropType<boolean>,
|
||||
confirmLoading: propTypes.bool,
|
||||
/**
|
||||
* @description: Show close button
|
||||
*/
|
||||
showCancelBtn: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
showCancelBtn: propTypes.bool.def(true),
|
||||
cancelButtonProps: Object as PropType<any>,
|
||||
cancelText: {
|
||||
type: String as PropType<string>,
|
||||
default: t('cancelText'),
|
||||
},
|
||||
cancelText: propTypes.string.def(t('cancelText')),
|
||||
/**
|
||||
* @description: Show confirmation button
|
||||
*/
|
||||
showOkBtn: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
okButtonProps: Object as PropType<any>,
|
||||
okText: {
|
||||
type: String as PropType<string>,
|
||||
default: t('okText'),
|
||||
},
|
||||
okType: {
|
||||
type: String as PropType<string>,
|
||||
default: 'primary',
|
||||
},
|
||||
showFooter: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
showOkBtn: propTypes.bool.def(true),
|
||||
okButtonProps: propTypes.any,
|
||||
okText: propTypes.string.def(t('okText')),
|
||||
okType: propTypes.string.def('primary'),
|
||||
showFooter: propTypes.bool,
|
||||
footerHeight: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 60,
|
||||
},
|
||||
};
|
||||
export const basicProps = {
|
||||
isDetail: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
showDetailBack: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
visible: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
maskClosable: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
isDetail: propTypes.bool,
|
||||
title: propTypes.string.def(''),
|
||||
showDetailBack: propTypes.bool.def(true),
|
||||
visible: propTypes.bool,
|
||||
loading: propTypes.bool,
|
||||
maskClosable: propTypes.bool.def(true),
|
||||
getContainer: {
|
||||
type: [Object, String] as PropType<any>,
|
||||
},
|
||||
@@ -78,10 +43,7 @@ export const basicProps = {
|
||||
type: [Function, Object] as PropType<any>,
|
||||
default: null,
|
||||
},
|
||||
triggerWindowResize: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
destroyOnClose: Boolean as PropType<boolean>,
|
||||
triggerWindowResize: propTypes.bool,
|
||||
destroyOnClose: propTypes.bool,
|
||||
...footerProps,
|
||||
};
|
||||
|
@@ -75,7 +75,7 @@ export interface DrawerProps extends DrawerFooterProps {
|
||||
* @type ScrollContainerOptions
|
||||
*/
|
||||
scrollOptions?: ScrollContainerOptions;
|
||||
closeFunc?: () => Promise<void>;
|
||||
closeFunc?: () => Promise<any>;
|
||||
triggerWindowResize?: boolean;
|
||||
/**
|
||||
* Whether a close (x) button is visible on top right of the Drawer dialog or not.
|
||||
|
@@ -6,12 +6,15 @@ import type {
|
||||
UseDrawerInnerReturnType,
|
||||
} from './types';
|
||||
|
||||
import { ref, getCurrentInstance, onUnmounted, unref, reactive, watchEffect, nextTick } from 'vue';
|
||||
import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw } from 'vue';
|
||||
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
const dataTransferRef = reactive<any>({});
|
||||
|
||||
/**
|
||||
* @description: Applicable to separate drawer and call outside
|
||||
*/
|
||||
@@ -19,21 +22,23 @@ export function useDrawer(): UseDrawerReturnType {
|
||||
if (!getCurrentInstance()) {
|
||||
throw new Error('Please put useDrawer function in the setup function!');
|
||||
}
|
||||
|
||||
const drawerRef = ref<DrawerInstance | null>(null);
|
||||
const loadedRef = ref<boolean | null>(false);
|
||||
const loadedRef = ref<Nullable<boolean>>(false);
|
||||
const uidRef = ref<string>('');
|
||||
|
||||
function getDrawer(drawerInstance: DrawerInstance, uuid: string) {
|
||||
uidRef.value = uuid;
|
||||
function register(drawerInstance: DrawerInstance, uuid: string) {
|
||||
isProdMode() &&
|
||||
onUnmounted(() => {
|
||||
tryOnUnmounted(() => {
|
||||
drawerRef.value = null;
|
||||
loadedRef.value = null;
|
||||
dataTransferRef[unref(uidRef)] = null;
|
||||
});
|
||||
|
||||
if (unref(loadedRef) && isProdMode() && drawerInstance === unref(drawerRef)) {
|
||||
return;
|
||||
}
|
||||
uidRef.value = uuid;
|
||||
drawerRef.value = drawerInstance;
|
||||
loadedRef.value = true;
|
||||
}
|
||||
@@ -55,37 +60,46 @@ export function useDrawer(): UseDrawerReturnType {
|
||||
getInstance().setDrawerProps({
|
||||
visible: visible,
|
||||
});
|
||||
if (data) {
|
||||
dataTransferRef[unref(uidRef)] = openOnSet
|
||||
? {
|
||||
...data,
|
||||
__t__: Date.now(),
|
||||
if (!data) return;
|
||||
|
||||
if (openOnSet) {
|
||||
dataTransferRef[unref(uidRef)] = null;
|
||||
dataTransferRef[unref(uidRef)] = data;
|
||||
return;
|
||||
}
|
||||
: data;
|
||||
const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), data);
|
||||
if (!equal) {
|
||||
dataTransferRef[unref(uidRef)] = data;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return [getDrawer, methods];
|
||||
return [register, methods];
|
||||
}
|
||||
|
||||
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
|
||||
const drawerInstanceRef = ref<DrawerInstance | null>(null);
|
||||
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
|
||||
const currentInstall = getCurrentInstance();
|
||||
const uidRef = ref<string>('');
|
||||
|
||||
if (!currentInstall) {
|
||||
throw new Error('instance is undefined!');
|
||||
throw new Error('useDrawerInner instance is undefined!');
|
||||
}
|
||||
|
||||
const getInstance = () => {
|
||||
const instance = unref(drawerInstanceRef);
|
||||
if (!instance) {
|
||||
throw new Error('instance is undefined!');
|
||||
throw new Error('useDrawerInner instance is undefined!');
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
const register = (modalInstance: DrawerInstance, uuid: string) => {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
drawerInstanceRef.value = null;
|
||||
});
|
||||
|
||||
uidRef.value = uuid;
|
||||
drawerInstanceRef.value = modalInstance;
|
||||
currentInstall.emit('register', modalInstance);
|
||||
|
@@ -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(() => {
|
||||
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 getProps = computed(
|
||||
(): ModalProps => {
|
||||
const opt = {
|
||||
...props,
|
||||
...((unref(propsRef) || {}) as any),
|
||||
...unref(getMergeProps),
|
||||
visible: unref(visibleRef),
|
||||
title: undefined,
|
||||
};
|
||||
const { wrapClassName = '' } = opt;
|
||||
const className = unref(fullScreenRef) ? `${wrapClassName} fullscreen-modal` : wrapClassName;
|
||||
|
||||
return {
|
||||
...opt,
|
||||
wrapClassName: className,
|
||||
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;
|
||||
}
|
||||
return (
|
||||
<div class="custom-close-icon">
|
||||
{unref(fullScreenRef) ? (
|
||||
|
||||
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={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,58 +18,43 @@ 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(() => {
|
||||
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,9 +40,24 @@
|
||||
height: 95%;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
> 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) {
|
||||
display: inline-block;
|
||||
@@ -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(),
|
||||
if (!data) return;
|
||||
|
||||
if (openOnSet) {
|
||||
dataTransferRef[unref(uidRef)] = null;
|
||||
dataTransferRef[unref(uidRef)] = data;
|
||||
return;
|
||||
}
|
||||
: data;
|
||||
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);
|
||||
});
|
||||
}
|
@@ -65,7 +65,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tryTsxEmit((instance) => {
|
||||
tryTsxEmit<any>((instance) => {
|
||||
instance.wrap = unref(wrapElRef);
|
||||
});
|
||||
|
||||
|
@@ -1,20 +1,19 @@
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import { computed, Ref, onMounted, unref, ref, nextTick, ComputedRef, watch } from 'vue';
|
||||
|
||||
import { injectModal } from '/@/components/Modal/src/provideModal';
|
||||
|
||||
import { getViewportOffset } from '/@/utils/domUtils';
|
||||
import { isBoolean } from '/@/utils/is';
|
||||
|
||||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
|
||||
import { useProps } from './useProps';
|
||||
import { useModalContext } from '/@/components/Modal';
|
||||
|
||||
export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) {
|
||||
const { propsRef } = useProps(refProps);
|
||||
|
||||
const tableHeightRef: Ref<number | null> = ref(null);
|
||||
|
||||
const redoModalHeight = injectModal();
|
||||
const modalFn = useModalContext();
|
||||
|
||||
watch(
|
||||
() => unref(propsRef).canResize,
|
||||
@@ -93,7 +92,7 @@ export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRe
|
||||
tableHeightRef.value =
|
||||
tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
|
||||
// 解决表格放modal内的时候,modal自适应高度计算问题
|
||||
redoModalHeight && redoModalHeight();
|
||||
modalFn?.redoModalHeight?.();
|
||||
}, 16);
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import './index.less';
|
||||
|
||||
import type { ReplaceFields, TreeItem, Keys, CheckKeys } from './types';
|
||||
import type { ReplaceFields, TreeItem, Keys, CheckKeys, TreeActionType } from './types';
|
||||
|
||||
import { defineComponent, reactive, computed, unref, ref, watchEffect, CSSProperties } from 'vue';
|
||||
import { Tree } from 'ant-design-vue';
|
||||
@@ -124,7 +124,6 @@ export default defineComponent({
|
||||
title: () => (
|
||||
<span class={`${prefixCls}-title`}>
|
||||
<span class={`${prefixCls}__content`} style={unref(getContentStyle)}>
|
||||
{' '}
|
||||
{titleField && anyItem[titleField]}
|
||||
</span>
|
||||
<span class={`${prefixCls}__actions`}> {renderAction(item)}</span>
|
||||
@@ -183,7 +182,7 @@ export default defineComponent({
|
||||
state.checkedKeys = props.checkedKeys;
|
||||
});
|
||||
|
||||
tryTsxEmit((currentInstance) => {
|
||||
tryTsxEmit<TreeActionType>((currentInstance) => {
|
||||
currentInstance.setExpandedKeys = setExpandedKeys;
|
||||
currentInstance.getExpandedKeys = getExpandedKeys;
|
||||
currentInstance.setSelectedKeys = setSelectedKeys;
|
||||
|
@@ -10,7 +10,7 @@ export function useTree(
|
||||
getReplaceFields: ComputedRef<ReplaceFields>
|
||||
) {
|
||||
// 更新节点
|
||||
function updateNodeByKey(key: string, node: TreeItem, list: TreeItem[]) {
|
||||
function updateNodeByKey(key: string, node: TreeItem, list?: TreeItem[]) {
|
||||
if (!key) return;
|
||||
const treeData = list || unref(treeDataRef);
|
||||
const { key: keyField, children: childrenField } = unref(getReplaceFields);
|
||||
@@ -75,7 +75,7 @@ export function useTree(
|
||||
}
|
||||
|
||||
// 删除节点
|
||||
function deleteNodeByKey(key: string, list: TreeItem[]) {
|
||||
function deleteNodeByKey(key: string, list?: TreeItem[]) {
|
||||
if (!key) return;
|
||||
const treeData = list || unref(treeDataRef);
|
||||
const { key: keyField, children: childrenField } = unref(getReplaceFields);
|
||||
|
@@ -6,6 +6,7 @@ import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import './DragVerify.less';
|
||||
import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
|
||||
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
|
||||
import type { DragVerifyActionType } from './types';
|
||||
export default defineComponent({
|
||||
name: 'BaseDargVerify',
|
||||
props: basicProps,
|
||||
@@ -210,7 +211,7 @@ export default defineComponent({
|
||||
contentEl.style.width = unref(getContentStyleRef).width;
|
||||
}
|
||||
|
||||
tryTsxEmit((instance) => {
|
||||
tryTsxEmit<DragVerifyActionType>((instance) => {
|
||||
instance.resume = resume;
|
||||
});
|
||||
|
||||
|
@@ -46,7 +46,7 @@ export function useRootSetting() {
|
||||
unref(getRootSetting).contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED
|
||||
);
|
||||
|
||||
function setRootSetting(setting: RootSetting) {
|
||||
function setRootSetting(setting: Partial<RootSetting>) {
|
||||
appStore.commitProjectConfigState(setting);
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
onUnmounted,
|
||||
nextTick,
|
||||
reactive,
|
||||
ComponentInternalInstance,
|
||||
} from 'vue';
|
||||
|
||||
export function explicitComputed<T, S>(source: WatchSource<S>, fn: () => T) {
|
||||
@@ -29,8 +30,10 @@ export function tryOnUnmounted(fn: () => Promise<void> | void) {
|
||||
getCurrentInstance() && onUnmounted(fn);
|
||||
}
|
||||
|
||||
export function tryTsxEmit(fn: (_instance: any) => Promise<void> | void) {
|
||||
const instance = getCurrentInstance();
|
||||
export function tryTsxEmit<T extends any = ComponentInternalInstance>(
|
||||
fn: (_instance: T) => Promise<void> | void
|
||||
) {
|
||||
const instance = getCurrentInstance() as any;
|
||||
instance && fn.call(null, instance);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user