Commit 81baf1d5c4606aab83c0e65397ce4b090c2e4e08

Authored by vben
1 parent 819127e8

perf: perf modal and drawer

CHANGELOG.zh_CN.md
... ... @@ -18,12 +18,18 @@
18 18  
19 19 - 缓存可以配置是否加密,默认生产环境开启 Aes 加密
20 20 - 新增标签页拖拽排序
  21 +- 新增 LayoutFooter.默认显示,可以在配置内关闭
  22 +
  23 +### ⚡ Performance Improvements
  24 +
  25 +- 优化`Modal`组件全屏动画不流畅问题
21 26  
22 27 ### 🐛 Bug Fixes
23 28  
24 29 - 修复 tree 文本超出挡住操作按钮问题
25 30 - 修复通过 useRedo 刷新页面参数丢失问题
26 31 - 修复表单校验先设置在校验及控制台错误信息问题
  32 +- 修复`modal`与`drawer`组件传递数组参数问题
27 33  
28 34 ### 🎫 Chores
29 35  
... ...
src/components/Drawer/index.ts
1   -export { default as BasicDrawer } from './src/BasicDrawer';
  1 +import BasicDrawerLib from './src/BasicDrawer';
  2 +import { withInstall } from '../util';
2 3  
3   -export { useDrawer, useDrawerInner } from './src/useDrawer';
4 4 export * from './src/types';
  5 +export { useDrawer, useDrawerInner } from './src/useDrawer';
  6 +export const BasicDrawer = withInstall(BasicDrawerLib);
... ...
src/components/Drawer/src/BasicDrawer.tsx
1 1 import './index.less';
2 2  
3 3 import type { DrawerInstance, DrawerProps } from './types';
  4 +import type { CSSProperties } from 'vue';
4 5  
5 6 import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw } from 'vue';
6 7 import { Drawer, Row, Col, Button } from 'ant-design-vue';
... ... @@ -9,53 +10,96 @@ import { BasicTitle } from '/@/components/Basic';
9 10 import { FullLoading } from '/@/components/Loading/index';
10 11 import { LeftOutlined } from '@ant-design/icons-vue';
11 12  
12   -import { basicProps } from './props';
  13 +import { useI18n } from '/@/hooks/web/useI18n';
13 14  
14 15 import { getSlot } from '/@/utils/helper/tsxHelper';
15 16 import { isFunction, isNumber } from '/@/utils/is';
16   -import { buildUUID } from '/@/utils/uuid';
17 17 import { deepMerge } from '/@/utils';
18   -import { useI18n } from '/@/hooks/web/useI18n';
  18 +import { tryTsxEmit } from '/@/utils/helper/vueHelper';
  19 +
  20 +import { basicProps } from './props';
19 21  
20 22 const prefixCls = 'basic-drawer';
21 23 export default defineComponent({
22   - // inheritAttrs: false,
  24 + inheritAttrs: false,
23 25 props: basicProps,
24 26 emits: ['visible-change', 'ok', 'close', 'register'],
25 27 setup(props, { slots, emit, attrs }) {
26 28 const scrollRef = ref<ElRef>(null);
27   -
28 29 const visibleRef = ref(false);
29   - const propsRef = ref<Partial<DrawerProps> | null>(null);
  30 + const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
30 31  
31 32 const { t } = useI18n('component.drawer');
32 33  
33   - const getMergeProps = computed((): any => {
34   - return deepMerge(toRaw(props), unref(propsRef));
35   - });
36   -
37   - const getProps = computed(() => {
38   - const opt: any = {
39   - placement: 'right',
40   - ...attrs,
41   - ...props,
42   - ...(unref(propsRef) as any),
43   - visible: unref(visibleRef),
44   - };
45   - opt.title = undefined;
  34 + const getMergeProps = computed(
  35 + (): DrawerProps => {
  36 + return deepMerge(toRaw(props), unref(propsRef));
  37 + }
  38 + );
46 39  
47   - if (opt.isDetail) {
48   - if (!opt.width) {
49   - opt.width = '100%';
50   - }
51   - opt.wrapClassName = opt.wrapClassName
52   - ? `${opt.wrapClassName} ${prefixCls}__detail`
53   - : `${prefixCls}__detail`;
54   - if (!opt.getContainer) {
55   - opt.getContainer = '.layout-content';
  40 + const getProps = computed(
  41 + (): DrawerProps => {
  42 + const opt = {
  43 + placement: 'right',
  44 + ...attrs,
  45 + ...unref(getMergeProps),
  46 + visible: unref(visibleRef),
  47 + };
  48 + opt.title = undefined;
  49 + const { isDetail, width, wrapClassName, getContainer } = opt;
  50 + if (isDetail) {
  51 + if (!width) {
  52 + opt.width = '100%';
  53 + }
  54 + const detailCls = `${prefixCls}__detail`;
  55 +
  56 + opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
  57 +
  58 + if (!getContainer) {
  59 + // TODO type error?
  60 + opt.getContainer = '.layout-content' as any;
  61 + }
56 62 }
  63 + return opt as DrawerProps;
57 64 }
58   - return opt;
  65 + );
  66 +
  67 + const getBindValues = computed(
  68 + (): DrawerProps => {
  69 + return {
  70 + ...attrs,
  71 + ...unref(getProps),
  72 + };
  73 + }
  74 + );
  75 +
  76 + // Custom implementation of the bottom button,
  77 + const getFooterHeight = computed(() => {
  78 + const { footerHeight, showFooter } = unref(getProps);
  79 +
  80 + if (showFooter && footerHeight) {
  81 + return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
  82 + }
  83 + return `0px`;
  84 + });
  85 +
  86 + const getScrollContentStyle = computed(
  87 + (): CSSProperties => {
  88 + const footerHeight = unref(getFooterHeight);
  89 + return {
  90 + position: 'relative',
  91 + height: `calc(100% - ${footerHeight})`,
  92 + overflow: 'auto',
  93 + padding: '16px',
  94 + paddingBottom: '30px',
  95 + };
  96 + }
  97 + );
  98 +
  99 + const getLoading = computed(() => {
  100 + return {
  101 + hidden: !unref(getProps).loading,
  102 + };
59 103 });
60 104  
61 105 watchEffect(() => {
... ... @@ -74,22 +118,13 @@ export default defineComponent({
74 118 }
75 119 );
76 120  
77   - // Custom implementation of the bottom button,
78   - const getFooterHeight = computed(() => {
79   - const { footerHeight, showFooter }: DrawerProps = unref(getProps);
80   - if (showFooter && footerHeight) {
81   - return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
82   - }
83   - return `0px`;
84   - });
85   -
86 121 // Cancel event
87   - async function onClose(e: any) {
  122 + async function onClose(e: ChangeEvent) {
88 123 const { closeFunc } = unref(getProps);
89 124 emit('close', e);
90 125 if (closeFunc && isFunction(closeFunc)) {
91 126 const res = await closeFunc();
92   - res && (visibleRef.value = false);
  127 + visibleRef.value = !res;
93 128 return;
94 129 }
95 130 visibleRef.value = false;
... ... @@ -98,12 +133,16 @@ export default defineComponent({
98 133 function setDrawerProps(props: Partial<DrawerProps>): void {
99 134 // Keep the last setDrawerProps
100 135 propsRef.value = deepMerge(unref(propsRef) || {}, props);
  136 +
101 137 if (Reflect.has(props, 'visible')) {
102 138 visibleRef.value = !!props.visible;
103 139 }
104 140 }
105 141  
106 142 function renderFooter() {
  143 + if (slots?.footer) {
  144 + return getSlot(slots, 'footer');
  145 + }
107 146 const {
108 147 showCancelBtn,
109 148 cancelButtonProps,
... ... @@ -114,65 +153,64 @@ export default defineComponent({
114 153 okButtonProps,
115 154 confirmLoading,
116 155 showFooter,
117   - }: DrawerProps = unref(getProps);
  156 + } = unref(getProps);
  157 + if (!showFooter) {
  158 + return null;
  159 + }
118 160  
119 161 return (
120   - getSlot(slots, 'footer') ||
121   - (showFooter && (
122   - <div class={`${prefixCls}__footer`}>
123   - {getSlot(slots, 'insertFooter')}
124   -
125   - {showCancelBtn && (
126   - <Button {...cancelButtonProps} onClick={onClose} class="mr-2">
127   - {() => cancelText}
128   - </Button>
129   - )}
130   - {getSlot(slots, 'centerFooter')}
131   - {showOkBtn && (
132   - <Button
133   - type={okType}
134   - onClick={() => {
135   - emit('ok');
136   - }}
137   - {...okButtonProps}
138   - loading={confirmLoading}
139   - >
140   - {() => okText}
141   - </Button>
142   - )}
143   -
144   - {getSlot(slots, 'appendFooter')}
145   - </div>
146   - ))
  162 + <div class={`${prefixCls}__footer`}>
  163 + {getSlot(slots, 'insertFooter')}
  164 + {showCancelBtn && (
  165 + <Button {...cancelButtonProps} onClick={onClose} class="mr-2">
  166 + {() => cancelText}
  167 + </Button>
  168 + )}
  169 + {getSlot(slots, 'centerFooter')}
  170 + {showOkBtn && (
  171 + <Button
  172 + type={okType}
  173 + onClick={() => {
  174 + emit('ok');
  175 + }}
  176 + {...okButtonProps}
  177 + loading={confirmLoading}
  178 + >
  179 + {() => okText}
  180 + </Button>
  181 + )}
  182 + {getSlot(slots, 'appendFooter')}
  183 + </div>
147 184 );
148 185 }
149 186  
150 187 function renderHeader() {
  188 + if (slots?.title) {
  189 + return getSlot(slots, 'title');
  190 + }
151 191 const { title } = unref(getMergeProps);
152   - return props.isDetail ? (
153   - getSlot(slots, 'title') || (
154   - <Row type="flex" align="middle" class={`${prefixCls}__detail-header`}>
155   - {() => (
156   - <>
157   - {props.showDetailBack && (
158   - <Button size="small" type="link" onClick={onClose}>
159   - {() => <LeftOutlined />}
160   - </Button>
161   - )}
162   -
163   - {title && (
164   - <Col style="flex:1" class={[`${prefixCls}__detail-title`, 'ellipsis', 'px-2']}>
165   - {() => title}
166   - </Col>
167   - )}
168   -
169   - {getSlot(slots, 'titleToolbar')}
170   - </>
171   - )}
172   - </Row>
173   - )
174   - ) : (
175   - <BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>
  192 +
  193 + if (!props.isDetail) {
  194 + return <BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>;
  195 + }
  196 + return (
  197 + <Row type="flex" align="middle" class={`${prefixCls}__detail-header`}>
  198 + {() => (
  199 + <>
  200 + {props.showDetailBack && (
  201 + <Button size="small" type="link" onClick={onClose}>
  202 + {() => <LeftOutlined />}
  203 + </Button>
  204 + )}
  205 + {title && (
  206 + <Col style="flex:1" class={[`${prefixCls}__detail-title`, 'ellipsis', 'px-2']}>
  207 + {() => title}
  208 + </Col>
  209 + )}
  210 + {getSlot(slots, 'titleToolbar')}
  211 + </>
  212 + )}
  213 + </Row>
176 214 );
177 215 }
178 216  
... ... @@ -180,41 +218,20 @@ export default defineComponent({
180 218 setDrawerProps: setDrawerProps,
181 219 };
182 220  
183   - const uuid = buildUUID();
184   - emit('register', drawerInstance, uuid);
  221 + tryTsxEmit((instance) => {
  222 + emit('register', drawerInstance, instance.uid);
  223 + });
185 224  
186 225 return () => {
187   - const footerHeight = unref(getFooterHeight);
188 226 return (
189   - <Drawer
190   - class={prefixCls}
191   - onClose={onClose}
192   - {...{
193   - ...attrs,
194   - ...unref(getProps),
195   - }}
196   - >
  227 + <Drawer class={prefixCls} onClose={onClose} {...unref(getBindValues)}>
197 228 {{
198 229 title: () => renderHeader(),
199 230 default: () => (
200 231 <>
201   - <div
202   - ref={scrollRef}
203   - {...attrs}
204   - style={{
205   - position: 'relative',
206   - height: `calc(100% - ${footerHeight})`,
207   - overflow: 'auto',
208   - padding: '16px',
209   - paddingBottom: '30px',
210   - }}
211   - >
212   - <FullLoading
213   - absolute
214   - tip={t('loadingText')}
215   - class={[!unref(getProps).loading ? 'hidden' : '']}
216   - />
217   - {getSlot(slots, 'default')}
  232 + <div ref={scrollRef} style={unref(getScrollContentStyle)}>
  233 + <FullLoading absolute tip={t('loadingText')} class={unref(getLoading)} />
  234 + {getSlot(slots)}
218 235 </div>
219 236 {renderFooter()}
220 237 </>
... ...
src/components/Drawer/src/props.ts
1 1 import type { PropType } from 'vue';
2 2  
3 3 import { useI18n } from '/@/hooks/web/useI18n';
  4 +import { propTypes } from '/@/utils/propTypes';
4 5 const { t } = useI18n('component.drawer');
5 6  
6 7 export const footerProps = {
7   - confirmLoading: Boolean as PropType<boolean>,
  8 + confirmLoading: propTypes.bool,
8 9 /**
9 10 * @description: Show close button
10 11 */
11   - showCancelBtn: {
12   - type: Boolean as PropType<boolean>,
13   - default: true,
14   - },
  12 + showCancelBtn: propTypes.bool.def(true),
15 13 cancelButtonProps: Object as PropType<any>,
16   - cancelText: {
17   - type: String as PropType<string>,
18   - default: t('cancelText'),
19   - },
  14 + cancelText: propTypes.string.def(t('cancelText')),
20 15 /**
21 16 * @description: Show confirmation button
22 17 */
23   - showOkBtn: {
24   - type: Boolean as PropType<boolean>,
25   - default: true,
26   - },
27   - okButtonProps: Object as PropType<any>,
28   - okText: {
29   - type: String as PropType<string>,
30   - default: t('okText'),
31   - },
32   - okType: {
33   - type: String as PropType<string>,
34   - default: 'primary',
35   - },
36   - showFooter: {
37   - type: Boolean as PropType<boolean>,
38   - default: false,
39   - },
  18 + showOkBtn: propTypes.bool.def(true),
  19 + okButtonProps: propTypes.any,
  20 + okText: propTypes.string.def(t('okText')),
  21 + okType: propTypes.string.def('primary'),
  22 + showFooter: propTypes.bool,
40 23 footerHeight: {
41 24 type: [String, Number] as PropType<string | number>,
42 25 default: 60,
43 26 },
44 27 };
45 28 export const basicProps = {
46   - isDetail: {
47   - type: Boolean as PropType<boolean>,
48   - default: false,
49   - },
50   - title: {
51   - type: String as PropType<string>,
52   - default: '',
53   - },
54   - showDetailBack: {
55   - type: Boolean as PropType<boolean>,
56   - default: true,
57   - },
58   - visible: {
59   - type: Boolean as PropType<boolean>,
60   - default: false,
61   - },
62   - loading: {
63   - type: Boolean as PropType<boolean>,
64   - default: false,
65   - },
66   - maskClosable: {
67   - type: Boolean as PropType<boolean>,
68   - default: true,
69   - },
  29 + isDetail: propTypes.bool,
  30 + title: propTypes.string.def(''),
  31 + showDetailBack: propTypes.bool.def(true),
  32 + visible: propTypes.bool,
  33 + loading: propTypes.bool,
  34 + maskClosable: propTypes.bool.def(true),
70 35 getContainer: {
71 36 type: [Object, String] as PropType<any>,
72 37 },
... ... @@ -78,10 +43,7 @@ export const basicProps = {
78 43 type: [Function, Object] as PropType<any>,
79 44 default: null,
80 45 },
81   - triggerWindowResize: {
82   - type: Boolean as PropType<boolean>,
83   - default: false,
84   - },
85   - destroyOnClose: Boolean as PropType<boolean>,
  46 + triggerWindowResize: propTypes.bool,
  47 + destroyOnClose: propTypes.bool,
86 48 ...footerProps,
87 49 };
... ...
src/components/Drawer/src/types.ts
... ... @@ -75,7 +75,7 @@ export interface DrawerProps extends DrawerFooterProps {
75 75 * @type ScrollContainerOptions
76 76 */
77 77 scrollOptions?: ScrollContainerOptions;
78   - closeFunc?: () => Promise<void>;
  78 + closeFunc?: () => Promise<any>;
79 79 triggerWindowResize?: boolean;
80 80 /**
81 81 * Whether a close (x) button is visible on top right of the Drawer dialog or not.
... ...
src/components/Drawer/src/useDrawer.ts
... ... @@ -6,12 +6,15 @@ import type {
6 6 UseDrawerInnerReturnType,
7 7 } from './types';
8 8  
9   -import { ref, getCurrentInstance, onUnmounted, unref, reactive, watchEffect, nextTick } from 'vue';
  9 +import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw } from 'vue';
10 10  
11 11 import { isProdMode } from '/@/utils/env';
12 12 import { isFunction } from '/@/utils/is';
  13 +import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
  14 +import { isEqual } from 'lodash-es';
13 15  
14 16 const dataTransferRef = reactive<any>({});
  17 +
15 18 /**
16 19 * @description: Applicable to separate drawer and call outside
17 20 */
... ... @@ -19,21 +22,23 @@ export function useDrawer(): UseDrawerReturnType {
19 22 if (!getCurrentInstance()) {
20 23 throw new Error('Please put useDrawer function in the setup function!');
21 24 }
  25 +
22 26 const drawerRef = ref<DrawerInstance | null>(null);
23   - const loadedRef = ref<boolean | null>(false);
  27 + const loadedRef = ref<Nullable<boolean>>(false);
24 28 const uidRef = ref<string>('');
25 29  
26   - function getDrawer(drawerInstance: DrawerInstance, uuid: string) {
27   - uidRef.value = uuid;
  30 + function register(drawerInstance: DrawerInstance, uuid: string) {
28 31 isProdMode() &&
29   - onUnmounted(() => {
  32 + tryOnUnmounted(() => {
30 33 drawerRef.value = null;
31 34 loadedRef.value = null;
32 35 dataTransferRef[unref(uidRef)] = null;
33 36 });
  37 +
34 38 if (unref(loadedRef) && isProdMode() && drawerInstance === unref(drawerRef)) {
35 39 return;
36 40 }
  41 + uidRef.value = uuid;
37 42 drawerRef.value = drawerInstance;
38 43 loadedRef.value = true;
39 44 }
... ... @@ -55,37 +60,46 @@ export function useDrawer(): UseDrawerReturnType {
55 60 getInstance().setDrawerProps({
56 61 visible: visible,
57 62 });
58   - if (data) {
59   - dataTransferRef[unref(uidRef)] = openOnSet
60   - ? {
61   - ...data,
62   - __t__: Date.now(),
63   - }
64   - : data;
  63 + if (!data) return;
  64 +
  65 + if (openOnSet) {
  66 + dataTransferRef[unref(uidRef)] = null;
  67 + dataTransferRef[unref(uidRef)] = data;
  68 + return;
  69 + }
  70 + const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), data);
  71 + if (!equal) {
  72 + dataTransferRef[unref(uidRef)] = data;
65 73 }
66 74 },
67 75 };
68 76  
69   - return [getDrawer, methods];
  77 + return [register, methods];
70 78 }
  79 +
71 80 export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
72   - const drawerInstanceRef = ref<DrawerInstance | null>(null);
  81 + const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
73 82 const currentInstall = getCurrentInstance();
74 83 const uidRef = ref<string>('');
75 84  
76 85 if (!currentInstall) {
77   - throw new Error('instance is undefined!');
  86 + throw new Error('useDrawerInner instance is undefined!');
78 87 }
79 88  
80 89 const getInstance = () => {
81 90 const instance = unref(drawerInstanceRef);
82 91 if (!instance) {
83   - throw new Error('instance is undefined!');
  92 + throw new Error('useDrawerInner instance is undefined!');
84 93 }
85 94 return instance;
86 95 };
87 96  
88 97 const register = (modalInstance: DrawerInstance, uuid: string) => {
  98 + isProdMode() &&
  99 + tryOnUnmounted(() => {
  100 + drawerInstanceRef.value = null;
  101 + });
  102 +
89 103 uidRef.value = uuid;
90 104 drawerInstanceRef.value = modalInstance;
91 105 currentInstall.emit('register', modalInstance);
... ...
src/components/Modal/index.ts
1 1 import './src/index.less';
2   -export { default as BasicModal } from './src/BasicModal';
3   -export { default as Modal } from './src/Modal';
  2 +import BasicModalLib from './src/BasicModal';
  3 +import { withInstall } from '../util';
  4 +
  5 +export { useModalContext } from './src/useModalContext';
4 6 export { useModal, useModalInner } from './src/useModal';
5 7 export * from './src/types';
  8 +export const BasicModal = withInstall(BasicModalLib);
... ...
src/components/Modal/src/BasicModal.tsx
1 1 import type { ModalProps, ModalMethods } from './types';
2 2  
3   -import { defineComponent, computed, ref, watch, unref, watchEffect } from 'vue';
  3 +import { defineComponent, computed, ref, watch, unref, watchEffect, toRef } from 'vue';
4 4  
5 5 import Modal from './Modal';
6 6 import { Button } from '/@/components/Button';
... ... @@ -11,10 +11,10 @@ import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from &#39;@ant-
11 11 import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper';
12 12 import { isFunction } from '/@/utils/is';
13 13 import { deepMerge } from '/@/utils';
14   -import { buildUUID } from '/@/utils/uuid';
  14 +import { tryTsxEmit } from '/@/utils/helper/vueHelper';
15 15  
16 16 import { basicProps } from './props';
17   -// import { triggerWindowResize } from '@/utils/event/triggerWindowResizeEvent';
  17 +import { useFullScreen } from './useFullScreen';
18 18 export default defineComponent({
19 19 name: 'BasicModal',
20 20 props: basicProps,
... ... @@ -26,31 +26,41 @@ export default defineComponent({
26 26 // modal Bottom and top height
27 27 const extHeightRef = ref(0);
28 28 // Unexpanded height of the popup
29   - const formerHeightRef = ref(0);
30   - const fullScreenRef = ref(false);
31 29  
32 30 // Custom title component: get title
33   - const getMergeProps = computed(() => {
34   - return {
35   - ...props,
36   - ...(unref(propsRef) as any),
37   - };
  31 + const getMergeProps = computed(
  32 + (): ModalProps => {
  33 + return {
  34 + ...props,
  35 + ...(unref(propsRef) as any),
  36 + };
  37 + }
  38 + );
  39 +
  40 + const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
  41 + modalWrapperRef,
  42 + extHeightRef,
  43 + wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
38 44 });
39 45  
40 46 // modal component does not need title
41   - const getProps = computed((): any => {
42   - const opt = {
43   - ...props,
44   - ...((unref(propsRef) || {}) as any),
45   - visible: unref(visibleRef),
46   - title: undefined,
47   - };
48   - const { wrapClassName = '' } = opt;
49   - const className = unref(fullScreenRef) ? `${wrapClassName} fullscreen-modal` : wrapClassName;
50   - return {
51   - ...opt,
52   - wrapClassName: className,
53   - };
  47 + const getProps = computed(
  48 + (): ModalProps => {
  49 + const opt = {
  50 + ...unref(getMergeProps),
  51 + visible: unref(visibleRef),
  52 + title: undefined,
  53 + };
  54 +
  55 + return {
  56 + ...opt,
  57 + wrapClassName: unref(getWrapClassName),
  58 + };
  59 + }
  60 + );
  61 +
  62 + const getModalBindValue = computed((): any => {
  63 + return { ...attrs, ...unref(getProps) };
54 64 });
55 65  
56 66 watchEffect(() => {
... ... @@ -80,7 +90,35 @@ export default defineComponent({
80 90 );
81 91 }
82 92  
  93 + // 取消事件
  94 + async function handleCancel(e: Event) {
  95 + e?.stopPropagation();
  96 +
  97 + if (props.closeFunc && isFunction(props.closeFunc)) {
  98 + const isClose: boolean = await props.closeFunc();
  99 + visibleRef.value = !isClose;
  100 + return;
  101 + }
  102 +
  103 + visibleRef.value = false;
  104 + emit('cancel');
  105 + }
  106 +
  107 + /**
  108 + * @description: 设置modal参数
  109 + */
  110 + function setModalProps(props: Partial<ModalProps>): void {
  111 + // Keep the last setModalProps
  112 + propsRef.value = deepMerge(unref(propsRef) || {}, props);
  113 + if (!Reflect.has(props, 'visible')) return;
  114 + visibleRef.value = !!props.visible;
  115 + }
  116 +
83 117 function renderContent() {
  118 + type OmitWrapperType = Omit<
  119 + ModalProps,
  120 + 'fullScreen' | 'modalFooterHeight' | 'visible' | 'loading'
  121 + >;
84 122 const { useWrapper, loading, wrapperProps } = unref(getProps);
85 123 if (!useWrapper) return getSlot(slots);
86 124  
... ... @@ -93,7 +131,7 @@ export default defineComponent({
93 131 loading={loading}
94 132 visible={unref(visibleRef)}
95 133 modalFooterHeight={showFooter}
96   - {...wrapperProps}
  134 + {...((wrapperProps as unknown) as OmitWrapperType)}
97 135 onGetExtHeight={(height: number) => {
98 136 extHeightRef.value = height;
99 137 }}
... ... @@ -106,18 +144,6 @@ export default defineComponent({
106 144 );
107 145 }
108 146  
109   - // 取消事件
110   - async function handleCancel(e: Event) {
111   - e && e.stopPropagation();
112   - if (props.closeFunc && isFunction(props.closeFunc)) {
113   - const isClose: boolean = await props.closeFunc();
114   - visibleRef.value = !isClose;
115   - return;
116   - }
117   - visibleRef.value = false;
118   - emit('cancel');
119   - }
120   -
121 147 // 底部按钮自定义实现,
122 148 function renderFooter() {
123 149 const {
... ... @@ -162,64 +188,37 @@ export default defineComponent({
162 188 */
163 189 function renderClose() {
164 190 const { canFullscreen } = unref(getProps);
165   - if (!canFullscreen) {
166   - return null;
167   - }
  191 +
  192 + const fullScreen = unref(fullScreenRef) ? (
  193 + <FullscreenExitOutlined role="full" onClick={handleFullScreen} />
  194 + ) : (
  195 + <FullscreenOutlined role="close" onClick={handleFullScreen} />
  196 + );
  197 +
  198 + const cls = [
  199 + 'custom-close-icon',
  200 + {
  201 + 'can-full': canFullscreen,
  202 + },
  203 + ];
  204 +
168 205 return (
169   - <div class="custom-close-icon">
170   - {unref(fullScreenRef) ? (
171   - <FullscreenExitOutlined role="full" onClick={handleFullScreen} />
172   - ) : (
173   - <FullscreenOutlined role="close" onClick={handleFullScreen} />
174   - )}
  206 + <div class={cls}>
  207 + {canFullscreen && fullScreen}
175 208 <CloseOutlined onClick={handleCancel} />
176 209 </div>
177 210 );
178 211 }
179 212  
180   - function handleFullScreen(e: Event) {
181   - e && e.stopPropagation();
182   - fullScreenRef.value = !unref(fullScreenRef);
183   -
184   - const modalWrapper = unref(modalWrapperRef);
185   - if (!modalWrapper) return;
186   -
187   - const wrapperEl = modalWrapper.$el as HTMLElement;
188   - if (!wrapperEl) return;
189   -
190   - const modalWrapSpinEl = wrapperEl.querySelector('.ant-spin-nested-loading') as HTMLElement;
191   - if (!modalWrapSpinEl) return;
192   -
193   - if (!unref(formerHeightRef) && unref(fullScreenRef)) {
194   - formerHeightRef.value = modalWrapSpinEl.offsetHeight;
195   - }
196   -
197   - if (unref(fullScreenRef)) {
198   - modalWrapSpinEl.style.height = `${window.innerHeight - unref(extHeightRef)}px`;
199   - } else {
200   - modalWrapSpinEl.style.height = `${unref(formerHeightRef)}px`;
201   - }
202   - }
203   -
204   - /**
205   - * @description: 设置modal参数
206   - */
207   - function setModalProps(props: Partial<ModalProps>): void {
208   - // Keep the last setModalProps
209   - propsRef.value = deepMerge(unref(propsRef) || {}, props);
210   - if (!Reflect.has(props, 'visible')) return;
211   - visibleRef.value = !!props.visible;
212   - }
213   -
214 213 const modalMethods: ModalMethods = {
215 214 setModalProps,
216 215 };
217 216  
218   - const uuid = buildUUID();
219   - emit('register', modalMethods, uuid);
220   -
  217 + tryTsxEmit((instance) => {
  218 + emit('register', modalMethods, instance.uid);
  219 + });
221 220 return () => (
222   - <Modal onCancel={handleCancel} {...{ ...attrs, ...props, ...unref(getProps) }}>
  221 + <Modal onCancel={handleCancel} {...unref(getModalBindValue)}>
223 222 {{
224 223 footer: () => renderFooter(),
225 224 closeIcon: () => renderClose(),
... ...
src/components/Modal/src/Modal.tsx
1 1 import { Modal } from 'ant-design-vue';
2   -import { defineComponent, watchEffect } from 'vue';
  2 +import { defineComponent, toRefs } from 'vue';
3 3 import { basicProps } from './props';
4   -import { useTimeoutFn } from '/@/hooks/core/useTimeout';
  4 +import { useModalDragMove } from './useModalDrag';
5 5 import { extendSlots } from '/@/utils/helper/tsxHelper';
6 6  
7 7 export default defineComponent({
... ... @@ -9,99 +9,12 @@ export default defineComponent({
9 9 inheritAttrs: false,
10 10 props: basicProps,
11 11 setup(props, { attrs, slots }) {
12   - const getStyle = (dom: any, attr: any) => {
13   - return getComputedStyle(dom)[attr];
14   - };
15   - const drag = (wrap: any) => {
16   - if (!wrap) return;
17   - wrap.setAttribute('data-drag', props.draggable);
18   - const dialogHeaderEl = wrap.querySelector('.ant-modal-header');
19   - const dragDom = wrap.querySelector('.ant-modal');
20   -
21   - if (!dialogHeaderEl || !dragDom || !props.draggable) return;
22   -
23   - dialogHeaderEl.style.cursor = 'move';
24   -
25   - dialogHeaderEl.onmousedown = (e: any) => {
26   - if (!e) return;
27   - // 鼠标按下,计算当前元素距离可视区的距离
28   - const disX = e.clientX;
29   - const disY = e.clientY;
30   - const screenWidth = document.body.clientWidth; // body当前宽度
31   - const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
32   -
33   - const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
34   - const dragDomheight = dragDom.offsetHeight; // 对话框高度
35   -
36   - const minDragDomLeft = dragDom.offsetLeft;
37   -
38   - const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
39   - const minDragDomTop = dragDom.offsetTop;
40   - const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
41   - // 获取到的值带px 正则匹配替换
42   - const domLeft = getStyle(dragDom, 'left');
43   - const domTop = getStyle(dragDom, 'top');
44   - let styL = +domLeft;
45   - let styT = +domTop;
46   -
47   - // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
48   - if (domLeft.includes('%')) {
49   - styL = +document.body.clientWidth * (+domLeft.replace(/%/g, '') / 100);
50   - styT = +document.body.clientHeight * (+domTop.replace(/%/g, '') / 100);
51   - } else {
52   - styL = +domLeft.replace(/px/g, '');
53   - styT = +domTop.replace(/px/g, '');
54   - }
55   -
56   - document.onmousemove = function (e) {
57   - // 通过事件委托,计算移动的距离
58   - let left = e.clientX - disX;
59   - let top = e.clientY - disY;
60   -
61   - // 边界处理
62   - if (-left > minDragDomLeft) {
63   - left = -minDragDomLeft;
64   - } else if (left > maxDragDomLeft) {
65   - left = maxDragDomLeft;
66   - }
67   -
68   - if (-top > minDragDomTop) {
69   - top = -minDragDomTop;
70   - } else if (top > maxDragDomTop) {
71   - top = maxDragDomTop;
72   - }
73   -
74   - // 移动当前元素
75   - dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
76   - };
77   -
78   - document.onmouseup = () => {
79   - document.onmousemove = null;
80   - document.onmouseup = null;
81   - };
82   - };
83   - };
84   -
85   - const handleDrag = () => {
86   - const dragWraps = document.querySelectorAll('.ant-modal-wrap');
87   - for (const wrap of dragWraps as any) {
88   - if (!wrap) continue;
89   - const display = getStyle(wrap, 'display');
90   - const draggable = wrap.getAttribute('data-drag');
91   - if (display !== 'none') {
92   - // 拖拽位置
93   - (draggable === null || props.destroyOnClose) && drag(wrap);
94   - }
95   - }
96   - };
  12 + const { visible, draggable, destroyOnClose } = toRefs(props);
97 13  
98   - watchEffect(() => {
99   - if (!props.visible) {
100   - return;
101   - }
102   - useTimeoutFn(() => {
103   - handleDrag();
104   - }, 30);
  14 + useModalDragMove({
  15 + visible,
  16 + destroyOnClose,
  17 + draggable,
105 18 });
106 19  
107 20 return () => {
... ...
src/components/Modal/src/ModalWrapper.tsx
1   -import type { PropType } from 'vue';
2 1 import type { ModalWrapperProps } from './types';
  2 +import type { CSSProperties } from 'vue';
3 3  
4 4 import {
5 5 defineComponent,
... ... @@ -18,59 +18,44 @@ import { useWindowSizeFn } from &#39;/@/hooks/event/useWindowSizeFn&#39;;
18 18  
19 19 import { getSlot } from '/@/utils/helper/tsxHelper';
20 20 import { useElResize } from '/@/hooks/event/useElResize';
21   -import { provideModal } from './provideModal';
  21 +import { propTypes } from '/@/utils/propTypes';
  22 +import { createModalContext } from './useModalContext';
22 23  
23 24 export default defineComponent({
24 25 name: 'ModalWrapper',
25 26 props: {
26   - loading: {
27   - type: Boolean as PropType<boolean>,
28   - default: false,
29   - },
30   - modalHeaderHeight: {
31   - type: Number as PropType<number>,
32   - default: 50,
33   - },
34   - modalFooterHeight: {
35   - type: Number as PropType<number>,
36   - default: 70,
37   - },
38   - minHeight: {
39   - type: Number as PropType<number>,
40   - default: 200,
41   - },
42   - footerOffset: {
43   - type: Number as PropType<number>,
44   - default: 0,
45   - },
46   - visible: {
47   - type: Boolean as PropType<boolean>,
48   - default: false,
49   - },
50   - fullScreen: {
51   - type: Boolean as PropType<boolean>,
52   - default: false,
53   - },
  27 + loading: propTypes.bool,
  28 + modalHeaderHeight: propTypes.number.def(50),
  29 + modalFooterHeight: propTypes.number.def(54),
  30 + minHeight: propTypes.number.def(200),
  31 + footerOffset: propTypes.number.def(0),
  32 + visible: propTypes.bool,
  33 + fullScreen: propTypes.bool,
54 34 },
55 35 emits: ['heightChange', 'getExtHeight'],
56 36 setup(props: ModalWrapperProps, { slots, emit }) {
57   - const wrapperRef = ref<HTMLElement | null>(null);
  37 + const wrapperRef = ref<ElRef>(null);
58 38 const spinRef = ref<ComponentRef>(null);
59 39 const realHeightRef = ref(0);
60   - // 重试次数
61   - // let tryCount = 0;
  40 +
62 41 let stopElResizeFn: Fn = () => {};
63 42  
64   - provideModal(setModalHeight);
  43 + useWindowSizeFn(setModalHeight);
65 44  
66   - const wrapStyle = computed(() => {
67   - return {
68   - minHeight: `${props.minHeight}px`,
69   - height: `${unref(realHeightRef)}px`,
70   - overflow: 'auto',
71   - };
  45 + createModalContext({
  46 + redoModalHeight: setModalHeight,
72 47 });
73 48  
  49 + const wrapStyle = computed(
  50 + (): CSSProperties => {
  51 + return {
  52 + minHeight: `${props.minHeight}px`,
  53 + height: `${unref(realHeightRef)}px`,
  54 + overflow: 'auto',
  55 + };
  56 + }
  57 + );
  58 +
74 59 watchEffect(() => {
75 60 setModalHeight();
76 61 });
... ... @@ -92,8 +77,6 @@ export default defineComponent({
92 77 stopElResizeFn && stopElResizeFn();
93 78 });
94 79  
95   - useWindowSizeFn(setModalHeight);
96   -
97 80 async function setModalHeight() {
98 81 // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
99 82 // 加上这个,就必须在使用的时候传递父级的visible
... ... @@ -107,9 +90,8 @@ export default defineComponent({
107 90  
108 91 try {
109 92 const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
110   - if (!modalDom) {
111   - return;
112   - }
  93 + if (!modalDom) return;
  94 +
113 95 const modalRect = getComputedStyle(modalDom).top;
114 96 const modalTop = Number.parseInt(modalRect);
115 97 let maxHeight =
... ... @@ -135,11 +117,12 @@ export default defineComponent({
135 117  
136 118 if (props.fullScreen) {
137 119 realHeightRef.value =
138   - window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 6;
  120 + window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight;
139 121 } else {
140 122 realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30;
141 123 }
142 124 emit('heightChange', unref(realHeightRef));
  125 +
143 126 nextTick(() => {
144 127 const el = spinEl.$el;
145 128 if (el) {
... ... @@ -154,8 +137,10 @@ export default defineComponent({
154 137 function listenElResize() {
155 138 const wrapper = unref(wrapperRef);
156 139 if (!wrapper) return;
  140 +
157 141 const container = wrapper.querySelector('.ant-spin-container');
158 142 if (!container) return;
  143 +
159 144 const [start, stop] = useElResize(container, () => {
160 145 setModalHeight();
161 146 });
... ...
src/components/Modal/src/index.less
... ... @@ -9,6 +9,11 @@
9 9 bottom: 0 !important;
10 10 left: 0 !important;
11 11 width: 100% !important;
  12 + height: 100%;
  13 +
  14 + &-content {
  15 + height: 100%;
  16 + }
12 17 }
13 18 }
14 19  
... ... @@ -35,8 +40,23 @@
35 40 height: 95%;
36 41 align-items: center;
37 42  
38   - > * {
39   - margin-left: 12px;
  43 + > span {
  44 + margin-left: 48px;
  45 + font-size: 16px;
  46 + }
  47 +
  48 + &.can-full {
  49 + > span {
  50 + margin-left: 12px;
  51 + }
  52 + }
  53 +
  54 + &:not(.can-full) {
  55 + > span:nth-child(1) {
  56 + &:hover {
  57 + font-weight: 700;
  58 + }
  59 + }
40 60 }
41 61  
42 62 & span:nth-child(1) {
... ... @@ -76,7 +96,7 @@
76 96 }
77 97  
78 98 &-footer {
79   - padding: 10px 26px 26px 16px;
  99 + // padding: 10px 26px 26px 16px;
80 100  
81 101 button + button {
82 102 margin-left: 10px;
... ...
src/components/Modal/src/props.ts
... ... @@ -2,66 +2,38 @@ import type { PropType } from &#39;vue&#39;;
2 2 import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
3 3  
4 4 import { useI18n } from '/@/hooks/web/useI18n';
  5 +import { propTypes } from '/@/utils/propTypes';
5 6 const { t } = useI18n('component.modal');
6 7  
7 8 export const modalProps = {
8   - visible: Boolean as PropType<boolean>,
  9 + visible: propTypes.bool,
9 10 // open drag
10   - draggable: {
11   - type: Boolean as PropType<boolean>,
12   - default: true,
13   - },
14   - centered: {
15   - type: Boolean as PropType<boolean>,
16   - default: false,
17   - },
18   - cancelText: {
19   - type: String as PropType<string>,
20   - default: t('cancelText'),
21   - },
22   - okText: {
23   - type: String as PropType<string>,
24   - default: t('okText'),
25   - },
  11 + draggable: propTypes.bool.def(true),
  12 + centered: propTypes.bool,
  13 + cancelText: propTypes.string.def(t('cancelText')),
  14 + okText: propTypes.string.def(t('okText')),
  15 +
26 16 closeFunc: Function as PropType<() => Promise<boolean>>,
27 17 };
28 18  
29 19 export const basicProps = Object.assign({}, modalProps, {
30 20 // Can it be full screen
31   - canFullscreen: {
32   - type: Boolean as PropType<boolean>,
33   - default: true,
34   - },
  21 + canFullscreen: propTypes.bool.def(true),
35 22 // After enabling the wrapper, the bottom can be increased in height
36   - wrapperFooterOffset: {
37   - type: Number as PropType<number>,
38   - default: 0,
39   - },
  23 + wrapperFooterOffset: propTypes.number.def(0),
40 24 // Warm reminder message
41 25 helpMessage: [String, Array] as PropType<string | string[]>,
42 26 // Whether to setting wrapper
43   - useWrapper: {
44   - type: Boolean as PropType<boolean>,
45   - default: true,
46   - },
47   - loading: {
48   - type: Boolean as PropType<boolean>,
49   - default: false,
50   - },
  27 + useWrapper: propTypes.bool.def(true),
  28 + loading: propTypes.bool,
51 29 /**
52 30 * @description: Show close button
53 31 */
54   - showCancelBtn: {
55   - type: Boolean as PropType<boolean>,
56   - default: true,
57   - },
  32 + showCancelBtn: propTypes.bool.def(true),
58 33 /**
59 34 * @description: Show confirmation button
60 35 */
61   - showOkBtn: {
62   - type: Boolean as PropType<boolean>,
63   - default: true,
64   - },
  36 + showOkBtn: propTypes.bool.def(true),
65 37  
66 38 wrapperProps: Object as PropType<any>,
67 39  
... ...
src/components/Modal/src/provideModal.ts deleted 100644 → 0
1   -import { provide, inject } from 'vue';
2   -
3   -const key = Symbol('basic-modal');
4   -
5   -export function provideModal(redoHeight: Fn) {
6   - provide(key, redoHeight);
7   -}
8   -
9   -export function injectModal(): Fn {
10   - return inject(key, () => {}) as Fn;
11   -}
src/components/Modal/src/types.ts
... ... @@ -8,9 +8,11 @@ export interface ModalMethods {
8 8 }
9 9  
10 10 export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void;
  11 +
11 12 export interface ReturnMethods extends ModalMethods {
12 13 openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void;
13 14 }
  15 +
14 16 export type UseModalReturnType = [RegisterFn, ReturnMethods];
15 17  
16 18 export interface ReturnInnerMethods extends ModalMethods {
... ... @@ -18,6 +20,7 @@ export interface ReturnInnerMethods extends ModalMethods {
18 20 changeLoading: (loading: boolean) => void;
19 21 changeOkLoading: (loading: boolean) => void;
20 22 }
  23 +
21 24 export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods];
22 25  
23 26 export interface ModalProps {
... ...
src/components/Modal/src/useFullScreen.ts 0 → 100644
  1 +import { computed, Ref, ref, unref } from 'vue';
  2 +
  3 +export interface UseFullScreenContext {
  4 + wrapClassName: Ref<string | undefined>;
  5 + modalWrapperRef: Ref<ComponentRef>;
  6 + extHeightRef: Ref<number>;
  7 +}
  8 +
  9 +export function useFullScreen(context: UseFullScreenContext) {
  10 + const formerHeightRef = ref(0);
  11 + const fullScreenRef = ref(false);
  12 +
  13 + const getWrapClassName = computed(() => {
  14 + const clsName = unref(context.wrapClassName) || '';
  15 +
  16 + return unref(fullScreenRef) ? `fullscreen-modal ${clsName} ` : unref(clsName);
  17 + });
  18 +
  19 + function handleFullScreen(e: Event) {
  20 + e && e.stopPropagation();
  21 + fullScreenRef.value = !unref(fullScreenRef);
  22 +
  23 + const modalWrapper = unref(context.modalWrapperRef);
  24 +
  25 + if (!modalWrapper) return;
  26 +
  27 + const wrapperEl = modalWrapper.$el as HTMLElement;
  28 + if (!wrapperEl) return;
  29 + const modalWrapSpinEl = wrapperEl.querySelector('.ant-spin-nested-loading') as HTMLElement;
  30 +
  31 + if (!modalWrapSpinEl) return;
  32 +
  33 + if (!unref(formerHeightRef) && unref(fullScreenRef)) {
  34 + formerHeightRef.value = modalWrapSpinEl.offsetHeight;
  35 + }
  36 +
  37 + if (unref(fullScreenRef)) {
  38 + modalWrapSpinEl.style.height = `${window.innerHeight - unref(context.extHeightRef)}px`;
  39 + } else {
  40 + modalWrapSpinEl.style.height = `${unref(formerHeightRef)}px`;
  41 + }
  42 + }
  43 + return { getWrapClassName, handleFullScreen, fullScreenRef };
  44 +}
... ...
src/components/Modal/src/useModal.ts
... ... @@ -5,9 +5,21 @@ import type {
5 5 ReturnMethods,
6 6 UseModalInnerReturnType,
7 7 } from './types';
8   -import { ref, onUnmounted, unref, getCurrentInstance, reactive, watchEffect, nextTick } from 'vue';
  8 +
  9 +import {
  10 + ref,
  11 + onUnmounted,
  12 + unref,
  13 + getCurrentInstance,
  14 + reactive,
  15 + watchEffect,
  16 + nextTick,
  17 + toRaw,
  18 +} from 'vue';
9 19 import { isProdMode } from '/@/utils/env';
10 20 import { isFunction } from '/@/utils/is';
  21 +import { isEqual } from 'lodash-es';
  22 +import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
11 23 const dataTransferRef = reactive<any>({});
12 24  
13 25 /**
... ... @@ -20,6 +32,7 @@ export function useModal(): UseModalReturnType {
20 32 const modalRef = ref<Nullable<ModalMethods>>(null);
21 33 const loadedRef = ref<Nullable<boolean>>(false);
22 34 const uidRef = ref<string>('');
  35 +
23 36 function register(modalMethod: ModalMethods, uuid: string) {
24 37 uidRef.value = uuid;
25 38  
... ... @@ -52,13 +65,16 @@ export function useModal(): UseModalReturnType {
52 65 visible: visible,
53 66 });
54 67  
55   - if (data) {
56   - dataTransferRef[unref(uidRef)] = openOnSet
57   - ? {
58   - ...data,
59   - __t__: Date.now(),
60   - }
61   - : data;
  68 + if (!data) return;
  69 +
  70 + if (openOnSet) {
  71 + dataTransferRef[unref(uidRef)] = null;
  72 + dataTransferRef[unref(uidRef)] = data;
  73 + return;
  74 + }
  75 + const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), data);
  76 + if (!equal) {
  77 + dataTransferRef[unref(uidRef)] = data;
62 78 }
63 79 },
64 80 };
... ... @@ -66,7 +82,7 @@ export function useModal(): UseModalReturnType {
66 82 }
67 83  
68 84 export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
69   - const modalInstanceRef = ref<ModalMethods | null>(null);
  85 + const modalInstanceRef = ref<Nullable<ModalMethods>>(null);
70 86 const currentInstall = getCurrentInstance();
71 87 const uidRef = ref<string>('');
72 88  
... ... @@ -83,6 +99,11 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType =&gt; {
83 99 };
84 100  
85 101 const register = (modalInstance: ModalMethods, uuid: string) => {
  102 + isProdMode() &&
  103 + tryOnUnmounted(() => {
  104 + modalInstanceRef.value = null;
  105 + });
  106 +
86 107 uidRef.value = uuid;
87 108 modalInstanceRef.value = modalInstance;
88 109 currentInstall.emit('register', modalInstance);
... ...
src/components/Modal/src/useModalContext.ts 0 → 100644
  1 +import { InjectionKey } from 'vue';
  2 +import { createContext, useContext } from '/@/hooks/core/useContext';
  3 +
  4 +export interface ModalContextProps {
  5 + redoModalHeight: () => void;
  6 +}
  7 +
  8 +const modalContextInjectKey: InjectionKey<ModalContextProps> = Symbol();
  9 +
  10 +export function createModalContext(context: ModalContextProps) {
  11 + return createContext<ModalContextProps>(context, modalContextInjectKey);
  12 +}
  13 +
  14 +export function useModalContext() {
  15 + return useContext<ModalContextProps>(modalContextInjectKey);
  16 +}
... ...
src/components/Modal/src/useModalDrag.ts 0 → 100644
  1 +import { Ref, unref, watchEffect } from 'vue';
  2 +import { useTimeoutFn } from '/@/hooks/core/useTimeout';
  3 +
  4 +export interface UseModalDragMoveContext {
  5 + draggable: Ref<boolean>;
  6 + destroyOnClose: Ref<boolean | undefined> | undefined;
  7 + visible: Ref<boolean>;
  8 +}
  9 +
  10 +export function useModalDragMove(context: UseModalDragMoveContext) {
  11 + const getStyle = (dom: any, attr: any) => {
  12 + return getComputedStyle(dom)[attr];
  13 + };
  14 + const drag = (wrap: any) => {
  15 + if (!wrap) return;
  16 + wrap.setAttribute('data-drag', unref(context.draggable));
  17 + const dialogHeaderEl = wrap.querySelector('.ant-modal-header');
  18 + const dragDom = wrap.querySelector('.ant-modal');
  19 +
  20 + if (!dialogHeaderEl || !dragDom || !unref(context.draggable)) return;
  21 +
  22 + dialogHeaderEl.style.cursor = 'move';
  23 +
  24 + dialogHeaderEl.onmousedown = (e: any) => {
  25 + if (!e) return;
  26 + // 鼠标按下,计算当前元素距离可视区的距离
  27 + const disX = e.clientX;
  28 + const disY = e.clientY;
  29 + const screenWidth = document.body.clientWidth; // body当前宽度
  30 + const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
  31 +
  32 + const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
  33 + const dragDomheight = dragDom.offsetHeight; // 对话框高度
  34 +
  35 + const minDragDomLeft = dragDom.offsetLeft;
  36 +
  37 + const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
  38 + const minDragDomTop = dragDom.offsetTop;
  39 + const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
  40 + // 获取到的值带px 正则匹配替换
  41 + const domLeft = getStyle(dragDom, 'left');
  42 + const domTop = getStyle(dragDom, 'top');
  43 + let styL = +domLeft;
  44 + let styT = +domTop;
  45 +
  46 + // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
  47 + if (domLeft.includes('%')) {
  48 + styL = +document.body.clientWidth * (+domLeft.replace(/%/g, '') / 100);
  49 + styT = +document.body.clientHeight * (+domTop.replace(/%/g, '') / 100);
  50 + } else {
  51 + styL = +domLeft.replace(/px/g, '');
  52 + styT = +domTop.replace(/px/g, '');
  53 + }
  54 +
  55 + document.onmousemove = function (e) {
  56 + // 通过事件委托,计算移动的距离
  57 + let left = e.clientX - disX;
  58 + let top = e.clientY - disY;
  59 +
  60 + // 边界处理
  61 + if (-left > minDragDomLeft) {
  62 + left = -minDragDomLeft;
  63 + } else if (left > maxDragDomLeft) {
  64 + left = maxDragDomLeft;
  65 + }
  66 +
  67 + if (-top > minDragDomTop) {
  68 + top = -minDragDomTop;
  69 + } else if (top > maxDragDomTop) {
  70 + top = maxDragDomTop;
  71 + }
  72 +
  73 + // 移动当前元素
  74 + dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
  75 + };
  76 +
  77 + document.onmouseup = () => {
  78 + document.onmousemove = null;
  79 + document.onmouseup = null;
  80 + };
  81 + };
  82 + };
  83 +
  84 + const handleDrag = () => {
  85 + const dragWraps = document.querySelectorAll('.ant-modal-wrap');
  86 + for (const wrap of Array.from(dragWraps)) {
  87 + if (!wrap) continue;
  88 + const display = getStyle(wrap, 'display');
  89 + const draggable = wrap.getAttribute('data-drag');
  90 + if (display !== 'none') {
  91 + // 拖拽位置
  92 + if (draggable === null || unref(context.destroyOnClose)) {
  93 + drag(wrap);
  94 + }
  95 + }
  96 + }
  97 + };
  98 +
  99 + watchEffect(() => {
  100 + if (!unref(context.visible) || !unref(context.draggable)) {
  101 + return;
  102 + }
  103 + useTimeoutFn(() => {
  104 + handleDrag();
  105 + }, 30);
  106 + });
  107 +}
... ...
src/components/Scrollbar/src/Scrollbar.tsx
... ... @@ -65,7 +65,7 @@ export default defineComponent({
65 65 }
66 66  
67 67 onMounted(() => {
68   - tryTsxEmit((instance) => {
  68 + tryTsxEmit<any>((instance) => {
69 69 instance.wrap = unref(wrapElRef);
70 70 });
71 71  
... ...
src/components/Table/src/hooks/useTableScroll.ts
1 1 import type { BasicTableProps } from '../types/table';
2 2 import { computed, Ref, onMounted, unref, ref, nextTick, ComputedRef, watch } from 'vue';
3 3  
4   -import { injectModal } from '/@/components/Modal/src/provideModal';
5   -
6 4 import { getViewportOffset } from '/@/utils/domUtils';
7 5 import { isBoolean } from '/@/utils/is';
8 6  
9 7 import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
10 8 import { useProps } from './useProps';
  9 +import { useModalContext } from '/@/components/Modal';
11 10  
12 11 export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) {
13 12 const { propsRef } = useProps(refProps);
14 13  
15 14 const tableHeightRef: Ref<number | null> = ref(null);
16 15  
17   - const redoModalHeight = injectModal();
  16 + const modalFn = useModalContext();
18 17  
19 18 watch(
20 19 () => unref(propsRef).canResize,
... ... @@ -93,7 +92,7 @@ export function useTableScroll(refProps: ComputedRef&lt;BasicTableProps&gt;, tableElRe
93 92 tableHeightRef.value =
94 93 tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
95 94 // 解决表格放modal内的时候,modal自适应高度计算问题
96   - redoModalHeight && redoModalHeight();
  95 + modalFn?.redoModalHeight?.();
97 96 }, 16);
98 97 }
99 98  
... ...
src/components/Tree/src/BasicTree.tsx
1 1 import './index.less';
2 2  
3   -import type { ReplaceFields, TreeItem, Keys, CheckKeys } from './types';
  3 +import type { ReplaceFields, TreeItem, Keys, CheckKeys, TreeActionType } from './types';
4 4  
5 5 import { defineComponent, reactive, computed, unref, ref, watchEffect, CSSProperties } from 'vue';
6 6 import { Tree } from 'ant-design-vue';
... ... @@ -124,7 +124,6 @@ export default defineComponent({
124 124 title: () => (
125 125 <span class={`${prefixCls}-title`}>
126 126 <span class={`${prefixCls}__content`} style={unref(getContentStyle)}>
127   - {' '}
128 127 {titleField && anyItem[titleField]}
129 128 </span>
130 129 <span class={`${prefixCls}__actions`}> {renderAction(item)}</span>
... ... @@ -183,7 +182,7 @@ export default defineComponent({
183 182 state.checkedKeys = props.checkedKeys;
184 183 });
185 184  
186   - tryTsxEmit((currentInstance) => {
  185 + tryTsxEmit<TreeActionType>((currentInstance) => {
187 186 currentInstance.setExpandedKeys = setExpandedKeys;
188 187 currentInstance.getExpandedKeys = getExpandedKeys;
189 188 currentInstance.setSelectedKeys = setSelectedKeys;
... ...
src/components/Tree/src/useTree.ts
... ... @@ -10,7 +10,7 @@ export function useTree(
10 10 getReplaceFields: ComputedRef<ReplaceFields>
11 11 ) {
12 12 // 更新节点
13   - function updateNodeByKey(key: string, node: TreeItem, list: TreeItem[]) {
  13 + function updateNodeByKey(key: string, node: TreeItem, list?: TreeItem[]) {
14 14 if (!key) return;
15 15 const treeData = list || unref(treeDataRef);
16 16 const { key: keyField, children: childrenField } = unref(getReplaceFields);
... ... @@ -75,7 +75,7 @@ export function useTree(
75 75 }
76 76  
77 77 // 删除节点
78   - function deleteNodeByKey(key: string, list: TreeItem[]) {
  78 + function deleteNodeByKey(key: string, list?: TreeItem[]) {
79 79 if (!key) return;
80 80 const treeData = list || unref(treeDataRef);
81 81 const { key: keyField, children: childrenField } = unref(getReplaceFields);
... ...
src/components/Verify/src/DragVerify.tsx
... ... @@ -6,6 +6,7 @@ import { getSlot } from &#39;/@/utils/helper/tsxHelper&#39;;
6 6 import './DragVerify.less';
7 7 import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
8 8 import { tryTsxEmit } from '/@/utils/helper/vueHelper';
  9 +import type { DragVerifyActionType } from './types';
9 10 export default defineComponent({
10 11 name: 'BaseDargVerify',
11 12 props: basicProps,
... ... @@ -210,7 +211,7 @@ export default defineComponent({
210 211 contentEl.style.width = unref(getContentStyleRef).width;
211 212 }
212 213  
213   - tryTsxEmit((instance) => {
  214 + tryTsxEmit<DragVerifyActionType>((instance) => {
214 215 instance.resume = resume;
215 216 });
216 217  
... ...
src/hooks/setting/useRootSetting.ts
... ... @@ -46,7 +46,7 @@ export function useRootSetting() {
46 46 unref(getRootSetting).contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED
47 47 );
48 48  
49   - function setRootSetting(setting: RootSetting) {
  49 + function setRootSetting(setting: Partial<RootSetting>) {
50 50 appStore.commitProjectConfigState(setting);
51 51 }
52 52  
... ...
src/utils/helper/vueHelper.ts
... ... @@ -7,6 +7,7 @@ import {
7 7 onUnmounted,
8 8 nextTick,
9 9 reactive,
  10 + ComponentInternalInstance,
10 11 } from 'vue';
11 12  
12 13 export function explicitComputed<T, S>(source: WatchSource<S>, fn: () => T) {
... ... @@ -29,8 +30,10 @@ export function tryOnUnmounted(fn: () =&gt; Promise&lt;void&gt; | void) {
29 30 getCurrentInstance() && onUnmounted(fn);
30 31 }
31 32  
32   -export function tryTsxEmit(fn: (_instance: any) => Promise<void> | void) {
33   - const instance = getCurrentInstance();
  33 +export function tryTsxEmit<T extends any = ComponentInternalInstance>(
  34 + fn: (_instance: T) => Promise<void> | void
  35 +) {
  36 + const instance = getCurrentInstance() as any;
34 37 instance && fn.call(null, instance);
35 38 }
36 39  
... ...