Commit ebf7c8aa53b7ed11c72734646d558a559e818473
1 parent
73cee06d
perf(modal-drawer): replace the scrollbar assembly
Showing
40 changed files
with
1088 additions
and
862 deletions
CHANGELOG.zh_CN.md
... | ... | @@ -12,6 +12,10 @@ |
12 | 12 | - form: 新增远程下拉`ApiSelect`及示例 |
13 | 13 | - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 |
14 | 14 | |
15 | +### ⚡ Performance Improvements | |
16 | + | |
17 | +- 优化`modal`与`drawer`滚动条组件 | |
18 | + | |
15 | 19 | ### 🐛 Bug Fixes |
16 | 20 | |
17 | 21 | - 修复混合模式下滚动条丢失问题 |
... | ... | @@ -21,6 +25,7 @@ |
21 | 25 | - 修复路由类型错误 |
22 | 26 | - 修复菜单分割时权限失效问题 |
23 | 27 | - 关闭多标签页时 iframe 提前加载 |
28 | +- 修复`modal`与`drawer`已知问题 | |
24 | 29 | |
25 | 30 | ## 2.0.0-rc.14 (2020-12-15) |
26 | 31 | ... | ... |
src/components/Container/src/LazyContainer.vue
1 | 1 | <template> |
2 | 2 | <transition-group |
3 | - class="lazy-container" | |
3 | + :class="prefixCls" | |
4 | 4 | v-bind="$attrs" |
5 | 5 | ref="elRef" |
6 | 6 | :name="transitionName" |
... | ... | @@ -25,6 +25,7 @@ |
25 | 25 | import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
26 | 26 | import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'; |
27 | 27 | import { propTypes } from '/@/utils/propTypes'; |
28 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
28 | 29 | |
29 | 30 | interface State { |
30 | 31 | isInit: boolean; |
... | ... | @@ -70,6 +71,8 @@ |
70 | 71 | intersectionObserverInstance: null, |
71 | 72 | }); |
72 | 73 | |
74 | + const { prefixCls } = useDesign('lazy-container'); | |
75 | + | |
73 | 76 | onMounted(() => { |
74 | 77 | immediateInit(); |
75 | 78 | initIntersectionObserver(); |
... | ... | @@ -129,13 +132,17 @@ |
129 | 132 | } |
130 | 133 | return { |
131 | 134 | elRef, |
135 | + prefixCls, | |
132 | 136 | ...toRefs(state), |
133 | 137 | }; |
134 | 138 | }, |
135 | 139 | }); |
136 | 140 | </script> |
137 | 141 | <style lang="less"> |
138 | - .lazy-container { | |
142 | + @import (reference) '../../../design/index.less'; | |
143 | + @prefix-cls: ~'@{namespace}-lazy-container'; | |
144 | + | |
145 | + .@{prefix-cls} { | |
139 | 146 | width: 100%; |
140 | 147 | height: 100%; |
141 | 148 | } | ... | ... |
src/components/Container/src/collapse/CollapseContainer.vue
1 | 1 | <template> |
2 | - <div class="collapse-container p-2"> | |
3 | - <CollapseHeader v-bind="$props" :show="show" @expand="handleExpand"> | |
2 | + <div :class="['p-2', prefixCls]"> | |
3 | + <CollapseHeader v-bind="$props" :prefixCls="prefixCls" :show="show" @expand="handleExpand"> | |
4 | 4 | <template #title> |
5 | 5 | <slot name="title" /> |
6 | 6 | </template> |
... | ... | @@ -8,7 +8,7 @@ |
8 | 8 | |
9 | 9 | <CollapseTransition :enable="canExpan"> |
10 | 10 | <Skeleton v-if="loading" /> |
11 | - <div class="collapse-container__body" v-else v-show="show"> | |
11 | + <div :class="`${prefixCls}__body`" v-else v-show="show"> | |
12 | 12 | <LazyContainer :timeout="lazyTime" v-if="lazy"> |
13 | 13 | <slot /> |
14 | 14 | <template #skeleton> |
... | ... | @@ -35,6 +35,7 @@ |
35 | 35 | // hook |
36 | 36 | import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
37 | 37 | import { propTypes } from '/@/utils/propTypes'; |
38 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
38 | 39 | |
39 | 40 | export default defineComponent({ |
40 | 41 | name: 'CollapseContainer', |
... | ... | @@ -64,6 +65,9 @@ |
64 | 65 | }, |
65 | 66 | setup(props) { |
66 | 67 | const show = ref(true); |
68 | + | |
69 | + const { prefixCls } = useDesign('collapse-container'); | |
70 | + | |
67 | 71 | /** |
68 | 72 | * @description: Handling development events |
69 | 73 | */ |
... | ... | @@ -77,20 +81,20 @@ |
77 | 81 | return { |
78 | 82 | show, |
79 | 83 | handleExpand, |
84 | + prefixCls, | |
80 | 85 | }; |
81 | 86 | }, |
82 | 87 | }); |
83 | 88 | </script> |
84 | 89 | <style lang="less"> |
85 | - .collapse-container { | |
90 | + @import (reference) '../../../../design/index.less'; | |
91 | + @prefix-cls: ~'@{namespace}-collapse-container'; | |
92 | + | |
93 | + .@{prefix-cls} { | |
86 | 94 | background: #fff; |
87 | 95 | border-radius: 2px; |
88 | 96 | transition: all 0.3s ease-in-out; |
89 | 97 | |
90 | - &.no-shadow { | |
91 | - box-shadow: none; | |
92 | - } | |
93 | - | |
94 | 98 | &__header { |
95 | 99 | display: flex; |
96 | 100 | height: 32px; | ... | ... |
src/components/Container/src/collapse/CollapseHeader.vue
1 | 1 | <template> |
2 | - <div class="collapse-container__header"> | |
2 | + <div :class="`${prefixCls}__header`"> | |
3 | 3 | <BasicTitle :helpMessage="$attrs.helpMessage"> |
4 | 4 | <template v-if="$attrs.title"> |
5 | 5 | {{ $attrs.title }} |
... | ... | @@ -9,7 +9,7 @@ |
9 | 9 | </template> |
10 | 10 | </BasicTitle> |
11 | 11 | |
12 | - <div class="collapse-container__action"> | |
12 | + <div :class="`${prefixCls}__action`"> | |
13 | 13 | <slot name="action" /> |
14 | 14 | <BasicArrow v-if="$attrs.canExpan" top :expand="$attrs.show" @click="$emit('expand')" /> |
15 | 15 | </div> |
... | ... | @@ -21,5 +21,8 @@ |
21 | 21 | export default defineComponent({ |
22 | 22 | inheritAttrs: false, |
23 | 23 | components: { BasicArrow, BasicTitle }, |
24 | + props: { | |
25 | + prefixCls: String, | |
26 | + }, | |
24 | 27 | }); |
25 | 28 | </script> | ... | ... |
src/components/Description/src/index.tsx
src/components/Description/src/props.ts
... | ... | @@ -13,7 +13,7 @@ export default { |
13 | 13 | bordered: propTypes.bool.def(true), |
14 | 14 | |
15 | 15 | column: { |
16 | - type: [Number, Object] as PropType<number | any>, | |
16 | + type: [Number, Object] as PropType<number | Recordable>, | |
17 | 17 | default: () => { |
18 | 18 | return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }; |
19 | 19 | }, | ... | ... |
src/components/Description/src/types.ts
... | ... | @@ -15,7 +15,10 @@ export interface DescItem { |
15 | 15 | span?: number; |
16 | 16 | show?: (...arg: any) => boolean; |
17 | 17 | // render |
18 | - render?: (val: string, data: any) => VNode | undefined | JSX.Element | Element | string | number; | |
18 | + render?: ( | |
19 | + val: string, | |
20 | + data: Recordable | |
21 | + ) => VNode | undefined | JSX.Element | Element | string | number; | |
19 | 22 | } |
20 | 23 | |
21 | 24 | export interface DescOptions extends DescriptionsProps { |
... | ... | @@ -30,7 +33,7 @@ export interface DescOptions extends DescriptionsProps { |
30 | 33 | * 数据 |
31 | 34 | * @type object |
32 | 35 | */ |
33 | - data: any; | |
36 | + data: Recordable; | |
34 | 37 | /** |
35 | 38 | * Built-in CollapseContainer component configuration |
36 | 39 | * @type CollapseContainerOptions | ... | ... |
src/components/Description/src/useDescription.ts
... | ... | @@ -19,7 +19,7 @@ export function useDescription(props?: Partial<DescOptions>): UseDescReturnType |
19 | 19 | |
20 | 20 | const methods: DescInstance = { |
21 | 21 | setDescProps: (descProps: Partial<DescOptions>): void => { |
22 | - unref(descRef)!.setDescProps(descProps); | |
22 | + unref(descRef)?.setDescProps(descProps); | |
23 | 23 | }, |
24 | 24 | }; |
25 | 25 | ... | ... |
src/components/Drawer/index.ts
src/components/Drawer/src/BasicDrawer.tsx deleted
100644 → 0
1 | -import './index.less'; | |
2 | - | |
3 | -import type { DrawerInstance, DrawerProps } from './types'; | |
4 | -import type { CSSProperties } from 'vue'; | |
5 | - | |
6 | -import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw } from 'vue'; | |
7 | -import { Drawer, Row, Col, Button } from 'ant-design-vue'; | |
8 | - | |
9 | -import { BasicTitle } from '/@/components/Basic'; | |
10 | -import { Loading } from '/@/components/Loading'; | |
11 | -import { LeftOutlined } from '@ant-design/icons-vue'; | |
12 | - | |
13 | -import { useI18n } from '/@/hooks/web/useI18n'; | |
14 | - | |
15 | -import { getSlot } from '/@/utils/helper/tsxHelper'; | |
16 | -import { isFunction, isNumber } from '/@/utils/is'; | |
17 | -import { deepMerge } from '/@/utils'; | |
18 | -import { tryTsxEmit } from '/@/utils/helper/vueHelper'; | |
19 | - | |
20 | -import { basicProps } from './props'; | |
21 | - | |
22 | -const prefixCls = 'basic-drawer'; | |
23 | -export default defineComponent({ | |
24 | - inheritAttrs: false, | |
25 | - props: basicProps, | |
26 | - emits: ['visible-change', 'ok', 'close', 'register'], | |
27 | - setup(props, { slots, emit, attrs }) { | |
28 | - const scrollRef = ref<ElRef>(null); | |
29 | - const visibleRef = ref(false); | |
30 | - const propsRef = ref<Partial<Nullable<DrawerProps>>>(null); | |
31 | - | |
32 | - const { t } = useI18n(); | |
33 | - | |
34 | - const getMergeProps = computed( | |
35 | - (): DrawerProps => { | |
36 | - return deepMerge(toRaw(props), unref(propsRef)); | |
37 | - } | |
38 | - ); | |
39 | - | |
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 | - } | |
62 | - } | |
63 | - return opt as DrawerProps; | |
64 | - } | |
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 !!unref(getProps)?.loading; | |
101 | - }); | |
102 | - | |
103 | - watchEffect(() => { | |
104 | - visibleRef.value = props.visible; | |
105 | - }); | |
106 | - | |
107 | - watch( | |
108 | - () => visibleRef.value, | |
109 | - (visible) => { | |
110 | - nextTick(() => { | |
111 | - emit('visible-change', visible); | |
112 | - }); | |
113 | - }, | |
114 | - { | |
115 | - immediate: false, | |
116 | - } | |
117 | - ); | |
118 | - | |
119 | - // Cancel event | |
120 | - async function onClose(e: ChangeEvent) { | |
121 | - const { closeFunc } = unref(getProps); | |
122 | - emit('close', e); | |
123 | - if (closeFunc && isFunction(closeFunc)) { | |
124 | - const res = await closeFunc(); | |
125 | - visibleRef.value = !res; | |
126 | - return; | |
127 | - } | |
128 | - visibleRef.value = false; | |
129 | - } | |
130 | - | |
131 | - function setDrawerProps(props: Partial<DrawerProps>): void { | |
132 | - // Keep the last setDrawerProps | |
133 | - propsRef.value = deepMerge(unref(propsRef) || {}, props); | |
134 | - | |
135 | - if (Reflect.has(props, 'visible')) { | |
136 | - visibleRef.value = !!props.visible; | |
137 | - } | |
138 | - } | |
139 | - | |
140 | - function renderFooter() { | |
141 | - if (slots?.footer) { | |
142 | - return getSlot(slots, 'footer'); | |
143 | - } | |
144 | - const { | |
145 | - showCancelBtn, | |
146 | - cancelButtonProps, | |
147 | - cancelText, | |
148 | - showOkBtn, | |
149 | - okType, | |
150 | - okText, | |
151 | - okButtonProps, | |
152 | - confirmLoading, | |
153 | - showFooter, | |
154 | - } = unref(getProps); | |
155 | - if (!showFooter) { | |
156 | - return null; | |
157 | - } | |
158 | - | |
159 | - return ( | |
160 | - <div class={`${prefixCls}__footer`}> | |
161 | - {getSlot(slots, 'insertFooter')} | |
162 | - {showCancelBtn && ( | |
163 | - <Button {...cancelButtonProps} onClick={onClose} class="mr-2"> | |
164 | - {() => cancelText} | |
165 | - </Button> | |
166 | - )} | |
167 | - {getSlot(slots, 'centerFooter')} | |
168 | - {showOkBtn && ( | |
169 | - <Button | |
170 | - type={okType} | |
171 | - onClick={() => { | |
172 | - emit('ok'); | |
173 | - }} | |
174 | - {...okButtonProps} | |
175 | - loading={confirmLoading} | |
176 | - > | |
177 | - {() => okText} | |
178 | - </Button> | |
179 | - )} | |
180 | - {getSlot(slots, 'appendFooter')} | |
181 | - </div> | |
182 | - ); | |
183 | - } | |
184 | - | |
185 | - function renderHeader() { | |
186 | - if (slots?.title) { | |
187 | - return getSlot(slots, 'title'); | |
188 | - } | |
189 | - const { title } = unref(getMergeProps); | |
190 | - | |
191 | - if (!props.isDetail) { | |
192 | - return <BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>; | |
193 | - } | |
194 | - return ( | |
195 | - <Row type="flex" align="middle" class={`${prefixCls}__detail-header`}> | |
196 | - {() => ( | |
197 | - <> | |
198 | - {props.showDetailBack && ( | |
199 | - <Button size="small" type="link" onClick={onClose}> | |
200 | - {() => <LeftOutlined />} | |
201 | - </Button> | |
202 | - )} | |
203 | - {title && ( | |
204 | - <Col style="flex:1" class={[`${prefixCls}__detail-title`, 'ellipsis', 'px-2']}> | |
205 | - {() => title} | |
206 | - </Col> | |
207 | - )} | |
208 | - {getSlot(slots, 'titleToolbar')} | |
209 | - </> | |
210 | - )} | |
211 | - </Row> | |
212 | - ); | |
213 | - } | |
214 | - | |
215 | - const drawerInstance: DrawerInstance = { | |
216 | - setDrawerProps: setDrawerProps, | |
217 | - }; | |
218 | - | |
219 | - tryTsxEmit((instance) => { | |
220 | - emit('register', drawerInstance, instance.uid); | |
221 | - }); | |
222 | - | |
223 | - return () => { | |
224 | - return ( | |
225 | - <Drawer class={prefixCls} onClose={onClose} {...unref(getBindValues)}> | |
226 | - {{ | |
227 | - title: () => renderHeader(), | |
228 | - default: () => ( | |
229 | - <> | |
230 | - <div ref={scrollRef} style={unref(getScrollContentStyle)}> | |
231 | - <Loading | |
232 | - absolute | |
233 | - tip={t('component.drawer.loadingText')} | |
234 | - loading={unref(getLoading)} | |
235 | - /> | |
236 | - {getSlot(slots)} | |
237 | - </div> | |
238 | - {renderFooter()} | |
239 | - </> | |
240 | - ), | |
241 | - }} | |
242 | - </Drawer> | |
243 | - ); | |
244 | - }; | |
245 | - }, | |
246 | -}); |
src/components/Drawer/src/BasicDrawer.vue
0 → 100644
1 | +<template> | |
2 | + <Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues"> | |
3 | + <template #title v-if="!$slots.title"> | |
4 | + <DrawerHeader | |
5 | + :title="getMergeProps.title" | |
6 | + :isDetail="isDetail" | |
7 | + :showDetailBack="showDetailBack" | |
8 | + @close="onClose" | |
9 | + > | |
10 | + <template #titleToolbar> | |
11 | + <slot name="titleToolbar" /> | |
12 | + </template> | |
13 | + </DrawerHeader> | |
14 | + </template> | |
15 | + | |
16 | + <ScrollContainer | |
17 | + :style="getScrollContentStyle" | |
18 | + v-loading="getLoading" | |
19 | + :loading-tip="loadingText || t('component.drawer.loadingText')" | |
20 | + > | |
21 | + <slot /> | |
22 | + </ScrollContainer> | |
23 | + <DrawerFooter v-bind="getProps" @close="onClose" @ok="handleOk" :height="getFooterHeight"> | |
24 | + <template #[item]="data" v-for="item in Object.keys($slots)"> | |
25 | + <slot :name="item" v-bind="data" /> | |
26 | + </template> | |
27 | + </DrawerFooter> | |
28 | + </Drawer> | |
29 | +</template> | |
30 | +<script lang="ts"> | |
31 | + import type { DrawerInstance, DrawerProps } from './types'; | |
32 | + import type { CSSProperties } from 'vue'; | |
33 | + | |
34 | + import { | |
35 | + defineComponent, | |
36 | + ref, | |
37 | + computed, | |
38 | + watchEffect, | |
39 | + watch, | |
40 | + unref, | |
41 | + nextTick, | |
42 | + toRaw, | |
43 | + getCurrentInstance, | |
44 | + } from 'vue'; | |
45 | + import { Drawer } from 'ant-design-vue'; | |
46 | + | |
47 | + import { useI18n } from '/@/hooks/web/useI18n'; | |
48 | + | |
49 | + import { isFunction, isNumber } from '/@/utils/is'; | |
50 | + import { deepMerge } from '/@/utils'; | |
51 | + import DrawerFooter from './components/DrawerFooter.vue'; | |
52 | + import DrawerHeader from './components/DrawerHeader.vue'; | |
53 | + import { ScrollContainer } from '/@/components/Container'; | |
54 | + | |
55 | + import { basicProps } from './props'; | |
56 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
57 | + import { useAttrs } from '/@/hooks/core/useAttrs'; | |
58 | + | |
59 | + export default defineComponent({ | |
60 | + inheritAttrs: false, | |
61 | + components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader }, | |
62 | + props: basicProps, | |
63 | + emits: ['visible-change', 'ok', 'close', 'register'], | |
64 | + setup(props, { emit }) { | |
65 | + const visibleRef = ref(false); | |
66 | + const attrs = useAttrs(); | |
67 | + const propsRef = ref<Partial<Nullable<DrawerProps>>>(null); | |
68 | + | |
69 | + const { t } = useI18n(); | |
70 | + const { prefixVar, prefixCls } = useDesign('basic-drawer'); | |
71 | + | |
72 | + const drawerInstance: DrawerInstance = { | |
73 | + setDrawerProps: setDrawerProps, | |
74 | + emitVisible: undefined, | |
75 | + }; | |
76 | + | |
77 | + const instance = getCurrentInstance(); | |
78 | + | |
79 | + instance && emit('register', drawerInstance, instance.uid); | |
80 | + | |
81 | + const getMergeProps = computed( | |
82 | + (): DrawerProps => { | |
83 | + return deepMerge(toRaw(props), unref(propsRef)); | |
84 | + } | |
85 | + ); | |
86 | + | |
87 | + const getProps = computed( | |
88 | + (): DrawerProps => { | |
89 | + const opt = { | |
90 | + placement: 'right', | |
91 | + ...unref(attrs), | |
92 | + ...unref(getMergeProps), | |
93 | + visible: unref(visibleRef), | |
94 | + }; | |
95 | + opt.title = undefined; | |
96 | + const { isDetail, width, wrapClassName, getContainer } = opt; | |
97 | + if (isDetail) { | |
98 | + if (!width) { | |
99 | + opt.width = '100%'; | |
100 | + } | |
101 | + const detailCls = `${prefixCls}__detail`; | |
102 | + opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls; | |
103 | + | |
104 | + if (!getContainer) { | |
105 | + // TODO type error? | |
106 | + opt.getContainer = `.${prefixVar}-layout-content` as any; | |
107 | + } | |
108 | + } | |
109 | + return opt as DrawerProps; | |
110 | + } | |
111 | + ); | |
112 | + | |
113 | + const getBindValues = computed( | |
114 | + (): DrawerProps => { | |
115 | + return { | |
116 | + ...attrs, | |
117 | + ...unref(getProps), | |
118 | + }; | |
119 | + } | |
120 | + ); | |
121 | + | |
122 | + // Custom implementation of the bottom button, | |
123 | + const getFooterHeight = computed(() => { | |
124 | + const { footerHeight, showFooter } = unref(getProps); | |
125 | + if (showFooter && footerHeight) { | |
126 | + return isNumber(footerHeight) | |
127 | + ? `${footerHeight}px` | |
128 | + : `${footerHeight.replace('px', '')}px`; | |
129 | + } | |
130 | + return `0px`; | |
131 | + }); | |
132 | + | |
133 | + const getScrollContentStyle = computed( | |
134 | + (): CSSProperties => { | |
135 | + const footerHeight = unref(getFooterHeight); | |
136 | + return { | |
137 | + position: 'relative', | |
138 | + height: `calc(100% - ${footerHeight})`, | |
139 | + }; | |
140 | + } | |
141 | + ); | |
142 | + | |
143 | + const getLoading = computed(() => { | |
144 | + return !!unref(getProps)?.loading; | |
145 | + }); | |
146 | + | |
147 | + watchEffect(() => { | |
148 | + visibleRef.value = props.visible; | |
149 | + }); | |
150 | + | |
151 | + watch( | |
152 | + () => visibleRef.value, | |
153 | + (visible) => { | |
154 | + nextTick(() => { | |
155 | + emit('visible-change', visible); | |
156 | + instance && drawerInstance.emitVisible?.(visible, instance.uid); | |
157 | + }); | |
158 | + } | |
159 | + ); | |
160 | + | |
161 | + // Cancel event | |
162 | + async function onClose(e: Recordable) { | |
163 | + const { closeFunc } = unref(getProps); | |
164 | + emit('close', e); | |
165 | + if (closeFunc && isFunction(closeFunc)) { | |
166 | + const res = await closeFunc(); | |
167 | + visibleRef.value = !res; | |
168 | + return; | |
169 | + } | |
170 | + visibleRef.value = false; | |
171 | + } | |
172 | + | |
173 | + function setDrawerProps(props: Partial<DrawerProps>): void { | |
174 | + // Keep the last setDrawerProps | |
175 | + propsRef.value = deepMerge(unref(propsRef) || {}, props); | |
176 | + | |
177 | + if (Reflect.has(props, 'visible')) { | |
178 | + visibleRef.value = !!props.visible; | |
179 | + } | |
180 | + } | |
181 | + | |
182 | + function handleOk() { | |
183 | + emit('ok'); | |
184 | + } | |
185 | + | |
186 | + return { | |
187 | + onClose, | |
188 | + t, | |
189 | + prefixCls, | |
190 | + getMergeProps, | |
191 | + getScrollContentStyle, | |
192 | + getProps, | |
193 | + getLoading, | |
194 | + getBindValues, | |
195 | + getFooterHeight, | |
196 | + handleOk, | |
197 | + }; | |
198 | + }, | |
199 | + }); | |
200 | +</script> | |
201 | +<style lang="less"> | |
202 | + @import (reference) '../../../design/index.less'; | |
203 | + @header-height: 60px; | |
204 | + @detail-header-height: 40px; | |
205 | + @prefix-cls: ~'@{namespace}-basic-drawer'; | |
206 | + @prefix-cls-detail: ~'@{namespace}-basic-drawer__detail'; | |
207 | + | |
208 | + .@{prefix-cls} { | |
209 | + .ant-drawer-wrapper-body { | |
210 | + overflow: hidden; | |
211 | + } | |
212 | + | |
213 | + .ant-drawer-close { | |
214 | + &:hover { | |
215 | + color: @error-color; | |
216 | + } | |
217 | + } | |
218 | + | |
219 | + .ant-drawer-body { | |
220 | + height: calc(100% - @header-height); | |
221 | + padding: 0; | |
222 | + background-color: @background-color-dark; | |
223 | + | |
224 | + .scrollbar__wrap { | |
225 | + padding: 16px !important; | |
226 | + margin-bottom: 0 !important; | |
227 | + } | |
228 | + } | |
229 | + } | |
230 | + | |
231 | + .@{prefix-cls-detail} { | |
232 | + position: absolute; | |
233 | + | |
234 | + .ant-drawer-header { | |
235 | + width: 100%; | |
236 | + height: @detail-header-height; | |
237 | + padding: 0; | |
238 | + border-top: 1px solid @border-color-base; | |
239 | + box-sizing: border-box; | |
240 | + } | |
241 | + | |
242 | + .ant-drawer-title { | |
243 | + height: 100%; | |
244 | + } | |
245 | + | |
246 | + .ant-drawer-close { | |
247 | + height: @detail-header-height; | |
248 | + line-height: @detail-header-height; | |
249 | + } | |
250 | + | |
251 | + .scrollbar__wrap { | |
252 | + padding: 0 !important; | |
253 | + } | |
254 | + | |
255 | + .ant-drawer-body { | |
256 | + height: calc(100% - @detail-header-height); | |
257 | + } | |
258 | + } | |
259 | +</style> | ... | ... |
src/components/Drawer/src/components/DrawerFooter.vue
0 → 100644
1 | +<template> | |
2 | + <div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer"> | |
3 | + <template v-if="!$slots.footer"> | |
4 | + <slot name="insertFooter" /> | |
5 | + <a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn"> | |
6 | + {{ cancelText }} | |
7 | + </a-button> | |
8 | + <slot name="centerFooter" /> | |
9 | + <a-button | |
10 | + :type="okType" | |
11 | + @click="handleOk" | |
12 | + v-bind="okButtonProps" | |
13 | + class="mr-2" | |
14 | + :loading="confirmLoading" | |
15 | + v-if="showOkBtn" | |
16 | + > | |
17 | + {{ okText }} | |
18 | + </a-button> | |
19 | + <slot name="appendFooter" /> | |
20 | + </template> | |
21 | + | |
22 | + <template v-else> | |
23 | + <slot name="footer" /> | |
24 | + </template> | |
25 | + </div> | |
26 | +</template> | |
27 | +<script lang="ts"> | |
28 | + import type { CSSProperties } from 'vue'; | |
29 | + import { defineComponent, computed } from 'vue'; | |
30 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
31 | + | |
32 | + import { footerProps } from '../props'; | |
33 | + export default defineComponent({ | |
34 | + name: 'BasicDrawerFooter', | |
35 | + props: { | |
36 | + ...footerProps, | |
37 | + height: { | |
38 | + type: String, | |
39 | + default: '60px', | |
40 | + }, | |
41 | + }, | |
42 | + emits: ['ok', 'close'], | |
43 | + setup(props, { emit }) { | |
44 | + const { prefixCls } = useDesign('basic-drawer-footer'); | |
45 | + | |
46 | + const getStyle = computed( | |
47 | + (): CSSProperties => { | |
48 | + const heightStr = `${props.height}`; | |
49 | + return { | |
50 | + height: heightStr, | |
51 | + lineHeight: heightStr, | |
52 | + }; | |
53 | + } | |
54 | + ); | |
55 | + | |
56 | + function handleOk() { | |
57 | + emit('ok'); | |
58 | + } | |
59 | + | |
60 | + function handleClose() { | |
61 | + emit('close'); | |
62 | + } | |
63 | + return { handleOk, prefixCls, handleClose, getStyle }; | |
64 | + }, | |
65 | + }); | |
66 | +</script> | |
67 | + | |
68 | +<style lang="less"> | |
69 | + @import (reference) '../../../../design/index.less'; | |
70 | + @prefix-cls: ~'@{namespace}-basic-drawer-footer'; | |
71 | + @footer-height: 60px; | |
72 | + .@{prefix-cls} { | |
73 | + position: absolute; | |
74 | + bottom: 0; | |
75 | + width: 100%; | |
76 | + padding: 0 12px 0 20px; | |
77 | + text-align: right; | |
78 | + background: #fff; | |
79 | + border-top: 1px solid @border-color-base; | |
80 | + | |
81 | + > * { | |
82 | + margin-right: 8px; | |
83 | + } | |
84 | + } | |
85 | +</style> | ... | ... |
src/components/Drawer/src/components/DrawerHeader.vue
0 → 100644
1 | +<template> | |
2 | + <BasicTitle v-if="!isDetail" :class="prefixCls"> | |
3 | + <slot name="title" /> | |
4 | + {{ !$slots.title ? title : '' }} | |
5 | + </BasicTitle> | |
6 | + | |
7 | + <div :class="[prefixCls, `${prefixCls}--detail`]" v-else> | |
8 | + <span :class="`${prefixCls}__twrap`"> | |
9 | + <span @click="handleClose" v-if="showDetailBack"> | |
10 | + <ArrowLeftOutlined :class="`${prefixCls}__back`" /> | |
11 | + </span> | |
12 | + <span v-if="title">{{ title }}</span> | |
13 | + </span> | |
14 | + | |
15 | + <span :class="`${prefixCls}__toolbar`"> | |
16 | + <slot name="titleToolbar" /> | |
17 | + </span> | |
18 | + </div> | |
19 | +</template> | |
20 | +<script lang="ts"> | |
21 | + import { defineComponent } from 'vue'; | |
22 | + import { BasicTitle } from '/@/components/Basic'; | |
23 | + import { ArrowLeftOutlined } from '@ant-design/icons-vue'; | |
24 | + | |
25 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
26 | + | |
27 | + import { propTypes } from '/@/utils/propTypes'; | |
28 | + export default defineComponent({ | |
29 | + name: 'BasicDrawerHeader', | |
30 | + components: { BasicTitle, ArrowLeftOutlined }, | |
31 | + props: { | |
32 | + isDetail: propTypes.bool, | |
33 | + showDetailBack: propTypes.bool, | |
34 | + title: propTypes.string, | |
35 | + }, | |
36 | + setup(_, { emit }) { | |
37 | + const { prefixCls } = useDesign('basic-drawer-header'); | |
38 | + | |
39 | + function handleClose() { | |
40 | + emit('close'); | |
41 | + } | |
42 | + return { prefixCls, handleClose }; | |
43 | + }, | |
44 | + }); | |
45 | +</script> | |
46 | + | |
47 | +<style lang="less"> | |
48 | + @import (reference) '../../../../design/index.less'; | |
49 | + @prefix-cls: ~'@{namespace}-basic-drawer-header'; | |
50 | + @footer-height: 60px; | |
51 | + .@{prefix-cls} { | |
52 | + display: flex; | |
53 | + height: 100%; | |
54 | + align-items: center; | |
55 | + | |
56 | + &__back { | |
57 | + padding: 0 12px; | |
58 | + cursor: pointer; | |
59 | + | |
60 | + &:hover { | |
61 | + color: @primary-color; | |
62 | + } | |
63 | + } | |
64 | + | |
65 | + &__twrap { | |
66 | + flex: 1; | |
67 | + } | |
68 | + | |
69 | + &__toolbar { | |
70 | + padding-right: 50px; | |
71 | + } | |
72 | + } | |
73 | +</style> | ... | ... |
src/components/Drawer/src/index.less deleted
100644 → 0
1 | -@import (reference) '../../../design/index.less'; | |
2 | -@header-height: 40px; | |
3 | -@footer-height: 60px; | |
4 | - | |
5 | -.basic-drawer { | |
6 | - .ant-drawer-wrapper-body { | |
7 | - overflow: hidden; | |
8 | - } | |
9 | - | |
10 | - .ant-drawer-close { | |
11 | - &:hover { | |
12 | - color: @error-color; | |
13 | - } | |
14 | - } | |
15 | - | |
16 | - .ant-drawer-body { | |
17 | - height: calc(100% - @header-height); | |
18 | - padding: 0; | |
19 | - background-color: @background-color-dark; | |
20 | - | |
21 | - .scrollbar__wrap { | |
22 | - padding: 16px; | |
23 | - } | |
24 | - } | |
25 | - | |
26 | - &__detail { | |
27 | - position: absolute; | |
28 | - | |
29 | - &-header { | |
30 | - height: 100%; | |
31 | - } | |
32 | - | |
33 | - .ant-drawer-header { | |
34 | - width: 100%; | |
35 | - height: @header-height; | |
36 | - padding: 0; | |
37 | - border-top: 1px solid @border-color-base; | |
38 | - box-sizing: border-box; | |
39 | - } | |
40 | - | |
41 | - .ant-drawer-title { | |
42 | - height: 100%; | |
43 | - } | |
44 | - | |
45 | - .ant-drawer-close { | |
46 | - height: @header-height; | |
47 | - line-height: @header-height; | |
48 | - } | |
49 | - | |
50 | - .scrollbar__wrap { | |
51 | - padding: 0; | |
52 | - } | |
53 | - } | |
54 | - | |
55 | - &__footer { | |
56 | - position: absolute; | |
57 | - bottom: 0; | |
58 | - width: 100%; | |
59 | - height: @footer-height; | |
60 | - padding: 0 26px; | |
61 | - line-height: @footer-height; | |
62 | - text-align: right; | |
63 | - background: #fff; | |
64 | - border-top: 1px solid @border-color-base; | |
65 | - } | |
66 | -} |
src/components/Drawer/src/props.ts
... | ... | @@ -10,13 +10,13 @@ export const footerProps = { |
10 | 10 | * @description: Show close button |
11 | 11 | */ |
12 | 12 | showCancelBtn: propTypes.bool.def(true), |
13 | - cancelButtonProps: Object as PropType<any>, | |
13 | + cancelButtonProps: Object as PropType<Recordable>, | |
14 | 14 | cancelText: propTypes.string.def(t('component.drawer.cancelText')), |
15 | 15 | /** |
16 | 16 | * @description: Show confirmation button |
17 | 17 | */ |
18 | 18 | showOkBtn: propTypes.bool.def(true), |
19 | - okButtonProps: propTypes.any, | |
19 | + okButtonProps: Object as PropType<Recordable>, | |
20 | 20 | okText: propTypes.string.def(t('component.drawer.okText')), |
21 | 21 | okType: propTypes.string.def('primary'), |
22 | 22 | showFooter: propTypes.bool, |
... | ... | @@ -28,6 +28,7 @@ export const footerProps = { |
28 | 28 | export const basicProps = { |
29 | 29 | isDetail: propTypes.bool, |
30 | 30 | title: propTypes.string.def(''), |
31 | + loadingText: propTypes.string, | |
31 | 32 | showDetailBack: propTypes.bool.def(true), |
32 | 33 | visible: propTypes.bool, |
33 | 34 | loading: propTypes.bool, | ... | ... |
src/components/Drawer/src/types.ts
1 | 1 | import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; |
2 | -import type { CSSProperties, VNodeChild } from 'vue'; | |
2 | +import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'; | |
3 | 3 | import type { ScrollContainerOptions } from '/@/components/Container/index'; |
4 | 4 | |
5 | 5 | export interface DrawerInstance { |
6 | 6 | setDrawerProps: (props: Partial<DrawerProps> | boolean) => void; |
7 | + emitVisible?: (visible: boolean, uid: number) => void; | |
7 | 8 | } |
8 | 9 | |
9 | 10 | export interface ReturnMethods extends DrawerInstance { |
10 | 11 | openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void; |
12 | + getVisible?: ComputedRef<boolean>; | |
11 | 13 | } |
12 | 14 | |
13 | 15 | export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void; |
... | ... | @@ -16,6 +18,7 @@ export interface ReturnInnerMethods extends DrawerInstance { |
16 | 18 | closeDrawer: () => void; |
17 | 19 | changeLoading: (loading: boolean) => void; |
18 | 20 | changeOkLoading: (loading: boolean) => void; |
21 | + getVisible?: ComputedRef<boolean>; | |
19 | 22 | } |
20 | 23 | |
21 | 24 | export type UseDrawerReturnType = [RegisterFn, ReturnMethods]; | ... | ... |
src/components/Drawer/src/useDrawer.ts
... | ... | @@ -6,22 +6,32 @@ import type { |
6 | 6 | UseDrawerInnerReturnType, |
7 | 7 | } from './types'; |
8 | 8 | |
9 | -import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw } from 'vue'; | |
9 | +import { | |
10 | + ref, | |
11 | + getCurrentInstance, | |
12 | + unref, | |
13 | + reactive, | |
14 | + watchEffect, | |
15 | + nextTick, | |
16 | + toRaw, | |
17 | + computed, | |
18 | +} from 'vue'; | |
10 | 19 | |
11 | 20 | import { isProdMode } from '/@/utils/env'; |
12 | 21 | import { isFunction } from '/@/utils/is'; |
13 | -import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; | |
22 | +import { tryOnUnmounted, isInSetup } from '/@/utils/helper/vueHelper'; | |
14 | 23 | import { isEqual } from 'lodash-es'; |
24 | +import { error } from '/@/utils/log'; | |
15 | 25 | |
16 | 26 | const dataTransferRef = reactive<any>({}); |
17 | 27 | |
28 | +const visibleData = reactive<{ [key: number]: boolean }>({}); | |
29 | + | |
18 | 30 | /** |
19 | 31 | * @description: Applicable to separate drawer and call outside |
20 | 32 | */ |
21 | 33 | export function useDrawer(): UseDrawerReturnType { |
22 | - if (!getCurrentInstance()) { | |
23 | - throw new Error('Please put useDrawer function in the setup function!'); | |
24 | - } | |
34 | + isInSetup(); | |
25 | 35 | |
26 | 36 | const drawerRef = ref<DrawerInstance | null>(null); |
27 | 37 | const loadedRef = ref<Nullable<boolean>>(false); |
... | ... | @@ -41,23 +51,31 @@ export function useDrawer(): UseDrawerReturnType { |
41 | 51 | uidRef.value = uuid; |
42 | 52 | drawerRef.value = drawerInstance; |
43 | 53 | loadedRef.value = true; |
54 | + | |
55 | + drawerInstance.emitVisible = (visible: boolean, uid: number) => { | |
56 | + visibleData[uid] = visible; | |
57 | + }; | |
44 | 58 | } |
45 | 59 | |
46 | 60 | const getInstance = () => { |
47 | 61 | const instance = unref(drawerRef); |
48 | 62 | if (!instance) { |
49 | - throw new Error('instance is undefined!'); | |
63 | + error('useDrawer instance is undefined!'); | |
50 | 64 | } |
51 | 65 | return instance; |
52 | 66 | }; |
53 | 67 | |
54 | 68 | const methods: ReturnMethods = { |
55 | 69 | setDrawerProps: (props: Partial<DrawerProps>): void => { |
56 | - getInstance().setDrawerProps(props); | |
70 | + getInstance()?.setDrawerProps(props); | |
57 | 71 | }, |
58 | 72 | |
73 | + getVisible: computed((): boolean => { | |
74 | + return visibleData[~~unref(uidRef)]; | |
75 | + }), | |
76 | + | |
59 | 77 | openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => { |
60 | - getInstance().setDrawerProps({ | |
78 | + getInstance()?.setDrawerProps({ | |
61 | 79 | visible: visible, |
62 | 80 | }); |
63 | 81 | if (!data) return; |
... | ... | @@ -79,17 +97,18 @@ export function useDrawer(): UseDrawerReturnType { |
79 | 97 | |
80 | 98 | export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { |
81 | 99 | const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null); |
82 | - const currentInstall = getCurrentInstance(); | |
100 | + const currentInstance = getCurrentInstance(); | |
83 | 101 | const uidRef = ref<string>(''); |
84 | 102 | |
85 | - if (!currentInstall) { | |
86 | - throw new Error('useDrawerInner instance is undefined!'); | |
103 | + if (!currentInstance) { | |
104 | + error('useDrawerInner instance is undefined!'); | |
87 | 105 | } |
88 | 106 | |
89 | 107 | const getInstance = () => { |
90 | 108 | const instance = unref(drawerInstanceRef); |
91 | 109 | if (!instance) { |
92 | - throw new Error('useDrawerInner instance is undefined!'); | |
110 | + error('useDrawerInner instance is undefined!'); | |
111 | + return; | |
93 | 112 | } |
94 | 113 | return instance; |
95 | 114 | }; |
... | ... | @@ -102,7 +121,7 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { |
102 | 121 | |
103 | 122 | uidRef.value = uuid; |
104 | 123 | drawerInstanceRef.value = modalInstance; |
105 | - currentInstall.emit('register', modalInstance, uuid); | |
124 | + currentInstance?.emit('register', modalInstance, uuid); | |
106 | 125 | }; |
107 | 126 | |
108 | 127 | watchEffect(() => { |
... | ... | @@ -118,19 +137,22 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { |
118 | 137 | register, |
119 | 138 | { |
120 | 139 | changeLoading: (loading = true) => { |
121 | - getInstance().setDrawerProps({ loading }); | |
140 | + getInstance()?.setDrawerProps({ loading }); | |
122 | 141 | }, |
123 | 142 | |
124 | 143 | changeOkLoading: (loading = true) => { |
125 | - getInstance().setDrawerProps({ confirmLoading: loading }); | |
144 | + getInstance()?.setDrawerProps({ confirmLoading: loading }); | |
126 | 145 | }, |
146 | + getVisible: computed((): boolean => { | |
147 | + return visibleData[~~unref(uidRef)]; | |
148 | + }), | |
127 | 149 | |
128 | 150 | closeDrawer: () => { |
129 | - getInstance().setDrawerProps({ visible: false }); | |
151 | + getInstance()?.setDrawerProps({ visible: false }); | |
130 | 152 | }, |
131 | 153 | |
132 | 154 | setDrawerProps: (props: Partial<DrawerProps>) => { |
133 | - getInstance().setDrawerProps(props); | |
155 | + getInstance()?.setDrawerProps(props); | |
134 | 156 | }, |
135 | 157 | }, |
136 | 158 | ]; | ... | ... |
src/components/Form/src/components/FormAction.vue
1 | 1 | <template> |
2 | - <a-col | |
3 | - v-bind="actionColOpt" | |
4 | - class="mb-2" | |
5 | - :style="{ textAlign: 'right' }" | |
6 | - v-if="showActionButtonGroup" | |
7 | - > | |
2 | + <a-col v-bind="actionColOpt" :style="{ textAlign: 'right' }" v-if="showActionButtonGroup"> | |
8 | 3 | <FormItem> |
9 | 4 | <slot name="resetBefore" /> |
10 | 5 | <Button | ... | ... |
src/components/Modal/index.ts
1 | 1 | import './src/index.less'; |
2 | 2 | import { withInstall } from '../util'; |
3 | -import BasicModal from './src/BasicModal'; | |
3 | +import BasicModal from './src/BasicModal.vue'; | |
4 | 4 | |
5 | 5 | withInstall(BasicModal); |
6 | 6 | |
7 | 7 | export { BasicModal }; |
8 | -export { useModalContext } from './src/useModalContext'; | |
9 | -export { useModal, useModalInner } from './src/useModal'; | |
8 | +export { useModalContext } from './src/hooks/useModalContext'; | |
9 | +export { useModal, useModalInner } from './src/hooks/useModal'; | |
10 | 10 | export * from './src/types'; | ... | ... |
src/components/Modal/src/BasicModal.tsx deleted
100644 → 0
1 | -import type { ModalProps, ModalMethods } from './types'; | |
2 | - | |
3 | -import { defineComponent, computed, ref, watch, unref, watchEffect, toRef } from 'vue'; | |
4 | - | |
5 | -import Modal from './Modal'; | |
6 | -import { Button } from '/@/components/Button'; | |
7 | -import ModalWrapper from './ModalWrapper'; | |
8 | -import { BasicTitle } from '/@/components/Basic'; | |
9 | -import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue'; | |
10 | - | |
11 | -import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper'; | |
12 | -import { isFunction } from '/@/utils/is'; | |
13 | -import { deepMerge } from '/@/utils'; | |
14 | -import { tryTsxEmit } from '/@/utils/helper/vueHelper'; | |
15 | - | |
16 | -import { basicProps } from './props'; | |
17 | -import { useFullScreen } from './useFullScreen'; | |
18 | -export default defineComponent({ | |
19 | - name: 'BasicModal', | |
20 | - props: basicProps, | |
21 | - emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register'], | |
22 | - setup(props, { slots, emit, attrs }) { | |
23 | - const visibleRef = ref(false); | |
24 | - const propsRef = ref<Partial<ModalProps> | null>(null); | |
25 | - const modalWrapperRef = ref<ComponentRef>(null); | |
26 | - // modal Bottom and top height | |
27 | - const extHeightRef = ref(0); | |
28 | - // Unexpanded height of the popup | |
29 | - | |
30 | - // Custom title component: get title | |
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'), | |
44 | - }); | |
45 | - | |
46 | - // modal component does not need title | |
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) }; | |
64 | - }); | |
65 | - | |
66 | - watchEffect(() => { | |
67 | - visibleRef.value = !!props.visible; | |
68 | - }); | |
69 | - | |
70 | - watch( | |
71 | - () => unref(visibleRef), | |
72 | - (v) => { | |
73 | - emit('visible-change', v); | |
74 | - }, | |
75 | - { | |
76 | - immediate: false, | |
77 | - } | |
78 | - ); | |
79 | - | |
80 | - /** | |
81 | - * @description: 渲染标题 | |
82 | - */ | |
83 | - function renderTitle() { | |
84 | - const { helpMessage } = unref(getProps); | |
85 | - const { title } = unref(getMergeProps); | |
86 | - return ( | |
87 | - <BasicTitle helpMessage={helpMessage}> | |
88 | - {() => (slots.title ? getSlot(slots, 'title') : title)} | |
89 | - </BasicTitle> | |
90 | - ); | |
91 | - } | |
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 | - | |
117 | - function renderContent() { | |
118 | - type OmitWrapperType = Omit< | |
119 | - ModalProps, | |
120 | - 'fullScreen' | 'modalFooterHeight' | 'visible' | 'loading' | |
121 | - >; | |
122 | - const { useWrapper, loading, wrapperProps } = unref(getProps); | |
123 | - if (!useWrapper) return getSlot(slots); | |
124 | - | |
125 | - const showFooter = props.footer !== undefined && !props.footer ? 0 : undefined; | |
126 | - return ( | |
127 | - <ModalWrapper | |
128 | - footerOffset={props.wrapperFooterOffset} | |
129 | - fullScreen={unref(fullScreenRef)} | |
130 | - ref={modalWrapperRef} | |
131 | - loading={loading} | |
132 | - visible={unref(visibleRef)} | |
133 | - modalFooterHeight={showFooter} | |
134 | - {...((wrapperProps as unknown) as OmitWrapperType)} | |
135 | - onGetExtHeight={(height: number) => { | |
136 | - extHeightRef.value = height; | |
137 | - }} | |
138 | - onHeightChange={(height: string) => { | |
139 | - emit('height-change', height); | |
140 | - }} | |
141 | - > | |
142 | - {() => getSlot(slots)} | |
143 | - </ModalWrapper> | |
144 | - ); | |
145 | - } | |
146 | - | |
147 | - // 底部按钮自定义实现, | |
148 | - function renderFooter() { | |
149 | - const { | |
150 | - showCancelBtn, | |
151 | - cancelButtonProps, | |
152 | - cancelText, | |
153 | - showOkBtn, | |
154 | - okType, | |
155 | - okText, | |
156 | - okButtonProps, | |
157 | - confirmLoading, | |
158 | - } = unref(getProps); | |
159 | - | |
160 | - return ( | |
161 | - <> | |
162 | - {getSlot(slots, 'insertFooter')} | |
163 | - {showCancelBtn && ( | |
164 | - <Button {...cancelButtonProps} onClick={handleCancel}> | |
165 | - {() => cancelText} | |
166 | - </Button> | |
167 | - )} | |
168 | - {getSlot(slots, 'centerdFooter')} | |
169 | - {showOkBtn && ( | |
170 | - <Button | |
171 | - type={okType as any} | |
172 | - loading={confirmLoading} | |
173 | - onClick={() => { | |
174 | - emit('ok'); | |
175 | - }} | |
176 | - {...okButtonProps} | |
177 | - > | |
178 | - {() => okText} | |
179 | - </Button> | |
180 | - )} | |
181 | - {getSlot(slots, 'appendFooter')} | |
182 | - </> | |
183 | - ); | |
184 | - } | |
185 | - | |
186 | - /** | |
187 | - * @description: 关闭按钮 | |
188 | - */ | |
189 | - function renderClose() { | |
190 | - const { canFullscreen } = unref(getProps); | |
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 | - | |
205 | - return ( | |
206 | - <div class={cls}> | |
207 | - {canFullscreen && fullScreen} | |
208 | - <CloseOutlined onClick={handleCancel} /> | |
209 | - </div> | |
210 | - ); | |
211 | - } | |
212 | - | |
213 | - const modalMethods: ModalMethods = { | |
214 | - setModalProps, | |
215 | - }; | |
216 | - | |
217 | - tryTsxEmit((instance) => { | |
218 | - emit('register', modalMethods, instance.uid); | |
219 | - }); | |
220 | - return () => ( | |
221 | - <Modal onCancel={handleCancel} {...unref(getModalBindValue)}> | |
222 | - {{ | |
223 | - footer: () => renderFooter(), | |
224 | - closeIcon: () => renderClose(), | |
225 | - title: () => renderTitle(), | |
226 | - ...extendSlots(slots, ['default']), | |
227 | - default: () => renderContent(), | |
228 | - }} | |
229 | - </Modal> | |
230 | - ); | |
231 | - }, | |
232 | -}); |
src/components/Modal/src/BasicModal.vue
0 → 100644
1 | +<template> | |
2 | + <Modal @cancel="handleCancel" v-bind="getBindValue"> | |
3 | + <template #closeIcon v-if="!$slots.closeIcon"> | |
4 | + <ModalClose | |
5 | + :canFullscreen="getProps.canFullscreen" | |
6 | + :fullScreen="fullScreenRef" | |
7 | + @cancel="handleCancel" | |
8 | + @fullscreen="handleFullScreen" | |
9 | + /> | |
10 | + </template> | |
11 | + | |
12 | + <template #title v-if="!$slots.title"> | |
13 | + <ModalHeader :helpMessage="getProps.helpMessage" :title="getMergeProps.title" /> | |
14 | + </template> | |
15 | + | |
16 | + <template #footer v-if="!$slots.footer"> | |
17 | + <ModalFooter v-bind="getProps" @ok="handleOk" @cancel="handleCancel" /> | |
18 | + </template> | |
19 | + <ModalWrapper | |
20 | + :useWrapper="getProps.useWrapper" | |
21 | + :footerOffset="wrapperFooterOffset" | |
22 | + :fullScreen="fullScreenRef" | |
23 | + ref="modalWrapperRef" | |
24 | + :loading="getProps.loading" | |
25 | + :visible="visibleRef" | |
26 | + :modalFooterHeight="footer !== undefined && !footer ? 0 : undefined" | |
27 | + v-bind="omit(getProps.wrapperProps, 'visible')" | |
28 | + @ext-height="handleExtHeight" | |
29 | + @height-change="handleHeightChange" | |
30 | + > | |
31 | + <slot /> | |
32 | + </ModalWrapper> | |
33 | + </Modal> | |
34 | +</template> | |
35 | +<script lang="ts"> | |
36 | + import type { ModalProps, ModalMethods } from './types'; | |
37 | + | |
38 | + import { | |
39 | + defineComponent, | |
40 | + computed, | |
41 | + ref, | |
42 | + watch, | |
43 | + unref, | |
44 | + watchEffect, | |
45 | + toRef, | |
46 | + getCurrentInstance, | |
47 | + } from 'vue'; | |
48 | + | |
49 | + import Modal from './components/Modal'; | |
50 | + import ModalWrapper from './components/ModalWrapper.vue'; | |
51 | + import ModalClose from './components/ModalClose.vue'; | |
52 | + import ModalFooter from './components/ModalFooter.vue'; | |
53 | + import ModalHeader from './components/ModalHeader.vue'; | |
54 | + | |
55 | + import { isFunction } from '/@/utils/is'; | |
56 | + import { deepMerge } from '/@/utils'; | |
57 | + | |
58 | + import { basicProps } from './props'; | |
59 | + import { useFullScreen } from './hooks/useModalFullScreen'; | |
60 | + import { omit } from 'lodash-es'; | |
61 | + export default defineComponent({ | |
62 | + name: 'BasicModal', | |
63 | + components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader }, | |
64 | + props: basicProps, | |
65 | + emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register'], | |
66 | + setup(props, { emit, attrs }) { | |
67 | + const visibleRef = ref(false); | |
68 | + const propsRef = ref<Partial<ModalProps> | null>(null); | |
69 | + const modalWrapperRef = ref<ComponentRef>(null); | |
70 | + // modal Bottom and top height | |
71 | + const extHeightRef = ref(0); | |
72 | + const modalMethods: ModalMethods = { | |
73 | + setModalProps, | |
74 | + emitVisible: undefined, | |
75 | + }; | |
76 | + const instance = getCurrentInstance(); | |
77 | + if (instance) { | |
78 | + emit('register', modalMethods, instance.uid); | |
79 | + } | |
80 | + | |
81 | + // Custom title component: get title | |
82 | + const getMergeProps = computed( | |
83 | + (): ModalProps => { | |
84 | + return { | |
85 | + ...props, | |
86 | + ...(unref(propsRef) as any), | |
87 | + }; | |
88 | + } | |
89 | + ); | |
90 | + | |
91 | + const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({ | |
92 | + modalWrapperRef, | |
93 | + extHeightRef, | |
94 | + wrapClassName: toRef(getMergeProps.value, 'wrapClassName'), | |
95 | + }); | |
96 | + | |
97 | + // modal component does not need title | |
98 | + const getProps = computed( | |
99 | + (): ModalProps => { | |
100 | + const opt = { | |
101 | + ...unref(getMergeProps), | |
102 | + visible: unref(visibleRef), | |
103 | + title: undefined, | |
104 | + }; | |
105 | + | |
106 | + return { | |
107 | + ...opt, | |
108 | + wrapClassName: unref(getWrapClassName), | |
109 | + }; | |
110 | + } | |
111 | + ); | |
112 | + | |
113 | + const getBindValue = computed((): any => { | |
114 | + return { ...attrs, ...unref(getProps) }; | |
115 | + }); | |
116 | + | |
117 | + watchEffect(() => { | |
118 | + visibleRef.value = !!props.visible; | |
119 | + }); | |
120 | + | |
121 | + watch( | |
122 | + () => unref(visibleRef), | |
123 | + (v) => { | |
124 | + emit('visible-change', v); | |
125 | + instance && modalMethods.emitVisible?.(v, instance.uid); | |
126 | + }, | |
127 | + { | |
128 | + immediate: false, | |
129 | + } | |
130 | + ); | |
131 | + | |
132 | + // 取消事件 | |
133 | + async function handleCancel(e: Event) { | |
134 | + e?.stopPropagation(); | |
135 | + | |
136 | + if (props.closeFunc && isFunction(props.closeFunc)) { | |
137 | + const isClose: boolean = await props.closeFunc(); | |
138 | + visibleRef.value = !isClose; | |
139 | + return; | |
140 | + } | |
141 | + | |
142 | + visibleRef.value = false; | |
143 | + emit('cancel'); | |
144 | + } | |
145 | + | |
146 | + /** | |
147 | + * @description: 设置modal参数 | |
148 | + */ | |
149 | + function setModalProps(props: Partial<ModalProps>): void { | |
150 | + // Keep the last setModalProps | |
151 | + propsRef.value = deepMerge(unref(propsRef) || {}, props); | |
152 | + if (!Reflect.has(props, 'visible')) return; | |
153 | + visibleRef.value = !!props.visible; | |
154 | + } | |
155 | + | |
156 | + function handleOk() { | |
157 | + emit('ok'); | |
158 | + } | |
159 | + | |
160 | + function handleHeightChange(height: string) { | |
161 | + emit('height-change', height); | |
162 | + } | |
163 | + | |
164 | + function handleExtHeight(height: number) { | |
165 | + extHeightRef.value = height; | |
166 | + } | |
167 | + | |
168 | + return { | |
169 | + handleCancel, | |
170 | + getBindValue, | |
171 | + getProps, | |
172 | + handleFullScreen, | |
173 | + fullScreenRef, | |
174 | + getMergeProps, | |
175 | + handleOk, | |
176 | + visibleRef, | |
177 | + omit, | |
178 | + modalWrapperRef, | |
179 | + handleExtHeight, | |
180 | + handleHeightChange, | |
181 | + }; | |
182 | + }, | |
183 | + }); | |
184 | +</script> | ... | ... |
src/components/Modal/src/Modal.tsx renamed to src/components/Modal/src/components/Modal.tsx
1 | 1 | import { Modal } from 'ant-design-vue'; |
2 | -import { defineComponent, toRefs } from 'vue'; | |
3 | -import { basicProps } from './props'; | |
4 | -import { useModalDragMove } from './useModalDrag'; | |
2 | +import { defineComponent, toRefs, unref } from 'vue'; | |
3 | +import { basicProps } from '../props'; | |
4 | +import { useModalDragMove } from '../hooks/useModalDrag'; | |
5 | +import { useAttrs } from '/@/hooks/core/useAttrs'; | |
5 | 6 | import { extendSlots } from '/@/utils/helper/tsxHelper'; |
6 | 7 | |
7 | 8 | export default defineComponent({ |
8 | 9 | name: 'Modal', |
9 | 10 | inheritAttrs: false, |
10 | 11 | props: basicProps, |
11 | - setup(props, { attrs, slots }) { | |
12 | + setup(props, { slots }) { | |
12 | 13 | const { visible, draggable, destroyOnClose } = toRefs(props); |
13 | - | |
14 | + const attrs = useAttrs(); | |
14 | 15 | useModalDragMove({ |
15 | 16 | visible, |
16 | 17 | destroyOnClose, |
... | ... | @@ -18,7 +19,8 @@ export default defineComponent({ |
18 | 19 | }); |
19 | 20 | |
20 | 21 | return () => { |
21 | - const propsData = { ...attrs, ...props } as any; | |
22 | + const propsData = { ...unref(attrs), ...props } as Recordable; | |
23 | + | |
22 | 24 | return <Modal {...propsData}>{extendSlots(slots)}</Modal>; |
23 | 25 | }; |
24 | 26 | }, | ... | ... |
src/components/Modal/src/components/ModalClose.vue
0 → 100644
1 | +<template> | |
2 | + <div :class="getClass"> | |
3 | + <template v-if="canFullscreen"> | |
4 | + <FullscreenExitOutlined role="full" @click="handleFullScreen" v-if="fullScreen" /> | |
5 | + | |
6 | + <FullscreenOutlined role="close" @click="handleFullScreen" v-else /> | |
7 | + </template> | |
8 | + <CloseOutlined @click="handleCancel" /> | |
9 | + </div> | |
10 | +</template> | |
11 | +<script lang="ts"> | |
12 | + import { defineComponent, computed } from 'vue'; | |
13 | + import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue'; | |
14 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
15 | + import { propTypes } from '/@/utils/propTypes'; | |
16 | + | |
17 | + export default defineComponent({ | |
18 | + name: 'ModalClose', | |
19 | + components: { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined }, | |
20 | + props: { | |
21 | + canFullscreen: propTypes.bool.def(true), | |
22 | + fullScreen: propTypes.bool, | |
23 | + }, | |
24 | + emits: ['cancel', 'fullscreen'], | |
25 | + setup(props, { emit }) { | |
26 | + const { prefixCls } = useDesign('basic-modal-close'); | |
27 | + | |
28 | + const getClass = computed(() => { | |
29 | + return [ | |
30 | + prefixCls, | |
31 | + `${prefixCls}--custom`, | |
32 | + { | |
33 | + [`${prefixCls}--can-full`]: props.canFullscreen, | |
34 | + }, | |
35 | + ]; | |
36 | + }); | |
37 | + | |
38 | + function handleCancel() { | |
39 | + emit('cancel'); | |
40 | + } | |
41 | + function handleFullScreen(e: Event) { | |
42 | + e?.stopPropagation(); | |
43 | + e?.preventDefault(); | |
44 | + emit('fullscreen'); | |
45 | + } | |
46 | + | |
47 | + return { | |
48 | + getClass, | |
49 | + prefixCls, | |
50 | + handleCancel, | |
51 | + handleFullScreen, | |
52 | + }; | |
53 | + }, | |
54 | + }); | |
55 | +</script> | |
56 | +<style lang="less"> | |
57 | + @import (reference) '../../../../design/index.less'; | |
58 | + @prefix-cls: ~'@{namespace}-basic-modal-close'; | |
59 | + .@{prefix-cls} { | |
60 | + display: flex; | |
61 | + height: 95%; | |
62 | + align-items: center; | |
63 | + | |
64 | + > span { | |
65 | + margin-left: 48px; | |
66 | + font-size: 16px; | |
67 | + } | |
68 | + | |
69 | + &--can-full { | |
70 | + > span { | |
71 | + margin-left: 12px; | |
72 | + } | |
73 | + } | |
74 | + | |
75 | + &:not(&--can-full) { | |
76 | + > span:nth-child(1) { | |
77 | + &:hover { | |
78 | + font-weight: 700; | |
79 | + } | |
80 | + } | |
81 | + } | |
82 | + | |
83 | + & span:nth-child(1) { | |
84 | + display: inline-block; | |
85 | + padding: 10px; | |
86 | + | |
87 | + &:hover { | |
88 | + color: @primary-color; | |
89 | + } | |
90 | + } | |
91 | + | |
92 | + & span:nth-child(2) { | |
93 | + &:hover { | |
94 | + color: @error-color; | |
95 | + } | |
96 | + } | |
97 | + } | |
98 | +</style> | ... | ... |
src/components/Modal/src/components/ModalFooter.vue
0 → 100644
1 | +<template> | |
2 | + <div> | |
3 | + <slot name="insertFooter" /> | |
4 | + <a-button v-bind="cancelButtonProps" @click="handleCancel" v-if="showCancelBtn"> | |
5 | + {{ cancelText }} | |
6 | + </a-button> | |
7 | + <slot name="centerFooter" /> | |
8 | + <a-button | |
9 | + :type="okType" | |
10 | + @click="handleOk" | |
11 | + :loading="confirmLoading" | |
12 | + v-bind="okButtonProps" | |
13 | + v-if="showOkBtn" | |
14 | + > | |
15 | + {{ okText }} | |
16 | + </a-button> | |
17 | + <slot name="appendFooter" /> | |
18 | + </div> | |
19 | +</template> | |
20 | +<script lang="ts"> | |
21 | + import { defineComponent } from 'vue'; | |
22 | + | |
23 | + import { basicProps } from '../props'; | |
24 | + export default defineComponent({ | |
25 | + name: 'BasicModalFooter', | |
26 | + props: basicProps, | |
27 | + emits: ['ok', 'cancel'], | |
28 | + setup(_, { emit }) { | |
29 | + function handleOk() { | |
30 | + emit('ok'); | |
31 | + } | |
32 | + | |
33 | + function handleCancel() { | |
34 | + emit('cancel'); | |
35 | + } | |
36 | + return { handleOk, handleCancel }; | |
37 | + }, | |
38 | + }); | |
39 | +</script> | ... | ... |
src/components/Modal/src/components/ModalHeader.vue
0 → 100644
1 | +<template> | |
2 | + <BasicTitle :helpMessage="helpMessage"> | |
3 | + {{ title }} | |
4 | + </BasicTitle> | |
5 | +</template> | |
6 | +<script lang="ts"> | |
7 | + import type { PropType } from 'vue'; | |
8 | + import { defineComponent } from 'vue'; | |
9 | + import { BasicTitle } from '/@/components/Basic'; | |
10 | + | |
11 | + import { propTypes } from '/@/utils/propTypes'; | |
12 | + export default defineComponent({ | |
13 | + name: 'BasicModalHeader', | |
14 | + components: { BasicTitle }, | |
15 | + props: { | |
16 | + helpMessage: { | |
17 | + type: [String, Array] as PropType<string | string[]>, | |
18 | + }, | |
19 | + title: propTypes.string, | |
20 | + }, | |
21 | + }); | |
22 | +</script> | ... | ... |
src/components/Modal/src/ModalWrapper.tsx renamed to src/components/Modal/src/components/ModalWrapper.vue
1 | -import type { ModalWrapperProps } from './types'; | |
2 | -import type { CSSProperties } from 'vue'; | |
3 | - | |
4 | -import { | |
5 | - defineComponent, | |
6 | - computed, | |
7 | - ref, | |
8 | - watchEffect, | |
9 | - unref, | |
10 | - watch, | |
11 | - onMounted, | |
12 | - nextTick, | |
13 | - onUnmounted, | |
14 | -} from 'vue'; | |
15 | -import { Spin } from 'ant-design-vue'; | |
16 | - | |
17 | -import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; | |
18 | - | |
19 | -import { getSlot } from '/@/utils/helper/tsxHelper'; | |
20 | -import { useElResize } from '/@/hooks/event/useElResize'; | |
21 | -import { propTypes } from '/@/utils/propTypes'; | |
22 | -import { createModalContext } from './useModalContext'; | |
23 | - | |
24 | -export default defineComponent({ | |
25 | - name: 'ModalWrapper', | |
26 | - props: { | |
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, | |
34 | - }, | |
35 | - emits: ['heightChange', 'getExtHeight'], | |
36 | - setup(props: ModalWrapperProps, { slots, emit }) { | |
37 | - const wrapperRef = ref<ElRef>(null); | |
38 | - const spinRef = ref<ComponentRef>(null); | |
39 | - const realHeightRef = ref(0); | |
40 | - | |
41 | - let stopElResizeFn: Fn = () => {}; | |
42 | - | |
43 | - useWindowSizeFn(setModalHeight); | |
44 | - | |
45 | - createModalContext({ | |
46 | - redoModalHeight: setModalHeight, | |
47 | - }); | |
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 | - ); | |
1 | +<template> | |
2 | + <ScrollContainer ref="wrapperRef" :style="wrapStyle"> | |
3 | + <div ref="spinRef" :style="spinStyle" v-loading="loading" :loading-tip="loadingTip"> | |
4 | + <slot /> | |
5 | + </div> | |
6 | + </ScrollContainer> | |
7 | +</template> | |
8 | +<script lang="ts"> | |
9 | + import type { ModalWrapperProps } from '../types'; | |
10 | + import type { CSSProperties } from 'vue'; | |
11 | + | |
12 | + import { | |
13 | + defineComponent, | |
14 | + computed, | |
15 | + ref, | |
16 | + watchEffect, | |
17 | + unref, | |
18 | + watch, | |
19 | + onMounted, | |
20 | + nextTick, | |
21 | + onUnmounted, | |
22 | + } from 'vue'; | |
23 | + import { Spin } from 'ant-design-vue'; | |
24 | + | |
25 | + import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; | |
26 | + import { ScrollContainer } from '/@/components/Container'; | |
27 | + | |
28 | + // import { useElResize } from '/@/hooks/event/useElResize'; | |
29 | + import { propTypes } from '/@/utils/propTypes'; | |
30 | + import { createModalContext } from '../hooks/useModalContext'; | |
31 | + | |
32 | + export default defineComponent({ | |
33 | + name: 'ModalWrapper', | |
34 | + components: { Spin, ScrollContainer }, | |
35 | + props: { | |
36 | + loading: propTypes.bool, | |
37 | + useWrapper: propTypes.bool.def(true), | |
38 | + modalHeaderHeight: propTypes.number.def(50), | |
39 | + modalFooterHeight: propTypes.number.def(54), | |
40 | + minHeight: propTypes.number.def(200), | |
41 | + footerOffset: propTypes.number.def(0), | |
42 | + visible: propTypes.bool, | |
43 | + fullScreen: propTypes.bool, | |
44 | + loadingTip: propTypes.string, | |
45 | + }, | |
46 | + emits: ['height-change', 'ext-height'], | |
47 | + setup(props: ModalWrapperProps, { emit }) { | |
48 | + const wrapperRef = ref<ComponentRef>(null); | |
49 | + const spinRef = ref<ElRef>(null); | |
50 | + const realHeightRef = ref(0); | |
51 | + | |
52 | + let stopElResizeFn: Fn = () => {}; | |
53 | + | |
54 | + useWindowSizeFn(setModalHeight); | |
55 | + | |
56 | + createModalContext({ | |
57 | + redoModalHeight: setModalHeight, | |
58 | + }); | |
58 | 59 | |
59 | - watchEffect(() => { | |
60 | - setModalHeight(); | |
61 | - }); | |
60 | + const wrapStyle = computed( | |
61 | + (): CSSProperties => { | |
62 | + return { | |
63 | + minHeight: `${props.minHeight}px`, | |
64 | + height: `${unref(realHeightRef)}px`, | |
65 | + // overflow: 'auto', | |
66 | + }; | |
67 | + } | |
68 | + ); | |
62 | 69 | |
63 | - watch( | |
64 | - () => props.fullScreen, | |
65 | - (v) => { | |
66 | - !v && setModalHeight(); | |
67 | - } | |
68 | - ); | |
69 | - | |
70 | - onMounted(() => { | |
71 | - const { modalHeaderHeight, modalFooterHeight } = props; | |
72 | - emit('getExtHeight', modalHeaderHeight + modalFooterHeight); | |
73 | - listenElResize(); | |
74 | - }); | |
75 | - | |
76 | - onUnmounted(() => { | |
77 | - stopElResizeFn && stopElResizeFn(); | |
78 | - }); | |
79 | - | |
80 | - async function setModalHeight() { | |
81 | - // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度 | |
82 | - // 加上这个,就必须在使用的时候传递父级的visible | |
83 | - if (!props.visible) return; | |
84 | - const wrapperRefDom = unref(wrapperRef); | |
85 | - if (!wrapperRefDom) return; | |
86 | - const bodyDom = wrapperRefDom.parentElement; | |
87 | - if (!bodyDom) return; | |
88 | - bodyDom.style.padding = '0'; | |
89 | - await nextTick(); | |
90 | - | |
91 | - try { | |
92 | - const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement; | |
93 | - if (!modalDom) return; | |
94 | - | |
95 | - const modalRect = getComputedStyle(modalDom).top; | |
96 | - const modalTop = Number.parseInt(modalRect); | |
97 | - let maxHeight = | |
98 | - window.innerHeight - | |
99 | - modalTop * 2 + | |
100 | - (props.footerOffset! || 0) - | |
101 | - props.modalFooterHeight - | |
102 | - props.modalHeaderHeight; | |
103 | - | |
104 | - // 距离顶部过进会出现滚动条 | |
105 | - if (modalTop < 40) { | |
106 | - maxHeight -= 26; | |
70 | + const spinStyle = computed( | |
71 | + (): CSSProperties => { | |
72 | + return { | |
73 | + // padding 28 | |
74 | + height: `${unref(realHeightRef) - 28}px`, | |
75 | + }; | |
107 | 76 | } |
108 | - await nextTick(); | |
109 | - const spinEl = unref(spinRef); | |
77 | + ); | |
78 | + | |
79 | + watchEffect(() => { | |
80 | + props.useWrapper && setModalHeight(); | |
81 | + }); | |
110 | 82 | |
111 | - if (!spinEl) return; | |
83 | + watch( | |
84 | + () => props.fullScreen, | |
85 | + () => { | |
86 | + setTimeout(() => { | |
87 | + setModalHeight(); | |
88 | + }, 0); | |
89 | + } | |
90 | + ); | |
112 | 91 | |
113 | - const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement; | |
114 | - if (!spinContainerEl) return; | |
92 | + onMounted(() => { | |
93 | + const { modalHeaderHeight, modalFooterHeight } = props; | |
94 | + emit('ext-height', modalHeaderHeight + modalFooterHeight); | |
95 | + // listenElResize(); | |
96 | + }); | |
115 | 97 | |
116 | - const realHeight = spinContainerEl.scrollHeight; | |
98 | + onUnmounted(() => { | |
99 | + stopElResizeFn && stopElResizeFn(); | |
100 | + }); | |
117 | 101 | |
118 | - if (props.fullScreen) { | |
119 | - realHeightRef.value = | |
120 | - window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight; | |
121 | - } else { | |
122 | - realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30; | |
123 | - } | |
124 | - emit('heightChange', unref(realHeightRef)); | |
102 | + async function setModalHeight() { | |
103 | + // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度 | |
104 | + // 加上这个,就必须在使用的时候传递父级的visible | |
105 | + if (!props.visible) return; | |
106 | + const wrapperRefDom = unref(wrapperRef); | |
107 | + if (!wrapperRefDom) return; | |
108 | + const bodyDom = wrapperRefDom.$el.parentElement; | |
109 | + if (!bodyDom) return; | |
110 | + bodyDom.style.padding = '0'; | |
111 | + await nextTick(); | |
125 | 112 | |
126 | - nextTick(() => { | |
127 | - const el = spinEl.$el; | |
128 | - if (el) { | |
129 | - el.style.height = `${unref(realHeightRef)}px`; | |
113 | + try { | |
114 | + const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement; | |
115 | + if (!modalDom) return; | |
116 | + | |
117 | + const modalRect = getComputedStyle(modalDom).top; | |
118 | + const modalTop = Number.parseInt(modalRect); | |
119 | + let maxHeight = | |
120 | + window.innerHeight - | |
121 | + modalTop * 2 + | |
122 | + (props.footerOffset! || 0) - | |
123 | + props.modalFooterHeight - | |
124 | + props.modalHeaderHeight; | |
125 | + | |
126 | + // 距离顶部过进会出现滚动条 | |
127 | + if (modalTop < 40) { | |
128 | + maxHeight -= 26; | |
130 | 129 | } |
131 | - }); | |
132 | - } catch (error) { | |
133 | - console.log(error); | |
134 | - } | |
135 | - } | |
130 | + await nextTick(); | |
131 | + const spinEl = unref(spinRef); | |
136 | 132 | |
137 | - function listenElResize() { | |
138 | - const wrapper = unref(wrapperRef); | |
139 | - if (!wrapper) return; | |
133 | + if (!spinEl) return; | |
140 | 134 | |
141 | - const container = wrapper.querySelector('.ant-spin-container'); | |
142 | - if (!container) return; | |
135 | + const realHeight = spinEl.scrollHeight; | |
143 | 136 | |
144 | - const [start, stop] = useElResize(container, () => { | |
145 | - setModalHeight(); | |
146 | - }); | |
147 | - stopElResizeFn = stop; | |
148 | - start(); | |
149 | - } | |
150 | - | |
151 | - return () => { | |
152 | - return ( | |
153 | - <div ref={wrapperRef} style={unref(wrapStyle)}> | |
154 | - <Spin ref={spinRef} spinning={props.loading}> | |
155 | - {() => getSlot(slots)} | |
156 | - </Spin> | |
157 | - </div> | |
158 | - ); | |
159 | - }; | |
160 | - }, | |
161 | -}); | |
137 | + if (props.fullScreen) { | |
138 | + realHeightRef.value = | |
139 | + window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight; | |
140 | + } else { | |
141 | + realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30; | |
142 | + } | |
143 | + emit('height-change', unref(realHeightRef)); | |
144 | + } catch (error) { | |
145 | + console.log(error); | |
146 | + } | |
147 | + } | |
148 | + | |
149 | + return { wrapStyle, wrapperRef, spinRef, spinStyle }; | |
150 | + }, | |
151 | + }); | |
152 | +</script> | ... | ... |
src/components/Modal/src/useModal.ts renamed to src/components/Modal/src/hooks/useModal.ts
... | ... | @@ -4,7 +4,7 @@ import type { |
4 | 4 | ModalProps, |
5 | 5 | ReturnMethods, |
6 | 6 | UseModalInnerReturnType, |
7 | -} from './types'; | |
7 | +} from '../types'; | |
8 | 8 | |
9 | 9 | import { |
10 | 10 | ref, |
... | ... | @@ -19,16 +19,18 @@ import { |
19 | 19 | import { isProdMode } from '/@/utils/env'; |
20 | 20 | import { isFunction } from '/@/utils/is'; |
21 | 21 | import { isEqual } from 'lodash-es'; |
22 | -import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; | |
22 | +import { tryOnUnmounted, isInSetup } from '/@/utils/helper/vueHelper'; | |
23 | +import { error } from '/@/utils/log'; | |
24 | +import { computed } from 'vue'; | |
23 | 25 | const dataTransferRef = reactive<any>({}); |
24 | 26 | |
27 | +const visibleData = reactive<{ [key: number]: boolean }>({}); | |
28 | + | |
25 | 29 | /** |
26 | 30 | * @description: Applicable to independent modal and call outside |
27 | 31 | */ |
28 | 32 | export function useModal(): UseModalReturnType { |
29 | - if (!getCurrentInstance()) { | |
30 | - throw new Error('Please put useModal function in the setup function!'); | |
31 | - } | |
33 | + isInSetup(); | |
32 | 34 | const modalRef = ref<Nullable<ModalMethods>>(null); |
33 | 35 | const loadedRef = ref<Nullable<boolean>>(false); |
34 | 36 | const uidRef = ref<string>(''); |
... | ... | @@ -45,23 +47,29 @@ export function useModal(): UseModalReturnType { |
45 | 47 | if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return; |
46 | 48 | |
47 | 49 | modalRef.value = modalMethod; |
50 | + modalMethod.emitVisible = (visible: boolean, uid: number) => { | |
51 | + visibleData[uid] = visible; | |
52 | + }; | |
48 | 53 | } |
49 | 54 | |
50 | 55 | const getInstance = () => { |
51 | 56 | const instance = unref(modalRef); |
52 | 57 | if (!instance) { |
53 | - throw new Error('instance is undefined!'); | |
58 | + error('useModal instance is undefined!'); | |
54 | 59 | } |
55 | 60 | return instance; |
56 | 61 | }; |
57 | 62 | |
58 | 63 | const methods: ReturnMethods = { |
59 | 64 | setModalProps: (props: Partial<ModalProps>): void => { |
60 | - getInstance().setModalProps(props); | |
65 | + getInstance()?.setModalProps(props); | |
61 | 66 | }, |
67 | + getVisible: computed((): boolean => { | |
68 | + return visibleData[~~unref(uidRef)]; | |
69 | + }), | |
62 | 70 | |
63 | 71 | openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => { |
64 | - getInstance().setModalProps({ | |
72 | + getInstance()?.setModalProps({ | |
65 | 73 | visible: visible, |
66 | 74 | }); |
67 | 75 | |
... | ... | @@ -83,20 +91,16 @@ export function useModal(): UseModalReturnType { |
83 | 91 | |
84 | 92 | export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { |
85 | 93 | const modalInstanceRef = ref<Nullable<ModalMethods>>(null); |
86 | - const currentInstall = getCurrentInstance(); | |
94 | + const currentInstance = getCurrentInstance(); | |
87 | 95 | const uidRef = ref<string>(''); |
88 | 96 | |
89 | - if (!currentInstall) { | |
90 | - throw new Error('instance is undefined!'); | |
91 | - } | |
92 | - | |
93 | 97 | // currentInstall.type.emits = [...currentInstall.type.emits, 'register']; |
94 | 98 | // Object.assign(currentInstall.type.emits, ['register']); |
95 | 99 | |
96 | 100 | const getInstance = () => { |
97 | 101 | const instance = unref(modalInstanceRef); |
98 | 102 | if (!instance) { |
99 | - throw new Error('instance is undefined!'); | |
103 | + error('useModalInner instance is undefined!'); | |
100 | 104 | } |
101 | 105 | return instance; |
102 | 106 | }; |
... | ... | @@ -108,7 +112,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { |
108 | 112 | }); |
109 | 113 | uidRef.value = uuid; |
110 | 114 | modalInstanceRef.value = modalInstance; |
111 | - currentInstall.emit('register', modalInstance, uuid); | |
115 | + currentInstance?.emit('register', modalInstance, uuid); | |
112 | 116 | }; |
113 | 117 | |
114 | 118 | watchEffect(() => { |
... | ... | @@ -124,19 +128,22 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { |
124 | 128 | register, |
125 | 129 | { |
126 | 130 | changeLoading: (loading = true) => { |
127 | - getInstance().setModalProps({ loading }); | |
131 | + getInstance()?.setModalProps({ loading }); | |
128 | 132 | }, |
133 | + getVisible: computed((): boolean => { | |
134 | + return visibleData[~~unref(uidRef)]; | |
135 | + }), | |
129 | 136 | |
130 | 137 | changeOkLoading: (loading = true) => { |
131 | - getInstance().setModalProps({ confirmLoading: loading }); | |
138 | + getInstance()?.setModalProps({ confirmLoading: loading }); | |
132 | 139 | }, |
133 | 140 | |
134 | 141 | closeModal: () => { |
135 | - getInstance().setModalProps({ visible: false }); | |
142 | + getInstance()?.setModalProps({ visible: false }); | |
136 | 143 | }, |
137 | 144 | |
138 | 145 | setModalProps: (props: Partial<ModalProps>) => { |
139 | - getInstance().setModalProps(props); | |
146 | + getInstance()?.setModalProps(props); | |
140 | 147 | }, |
141 | 148 | }, |
142 | 149 | ]; | ... | ... |
src/components/Modal/src/useModalContext.ts renamed to src/components/Modal/src/hooks/useModalContext.ts
src/components/Modal/src/useModalDrag.ts renamed to src/components/Modal/src/hooks/useModalDrag.ts
src/components/Modal/src/useFullScreen.ts renamed to src/components/Modal/src/hooks/useModalFullScreen.ts
src/components/Modal/src/index.less
... | ... | @@ -21,9 +21,12 @@ |
21 | 21 | width: 520px; |
22 | 22 | padding-bottom: 0; |
23 | 23 | |
24 | - .ant-spin-nested-loading { | |
25 | - padding: 16px; | |
24 | + .scroll-container { | |
25 | + padding: 14px; | |
26 | 26 | } |
27 | + // .ant-spin-nested-loading { | |
28 | + // padding: 16px; | |
29 | + // } | |
27 | 30 | |
28 | 31 | &-title { |
29 | 32 | font-size: 16px; |
... | ... | @@ -35,46 +38,6 @@ |
35 | 38 | } |
36 | 39 | } |
37 | 40 | |
38 | - .custom-close-icon { | |
39 | - display: flex; | |
40 | - height: 95%; | |
41 | - align-items: center; | |
42 | - | |
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 | - } | |
60 | - } | |
61 | - | |
62 | - & span:nth-child(1) { | |
63 | - display: inline-block; | |
64 | - padding: 10px; | |
65 | - | |
66 | - &:hover { | |
67 | - color: @primary-color; | |
68 | - } | |
69 | - } | |
70 | - | |
71 | - & span:nth-child(2) { | |
72 | - &:hover { | |
73 | - color: @error-color; | |
74 | - } | |
75 | - } | |
76 | - } | |
77 | - | |
78 | 41 | .ant-modal-body { |
79 | 42 | padding: 0; |
80 | 43 | } |
... | ... | @@ -96,8 +59,6 @@ |
96 | 59 | } |
97 | 60 | |
98 | 61 | &-footer { |
99 | - // padding: 10px 26px 26px 16px; | |
100 | - | |
101 | 62 | button + button { |
102 | 63 | margin-left: 10px; |
103 | 64 | } | ... | ... |
src/components/Modal/src/props.ts
1 | -import type { PropType } from 'vue'; | |
1 | +import type { PropType, CSSProperties } from 'vue'; | |
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 | +import { propTypes, VueNode } from '/@/utils/propTypes'; | |
6 | +import type { ModalWrapperProps } from './types'; | |
6 | 7 | const { t } = useI18n(); |
7 | 8 | |
8 | 9 | export const modalProps = { |
... | ... | @@ -26,6 +27,7 @@ export const basicProps = Object.assign({}, modalProps, { |
26 | 27 | // Whether to setting wrapper |
27 | 28 | useWrapper: propTypes.bool.def(true), |
28 | 29 | loading: propTypes.bool, |
30 | + loadingTip: propTypes.string, | |
29 | 31 | /** |
30 | 32 | * @description: Show close button |
31 | 33 | */ |
... | ... | @@ -35,65 +37,44 @@ export const basicProps = Object.assign({}, modalProps, { |
35 | 37 | */ |
36 | 38 | showOkBtn: propTypes.bool.def(true), |
37 | 39 | |
38 | - wrapperProps: Object as PropType<any>, | |
40 | + wrapperProps: Object as PropType<Partial<ModalWrapperProps>>, | |
39 | 41 | |
40 | - afterClose: Function as PropType<() => Promise<any>>, | |
42 | + afterClose: Function as PropType<() => Promise<VueNode>>, | |
41 | 43 | |
42 | - bodyStyle: Object as PropType<any>, | |
44 | + bodyStyle: Object as PropType<CSSProperties>, | |
43 | 45 | |
44 | - closable: { | |
45 | - type: Boolean as PropType<boolean>, | |
46 | - default: true, | |
47 | - }, | |
46 | + closable: propTypes.bool.def(true), | |
48 | 47 | |
49 | - closeIcon: Object as PropType<any>, | |
48 | + closeIcon: Object as PropType<VueNode>, | |
50 | 49 | |
51 | - confirmLoading: Boolean as PropType<boolean>, | |
50 | + confirmLoading: propTypes.bool, | |
52 | 51 | |
53 | - destroyOnClose: Boolean as PropType<boolean>, | |
52 | + destroyOnClose: propTypes.bool, | |
54 | 53 | |
55 | - footer: Object as PropType<any>, | |
54 | + footer: Object as PropType<VueNode>, | |
56 | 55 | |
57 | 56 | getContainer: Function as PropType<() => any>, |
58 | 57 | |
59 | - mask: { | |
60 | - type: Boolean as PropType<boolean>, | |
61 | - default: true, | |
62 | - }, | |
58 | + mask: propTypes.bool.def(true), | |
63 | 59 | |
64 | - maskClosable: { | |
65 | - type: Boolean as PropType<boolean>, | |
66 | - default: true, | |
67 | - }, | |
68 | - keyboard: { | |
69 | - type: Boolean as PropType<boolean>, | |
70 | - default: true, | |
71 | - }, | |
60 | + maskClosable: propTypes.bool.def(true), | |
61 | + keyboard: propTypes.bool.def(true), | |
72 | 62 | |
73 | - maskStyle: Object as PropType<any>, | |
63 | + maskStyle: Object as PropType<CSSProperties>, | |
74 | 64 | |
75 | - okType: { | |
76 | - type: String as PropType<string>, | |
77 | - default: 'primary', | |
78 | - }, | |
65 | + okType: propTypes.string.def('primary'), | |
79 | 66 | |
80 | 67 | okButtonProps: Object as PropType<ButtonProps>, |
81 | 68 | |
82 | 69 | cancelButtonProps: Object as PropType<ButtonProps>, |
83 | 70 | |
84 | - title: { | |
85 | - type: String as PropType<string>, | |
86 | - }, | |
71 | + title: propTypes.string, | |
87 | 72 | |
88 | - visible: Boolean as PropType<boolean>, | |
73 | + visible: propTypes.bool, | |
89 | 74 | |
90 | 75 | width: [String, Number] as PropType<string | number>, |
91 | 76 | |
92 | - wrapClassName: { | |
93 | - type: String as PropType<string>, | |
94 | - }, | |
77 | + wrapClassName: propTypes.string, | |
95 | 78 | |
96 | - zIndex: { | |
97 | - type: Number as PropType<number>, | |
98 | - }, | |
79 | + zIndex: propTypes.number, | |
99 | 80 | }); | ... | ... |
src/components/Modal/src/types.ts
1 | 1 | import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; |
2 | -import type { CSSProperties, VNodeChild } from 'vue'; | |
2 | +import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'; | |
3 | 3 | /** |
4 | 4 | * @description: 弹窗对外暴露的方法 |
5 | 5 | */ |
6 | 6 | export interface ModalMethods { |
7 | 7 | setModalProps: (props: Partial<ModalProps>) => void; |
8 | + emitVisible?: (visible: boolean, uid: number) => void; | |
8 | 9 | } |
9 | 10 | |
10 | 11 | export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void; |
11 | 12 | |
12 | 13 | export interface ReturnMethods extends ModalMethods { |
13 | 14 | openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void; |
15 | + getVisible?: ComputedRef<boolean>; | |
14 | 16 | } |
15 | 17 | |
16 | 18 | export type UseModalReturnType = [RegisterFn, ReturnMethods]; |
... | ... | @@ -19,6 +21,7 @@ export interface ReturnInnerMethods extends ModalMethods { |
19 | 21 | closeModal: () => void; |
20 | 22 | changeLoading: (loading: boolean) => void; |
21 | 23 | changeOkLoading: (loading: boolean) => void; |
24 | + getVisible?: ComputedRef<boolean>; | |
22 | 25 | } |
23 | 26 | |
24 | 27 | export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]; |
... | ... | @@ -38,6 +41,7 @@ export interface ModalProps { |
38 | 41 | useWrapper: boolean; |
39 | 42 | |
40 | 43 | loading: boolean; |
44 | + loadingTip?: string; | |
41 | 45 | |
42 | 46 | wrapperProps: Omit<ModalWrapperProps, 'loading'>; |
43 | 47 | |
... | ... | @@ -193,4 +197,5 @@ export interface ModalWrapperProps { |
193 | 197 | minHeight: number; |
194 | 198 | visible: boolean; |
195 | 199 | fullScreen: boolean; |
200 | + useWrapper: boolean; | |
196 | 201 | } | ... | ... |
src/hooks/core/useAttrs.ts
1 | 1 | import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'; |
2 | - | |
2 | +import type { Ref } from 'vue'; | |
3 | 3 | interface Params { |
4 | 4 | excludeListeners?: boolean; |
5 | 5 | excludeKeys?: string[]; |
... | ... | @@ -12,7 +12,7 @@ export function entries<T>(obj: Hash<T>): [string, T][] { |
12 | 12 | return Object.keys(obj).map((key: string) => [key, obj[key]]); |
13 | 13 | } |
14 | 14 | |
15 | -export function useAttrs(params: Params = {}) { | |
15 | +export function useAttrs(params: Params = {}): Ref<Recordable> | {} { | |
16 | 16 | const instance = getCurrentInstance(); |
17 | 17 | if (!instance) return {}; |
18 | 18 | ... | ... |
src/settings/projectSetting.ts
src/utils/propTypes.ts
1 | 1 | import { CSSProperties, VNodeChild } from 'vue'; |
2 | 2 | import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; |
3 | 3 | |
4 | -type VueNode = VNodeChild | JSX.Element; | |
4 | +export type VueNode = VNodeChild | JSX.Element; | |
5 | 5 | |
6 | 6 | type PropTypes = VueTypesInterface & { |
7 | 7 | readonly style: VueTypeValidableDef<CSSProperties>; | ... | ... |
src/views/demo/comp/drawer/Drawer3.vue
1 | 1 | <template> |
2 | - <BasicDrawer v-bind="$attrs" title="Modal Title" width="50%" showFooter> | |
2 | + <BasicDrawer v-bind="$attrs" title="Modal Title" width="50%" showFooter @ok="handleOk"> | |
3 | 3 | <p class="h-20" v-for="index in 40" :key="index">根据屏幕高度自适应</p> |
4 | + <template #insertFooter> | |
5 | + <a-button> btn</a-button> | |
6 | + </template> | |
7 | + <template #centerFooter> | |
8 | + <a-button> btn2</a-button> | |
9 | + </template> | |
10 | + | |
11 | + <template #appendFooter> | |
12 | + <a-button> btn3</a-button> | |
13 | + </template> | |
14 | + | |
15 | + <!-- <template #footer> | |
16 | + <a-button> customerFooter</a-button> | |
17 | + </template> --> | |
4 | 18 | </BasicDrawer> |
5 | 19 | </template> |
6 | 20 | <script lang="ts"> |
... | ... | @@ -9,7 +23,13 @@ |
9 | 23 | export default defineComponent({ |
10 | 24 | components: { BasicDrawer }, |
11 | 25 | setup() { |
12 | - return {}; | |
26 | + return { | |
27 | + handleOk: () => { | |
28 | + console.log('====================='); | |
29 | + console.log('ok'); | |
30 | + console.log('======================'); | |
31 | + }, | |
32 | + }; | |
13 | 33 | }, |
14 | 34 | }); |
15 | 35 | </script> | ... | ... |
src/views/demo/comp/drawer/Drawer5.vue
1 | 1 | <template> |
2 | 2 | <BasicDrawer v-bind="$attrs" :isDetail="true" title="Drawer Title5"> |
3 | 3 | <p class="h-20">Content Message</p> |
4 | + <template #titleToolbar> toolbar </template> | |
4 | 5 | </BasicDrawer> |
5 | 6 | </template> |
6 | 7 | <script lang="ts"> |
... | ... | @@ -8,6 +9,5 @@ |
8 | 9 | import { BasicDrawer } from '/@/components/Drawer'; |
9 | 10 | export default defineComponent({ |
10 | 11 | components: { BasicDrawer }, |
11 | - setup() {}, | |
12 | 12 | }); |
13 | 13 | </script> | ... | ... |
src/views/demo/comp/drawer/index.vue
... | ... | @@ -3,10 +3,10 @@ |
3 | 3 | <Alert message="使用 useDrawer 进行抽屉操作" show-icon /> |
4 | 4 | <a-button type="primary" class="my-4" @click="openDrawerLoading">打开Drawer</a-button> |
5 | 5 | |
6 | - <Alert message="内外同时同时显示隐藏" show-icon /> | |
7 | - <a-button type="primary" class="my-4" @click="openDrawer2">打开Drawer</a-button> | |
6 | + <Alert message="内外同时控制显示隐藏" show-icon /> | |
7 | + <a-button type="primary" class="my-4" @click="openDrawer2(true)">打开Drawer</a-button> | |
8 | 8 | <Alert message="自适应高度/显示footer" show-icon /> |
9 | - <a-button type="primary" class="my-4" @click="openDrawer3">打开Drawer</a-button> | |
9 | + <a-button type="primary" class="my-4" @click="openDrawer3(true)">打开Drawer</a-button> | |
10 | 10 | |
11 | 11 | <Alert |
12 | 12 | message="内外数据交互,外部通过 transferModalData 发送,内部通过 receiveDrawerDataRef 接收。该数据具有响应式" |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /> |
15 | 15 | <a-button type="primary" class="my-4" @click="send">打开Drawer并传递数据</a-button> |
16 | 16 | <Alert message="详情页模式" show-icon /> |
17 | - <a-button type="primary" class="my-4" @click="openDrawer5">打开详情Drawer</a-button> | |
17 | + <a-button type="primary" class="my-4" @click="openDrawer5(true)">打开详情Drawer</a-button> | |
18 | 18 | <Drawer1 @register="register1" /> |
19 | 19 | <Drawer2 @register="register2" /> |
20 | 20 | <Drawer3 @register="register3" /> | ... | ... |
src/views/demo/feat/tabs/index.vue
... | ... | @@ -4,7 +4,7 @@ |
4 | 4 | <a-input placeholder="请输入" /> |
5 | 5 | </CollapseContainer> |
6 | 6 | |
7 | - <CollapseContainer class="mt-4 px-4" title="标签页操作"> | |
7 | + <CollapseContainer class="mt-4" title="标签页操作"> | |
8 | 8 | <a-button class="mr-2" @click="closeAll">关闭所有</a-button> |
9 | 9 | <a-button class="mr-2" @click="closeLeft">关闭左侧</a-button> |
10 | 10 | <a-button class="mr-2" @click="closeRight">关闭右侧</a-button> | ... | ... |