Commit 81baf1d5c4606aab83c0e65397ce4b090c2e4e08
1 parent
819127e8
perf: perf modal and drawer
Showing
25 changed files
with
572 additions
and
497 deletions
CHANGELOG.zh_CN.md
@@ -18,12 +18,18 @@ | @@ -18,12 +18,18 @@ | ||
18 | 18 | ||
19 | - 缓存可以配置是否加密,默认生产环境开启 Aes 加密 | 19 | - 缓存可以配置是否加密,默认生产环境开启 Aes 加密 |
20 | - 新增标签页拖拽排序 | 20 | - 新增标签页拖拽排序 |
21 | +- 新增 LayoutFooter.默认显示,可以在配置内关闭 | ||
22 | + | ||
23 | +### ⚡ Performance Improvements | ||
24 | + | ||
25 | +- 优化`Modal`组件全屏动画不流畅问题 | ||
21 | 26 | ||
22 | ### 🐛 Bug Fixes | 27 | ### 🐛 Bug Fixes |
23 | 28 | ||
24 | - 修复 tree 文本超出挡住操作按钮问题 | 29 | - 修复 tree 文本超出挡住操作按钮问题 |
25 | - 修复通过 useRedo 刷新页面参数丢失问题 | 30 | - 修复通过 useRedo 刷新页面参数丢失问题 |
26 | - 修复表单校验先设置在校验及控制台错误信息问题 | 31 | - 修复表单校验先设置在校验及控制台错误信息问题 |
32 | +- 修复`modal`与`drawer`组件传递数组参数问题 | ||
27 | 33 | ||
28 | ### 🎫 Chores | 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 | export * from './src/types'; | 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 | import './index.less'; | 1 | import './index.less'; |
2 | 2 | ||
3 | import type { DrawerInstance, DrawerProps } from './types'; | 3 | import type { DrawerInstance, DrawerProps } from './types'; |
4 | +import type { CSSProperties } from 'vue'; | ||
4 | 5 | ||
5 | import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw } from 'vue'; | 6 | import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw } from 'vue'; |
6 | import { Drawer, Row, Col, Button } from 'ant-design-vue'; | 7 | 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'; | ||
9 | import { FullLoading } from '/@/components/Loading/index'; | 10 | import { FullLoading } from '/@/components/Loading/index'; |
10 | import { LeftOutlined } from '@ant-design/icons-vue'; | 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 | import { getSlot } from '/@/utils/helper/tsxHelper'; | 15 | import { getSlot } from '/@/utils/helper/tsxHelper'; |
15 | import { isFunction, isNumber } from '/@/utils/is'; | 16 | import { isFunction, isNumber } from '/@/utils/is'; |
16 | -import { buildUUID } from '/@/utils/uuid'; | ||
17 | import { deepMerge } from '/@/utils'; | 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 | const prefixCls = 'basic-drawer'; | 22 | const prefixCls = 'basic-drawer'; |
21 | export default defineComponent({ | 23 | export default defineComponent({ |
22 | - // inheritAttrs: false, | 24 | + inheritAttrs: false, |
23 | props: basicProps, | 25 | props: basicProps, |
24 | emits: ['visible-change', 'ok', 'close', 'register'], | 26 | emits: ['visible-change', 'ok', 'close', 'register'], |
25 | setup(props, { slots, emit, attrs }) { | 27 | setup(props, { slots, emit, attrs }) { |
26 | const scrollRef = ref<ElRef>(null); | 28 | const scrollRef = ref<ElRef>(null); |
27 | - | ||
28 | const visibleRef = ref(false); | 29 | const visibleRef = ref(false); |
29 | - const propsRef = ref<Partial<DrawerProps> | null>(null); | 30 | + const propsRef = ref<Partial<Nullable<DrawerProps>>>(null); |
30 | 31 | ||
31 | const { t } = useI18n('component.drawer'); | 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 | watchEffect(() => { | 105 | watchEffect(() => { |
@@ -74,22 +118,13 @@ export default defineComponent({ | @@ -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 | // Cancel event | 121 | // Cancel event |
87 | - async function onClose(e: any) { | 122 | + async function onClose(e: ChangeEvent) { |
88 | const { closeFunc } = unref(getProps); | 123 | const { closeFunc } = unref(getProps); |
89 | emit('close', e); | 124 | emit('close', e); |
90 | if (closeFunc && isFunction(closeFunc)) { | 125 | if (closeFunc && isFunction(closeFunc)) { |
91 | const res = await closeFunc(); | 126 | const res = await closeFunc(); |
92 | - res && (visibleRef.value = false); | 127 | + visibleRef.value = !res; |
93 | return; | 128 | return; |
94 | } | 129 | } |
95 | visibleRef.value = false; | 130 | visibleRef.value = false; |
@@ -98,12 +133,16 @@ export default defineComponent({ | @@ -98,12 +133,16 @@ export default defineComponent({ | ||
98 | function setDrawerProps(props: Partial<DrawerProps>): void { | 133 | function setDrawerProps(props: Partial<DrawerProps>): void { |
99 | // Keep the last setDrawerProps | 134 | // Keep the last setDrawerProps |
100 | propsRef.value = deepMerge(unref(propsRef) || {}, props); | 135 | propsRef.value = deepMerge(unref(propsRef) || {}, props); |
136 | + | ||
101 | if (Reflect.has(props, 'visible')) { | 137 | if (Reflect.has(props, 'visible')) { |
102 | visibleRef.value = !!props.visible; | 138 | visibleRef.value = !!props.visible; |
103 | } | 139 | } |
104 | } | 140 | } |
105 | 141 | ||
106 | function renderFooter() { | 142 | function renderFooter() { |
143 | + if (slots?.footer) { | ||
144 | + return getSlot(slots, 'footer'); | ||
145 | + } | ||
107 | const { | 146 | const { |
108 | showCancelBtn, | 147 | showCancelBtn, |
109 | cancelButtonProps, | 148 | cancelButtonProps, |
@@ -114,65 +153,64 @@ export default defineComponent({ | @@ -114,65 +153,64 @@ export default defineComponent({ | ||
114 | okButtonProps, | 153 | okButtonProps, |
115 | confirmLoading, | 154 | confirmLoading, |
116 | showFooter, | 155 | showFooter, |
117 | - }: DrawerProps = unref(getProps); | 156 | + } = unref(getProps); |
157 | + if (!showFooter) { | ||
158 | + return null; | ||
159 | + } | ||
118 | 160 | ||
119 | return ( | 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 | function renderHeader() { | 187 | function renderHeader() { |
188 | + if (slots?.title) { | ||
189 | + return getSlot(slots, 'title'); | ||
190 | + } | ||
151 | const { title } = unref(getMergeProps); | 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,41 +218,20 @@ export default defineComponent({ | ||
180 | setDrawerProps: setDrawerProps, | 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 | return () => { | 225 | return () => { |
187 | - const footerHeight = unref(getFooterHeight); | ||
188 | return ( | 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 | title: () => renderHeader(), | 229 | title: () => renderHeader(), |
199 | default: () => ( | 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 | </div> | 235 | </div> |
219 | {renderFooter()} | 236 | {renderFooter()} |
220 | </> | 237 | </> |
src/components/Drawer/src/props.ts
1 | import type { PropType } from 'vue'; | 1 | import type { PropType } from 'vue'; |
2 | 2 | ||
3 | import { useI18n } from '/@/hooks/web/useI18n'; | 3 | import { useI18n } from '/@/hooks/web/useI18n'; |
4 | +import { propTypes } from '/@/utils/propTypes'; | ||
4 | const { t } = useI18n('component.drawer'); | 5 | const { t } = useI18n('component.drawer'); |
5 | 6 | ||
6 | export const footerProps = { | 7 | export const footerProps = { |
7 | - confirmLoading: Boolean as PropType<boolean>, | 8 | + confirmLoading: propTypes.bool, |
8 | /** | 9 | /** |
9 | * @description: Show close button | 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 | cancelButtonProps: Object as PropType<any>, | 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 | * @description: Show confirmation button | 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 | footerHeight: { | 23 | footerHeight: { |
41 | type: [String, Number] as PropType<string | number>, | 24 | type: [String, Number] as PropType<string | number>, |
42 | default: 60, | 25 | default: 60, |
43 | }, | 26 | }, |
44 | }; | 27 | }; |
45 | export const basicProps = { | 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 | getContainer: { | 35 | getContainer: { |
71 | type: [Object, String] as PropType<any>, | 36 | type: [Object, String] as PropType<any>, |
72 | }, | 37 | }, |
@@ -78,10 +43,7 @@ export const basicProps = { | @@ -78,10 +43,7 @@ export const basicProps = { | ||
78 | type: [Function, Object] as PropType<any>, | 43 | type: [Function, Object] as PropType<any>, |
79 | default: null, | 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 | ...footerProps, | 48 | ...footerProps, |
87 | }; | 49 | }; |
src/components/Drawer/src/types.ts
@@ -75,7 +75,7 @@ export interface DrawerProps extends DrawerFooterProps { | @@ -75,7 +75,7 @@ export interface DrawerProps extends DrawerFooterProps { | ||
75 | * @type ScrollContainerOptions | 75 | * @type ScrollContainerOptions |
76 | */ | 76 | */ |
77 | scrollOptions?: ScrollContainerOptions; | 77 | scrollOptions?: ScrollContainerOptions; |
78 | - closeFunc?: () => Promise<void>; | 78 | + closeFunc?: () => Promise<any>; |
79 | triggerWindowResize?: boolean; | 79 | triggerWindowResize?: boolean; |
80 | /** | 80 | /** |
81 | * Whether a close (x) button is visible on top right of the Drawer dialog or not. | 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,12 +6,15 @@ import type { | ||
6 | UseDrawerInnerReturnType, | 6 | UseDrawerInnerReturnType, |
7 | } from './types'; | 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 | import { isProdMode } from '/@/utils/env'; | 11 | import { isProdMode } from '/@/utils/env'; |
12 | import { isFunction } from '/@/utils/is'; | 12 | import { isFunction } from '/@/utils/is'; |
13 | +import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; | ||
14 | +import { isEqual } from 'lodash-es'; | ||
13 | 15 | ||
14 | const dataTransferRef = reactive<any>({}); | 16 | const dataTransferRef = reactive<any>({}); |
17 | + | ||
15 | /** | 18 | /** |
16 | * @description: Applicable to separate drawer and call outside | 19 | * @description: Applicable to separate drawer and call outside |
17 | */ | 20 | */ |
@@ -19,21 +22,23 @@ export function useDrawer(): UseDrawerReturnType { | @@ -19,21 +22,23 @@ export function useDrawer(): UseDrawerReturnType { | ||
19 | if (!getCurrentInstance()) { | 22 | if (!getCurrentInstance()) { |
20 | throw new Error('Please put useDrawer function in the setup function!'); | 23 | throw new Error('Please put useDrawer function in the setup function!'); |
21 | } | 24 | } |
25 | + | ||
22 | const drawerRef = ref<DrawerInstance | null>(null); | 26 | const drawerRef = ref<DrawerInstance | null>(null); |
23 | - const loadedRef = ref<boolean | null>(false); | 27 | + const loadedRef = ref<Nullable<boolean>>(false); |
24 | const uidRef = ref<string>(''); | 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 | isProdMode() && | 31 | isProdMode() && |
29 | - onUnmounted(() => { | 32 | + tryOnUnmounted(() => { |
30 | drawerRef.value = null; | 33 | drawerRef.value = null; |
31 | loadedRef.value = null; | 34 | loadedRef.value = null; |
32 | dataTransferRef[unref(uidRef)] = null; | 35 | dataTransferRef[unref(uidRef)] = null; |
33 | }); | 36 | }); |
37 | + | ||
34 | if (unref(loadedRef) && isProdMode() && drawerInstance === unref(drawerRef)) { | 38 | if (unref(loadedRef) && isProdMode() && drawerInstance === unref(drawerRef)) { |
35 | return; | 39 | return; |
36 | } | 40 | } |
41 | + uidRef.value = uuid; | ||
37 | drawerRef.value = drawerInstance; | 42 | drawerRef.value = drawerInstance; |
38 | loadedRef.value = true; | 43 | loadedRef.value = true; |
39 | } | 44 | } |
@@ -55,37 +60,46 @@ export function useDrawer(): UseDrawerReturnType { | @@ -55,37 +60,46 @@ export function useDrawer(): UseDrawerReturnType { | ||
55 | getInstance().setDrawerProps({ | 60 | getInstance().setDrawerProps({ |
56 | visible: visible, | 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 | export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { | 80 | export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { |
72 | - const drawerInstanceRef = ref<DrawerInstance | null>(null); | 81 | + const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null); |
73 | const currentInstall = getCurrentInstance(); | 82 | const currentInstall = getCurrentInstance(); |
74 | const uidRef = ref<string>(''); | 83 | const uidRef = ref<string>(''); |
75 | 84 | ||
76 | if (!currentInstall) { | 85 | if (!currentInstall) { |
77 | - throw new Error('instance is undefined!'); | 86 | + throw new Error('useDrawerInner instance is undefined!'); |
78 | } | 87 | } |
79 | 88 | ||
80 | const getInstance = () => { | 89 | const getInstance = () => { |
81 | const instance = unref(drawerInstanceRef); | 90 | const instance = unref(drawerInstanceRef); |
82 | if (!instance) { | 91 | if (!instance) { |
83 | - throw new Error('instance is undefined!'); | 92 | + throw new Error('useDrawerInner instance is undefined!'); |
84 | } | 93 | } |
85 | return instance; | 94 | return instance; |
86 | }; | 95 | }; |
87 | 96 | ||
88 | const register = (modalInstance: DrawerInstance, uuid: string) => { | 97 | const register = (modalInstance: DrawerInstance, uuid: string) => { |
98 | + isProdMode() && | ||
99 | + tryOnUnmounted(() => { | ||
100 | + drawerInstanceRef.value = null; | ||
101 | + }); | ||
102 | + | ||
89 | uidRef.value = uuid; | 103 | uidRef.value = uuid; |
90 | drawerInstanceRef.value = modalInstance; | 104 | drawerInstanceRef.value = modalInstance; |
91 | currentInstall.emit('register', modalInstance); | 105 | currentInstall.emit('register', modalInstance); |
src/components/Modal/index.ts
1 | import './src/index.less'; | 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 | export { useModal, useModalInner } from './src/useModal'; | 6 | export { useModal, useModalInner } from './src/useModal'; |
5 | export * from './src/types'; | 7 | export * from './src/types'; |
8 | +export const BasicModal = withInstall(BasicModalLib); |
src/components/Modal/src/BasicModal.tsx
1 | import type { ModalProps, ModalMethods } from './types'; | 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 | import Modal from './Modal'; | 5 | import Modal from './Modal'; |
6 | import { Button } from '/@/components/Button'; | 6 | 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- | ||
11 | import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper'; | 11 | import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper'; |
12 | import { isFunction } from '/@/utils/is'; | 12 | import { isFunction } from '/@/utils/is'; |
13 | import { deepMerge } from '/@/utils'; | 13 | import { deepMerge } from '/@/utils'; |
14 | -import { buildUUID } from '/@/utils/uuid'; | 14 | +import { tryTsxEmit } from '/@/utils/helper/vueHelper'; |
15 | 15 | ||
16 | import { basicProps } from './props'; | 16 | import { basicProps } from './props'; |
17 | -// import { triggerWindowResize } from '@/utils/event/triggerWindowResizeEvent'; | 17 | +import { useFullScreen } from './useFullScreen'; |
18 | export default defineComponent({ | 18 | export default defineComponent({ |
19 | name: 'BasicModal', | 19 | name: 'BasicModal', |
20 | props: basicProps, | 20 | props: basicProps, |
@@ -26,31 +26,41 @@ export default defineComponent({ | @@ -26,31 +26,41 @@ export default defineComponent({ | ||
26 | // modal Bottom and top height | 26 | // modal Bottom and top height |
27 | const extHeightRef = ref(0); | 27 | const extHeightRef = ref(0); |
28 | // Unexpanded height of the popup | 28 | // Unexpanded height of the popup |
29 | - const formerHeightRef = ref(0); | ||
30 | - const fullScreenRef = ref(false); | ||
31 | 29 | ||
32 | // Custom title component: get title | 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 | // modal component does not need title | 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 | watchEffect(() => { | 66 | watchEffect(() => { |
@@ -80,7 +90,35 @@ export default defineComponent({ | @@ -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 | function renderContent() { | 117 | function renderContent() { |
118 | + type OmitWrapperType = Omit< | ||
119 | + ModalProps, | ||
120 | + 'fullScreen' | 'modalFooterHeight' | 'visible' | 'loading' | ||
121 | + >; | ||
84 | const { useWrapper, loading, wrapperProps } = unref(getProps); | 122 | const { useWrapper, loading, wrapperProps } = unref(getProps); |
85 | if (!useWrapper) return getSlot(slots); | 123 | if (!useWrapper) return getSlot(slots); |
86 | 124 | ||
@@ -93,7 +131,7 @@ export default defineComponent({ | @@ -93,7 +131,7 @@ export default defineComponent({ | ||
93 | loading={loading} | 131 | loading={loading} |
94 | visible={unref(visibleRef)} | 132 | visible={unref(visibleRef)} |
95 | modalFooterHeight={showFooter} | 133 | modalFooterHeight={showFooter} |
96 | - {...wrapperProps} | 134 | + {...((wrapperProps as unknown) as OmitWrapperType)} |
97 | onGetExtHeight={(height: number) => { | 135 | onGetExtHeight={(height: number) => { |
98 | extHeightRef.value = height; | 136 | extHeightRef.value = height; |
99 | }} | 137 | }} |
@@ -106,18 +144,6 @@ export default defineComponent({ | @@ -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 | function renderFooter() { | 148 | function renderFooter() { |
123 | const { | 149 | const { |
@@ -162,64 +188,37 @@ export default defineComponent({ | @@ -162,64 +188,37 @@ export default defineComponent({ | ||
162 | */ | 188 | */ |
163 | function renderClose() { | 189 | function renderClose() { |
164 | const { canFullscreen } = unref(getProps); | 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 | return ( | 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 | <CloseOutlined onClick={handleCancel} /> | 208 | <CloseOutlined onClick={handleCancel} /> |
176 | </div> | 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 | const modalMethods: ModalMethods = { | 213 | const modalMethods: ModalMethods = { |
215 | setModalProps, | 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 | return () => ( | 220 | return () => ( |
222 | - <Modal onCancel={handleCancel} {...{ ...attrs, ...props, ...unref(getProps) }}> | 221 | + <Modal onCancel={handleCancel} {...unref(getModalBindValue)}> |
223 | {{ | 222 | {{ |
224 | footer: () => renderFooter(), | 223 | footer: () => renderFooter(), |
225 | closeIcon: () => renderClose(), | 224 | closeIcon: () => renderClose(), |
src/components/Modal/src/Modal.tsx
1 | import { Modal } from 'ant-design-vue'; | 1 | import { Modal } from 'ant-design-vue'; |
2 | -import { defineComponent, watchEffect } from 'vue'; | 2 | +import { defineComponent, toRefs } from 'vue'; |
3 | import { basicProps } from './props'; | 3 | import { basicProps } from './props'; |
4 | -import { useTimeoutFn } from '/@/hooks/core/useTimeout'; | 4 | +import { useModalDragMove } from './useModalDrag'; |
5 | import { extendSlots } from '/@/utils/helper/tsxHelper'; | 5 | import { extendSlots } from '/@/utils/helper/tsxHelper'; |
6 | 6 | ||
7 | export default defineComponent({ | 7 | export default defineComponent({ |
@@ -9,99 +9,12 @@ export default defineComponent({ | @@ -9,99 +9,12 @@ export default defineComponent({ | ||
9 | inheritAttrs: false, | 9 | inheritAttrs: false, |
10 | props: basicProps, | 10 | props: basicProps, |
11 | setup(props, { attrs, slots }) { | 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 | return () => { | 20 | return () => { |
src/components/Modal/src/ModalWrapper.tsx
1 | -import type { PropType } from 'vue'; | ||
2 | import type { ModalWrapperProps } from './types'; | 1 | import type { ModalWrapperProps } from './types'; |
2 | +import type { CSSProperties } from 'vue'; | ||
3 | 3 | ||
4 | import { | 4 | import { |
5 | defineComponent, | 5 | defineComponent, |
@@ -18,59 +18,44 @@ import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; | @@ -18,59 +18,44 @@ import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; | ||
18 | 18 | ||
19 | import { getSlot } from '/@/utils/helper/tsxHelper'; | 19 | import { getSlot } from '/@/utils/helper/tsxHelper'; |
20 | import { useElResize } from '/@/hooks/event/useElResize'; | 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 | export default defineComponent({ | 24 | export default defineComponent({ |
24 | name: 'ModalWrapper', | 25 | name: 'ModalWrapper', |
25 | props: { | 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 | emits: ['heightChange', 'getExtHeight'], | 35 | emits: ['heightChange', 'getExtHeight'], |
56 | setup(props: ModalWrapperProps, { slots, emit }) { | 36 | setup(props: ModalWrapperProps, { slots, emit }) { |
57 | - const wrapperRef = ref<HTMLElement | null>(null); | 37 | + const wrapperRef = ref<ElRef>(null); |
58 | const spinRef = ref<ComponentRef>(null); | 38 | const spinRef = ref<ComponentRef>(null); |
59 | const realHeightRef = ref(0); | 39 | const realHeightRef = ref(0); |
60 | - // 重试次数 | ||
61 | - // let tryCount = 0; | 40 | + |
62 | let stopElResizeFn: Fn = () => {}; | 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 | watchEffect(() => { | 59 | watchEffect(() => { |
75 | setModalHeight(); | 60 | setModalHeight(); |
76 | }); | 61 | }); |
@@ -92,8 +77,6 @@ export default defineComponent({ | @@ -92,8 +77,6 @@ export default defineComponent({ | ||
92 | stopElResizeFn && stopElResizeFn(); | 77 | stopElResizeFn && stopElResizeFn(); |
93 | }); | 78 | }); |
94 | 79 | ||
95 | - useWindowSizeFn(setModalHeight); | ||
96 | - | ||
97 | async function setModalHeight() { | 80 | async function setModalHeight() { |
98 | // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度 | 81 | // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度 |
99 | // 加上这个,就必须在使用的时候传递父级的visible | 82 | // 加上这个,就必须在使用的时候传递父级的visible |
@@ -107,9 +90,8 @@ export default defineComponent({ | @@ -107,9 +90,8 @@ export default defineComponent({ | ||
107 | 90 | ||
108 | try { | 91 | try { |
109 | const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement; | 92 | const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement; |
110 | - if (!modalDom) { | ||
111 | - return; | ||
112 | - } | 93 | + if (!modalDom) return; |
94 | + | ||
113 | const modalRect = getComputedStyle(modalDom).top; | 95 | const modalRect = getComputedStyle(modalDom).top; |
114 | const modalTop = Number.parseInt(modalRect); | 96 | const modalTop = Number.parseInt(modalRect); |
115 | let maxHeight = | 97 | let maxHeight = |
@@ -135,11 +117,12 @@ export default defineComponent({ | @@ -135,11 +117,12 @@ export default defineComponent({ | ||
135 | 117 | ||
136 | if (props.fullScreen) { | 118 | if (props.fullScreen) { |
137 | realHeightRef.value = | 119 | realHeightRef.value = |
138 | - window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 6; | 120 | + window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight; |
139 | } else { | 121 | } else { |
140 | realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30; | 122 | realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30; |
141 | } | 123 | } |
142 | emit('heightChange', unref(realHeightRef)); | 124 | emit('heightChange', unref(realHeightRef)); |
125 | + | ||
143 | nextTick(() => { | 126 | nextTick(() => { |
144 | const el = spinEl.$el; | 127 | const el = spinEl.$el; |
145 | if (el) { | 128 | if (el) { |
@@ -154,8 +137,10 @@ export default defineComponent({ | @@ -154,8 +137,10 @@ export default defineComponent({ | ||
154 | function listenElResize() { | 137 | function listenElResize() { |
155 | const wrapper = unref(wrapperRef); | 138 | const wrapper = unref(wrapperRef); |
156 | if (!wrapper) return; | 139 | if (!wrapper) return; |
140 | + | ||
157 | const container = wrapper.querySelector('.ant-spin-container'); | 141 | const container = wrapper.querySelector('.ant-spin-container'); |
158 | if (!container) return; | 142 | if (!container) return; |
143 | + | ||
159 | const [start, stop] = useElResize(container, () => { | 144 | const [start, stop] = useElResize(container, () => { |
160 | setModalHeight(); | 145 | setModalHeight(); |
161 | }); | 146 | }); |
src/components/Modal/src/index.less
@@ -9,6 +9,11 @@ | @@ -9,6 +9,11 @@ | ||
9 | bottom: 0 !important; | 9 | bottom: 0 !important; |
10 | left: 0 !important; | 10 | left: 0 !important; |
11 | width: 100% !important; | 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,8 +40,23 @@ | ||
35 | height: 95%; | 40 | height: 95%; |
36 | align-items: center; | 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 | & span:nth-child(1) { | 62 | & span:nth-child(1) { |
@@ -76,7 +96,7 @@ | @@ -76,7 +96,7 @@ | ||
76 | } | 96 | } |
77 | 97 | ||
78 | &-footer { | 98 | &-footer { |
79 | - padding: 10px 26px 26px 16px; | 99 | + // padding: 10px 26px 26px 16px; |
80 | 100 | ||
81 | button + button { | 101 | button + button { |
82 | margin-left: 10px; | 102 | margin-left: 10px; |
src/components/Modal/src/props.ts
@@ -2,66 +2,38 @@ import type { PropType } from 'vue'; | @@ -2,66 +2,38 @@ import type { PropType } from 'vue'; | ||
2 | import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; | 2 | import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; |
3 | 3 | ||
4 | import { useI18n } from '/@/hooks/web/useI18n'; | 4 | import { useI18n } from '/@/hooks/web/useI18n'; |
5 | +import { propTypes } from '/@/utils/propTypes'; | ||
5 | const { t } = useI18n('component.modal'); | 6 | const { t } = useI18n('component.modal'); |
6 | 7 | ||
7 | export const modalProps = { | 8 | export const modalProps = { |
8 | - visible: Boolean as PropType<boolean>, | 9 | + visible: propTypes.bool, |
9 | // open drag | 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 | closeFunc: Function as PropType<() => Promise<boolean>>, | 16 | closeFunc: Function as PropType<() => Promise<boolean>>, |
27 | }; | 17 | }; |
28 | 18 | ||
29 | export const basicProps = Object.assign({}, modalProps, { | 19 | export const basicProps = Object.assign({}, modalProps, { |
30 | // Can it be full screen | 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 | // After enabling the wrapper, the bottom can be increased in height | 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 | // Warm reminder message | 24 | // Warm reminder message |
41 | helpMessage: [String, Array] as PropType<string | string[]>, | 25 | helpMessage: [String, Array] as PropType<string | string[]>, |
42 | // Whether to setting wrapper | 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 | * @description: Show close button | 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 | * @description: Show confirmation button | 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 | wrapperProps: Object as PropType<any>, | 38 | wrapperProps: Object as PropType<any>, |
67 | 39 |
src/components/Modal/src/provideModal.ts deleted
100644 → 0
src/components/Modal/src/types.ts
@@ -8,9 +8,11 @@ export interface ModalMethods { | @@ -8,9 +8,11 @@ export interface ModalMethods { | ||
8 | } | 8 | } |
9 | 9 | ||
10 | export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void; | 10 | export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void; |
11 | + | ||
11 | export interface ReturnMethods extends ModalMethods { | 12 | export interface ReturnMethods extends ModalMethods { |
12 | openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void; | 13 | openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void; |
13 | } | 14 | } |
15 | + | ||
14 | export type UseModalReturnType = [RegisterFn, ReturnMethods]; | 16 | export type UseModalReturnType = [RegisterFn, ReturnMethods]; |
15 | 17 | ||
16 | export interface ReturnInnerMethods extends ModalMethods { | 18 | export interface ReturnInnerMethods extends ModalMethods { |
@@ -18,6 +20,7 @@ export interface ReturnInnerMethods extends ModalMethods { | @@ -18,6 +20,7 @@ export interface ReturnInnerMethods extends ModalMethods { | ||
18 | changeLoading: (loading: boolean) => void; | 20 | changeLoading: (loading: boolean) => void; |
19 | changeOkLoading: (loading: boolean) => void; | 21 | changeOkLoading: (loading: boolean) => void; |
20 | } | 22 | } |
23 | + | ||
21 | export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]; | 24 | export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]; |
22 | 25 | ||
23 | export interface ModalProps { | 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,9 +5,21 @@ import type { | ||
5 | ReturnMethods, | 5 | ReturnMethods, |
6 | UseModalInnerReturnType, | 6 | UseModalInnerReturnType, |
7 | } from './types'; | 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 | import { isProdMode } from '/@/utils/env'; | 19 | import { isProdMode } from '/@/utils/env'; |
10 | import { isFunction } from '/@/utils/is'; | 20 | import { isFunction } from '/@/utils/is'; |
21 | +import { isEqual } from 'lodash-es'; | ||
22 | +import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; | ||
11 | const dataTransferRef = reactive<any>({}); | 23 | const dataTransferRef = reactive<any>({}); |
12 | 24 | ||
13 | /** | 25 | /** |
@@ -20,6 +32,7 @@ export function useModal(): UseModalReturnType { | @@ -20,6 +32,7 @@ export function useModal(): UseModalReturnType { | ||
20 | const modalRef = ref<Nullable<ModalMethods>>(null); | 32 | const modalRef = ref<Nullable<ModalMethods>>(null); |
21 | const loadedRef = ref<Nullable<boolean>>(false); | 33 | const loadedRef = ref<Nullable<boolean>>(false); |
22 | const uidRef = ref<string>(''); | 34 | const uidRef = ref<string>(''); |
35 | + | ||
23 | function register(modalMethod: ModalMethods, uuid: string) { | 36 | function register(modalMethod: ModalMethods, uuid: string) { |
24 | uidRef.value = uuid; | 37 | uidRef.value = uuid; |
25 | 38 | ||
@@ -52,13 +65,16 @@ export function useModal(): UseModalReturnType { | @@ -52,13 +65,16 @@ export function useModal(): UseModalReturnType { | ||
52 | visible: visible, | 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,7 +82,7 @@ export function useModal(): UseModalReturnType { | ||
66 | } | 82 | } |
67 | 83 | ||
68 | export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { | 84 | export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { |
69 | - const modalInstanceRef = ref<ModalMethods | null>(null); | 85 | + const modalInstanceRef = ref<Nullable<ModalMethods>>(null); |
70 | const currentInstall = getCurrentInstance(); | 86 | const currentInstall = getCurrentInstance(); |
71 | const uidRef = ref<string>(''); | 87 | const uidRef = ref<string>(''); |
72 | 88 | ||
@@ -83,6 +99,11 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { | @@ -83,6 +99,11 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { | ||
83 | }; | 99 | }; |
84 | 100 | ||
85 | const register = (modalInstance: ModalMethods, uuid: string) => { | 101 | const register = (modalInstance: ModalMethods, uuid: string) => { |
102 | + isProdMode() && | ||
103 | + tryOnUnmounted(() => { | ||
104 | + modalInstanceRef.value = null; | ||
105 | + }); | ||
106 | + | ||
86 | uidRef.value = uuid; | 107 | uidRef.value = uuid; |
87 | modalInstanceRef.value = modalInstance; | 108 | modalInstanceRef.value = modalInstance; |
88 | currentInstall.emit('register', modalInstance); | 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,7 +65,7 @@ export default defineComponent({ | ||
65 | } | 65 | } |
66 | 66 | ||
67 | onMounted(() => { | 67 | onMounted(() => { |
68 | - tryTsxEmit((instance) => { | 68 | + tryTsxEmit<any>((instance) => { |
69 | instance.wrap = unref(wrapElRef); | 69 | instance.wrap = unref(wrapElRef); |
70 | }); | 70 | }); |
71 | 71 |
src/components/Table/src/hooks/useTableScroll.ts
1 | import type { BasicTableProps } from '../types/table'; | 1 | import type { BasicTableProps } from '../types/table'; |
2 | import { computed, Ref, onMounted, unref, ref, nextTick, ComputedRef, watch } from 'vue'; | 2 | import { computed, Ref, onMounted, unref, ref, nextTick, ComputedRef, watch } from 'vue'; |
3 | 3 | ||
4 | -import { injectModal } from '/@/components/Modal/src/provideModal'; | ||
5 | - | ||
6 | import { getViewportOffset } from '/@/utils/domUtils'; | 4 | import { getViewportOffset } from '/@/utils/domUtils'; |
7 | import { isBoolean } from '/@/utils/is'; | 5 | import { isBoolean } from '/@/utils/is'; |
8 | 6 | ||
9 | import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; | 7 | import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; |
10 | import { useProps } from './useProps'; | 8 | import { useProps } from './useProps'; |
9 | +import { useModalContext } from '/@/components/Modal'; | ||
11 | 10 | ||
12 | export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) { | 11 | export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) { |
13 | const { propsRef } = useProps(refProps); | 12 | const { propsRef } = useProps(refProps); |
14 | 13 | ||
15 | const tableHeightRef: Ref<number | null> = ref(null); | 14 | const tableHeightRef: Ref<number | null> = ref(null); |
16 | 15 | ||
17 | - const redoModalHeight = injectModal(); | 16 | + const modalFn = useModalContext(); |
18 | 17 | ||
19 | watch( | 18 | watch( |
20 | () => unref(propsRef).canResize, | 19 | () => 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 | ||
93 | tableHeightRef.value = | 92 | tableHeightRef.value = |
94 | tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value; | 93 | tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value; |
95 | // 解决表格放modal内的时候,modal自适应高度计算问题 | 94 | // 解决表格放modal内的时候,modal自适应高度计算问题 |
96 | - redoModalHeight && redoModalHeight(); | 95 | + modalFn?.redoModalHeight?.(); |
97 | }, 16); | 96 | }, 16); |
98 | } | 97 | } |
99 | 98 |
src/components/Tree/src/BasicTree.tsx
1 | import './index.less'; | 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 | import { defineComponent, reactive, computed, unref, ref, watchEffect, CSSProperties } from 'vue'; | 5 | import { defineComponent, reactive, computed, unref, ref, watchEffect, CSSProperties } from 'vue'; |
6 | import { Tree } from 'ant-design-vue'; | 6 | import { Tree } from 'ant-design-vue'; |
@@ -124,7 +124,6 @@ export default defineComponent({ | @@ -124,7 +124,6 @@ export default defineComponent({ | ||
124 | title: () => ( | 124 | title: () => ( |
125 | <span class={`${prefixCls}-title`}> | 125 | <span class={`${prefixCls}-title`}> |
126 | <span class={`${prefixCls}__content`} style={unref(getContentStyle)}> | 126 | <span class={`${prefixCls}__content`} style={unref(getContentStyle)}> |
127 | - {' '} | ||
128 | {titleField && anyItem[titleField]} | 127 | {titleField && anyItem[titleField]} |
129 | </span> | 128 | </span> |
130 | <span class={`${prefixCls}__actions`}> {renderAction(item)}</span> | 129 | <span class={`${prefixCls}__actions`}> {renderAction(item)}</span> |
@@ -183,7 +182,7 @@ export default defineComponent({ | @@ -183,7 +182,7 @@ export default defineComponent({ | ||
183 | state.checkedKeys = props.checkedKeys; | 182 | state.checkedKeys = props.checkedKeys; |
184 | }); | 183 | }); |
185 | 184 | ||
186 | - tryTsxEmit((currentInstance) => { | 185 | + tryTsxEmit<TreeActionType>((currentInstance) => { |
187 | currentInstance.setExpandedKeys = setExpandedKeys; | 186 | currentInstance.setExpandedKeys = setExpandedKeys; |
188 | currentInstance.getExpandedKeys = getExpandedKeys; | 187 | currentInstance.getExpandedKeys = getExpandedKeys; |
189 | currentInstance.setSelectedKeys = setSelectedKeys; | 188 | currentInstance.setSelectedKeys = setSelectedKeys; |
src/components/Tree/src/useTree.ts
@@ -10,7 +10,7 @@ export function useTree( | @@ -10,7 +10,7 @@ export function useTree( | ||
10 | getReplaceFields: ComputedRef<ReplaceFields> | 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 | if (!key) return; | 14 | if (!key) return; |
15 | const treeData = list || unref(treeDataRef); | 15 | const treeData = list || unref(treeDataRef); |
16 | const { key: keyField, children: childrenField } = unref(getReplaceFields); | 16 | const { key: keyField, children: childrenField } = unref(getReplaceFields); |
@@ -75,7 +75,7 @@ export function useTree( | @@ -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 | if (!key) return; | 79 | if (!key) return; |
80 | const treeData = list || unref(treeDataRef); | 80 | const treeData = list || unref(treeDataRef); |
81 | const { key: keyField, children: childrenField } = unref(getReplaceFields); | 81 | const { key: keyField, children: childrenField } = unref(getReplaceFields); |
src/components/Verify/src/DragVerify.tsx
@@ -6,6 +6,7 @@ import { getSlot } from '/@/utils/helper/tsxHelper'; | @@ -6,6 +6,7 @@ import { getSlot } from '/@/utils/helper/tsxHelper'; | ||
6 | import './DragVerify.less'; | 6 | import './DragVerify.less'; |
7 | import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | 7 | import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; |
8 | import { tryTsxEmit } from '/@/utils/helper/vueHelper'; | 8 | import { tryTsxEmit } from '/@/utils/helper/vueHelper'; |
9 | +import type { DragVerifyActionType } from './types'; | ||
9 | export default defineComponent({ | 10 | export default defineComponent({ |
10 | name: 'BaseDargVerify', | 11 | name: 'BaseDargVerify', |
11 | props: basicProps, | 12 | props: basicProps, |
@@ -210,7 +211,7 @@ export default defineComponent({ | @@ -210,7 +211,7 @@ export default defineComponent({ | ||
210 | contentEl.style.width = unref(getContentStyleRef).width; | 211 | contentEl.style.width = unref(getContentStyleRef).width; |
211 | } | 212 | } |
212 | 213 | ||
213 | - tryTsxEmit((instance) => { | 214 | + tryTsxEmit<DragVerifyActionType>((instance) => { |
214 | instance.resume = resume; | 215 | instance.resume = resume; |
215 | }); | 216 | }); |
216 | 217 |
src/hooks/setting/useRootSetting.ts
@@ -46,7 +46,7 @@ export function useRootSetting() { | @@ -46,7 +46,7 @@ export function useRootSetting() { | ||
46 | unref(getRootSetting).contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED | 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 | appStore.commitProjectConfigState(setting); | 50 | appStore.commitProjectConfigState(setting); |
51 | } | 51 | } |
52 | 52 |
src/utils/helper/vueHelper.ts
@@ -7,6 +7,7 @@ import { | @@ -7,6 +7,7 @@ import { | ||
7 | onUnmounted, | 7 | onUnmounted, |
8 | nextTick, | 8 | nextTick, |
9 | reactive, | 9 | reactive, |
10 | + ComponentInternalInstance, | ||
10 | } from 'vue'; | 11 | } from 'vue'; |
11 | 12 | ||
12 | export function explicitComputed<T, S>(source: WatchSource<S>, fn: () => T) { | 13 | 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) { | ||
29 | getCurrentInstance() && onUnmounted(fn); | 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 | instance && fn.call(null, instance); | 37 | instance && fn.call(null, instance); |
35 | } | 38 | } |
36 | 39 |