提交 1c1755cf 编写于 作者: V Vben

fix(modal): ensure that the full screen height is calculated correctly

上级 639520ad
......@@ -2,9 +2,15 @@
### ✨ Features
- `Cropper` 头像裁剪新增圆形裁剪功能
- 新增头像上传组件
- `useDrawer`新增`closeDrawer`函数
- **CropperImage** `Cropper` 头像裁剪新增圆形裁剪功能
- **CropperAvatar** 新增头像上传组件
- **Drawer** `useDrawer`新增`closeDrawer`函数
### 🐛 Bug Fixes
- **Modal** 修复全屏高度计算错误
- **PageWrapper** 修复高度计算问题
- 修复后台模式下,Iframe 路由错误
## 2.4.2(2021-06-10)
......
......@@ -163,7 +163,7 @@
function setDrawerProps(props: Partial<DrawerProps>): void {
// Keep the last setDrawerProps
propsRef.value = deepMerge((unref(propsRef) as any) || {}, props);
propsRef.value = deepMerge(unref(propsRef), props);
if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible;
......
......@@ -5,7 +5,6 @@ import type {
DrawerProps,
UseDrawerInnerReturnType,
} from './typing';
import {
ref,
getCurrentInstance,
......@@ -16,11 +15,9 @@ import {
toRaw,
computed,
} from 'vue';
import { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is';
import { tryOnUnmounted } from '@vueuse/core';
import { isEqual } from 'lodash-es';
import { error } from '/@/utils/log';
......
import { withInstall } from '/@/utils';
import './src/index.less';
import BasicModal from './src/BasicModal.vue';
import basicModal from './src/BasicModal.vue';
export { BasicModal };
export const BasicModal = withInstall(basicModal);
export { useModalContext } from './src/hooks/useModalContext';
export { useModal, useModalInner } from './src/hooks/useModal';
export * from './src/types';
export * from './src/typing';
......@@ -49,7 +49,7 @@
</Modal>
</template>
<script lang="ts">
import type { ModalProps, ModalMethods } from './types';
import type { ModalProps, ModalMethods } from './typing';
import {
defineComponent,
......@@ -62,20 +62,17 @@
getCurrentInstance,
nextTick,
} from 'vue';
import Modal from './components/Modal';
import ModalWrapper from './components/ModalWrapper.vue';
import ModalClose from './components/ModalClose.vue';
import ModalFooter from './components/ModalFooter.vue';
import ModalHeader from './components/ModalHeader.vue';
import { isFunction } from '/@/utils/is';
import { deepMerge } from '/@/utils';
import { basicProps } from './props';
import { useFullScreen } from './hooks/useModalFullScreen';
import { omit } from 'lodash-es';
export default defineComponent({
name: 'BasicModal',
components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader },
......@@ -189,7 +186,7 @@
*/
function setModalProps(props: Partial<ModalProps>): void {
// Keep the last setModalProps
propsRef.value = deepMerge(unref(propsRef) || {}, props);
propsRef.value = deepMerge(unref(propsRef), props);
if (!Reflect.has(props, 'visible')) return;
visibleRef.value = !!props.visible;
}
......
......@@ -20,7 +20,6 @@ export default defineComponent({
return () => {
const propsData = { ...unref(attrs), ...props } as Recordable;
return <Modal {...propsData}>{extendSlots(slots)}</Modal>;
};
},
......
......@@ -2,7 +2,6 @@
<div :class="getClass">
<template v-if="canFullscreen">
<FullscreenExitOutlined role="full" @click="handleFullScreen" v-if="fullScreen" />
<FullscreenOutlined role="close" @click="handleFullScreen" v-else />
</template>
<CloseOutlined @click="handleCancel" />
......@@ -12,14 +11,13 @@
import { defineComponent, computed } from 'vue';
import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'ModalClose',
components: { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined },
props: {
canFullscreen: propTypes.bool.def(true),
fullScreen: propTypes.bool,
canFullscreen: { type: Boolean, default: true },
fullScreen: { type: Boolean },
},
emits: ['cancel', 'fullscreen'],
setup(props, { emit }) {
......@@ -38,6 +36,7 @@
function handleCancel(e: Event) {
emit('cancel', e);
}
function handleFullScreen(e: Event) {
e?.stopPropagation();
e?.preventDefault();
......
......@@ -33,6 +33,7 @@
function handleCancel(e: Event) {
emit('cancel', e);
}
return { handleOk, handleCancel };
},
});
......
......@@ -8,7 +8,6 @@
import { defineComponent } from 'vue';
import { BasicTitle } from '/@/components/Basic';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'BasicModalHeader',
components: { BasicTitle },
......@@ -16,7 +15,7 @@
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
},
title: propTypes.string,
title: { type: String },
},
});
</script>
......@@ -6,9 +6,7 @@
</ScrollContainer>
</template>
<script lang="ts">
import type { ModalWrapperProps } from '../types';
import type { CSSProperties } from 'vue';
import {
defineComponent,
computed,
......@@ -20,31 +18,31 @@
nextTick,
onUnmounted,
} from 'vue';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { ScrollContainer } from '/@/components/Container';
import { propTypes } from '/@/utils/propTypes';
import { createModalContext } from '../hooks/useModalContext';
import { useMutationObserver } from '@vueuse/core';
const props = {
loading: { type: Boolean },
useWrapper: { type: Boolean, default: true },
modalHeaderHeight: { type: Number, default: 57 },
modalFooterHeight: { type: Number, default: 74 },
minHeight: { type: Number, default: 200 },
height: { type: Number },
footerOffset: { type: Number, default: 0 },
visible: { type: Boolean },
fullScreen: { type: Boolean },
loadingTip: { type: String },
};
export default defineComponent({
name: 'ModalWrapper',
components: { ScrollContainer },
inheritAttrs: false,
props: {
loading: propTypes.bool,
useWrapper: propTypes.bool.def(true),
modalHeaderHeight: propTypes.number.def(57),
modalFooterHeight: propTypes.number.def(74),
minHeight: propTypes.number.def(200),
height: propTypes.number,
footerOffset: propTypes.number.def(0),
visible: propTypes.bool,
fullScreen: propTypes.bool,
loadingTip: propTypes.string,
},
props,
emits: ['height-change', 'ext-height'],
setup(props: ModalWrapperProps, { emit }) {
setup(props, { emit }) {
const wrapperRef = ref<ComponentRef>(null);
const spinRef = ref<ElRef>(null);
const realHeightRef = ref(0);
......@@ -56,6 +54,17 @@
useWindowSizeFn(setModalHeight.bind(null, false));
useMutationObserver(
spinRef,
() => {
setModalHeight();
},
{
attributes: true,
subtree: true,
}
);
createModalContext({
redoModalHeight: setModalHeight,
});
......@@ -63,8 +72,7 @@
const spinStyle = computed((): CSSProperties => {
return {
minHeight: `${props.minHeight}px`,
// padding 28
maxHeight: `${unref(realHeightRef)}px`,
[props.fullScreen ? 'height' : 'maxHeight']: `${unref(realHeightRef)}px`,
};
});
......@@ -87,7 +95,6 @@
onMounted(() => {
const { modalHeaderHeight, modalFooterHeight } = props;
emit('ext-height', modalHeaderHeight + modalFooterHeight);
// listenElResize();
});
onUnmounted(() => {
......
......@@ -4,8 +4,7 @@ import type {
ModalProps,
ReturnMethods,
UseModalInnerReturnType,
} from '../types';
} from '../typing';
import {
ref,
onUnmounted,
......@@ -20,10 +19,10 @@ import { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is';
import { isEqual } from 'lodash-es';
import { tryOnUnmounted } from '@vueuse/core';
import { error } from '/@/utils/log';
import { computed } from 'vue';
const dataTransferRef = reactive<any>({});
const dataTransfer = reactive<any>({});
const visibleData = reactive<{ [key: number]: boolean }>({});
......@@ -31,29 +30,31 @@ const visibleData = reactive<{ [key: number]: boolean }>({});
* @description: Applicable to independent modal and call outside
*/
export function useModal(): UseModalReturnType {
const modalRef = ref<Nullable<ModalMethods>>(null);
const loadedRef = ref<Nullable<boolean>>(false);
const uidRef = ref<string>('');
const modal = ref<Nullable<ModalMethods>>(null);
const loaded = ref<Nullable<boolean>>(false);
const uid = ref<string>('');
function register(modalMethod: ModalMethods, uuid: string) {
uidRef.value = uuid;
if (!getCurrentInstance()) {
throw new Error('useModal() can only be used inside setup() or functional components!');
}
uid.value = uuid;
isProdMode() &&
onUnmounted(() => {
modalRef.value = null;
loadedRef.value = false;
dataTransferRef[unref(uidRef)] = null;
modal.value = null;
loaded.value = false;
dataTransfer[unref(uid)] = null;
});
if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return;
if (unref(loaded) && isProdMode() && modalMethod === unref(modal)) return;
modalRef.value = modalMethod;
modal.value = modalMethod;
modalMethod.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible;
};
}
const getInstance = () => {
const instance = unref(modalRef);
const instance = unref(modal);
if (!instance) {
error('useModal instance is undefined!');
}
......@@ -66,7 +67,7 @@ export function useModal(): UseModalReturnType {
},
getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)];
return visibleData[~~unref(uid)];
}),
redoModalHeight: () => {
......@@ -79,15 +80,15 @@ export function useModal(): UseModalReturnType {
});
if (!data) return;
const id = unref(uid);
if (openOnSet) {
dataTransferRef[unref(uidRef)] = null;
dataTransferRef[unref(uidRef)] = toRaw(data);
dataTransfer[id] = null;
dataTransfer[id] = toRaw(data);
return;
}
const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), toRaw(data));
const equal = isEqual(toRaw(dataTransfer[id]), toRaw(data));
if (!equal) {
dataTransferRef[unref(uidRef)] = toRaw(data);
dataTransfer[id] = toRaw(data);
}
},
......@@ -103,9 +104,6 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
const currentInstance = getCurrentInstance();
const uidRef = ref<string>('');
// currentInstall.type.emits = [...currentInstall.type.emits, 'register'];
// Object.assign(currentInstall.type.emits, ['register']);
const getInstance = () => {
const instance = unref(modalInstanceRef);
if (!instance) {
......@@ -125,7 +123,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
};
watchEffect(() => {
const data = dataTransferRef[unref(uidRef)];
const data = dataTransfer[unref(uidRef)];
if (!data) return;
if (!callbackFn || !isFunction(callbackFn)) return;
nextTick(() => {
......
......@@ -12,7 +12,6 @@ export function useFullScreen(context: UseFullScreenContext) {
const getWrapClassName = computed(() => {
const clsName = unref(context.wrapClassName) || '';
return unref(fullScreenRef) ? `fullscreen-modal ${clsName} ` : unref(clsName);
});
......
import type { PropType, CSSProperties } from 'vue';
import type { ModalWrapperProps } from './typing';
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes, VueNode } from '/@/utils/propTypes';
import type { ModalWrapperProps } from './types';
const { t } = useI18n();
export const modalProps = {
visible: propTypes.bool,
scrollTop: propTypes.bool.def(true),
height: propTypes.number,
minHeight: propTypes.number,
visible: { type: Boolean },
scrollTop: { type: Boolean, default: true },
height: { type: Number },
minHeight: { type: Number },
// open drag
draggable: propTypes.bool.def(true),
centered: propTypes.bool,
cancelText: propTypes.string.def(t('common.cancelText')),
okText: propTypes.string.def(t('common.okText')),
draggable: { type: Boolean, default: true },
centered: { type: Boolean },
cancelText: { type: String, default: t('common.cancelText') },
okText: { type: String, default: t('common.okText') },
closeFunc: Function as PropType<() => Promise<boolean>>,
};
export const basicProps = Object.assign({}, modalProps, {
defaultFullscreen: propTypes.bool,
defaultFullscreen: { type: Boolean },
// Can it be full screen
canFullscreen: propTypes.bool.def(true),
canFullscreen: { type: Boolean, default: true },
// After enabling the wrapper, the bottom can be increased in height
wrapperFooterOffset: propTypes.number.def(0),
wrapperFooterOffset: { type: Number, default: 0 },
// Warm reminder message
helpMessage: [String, Array] as PropType<string | string[]>,
// Whether to setting wrapper
useWrapper: propTypes.bool.def(true),
loading: propTypes.bool,
loadingTip: propTypes.string,
useWrapper: { type: Boolean, default: true },
loading: { type: Boolean },
loadingTip: { type: String },
/**
* @description: Show close button
*/
showCancelBtn: propTypes.bool.def(true),
showCancelBtn: { type: Boolean, default: true },
/**
* @description: Show confirmation button
*/
showOkBtn: propTypes.bool.def(true),
showOkBtn: { type: Boolean, default: true },
wrapperProps: Object as PropType<Partial<ModalWrapperProps>>,
......@@ -47,38 +46,38 @@ export const basicProps = Object.assign({}, modalProps, {
bodyStyle: Object as PropType<CSSProperties>,
closable: propTypes.bool.def(true),
closable: { type: Boolean, default: true },
closeIcon: Object as PropType<VueNode>,
confirmLoading: propTypes.bool,
confirmLoading: { type: Boolean },
destroyOnClose: propTypes.bool,
destroyOnClose: { type: Boolean },
footer: Object as PropType<VueNode>,
getContainer: Function as PropType<() => any>,
mask: propTypes.bool.def(true),
mask: { type: Boolean, default: true },
maskClosable: propTypes.bool.def(true),
keyboard: propTypes.bool.def(true),
maskClosable: { type: Boolean, default: true },
keyboard: { type: Boolean, default: true },
maskStyle: Object as PropType<CSSProperties>,
okType: propTypes.string.def('primary'),
okType: { type: String, default: 'primary' },
okButtonProps: Object as PropType<ButtonProps>,
cancelButtonProps: Object as PropType<ButtonProps>,
title: propTypes.string,
title: { type: String },
visible: propTypes.bool,
visible: { type: Boolean },
width: [String, Number] as PropType<string | number>,
wrapClassName: propTypes.string,
wrapClassName: { type: String },
zIndex: propTypes.number,
zIndex: { type: Number },
});
export { default as QrCode } from './src/Qrcode.vue';
import { withInstall } from '/@/utils';
import qrCode from './src/Qrcode.vue';
export * from './src/types';
export const QrCode = withInstall(qrCode);
export * from './src/typing';
......@@ -8,7 +8,7 @@
import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus';
import { toDataURL } from 'qrcode';
import { downloadByUrl } from '/@/utils/file/download';
import { QrcodeDoneEventParams } from './types';
import { QrcodeDoneEventParams } from './typing';
export default defineComponent({
name: 'QrCode',
......
import { toCanvas } from 'qrcode';
import type { QRCodeRenderersOptions } from 'qrcode';
import { RenderQrCodeParams, ContentType } from './types';
import { RenderQrCodeParams, ContentType } from './typing';
export const renderQrCode = ({ canvas, content, width = 0, options = {} }: RenderQrCodeParams) => {
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content);
......
import { isString } from '/@/utils/is';
import { RenderQrCodeParams, LogoType } from './types';
import { RenderQrCodeParams, LogoType } from './typing';
export const drawLogo = ({ canvas, logo }: RenderQrCodeParams) => {
if (!logo) {
return new Promise((resolve) => {
resolve((canvas as HTMLCanvasElement).toDataURL());
});
}
const canvasWidth = (canvas as HTMLCanvasElement).width;
const {
logoSize = 0.15,
......
// 参考 qr-code-with-logo 进行ts版本修改
import { toCanvas } from './toCanvas';
export * from './types';
export * from './typing';
export { toCanvas };
import { renderQrCode } from './drawCanvas';
import { drawLogo } from './drawLogo';
import { RenderQrCodeParams } from './types';
import { RenderQrCodeParams } from './typing';
export const toCanvas = (options: RenderQrCodeParams) => {
return renderQrCode(options)
.then(() => {
......
......@@ -95,7 +95,7 @@
&__entry {
position: relative;
//height: 240px;
padding: 130px 30px 60px 30px;
padding: 130px 30px 30px 30px;
border-radius: 10px;
}
......
......@@ -8,10 +8,7 @@
<Alert message="自适应高度/显示footer" show-icon />
<a-button type="primary" class="my-4" @click="openDrawer3(true)"> 打开Drawer </a-button>
<Alert
message="内外数据交互,外部通过 transferModalData 发送,内部通过 receiveDrawerDataRef 接收。该数据具有响应式"
show-icon
/>
<Alert message="内外数据交互" show-icon />
<a-button type="primary" class="my-4" @click="send"> 打开Drawer并传递数据 </a-button>
<Alert message="详情页模式" show-icon />
<a-button type="primary" class="my-4" @click="openDrawer5(true)"> 打开详情Drawer </a-button>
......
......@@ -14,10 +14,7 @@
<Alert message="自适应高度" show-icon />
<a-button type="primary" class="my-4" @click="openModal3"> 打开弹窗 </a-button>
<Alert
message="内外数据交互,外部通过 transferModalData 发送,内部通过 receiveDrawerDataRef 接收。该数据具有响应式"
show-icon
/>
<Alert message="内外数据交互" show-icon />
<a-button type="primary" class="my-4" @click="send"> 打开弹窗并传递数据 </a-button>
<Modal1 @register="register1" :minHeight="100" />
......
import type {
ComponentRenderProxy,
VNode,
VNodeChild,
ComponentPublicInstance,
FunctionalComponent,
PropType as VuePropType,
......@@ -23,6 +24,7 @@ declare global {
// vue
declare type PropType<T> = VuePropType<T>;
declare type VueNode = VNodeChild | JSX.Element;
export type Writable<T> = {
-readonly [P in keyof T]: T[P];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册