提交 81baf1d5 编写于 作者: V vben

perf: perf modal and drawer

上级 819127e8
...@@ -18,12 +18,18 @@ ...@@ -18,12 +18,18 @@
- 缓存可以配置是否加密,默认生产环境开启 Aes 加密 - 缓存可以配置是否加密,默认生产环境开启 Aes 加密
- 新增标签页拖拽排序 - 新增标签页拖拽排序
- 新增 LayoutFooter.默认显示,可以在配置内关闭
### ⚡ Performance Improvements
- 优化`Modal`组件全屏动画不流畅问题
### 🐛 Bug Fixes ### 🐛 Bug Fixes
- 修复 tree 文本超出挡住操作按钮问题 - 修复 tree 文本超出挡住操作按钮问题
- 修复通过 useRedo 刷新页面参数丢失问题 - 修复通过 useRedo 刷新页面参数丢失问题
- 修复表单校验先设置在校验及控制台错误信息问题 - 修复表单校验先设置在校验及控制台错误信息问题
- 修复`modal``drawer`组件传递数组参数问题
### 🎫 Chores ### 🎫 Chores
......
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 * from './src/types';
export { useDrawer, useDrawerInner } from './src/useDrawer';
export const BasicDrawer = withInstall(BasicDrawerLib);
import './index.less'; import './index.less';
import type { DrawerInstance, DrawerProps } from './types'; import type { DrawerInstance, DrawerProps } from './types';
import type { CSSProperties } from 'vue';
import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw } from 'vue'; import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw } from 'vue';
import { Drawer, Row, Col, Button } from 'ant-design-vue'; import { Drawer, Row, Col, Button } from 'ant-design-vue';
...@@ -9,53 +10,96 @@ import { BasicTitle } from '/@/components/Basic'; ...@@ -9,53 +10,96 @@ import { BasicTitle } from '/@/components/Basic';
import { FullLoading } from '/@/components/Loading/index'; import { FullLoading } from '/@/components/Loading/index';
import { LeftOutlined } from '@ant-design/icons-vue'; import { LeftOutlined } from '@ant-design/icons-vue';
import { basicProps } from './props'; import { useI18n } from '/@/hooks/web/useI18n';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import { isFunction, isNumber } from '/@/utils/is'; import { isFunction, isNumber } from '/@/utils/is';
import { buildUUID } from '/@/utils/uuid';
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils';
import { useI18n } from '/@/hooks/web/useI18n'; import { tryTsxEmit } from '/@/utils/helper/vueHelper';
import { basicProps } from './props';
const prefixCls = 'basic-drawer'; const prefixCls = 'basic-drawer';
export default defineComponent({ export default defineComponent({
// inheritAttrs: false, inheritAttrs: false,
props: basicProps, props: basicProps,
emits: ['visible-change', 'ok', 'close', 'register'], emits: ['visible-change', 'ok', 'close', 'register'],
setup(props, { slots, emit, attrs }) { setup(props, { slots, emit, attrs }) {
const scrollRef = ref<ElRef>(null); const scrollRef = ref<ElRef>(null);
const visibleRef = ref(false); const visibleRef = ref(false);
const propsRef = ref<Partial<DrawerProps> | null>(null); const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
const { t } = useI18n('component.drawer'); const { t } = useI18n('component.drawer');
const getMergeProps = computed((): any => { const getMergeProps = computed(
return deepMerge(toRaw(props), unref(propsRef)); (): DrawerProps => {
}); return deepMerge(toRaw(props), unref(propsRef));
}
const getProps = computed(() => { );
const opt: any = {
placement: 'right',
...attrs,
...props,
...(unref(propsRef) as any),
visible: unref(visibleRef),
};
opt.title = undefined;
if (opt.isDetail) { const getProps = computed(
if (!opt.width) { (): DrawerProps => {
opt.width = '100%'; const opt = {
} placement: 'right',
opt.wrapClassName = opt.wrapClassName ...attrs,
? `${opt.wrapClassName} ${prefixCls}__detail` ...unref(getMergeProps),
: `${prefixCls}__detail`; visible: unref(visibleRef),
if (!opt.getContainer) { };
opt.getContainer = '.layout-content'; opt.title = undefined;
const { isDetail, width, wrapClassName, getContainer } = opt;
if (isDetail) {
if (!width) {
opt.width = '100%';
}
const detailCls = `${prefixCls}__detail`;
opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
if (!getContainer) {
// TODO type error?
opt.getContainer = '.layout-content' as any;
}
} }
return opt as DrawerProps;
} }
return opt; );
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(() => { watchEffect(() => {
...@@ -74,22 +118,13 @@ export default defineComponent({ ...@@ -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 // Cancel event
async function onClose(e: any) { async function onClose(e: ChangeEvent) {
const { closeFunc } = unref(getProps); const { closeFunc } = unref(getProps);
emit('close', e); emit('close', e);
if (closeFunc && isFunction(closeFunc)) { if (closeFunc && isFunction(closeFunc)) {
const res = await closeFunc(); const res = await closeFunc();
res && (visibleRef.value = false); visibleRef.value = !res;
return; return;
} }
visibleRef.value = false; visibleRef.value = false;
...@@ -98,12 +133,16 @@ export default defineComponent({ ...@@ -98,12 +133,16 @@ export default defineComponent({
function setDrawerProps(props: Partial<DrawerProps>): void { function setDrawerProps(props: Partial<DrawerProps>): void {
// Keep the last setDrawerProps // Keep the last setDrawerProps
propsRef.value = deepMerge(unref(propsRef) || {}, props); propsRef.value = deepMerge(unref(propsRef) || {}, props);
if (Reflect.has(props, 'visible')) { if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible; visibleRef.value = !!props.visible;
} }
} }
function renderFooter() { function renderFooter() {
if (slots?.footer) {
return getSlot(slots, 'footer');
}
const { const {
showCancelBtn, showCancelBtn,
cancelButtonProps, cancelButtonProps,
...@@ -114,65 +153,64 @@ export default defineComponent({ ...@@ -114,65 +153,64 @@ export default defineComponent({
okButtonProps, okButtonProps,
confirmLoading, confirmLoading,
showFooter, showFooter,
}: DrawerProps = unref(getProps); } = unref(getProps);
if (!showFooter) {
return null;
}
return ( return (
getSlot(slots, 'footer') || <div class={`${prefixCls}__footer`}>
(showFooter && ( {getSlot(slots, 'insertFooter')}
<div class={`${prefixCls}__footer`}> {showCancelBtn && (
{getSlot(slots, 'insertFooter')} <Button {...cancelButtonProps} onClick={onClose} class="mr-2">
{() => cancelText}
{showCancelBtn && ( </Button>
<Button {...cancelButtonProps} onClick={onClose} class="mr-2"> )}
{() => cancelText} {getSlot(slots, 'centerFooter')}
</Button> {showOkBtn && (
)} <Button
{getSlot(slots, 'centerFooter')} type={okType}
{showOkBtn && ( onClick={() => {
<Button emit('ok');
type={okType} }}
onClick={() => { {...okButtonProps}
emit('ok'); loading={confirmLoading}
}} >
{...okButtonProps} {() => okText}
loading={confirmLoading} </Button>
> )}
{() => okText} {getSlot(slots, 'appendFooter')}
</Button> </div>
)}
{getSlot(slots, 'appendFooter')}
</div>
))
); );
} }
function renderHeader() { function renderHeader() {
if (slots?.title) {
return getSlot(slots, 'title');
}
const { title } = unref(getMergeProps); const { title } = unref(getMergeProps);
return props.isDetail ? (
getSlot(slots, 'title') || ( if (!props.isDetail) {
<Row type="flex" align="middle" class={`${prefixCls}__detail-header`}> return <BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>;
{() => ( }
<> return (
{props.showDetailBack && ( <Row type="flex" align="middle" class={`${prefixCls}__detail-header`}>
<Button size="small" type="link" onClick={onClose}> {() => (
{() => <LeftOutlined />} <>
</Button> {props.showDetailBack && (
)} <Button size="small" type="link" onClick={onClose}>
{() => <LeftOutlined />}
{title && ( </Button>
<Col style="flex:1" class={[`${prefixCls}__detail-title`, 'ellipsis', 'px-2']}> )}
{() => title} {title && (
</Col> <Col style="flex:1" class={[`${prefixCls}__detail-title`, 'ellipsis', 'px-2']}>
)} {() => title}
</Col>
{getSlot(slots, 'titleToolbar')} )}
</> {getSlot(slots, 'titleToolbar')}
)} </>
</Row> )}
) </Row>
) : (
<BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>
); );
} }
...@@ -180,41 +218,20 @@ export default defineComponent({ ...@@ -180,41 +218,20 @@ export default defineComponent({
setDrawerProps: setDrawerProps, setDrawerProps: setDrawerProps,
}; };
const uuid = buildUUID(); tryTsxEmit((instance) => {
emit('register', drawerInstance, uuid); emit('register', drawerInstance, instance.uid);
});
return () => { return () => {
const footerHeight = unref(getFooterHeight);
return ( return (
<Drawer <Drawer class={prefixCls} onClose={onClose} {...unref(getBindValues)}>
class={prefixCls}
onClose={onClose}
{...{
...attrs,
...unref(getProps),
}}
>
{{ {{
title: () => renderHeader(), title: () => renderHeader(),
default: () => ( default: () => (
<> <>
<div <div ref={scrollRef} style={unref(getScrollContentStyle)}>
ref={scrollRef} <FullLoading absolute tip={t('loadingText')} class={unref(getLoading)} />
{...attrs} {getSlot(slots)}
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> </div>
{renderFooter()} {renderFooter()}
</> </>
......
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes';
const { t } = useI18n('component.drawer'); const { t } = useI18n('component.drawer');
export const footerProps = { export const footerProps = {
confirmLoading: Boolean as PropType<boolean>, confirmLoading: propTypes.bool,
/** /**
* @description: Show close button * @description: Show close button
*/ */
showCancelBtn: { showCancelBtn: propTypes.bool.def(true),
type: Boolean as PropType<boolean>,
default: true,
},
cancelButtonProps: Object as PropType<any>, cancelButtonProps: Object as PropType<any>,
cancelText: { cancelText: propTypes.string.def(t('cancelText')),
type: String as PropType<string>,
default: t('cancelText'),
},
/** /**
* @description: Show confirmation button * @description: Show confirmation button
*/ */
showOkBtn: { showOkBtn: propTypes.bool.def(true),
type: Boolean as PropType<boolean>, okButtonProps: propTypes.any,
default: true, okText: propTypes.string.def(t('okText')),
}, okType: propTypes.string.def('primary'),
okButtonProps: Object as PropType<any>, showFooter: propTypes.bool,
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,
},
footerHeight: { footerHeight: {
type: [String, Number] as PropType<string | number>, type: [String, Number] as PropType<string | number>,
default: 60, default: 60,
}, },
}; };
export const basicProps = { export const basicProps = {
isDetail: { isDetail: propTypes.bool,
type: Boolean as PropType<boolean>, title: propTypes.string.def(''),
default: false, showDetailBack: propTypes.bool.def(true),
}, visible: propTypes.bool,
title: { loading: propTypes.bool,
type: String as PropType<string>, maskClosable: propTypes.bool.def(true),
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,
},
getContainer: { getContainer: {
type: [Object, String] as PropType<any>, type: [Object, String] as PropType<any>,
}, },
...@@ -78,10 +43,7 @@ export const basicProps = { ...@@ -78,10 +43,7 @@ export const basicProps = {
type: [Function, Object] as PropType<any>, type: [Function, Object] as PropType<any>,
default: null, default: null,
}, },
triggerWindowResize: { triggerWindowResize: propTypes.bool,
type: Boolean as PropType<boolean>, destroyOnClose: propTypes.bool,
default: false,
},
destroyOnClose: Boolean as PropType<boolean>,
...footerProps, ...footerProps,
}; };
...@@ -75,7 +75,7 @@ export interface DrawerProps extends DrawerFooterProps { ...@@ -75,7 +75,7 @@ export interface DrawerProps extends DrawerFooterProps {
* @type ScrollContainerOptions * @type ScrollContainerOptions
*/ */
scrollOptions?: ScrollContainerOptions; scrollOptions?: ScrollContainerOptions;
closeFunc?: () => Promise<void>; closeFunc?: () => Promise<any>;
triggerWindowResize?: boolean; triggerWindowResize?: boolean;
/** /**
* Whether a close (x) button is visible on top right of the Drawer dialog or not. * Whether a close (x) button is visible on top right of the Drawer dialog or not.
......
...@@ -6,12 +6,15 @@ import type { ...@@ -6,12 +6,15 @@ import type {
UseDrawerInnerReturnType, UseDrawerInnerReturnType,
} from './types'; } 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 { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
import { isEqual } from 'lodash-es';
const dataTransferRef = reactive<any>({}); const dataTransferRef = reactive<any>({});
/** /**
* @description: Applicable to separate drawer and call outside * @description: Applicable to separate drawer and call outside
*/ */
...@@ -19,21 +22,23 @@ export function useDrawer(): UseDrawerReturnType { ...@@ -19,21 +22,23 @@ export function useDrawer(): UseDrawerReturnType {
if (!getCurrentInstance()) { if (!getCurrentInstance()) {
throw new Error('Please put useDrawer function in the setup function!'); throw new Error('Please put useDrawer function in the setup function!');
} }
const drawerRef = ref<DrawerInstance | null>(null); const drawerRef = ref<DrawerInstance | null>(null);
const loadedRef = ref<boolean | null>(false); const loadedRef = ref<Nullable<boolean>>(false);
const uidRef = ref<string>(''); const uidRef = ref<string>('');
function getDrawer(drawerInstance: DrawerInstance, uuid: string) { function register(drawerInstance: DrawerInstance, uuid: string) {
uidRef.value = uuid;
isProdMode() && isProdMode() &&
onUnmounted(() => { tryOnUnmounted(() => {
drawerRef.value = null; drawerRef.value = null;
loadedRef.value = null; loadedRef.value = null;
dataTransferRef[unref(uidRef)] = null; dataTransferRef[unref(uidRef)] = null;
}); });
if (unref(loadedRef) && isProdMode() && drawerInstance === unref(drawerRef)) { if (unref(loadedRef) && isProdMode() && drawerInstance === unref(drawerRef)) {
return; return;
} }
uidRef.value = uuid;
drawerRef.value = drawerInstance; drawerRef.value = drawerInstance;
loadedRef.value = true; loadedRef.value = true;
} }
...@@ -55,37 +60,46 @@ export function useDrawer(): UseDrawerReturnType { ...@@ -55,37 +60,46 @@ export function useDrawer(): UseDrawerReturnType {
getInstance().setDrawerProps({ getInstance().setDrawerProps({
visible: visible, visible: visible,
}); });
if (data) { if (!data) return;
dataTransferRef[unref(uidRef)] = openOnSet
? { if (openOnSet) {
...data, dataTransferRef[unref(uidRef)] = null;
__t__: Date.now(), 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 => { export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
const drawerInstanceRef = ref<DrawerInstance | null>(null); const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
const currentInstall = getCurrentInstance(); const currentInstall = getCurrentInstance();
const uidRef = ref<string>(''); const uidRef = ref<string>('');
if (!currentInstall) { if (!currentInstall) {
throw new Error('instance is undefined!'); throw new Error('useDrawerInner instance is undefined!');
} }
const getInstance = () => { const getInstance = () => {
const instance = unref(drawerInstanceRef); const instance = unref(drawerInstanceRef);
if (!instance) { if (!instance) {
throw new Error('instance is undefined!'); throw new Error('useDrawerInner instance is undefined!');
} }
return instance; return instance;
}; };
const register = (modalInstance: DrawerInstance, uuid: string) => { const register = (modalInstance: DrawerInstance, uuid: string) => {
isProdMode() &&
tryOnUnmounted(() => {
drawerInstanceRef.value = null;
});
uidRef.value = uuid; uidRef.value = uuid;
drawerInstanceRef.value = modalInstance; drawerInstanceRef.value = modalInstance;
currentInstall.emit('register', modalInstance); currentInstall.emit('register', modalInstance);
......
import './src/index.less'; import './src/index.less';
export { default as BasicModal } from './src/BasicModal'; import BasicModalLib from './src/BasicModal';
export { default as Modal } from './src/Modal'; import { withInstall } from '../util';
export { useModalContext } from './src/useModalContext';
export { useModal, useModalInner } from './src/useModal'; export { useModal, useModalInner } from './src/useModal';
export * from './src/types'; export * from './src/types';
export const BasicModal = withInstall(BasicModalLib);
import type { ModalProps, ModalMethods } from './types'; 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 Modal from './Modal';
import { Button } from '/@/components/Button'; import { Button } from '/@/components/Button';
...@@ -11,10 +11,10 @@ import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant- ...@@ -11,10 +11,10 @@ import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-
import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper'; import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils';
import { buildUUID } from '/@/utils/uuid'; import { tryTsxEmit } from '/@/utils/helper/vueHelper';
import { basicProps } from './props'; import { basicProps } from './props';
// import { triggerWindowResize } from '@/utils/event/triggerWindowResizeEvent'; import { useFullScreen } from './useFullScreen';
export default defineComponent({ export default defineComponent({
name: 'BasicModal', name: 'BasicModal',
props: basicProps, props: basicProps,
...@@ -26,31 +26,41 @@ export default defineComponent({ ...@@ -26,31 +26,41 @@ export default defineComponent({
// modal Bottom and top height // modal Bottom and top height
const extHeightRef = ref(0); const extHeightRef = ref(0);
// Unexpanded height of the popup // Unexpanded height of the popup
const formerHeightRef = ref(0);
const fullScreenRef = ref(false);
// Custom title component: get title // Custom title component: get title
const getMergeProps = computed(() => { const getMergeProps = computed(
return { (): ModalProps => {
...props, return {
...(unref(propsRef) as any), ...props,
}; ...(unref(propsRef) as any),
};
}
);
const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
modalWrapperRef,
extHeightRef,
wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
}); });
// modal component does not need title // modal component does not need title
const getProps = computed((): any => { const getProps = computed(
const opt = { (): ModalProps => {
...props, const opt = {
...((unref(propsRef) || {}) as any), ...unref(getMergeProps),
visible: unref(visibleRef), visible: unref(visibleRef),
title: undefined, title: undefined,
}; };
const { wrapClassName = '' } = opt;
const className = unref(fullScreenRef) ? `${wrapClassName} fullscreen-modal` : wrapClassName; return {
return { ...opt,
...opt, wrapClassName: unref(getWrapClassName),
wrapClassName: className, };
}; }
);
const getModalBindValue = computed((): any => {
return { ...attrs, ...unref(getProps) };
}); });
watchEffect(() => { watchEffect(() => {
...@@ -80,7 +90,35 @@ export default defineComponent({ ...@@ -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() { function renderContent() {
type OmitWrapperType = Omit<
ModalProps,
'fullScreen' | 'modalFooterHeight' | 'visible' | 'loading'
>;
const { useWrapper, loading, wrapperProps } = unref(getProps); const { useWrapper, loading, wrapperProps } = unref(getProps);
if (!useWrapper) return getSlot(slots); if (!useWrapper) return getSlot(slots);
...@@ -93,7 +131,7 @@ export default defineComponent({ ...@@ -93,7 +131,7 @@ export default defineComponent({
loading={loading} loading={loading}
visible={unref(visibleRef)} visible={unref(visibleRef)}
modalFooterHeight={showFooter} modalFooterHeight={showFooter}
{...wrapperProps} {...((wrapperProps as unknown) as OmitWrapperType)}
onGetExtHeight={(height: number) => { onGetExtHeight={(height: number) => {
extHeightRef.value = height; extHeightRef.value = height;
}} }}
...@@ -106,18 +144,6 @@ export default defineComponent({ ...@@ -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() { function renderFooter() {
const { const {
...@@ -162,64 +188,37 @@ export default defineComponent({ ...@@ -162,64 +188,37 @@ export default defineComponent({
*/ */
function renderClose() { function renderClose() {
const { canFullscreen } = unref(getProps); 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 ( return (
<div class="custom-close-icon"> <div class={cls}>
{unref(fullScreenRef) ? ( {canFullscreen && fullScreen}
<FullscreenExitOutlined role="full" onClick={handleFullScreen} />
) : (
<FullscreenOutlined role="close" onClick={handleFullScreen} />
)}
<CloseOutlined onClick={handleCancel} /> <CloseOutlined onClick={handleCancel} />
</div> </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 = { const modalMethods: ModalMethods = {
setModalProps, setModalProps,
}; };
const uuid = buildUUID(); tryTsxEmit((instance) => {
emit('register', modalMethods, uuid); emit('register', modalMethods, instance.uid);
});
return () => ( return () => (
<Modal onCancel={handleCancel} {...{ ...attrs, ...props, ...unref(getProps) }}> <Modal onCancel={handleCancel} {...unref(getModalBindValue)}>
{{ {{
footer: () => renderFooter(), footer: () => renderFooter(),
closeIcon: () => renderClose(), closeIcon: () => renderClose(),
......
import { Modal } from 'ant-design-vue'; import { Modal } from 'ant-design-vue';
import { defineComponent, watchEffect } from 'vue'; import { defineComponent, toRefs } from 'vue';
import { basicProps } from './props'; import { basicProps } from './props';
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useModalDragMove } from './useModalDrag';
import { extendSlots } from '/@/utils/helper/tsxHelper'; import { extendSlots } from '/@/utils/helper/tsxHelper';
export default defineComponent({ export default defineComponent({
...@@ -9,99 +9,12 @@ export default defineComponent({ ...@@ -9,99 +9,12 @@ export default defineComponent({
inheritAttrs: false, inheritAttrs: false,
props: basicProps, props: basicProps,
setup(props, { attrs, slots }) { setup(props, { attrs, slots }) {
const getStyle = (dom: any, attr: any) => { const { visible, draggable, destroyOnClose } = toRefs(props);
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');
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(() => { useModalDragMove({
if (!props.visible) { visible,
return; destroyOnClose,
} draggable,
useTimeoutFn(() => {
handleDrag();
}, 30);
}); });
return () => { return () => {
......
import type { PropType } from 'vue';
import type { ModalWrapperProps } from './types'; import type { ModalWrapperProps } from './types';
import type { CSSProperties } from 'vue';
import { import {
defineComponent, defineComponent,
...@@ -18,59 +18,44 @@ import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; ...@@ -18,59 +18,44 @@ import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import { useElResize } from '/@/hooks/event/useElResize'; import { useElResize } from '/@/hooks/event/useElResize';
import { provideModal } from './provideModal'; import { propTypes } from '/@/utils/propTypes';
import { createModalContext } from './useModalContext';
export default defineComponent({ export default defineComponent({
name: 'ModalWrapper', name: 'ModalWrapper',
props: { props: {
loading: { loading: propTypes.bool,
type: Boolean as PropType<boolean>, modalHeaderHeight: propTypes.number.def(50),
default: false, modalFooterHeight: propTypes.number.def(54),
}, minHeight: propTypes.number.def(200),
modalHeaderHeight: { footerOffset: propTypes.number.def(0),
type: Number as PropType<number>, visible: propTypes.bool,
default: 50, fullScreen: propTypes.bool,
},
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,
},
}, },
emits: ['heightChange', 'getExtHeight'], emits: ['heightChange', 'getExtHeight'],
setup(props: ModalWrapperProps, { slots, emit }) { setup(props: ModalWrapperProps, { slots, emit }) {
const wrapperRef = ref<HTMLElement | null>(null); const wrapperRef = ref<ElRef>(null);
const spinRef = ref<ComponentRef>(null); const spinRef = ref<ComponentRef>(null);
const realHeightRef = ref(0); const realHeightRef = ref(0);
// 重试次数
// let tryCount = 0;
let stopElResizeFn: Fn = () => {}; let stopElResizeFn: Fn = () => {};
provideModal(setModalHeight); useWindowSizeFn(setModalHeight);
const wrapStyle = computed(() => { createModalContext({
return { redoModalHeight: setModalHeight,
minHeight: `${props.minHeight}px`,
height: `${unref(realHeightRef)}px`,
overflow: 'auto',
};
}); });
const wrapStyle = computed(
(): CSSProperties => {
return {
minHeight: `${props.minHeight}px`,
height: `${unref(realHeightRef)}px`,
overflow: 'auto',
};
}
);
watchEffect(() => { watchEffect(() => {
setModalHeight(); setModalHeight();
}); });
...@@ -92,8 +77,6 @@ export default defineComponent({ ...@@ -92,8 +77,6 @@ export default defineComponent({
stopElResizeFn && stopElResizeFn(); stopElResizeFn && stopElResizeFn();
}); });
useWindowSizeFn(setModalHeight);
async function setModalHeight() { async function setModalHeight() {
// 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度 // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
// 加上这个,就必须在使用的时候传递父级的visible // 加上这个,就必须在使用的时候传递父级的visible
...@@ -107,9 +90,8 @@ export default defineComponent({ ...@@ -107,9 +90,8 @@ export default defineComponent({
try { try {
const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement; const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
if (!modalDom) { if (!modalDom) return;
return;
}
const modalRect = getComputedStyle(modalDom).top; const modalRect = getComputedStyle(modalDom).top;
const modalTop = Number.parseInt(modalRect); const modalTop = Number.parseInt(modalRect);
let maxHeight = let maxHeight =
...@@ -135,11 +117,12 @@ export default defineComponent({ ...@@ -135,11 +117,12 @@ export default defineComponent({
if (props.fullScreen) { if (props.fullScreen) {
realHeightRef.value = realHeightRef.value =
window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 6; window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight;
} else { } else {
realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30; realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30;
} }
emit('heightChange', unref(realHeightRef)); emit('heightChange', unref(realHeightRef));
nextTick(() => { nextTick(() => {
const el = spinEl.$el; const el = spinEl.$el;
if (el) { if (el) {
...@@ -154,8 +137,10 @@ export default defineComponent({ ...@@ -154,8 +137,10 @@ export default defineComponent({
function listenElResize() { function listenElResize() {
const wrapper = unref(wrapperRef); const wrapper = unref(wrapperRef);
if (!wrapper) return; if (!wrapper) return;
const container = wrapper.querySelector('.ant-spin-container'); const container = wrapper.querySelector('.ant-spin-container');
if (!container) return; if (!container) return;
const [start, stop] = useElResize(container, () => { const [start, stop] = useElResize(container, () => {
setModalHeight(); setModalHeight();
}); });
......
...@@ -9,6 +9,11 @@ ...@@ -9,6 +9,11 @@
bottom: 0 !important; bottom: 0 !important;
left: 0 !important; left: 0 !important;
width: 100% !important; width: 100% !important;
height: 100%;
&-content {
height: 100%;
}
} }
} }
...@@ -35,8 +40,23 @@ ...@@ -35,8 +40,23 @@
height: 95%; height: 95%;
align-items: center; align-items: center;
> * { > span {
margin-left: 12px; 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) { & span:nth-child(1) {
...@@ -76,7 +96,7 @@ ...@@ -76,7 +96,7 @@
} }
&-footer { &-footer {
padding: 10px 26px 26px 16px; // padding: 10px 26px 26px 16px;
button + button { button + button {
margin-left: 10px; margin-left: 10px;
......
...@@ -2,66 +2,38 @@ import type { PropType } from 'vue'; ...@@ -2,66 +2,38 @@ import type { PropType } from 'vue';
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes';
const { t } = useI18n('component.modal'); const { t } = useI18n('component.modal');
export const modalProps = { export const modalProps = {
visible: Boolean as PropType<boolean>, visible: propTypes.bool,
// open drag // open drag
draggable: { draggable: propTypes.bool.def(true),
type: Boolean as PropType<boolean>, centered: propTypes.bool,
default: true, cancelText: propTypes.string.def(t('cancelText')),
}, okText: propTypes.string.def(t('okText')),
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'),
},
closeFunc: Function as PropType<() => Promise<boolean>>, closeFunc: Function as PropType<() => Promise<boolean>>,
}; };
export const basicProps = Object.assign({}, modalProps, { export const basicProps = Object.assign({}, modalProps, {
// Can it be full screen // Can it be full screen
canFullscreen: { canFullscreen: propTypes.bool.def(true),
type: Boolean as PropType<boolean>,
default: true,
},
// After enabling the wrapper, the bottom can be increased in height // After enabling the wrapper, the bottom can be increased in height
wrapperFooterOffset: { wrapperFooterOffset: propTypes.number.def(0),
type: Number as PropType<number>,
default: 0,
},
// Warm reminder message // Warm reminder message
helpMessage: [String, Array] as PropType<string | string[]>, helpMessage: [String, Array] as PropType<string | string[]>,
// Whether to setting wrapper // Whether to setting wrapper
useWrapper: { useWrapper: propTypes.bool.def(true),
type: Boolean as PropType<boolean>, loading: propTypes.bool,
default: true,
},
loading: {
type: Boolean as PropType<boolean>,
default: false,
},
/** /**
* @description: Show close button * @description: Show close button
*/ */
showCancelBtn: { showCancelBtn: propTypes.bool.def(true),
type: Boolean as PropType<boolean>,
default: true,
},
/** /**
* @description: Show confirmation button * @description: Show confirmation button
*/ */
showOkBtn: { showOkBtn: propTypes.bool.def(true),
type: Boolean as PropType<boolean>,
default: true,
},
wrapperProps: Object as PropType<any>, wrapperProps: Object as PropType<any>,
......
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 { ...@@ -8,9 +8,11 @@ export interface ModalMethods {
} }
export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void; export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void;
export interface ReturnMethods extends ModalMethods { export interface ReturnMethods extends ModalMethods {
openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void; openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void;
} }
export type UseModalReturnType = [RegisterFn, ReturnMethods]; export type UseModalReturnType = [RegisterFn, ReturnMethods];
export interface ReturnInnerMethods extends ModalMethods { export interface ReturnInnerMethods extends ModalMethods {
...@@ -18,6 +20,7 @@ export interface ReturnInnerMethods extends ModalMethods { ...@@ -18,6 +20,7 @@ export interface ReturnInnerMethods extends ModalMethods {
changeLoading: (loading: boolean) => void; changeLoading: (loading: boolean) => void;
changeOkLoading: (loading: boolean) => void; changeOkLoading: (loading: boolean) => void;
} }
export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]; export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods];
export interface ModalProps { export interface ModalProps {
......
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 { ...@@ -5,9 +5,21 @@ import type {
ReturnMethods, ReturnMethods,
UseModalInnerReturnType, UseModalInnerReturnType,
} from './types'; } 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 { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { isEqual } from 'lodash-es';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
const dataTransferRef = reactive<any>({}); const dataTransferRef = reactive<any>({});
/** /**
...@@ -20,6 +32,7 @@ export function useModal(): UseModalReturnType { ...@@ -20,6 +32,7 @@ export function useModal(): UseModalReturnType {
const modalRef = ref<Nullable<ModalMethods>>(null); const modalRef = ref<Nullable<ModalMethods>>(null);
const loadedRef = ref<Nullable<boolean>>(false); const loadedRef = ref<Nullable<boolean>>(false);
const uidRef = ref<string>(''); const uidRef = ref<string>('');
function register(modalMethod: ModalMethods, uuid: string) { function register(modalMethod: ModalMethods, uuid: string) {
uidRef.value = uuid; uidRef.value = uuid;
...@@ -52,13 +65,16 @@ export function useModal(): UseModalReturnType { ...@@ -52,13 +65,16 @@ export function useModal(): UseModalReturnType {
visible: visible, visible: visible,
}); });
if (data) { if (!data) return;
dataTransferRef[unref(uidRef)] = openOnSet
? { if (openOnSet) {
...data, dataTransferRef[unref(uidRef)] = null;
__t__: Date.now(), 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 { ...@@ -66,7 +82,7 @@ export function useModal(): UseModalReturnType {
} }
export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
const modalInstanceRef = ref<ModalMethods | null>(null); const modalInstanceRef = ref<Nullable<ModalMethods>>(null);
const currentInstall = getCurrentInstance(); const currentInstall = getCurrentInstance();
const uidRef = ref<string>(''); const uidRef = ref<string>('');
...@@ -83,6 +99,11 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { ...@@ -83,6 +99,11 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
}; };
const register = (modalInstance: ModalMethods, uuid: string) => { const register = (modalInstance: ModalMethods, uuid: string) => {
isProdMode() &&
tryOnUnmounted(() => {
modalInstanceRef.value = null;
});
uidRef.value = uuid; uidRef.value = uuid;
modalInstanceRef.value = modalInstance; modalInstanceRef.value = modalInstance;
currentInstall.emit('register', modalInstance); currentInstall.emit('register', modalInstance);
......
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);
}
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({ ...@@ -65,7 +65,7 @@ export default defineComponent({
} }
onMounted(() => { onMounted(() => {
tryTsxEmit((instance) => { tryTsxEmit<any>((instance) => {
instance.wrap = unref(wrapElRef); instance.wrap = unref(wrapElRef);
}); });
......
import type { BasicTableProps } from '../types/table'; import type { BasicTableProps } from '../types/table';
import { computed, Ref, onMounted, unref, ref, nextTick, ComputedRef, watch } from 'vue'; import { computed, Ref, onMounted, unref, ref, nextTick, ComputedRef, watch } from 'vue';
import { injectModal } from '/@/components/Modal/src/provideModal';
import { getViewportOffset } from '/@/utils/domUtils'; import { getViewportOffset } from '/@/utils/domUtils';
import { isBoolean } from '/@/utils/is'; import { isBoolean } from '/@/utils/is';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { useProps } from './useProps'; import { useProps } from './useProps';
import { useModalContext } from '/@/components/Modal';
export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) { export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) {
const { propsRef } = useProps(refProps); const { propsRef } = useProps(refProps);
const tableHeightRef: Ref<number | null> = ref(null); const tableHeightRef: Ref<number | null> = ref(null);
const redoModalHeight = injectModal(); const modalFn = useModalContext();
watch( watch(
() => unref(propsRef).canResize, () => unref(propsRef).canResize,
...@@ -93,7 +92,7 @@ export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRe ...@@ -93,7 +92,7 @@ export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRe
tableHeightRef.value = tableHeightRef.value =
tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value; tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
// 解决表格放modal内的时候,modal自适应高度计算问题 // 解决表格放modal内的时候,modal自适应高度计算问题
redoModalHeight && redoModalHeight(); modalFn?.redoModalHeight?.();
}, 16); }, 16);
} }
......
import './index.less'; 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 { defineComponent, reactive, computed, unref, ref, watchEffect, CSSProperties } from 'vue';
import { Tree } from 'ant-design-vue'; import { Tree } from 'ant-design-vue';
...@@ -124,7 +124,6 @@ export default defineComponent({ ...@@ -124,7 +124,6 @@ export default defineComponent({
title: () => ( title: () => (
<span class={`${prefixCls}-title`}> <span class={`${prefixCls}-title`}>
<span class={`${prefixCls}__content`} style={unref(getContentStyle)}> <span class={`${prefixCls}__content`} style={unref(getContentStyle)}>
{' '}
{titleField && anyItem[titleField]} {titleField && anyItem[titleField]}
</span> </span>
<span class={`${prefixCls}__actions`}> {renderAction(item)}</span> <span class={`${prefixCls}__actions`}> {renderAction(item)}</span>
...@@ -183,7 +182,7 @@ export default defineComponent({ ...@@ -183,7 +182,7 @@ export default defineComponent({
state.checkedKeys = props.checkedKeys; state.checkedKeys = props.checkedKeys;
}); });
tryTsxEmit((currentInstance) => { tryTsxEmit<TreeActionType>((currentInstance) => {
currentInstance.setExpandedKeys = setExpandedKeys; currentInstance.setExpandedKeys = setExpandedKeys;
currentInstance.getExpandedKeys = getExpandedKeys; currentInstance.getExpandedKeys = getExpandedKeys;
currentInstance.setSelectedKeys = setSelectedKeys; currentInstance.setSelectedKeys = setSelectedKeys;
......
...@@ -10,7 +10,7 @@ export function useTree( ...@@ -10,7 +10,7 @@ export function useTree(
getReplaceFields: ComputedRef<ReplaceFields> getReplaceFields: ComputedRef<ReplaceFields>
) { ) {
// 更新节点 // 更新节点
function updateNodeByKey(key: string, node: TreeItem, list: TreeItem[]) { function updateNodeByKey(key: string, node: TreeItem, list?: TreeItem[]) {
if (!key) return; if (!key) return;
const treeData = list || unref(treeDataRef); const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getReplaceFields); const { key: keyField, children: childrenField } = unref(getReplaceFields);
...@@ -75,7 +75,7 @@ export function useTree( ...@@ -75,7 +75,7 @@ export function useTree(
} }
// 删除节点 // 删除节点
function deleteNodeByKey(key: string, list: TreeItem[]) { function deleteNodeByKey(key: string, list?: TreeItem[]) {
if (!key) return; if (!key) return;
const treeData = list || unref(treeDataRef); const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getReplaceFields); const { key: keyField, children: childrenField } = unref(getReplaceFields);
......
...@@ -6,6 +6,7 @@ import { getSlot } from '/@/utils/helper/tsxHelper'; ...@@ -6,6 +6,7 @@ import { getSlot } from '/@/utils/helper/tsxHelper';
import './DragVerify.less'; import './DragVerify.less';
import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
import { tryTsxEmit } from '/@/utils/helper/vueHelper'; import { tryTsxEmit } from '/@/utils/helper/vueHelper';
import type { DragVerifyActionType } from './types';
export default defineComponent({ export default defineComponent({
name: 'BaseDargVerify', name: 'BaseDargVerify',
props: basicProps, props: basicProps,
...@@ -210,7 +211,7 @@ export default defineComponent({ ...@@ -210,7 +211,7 @@ export default defineComponent({
contentEl.style.width = unref(getContentStyleRef).width; contentEl.style.width = unref(getContentStyleRef).width;
} }
tryTsxEmit((instance) => { tryTsxEmit<DragVerifyActionType>((instance) => {
instance.resume = resume; instance.resume = resume;
}); });
......
...@@ -46,7 +46,7 @@ export function useRootSetting() { ...@@ -46,7 +46,7 @@ export function useRootSetting() {
unref(getRootSetting).contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED unref(getRootSetting).contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED
); );
function setRootSetting(setting: RootSetting) { function setRootSetting(setting: Partial<RootSetting>) {
appStore.commitProjectConfigState(setting); appStore.commitProjectConfigState(setting);
} }
......
...@@ -7,6 +7,7 @@ import { ...@@ -7,6 +7,7 @@ import {
onUnmounted, onUnmounted,
nextTick, nextTick,
reactive, reactive,
ComponentInternalInstance,
} from 'vue'; } from 'vue';
export function explicitComputed<T, S>(source: WatchSource<S>, fn: () => T) { export function explicitComputed<T, S>(source: WatchSource<S>, fn: () => T) {
...@@ -29,8 +30,10 @@ export function tryOnUnmounted(fn: () => Promise<void> | void) { ...@@ -29,8 +30,10 @@ export function tryOnUnmounted(fn: () => Promise<void> | void) {
getCurrentInstance() && onUnmounted(fn); getCurrentInstance() && onUnmounted(fn);
} }
export function tryTsxEmit(fn: (_instance: any) => Promise<void> | void) { export function tryTsxEmit<T extends any = ComponentInternalInstance>(
const instance = getCurrentInstance(); fn: (_instance: T) => Promise<void> | void
) {
const instance = getCurrentInstance() as any;
instance && fn.call(null, instance); instance && fn.call(null, instance);
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册