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,6 +12,10 @@ | ||
12 | - form: 新增远程下拉`ApiSelect`及示例 | 12 | - form: 新增远程下拉`ApiSelect`及示例 |
13 | - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 | 13 | - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 |
14 | 14 | ||
15 | +### ⚡ Performance Improvements | ||
16 | + | ||
17 | +- 优化`modal`与`drawer`滚动条组件 | ||
18 | + | ||
15 | ### 🐛 Bug Fixes | 19 | ### 🐛 Bug Fixes |
16 | 20 | ||
17 | - 修复混合模式下滚动条丢失问题 | 21 | - 修复混合模式下滚动条丢失问题 |
@@ -21,6 +25,7 @@ | @@ -21,6 +25,7 @@ | ||
21 | - 修复路由类型错误 | 25 | - 修复路由类型错误 |
22 | - 修复菜单分割时权限失效问题 | 26 | - 修复菜单分割时权限失效问题 |
23 | - 关闭多标签页时 iframe 提前加载 | 27 | - 关闭多标签页时 iframe 提前加载 |
28 | +- 修复`modal`与`drawer`已知问题 | ||
24 | 29 | ||
25 | ## 2.0.0-rc.14 (2020-12-15) | 30 | ## 2.0.0-rc.14 (2020-12-15) |
26 | 31 |
src/components/Container/src/LazyContainer.vue
1 | <template> | 1 | <template> |
2 | <transition-group | 2 | <transition-group |
3 | - class="lazy-container" | 3 | + :class="prefixCls" |
4 | v-bind="$attrs" | 4 | v-bind="$attrs" |
5 | ref="elRef" | 5 | ref="elRef" |
6 | :name="transitionName" | 6 | :name="transitionName" |
@@ -25,6 +25,7 @@ | @@ -25,6 +25,7 @@ | ||
25 | import { useTimeoutFn } from '/@/hooks/core/useTimeout'; | 25 | import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
26 | import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'; | 26 | import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'; |
27 | import { propTypes } from '/@/utils/propTypes'; | 27 | import { propTypes } from '/@/utils/propTypes'; |
28 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
28 | 29 | ||
29 | interface State { | 30 | interface State { |
30 | isInit: boolean; | 31 | isInit: boolean; |
@@ -70,6 +71,8 @@ | @@ -70,6 +71,8 @@ | ||
70 | intersectionObserverInstance: null, | 71 | intersectionObserverInstance: null, |
71 | }); | 72 | }); |
72 | 73 | ||
74 | + const { prefixCls } = useDesign('lazy-container'); | ||
75 | + | ||
73 | onMounted(() => { | 76 | onMounted(() => { |
74 | immediateInit(); | 77 | immediateInit(); |
75 | initIntersectionObserver(); | 78 | initIntersectionObserver(); |
@@ -129,13 +132,17 @@ | @@ -129,13 +132,17 @@ | ||
129 | } | 132 | } |
130 | return { | 133 | return { |
131 | elRef, | 134 | elRef, |
135 | + prefixCls, | ||
132 | ...toRefs(state), | 136 | ...toRefs(state), |
133 | }; | 137 | }; |
134 | }, | 138 | }, |
135 | }); | 139 | }); |
136 | </script> | 140 | </script> |
137 | <style lang="less"> | 141 | <style lang="less"> |
138 | - .lazy-container { | 142 | + @import (reference) '../../../design/index.less'; |
143 | + @prefix-cls: ~'@{namespace}-lazy-container'; | ||
144 | + | ||
145 | + .@{prefix-cls} { | ||
139 | width: 100%; | 146 | width: 100%; |
140 | height: 100%; | 147 | height: 100%; |
141 | } | 148 | } |
src/components/Container/src/collapse/CollapseContainer.vue
1 | <template> | 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 | <template #title> | 4 | <template #title> |
5 | <slot name="title" /> | 5 | <slot name="title" /> |
6 | </template> | 6 | </template> |
@@ -8,7 +8,7 @@ | @@ -8,7 +8,7 @@ | ||
8 | 8 | ||
9 | <CollapseTransition :enable="canExpan"> | 9 | <CollapseTransition :enable="canExpan"> |
10 | <Skeleton v-if="loading" /> | 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 | <LazyContainer :timeout="lazyTime" v-if="lazy"> | 12 | <LazyContainer :timeout="lazyTime" v-if="lazy"> |
13 | <slot /> | 13 | <slot /> |
14 | <template #skeleton> | 14 | <template #skeleton> |
@@ -35,6 +35,7 @@ | @@ -35,6 +35,7 @@ | ||
35 | // hook | 35 | // hook |
36 | import { useTimeoutFn } from '/@/hooks/core/useTimeout'; | 36 | import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
37 | import { propTypes } from '/@/utils/propTypes'; | 37 | import { propTypes } from '/@/utils/propTypes'; |
38 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
38 | 39 | ||
39 | export default defineComponent({ | 40 | export default defineComponent({ |
40 | name: 'CollapseContainer', | 41 | name: 'CollapseContainer', |
@@ -64,6 +65,9 @@ | @@ -64,6 +65,9 @@ | ||
64 | }, | 65 | }, |
65 | setup(props) { | 66 | setup(props) { |
66 | const show = ref(true); | 67 | const show = ref(true); |
68 | + | ||
69 | + const { prefixCls } = useDesign('collapse-container'); | ||
70 | + | ||
67 | /** | 71 | /** |
68 | * @description: Handling development events | 72 | * @description: Handling development events |
69 | */ | 73 | */ |
@@ -77,20 +81,20 @@ | @@ -77,20 +81,20 @@ | ||
77 | return { | 81 | return { |
78 | show, | 82 | show, |
79 | handleExpand, | 83 | handleExpand, |
84 | + prefixCls, | ||
80 | }; | 85 | }; |
81 | }, | 86 | }, |
82 | }); | 87 | }); |
83 | </script> | 88 | </script> |
84 | <style lang="less"> | 89 | <style lang="less"> |
85 | - .collapse-container { | 90 | + @import (reference) '../../../../design/index.less'; |
91 | + @prefix-cls: ~'@{namespace}-collapse-container'; | ||
92 | + | ||
93 | + .@{prefix-cls} { | ||
86 | background: #fff; | 94 | background: #fff; |
87 | border-radius: 2px; | 95 | border-radius: 2px; |
88 | transition: all 0.3s ease-in-out; | 96 | transition: all 0.3s ease-in-out; |
89 | 97 | ||
90 | - &.no-shadow { | ||
91 | - box-shadow: none; | ||
92 | - } | ||
93 | - | ||
94 | &__header { | 98 | &__header { |
95 | display: flex; | 99 | display: flex; |
96 | height: 32px; | 100 | height: 32px; |
src/components/Container/src/collapse/CollapseHeader.vue
1 | <template> | 1 | <template> |
2 | - <div class="collapse-container__header"> | 2 | + <div :class="`${prefixCls}__header`"> |
3 | <BasicTitle :helpMessage="$attrs.helpMessage"> | 3 | <BasicTitle :helpMessage="$attrs.helpMessage"> |
4 | <template v-if="$attrs.title"> | 4 | <template v-if="$attrs.title"> |
5 | {{ $attrs.title }} | 5 | {{ $attrs.title }} |
@@ -9,7 +9,7 @@ | @@ -9,7 +9,7 @@ | ||
9 | </template> | 9 | </template> |
10 | </BasicTitle> | 10 | </BasicTitle> |
11 | 11 | ||
12 | - <div class="collapse-container__action"> | 12 | + <div :class="`${prefixCls}__action`"> |
13 | <slot name="action" /> | 13 | <slot name="action" /> |
14 | <BasicArrow v-if="$attrs.canExpan" top :expand="$attrs.show" @click="$emit('expand')" /> | 14 | <BasicArrow v-if="$attrs.canExpan" top :expand="$attrs.show" @click="$emit('expand')" /> |
15 | </div> | 15 | </div> |
@@ -21,5 +21,8 @@ | @@ -21,5 +21,8 @@ | ||
21 | export default defineComponent({ | 21 | export default defineComponent({ |
22 | inheritAttrs: false, | 22 | inheritAttrs: false, |
23 | components: { BasicArrow, BasicTitle }, | 23 | components: { BasicArrow, BasicTitle }, |
24 | + props: { | ||
25 | + prefixCls: String, | ||
26 | + }, | ||
24 | }); | 27 | }); |
25 | </script> | 28 | </script> |
src/components/Description/src/index.tsx
@@ -24,7 +24,7 @@ export default defineComponent({ | @@ -24,7 +24,7 @@ export default defineComponent({ | ||
24 | const getMergeProps = computed(() => { | 24 | const getMergeProps = computed(() => { |
25 | return { | 25 | return { |
26 | ...props, | 26 | ...props, |
27 | - ...(unref(propsRef) as any), | 27 | + ...(unref(propsRef) as Recordable), |
28 | } as DescOptions; | 28 | } as DescOptions; |
29 | }); | 29 | }); |
30 | 30 |
src/components/Description/src/props.ts
@@ -13,7 +13,7 @@ export default { | @@ -13,7 +13,7 @@ export default { | ||
13 | bordered: propTypes.bool.def(true), | 13 | bordered: propTypes.bool.def(true), |
14 | 14 | ||
15 | column: { | 15 | column: { |
16 | - type: [Number, Object] as PropType<number | any>, | 16 | + type: [Number, Object] as PropType<number | Recordable>, |
17 | default: () => { | 17 | default: () => { |
18 | return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }; | 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,7 +15,10 @@ export interface DescItem { | ||
15 | span?: number; | 15 | span?: number; |
16 | show?: (...arg: any) => boolean; | 16 | show?: (...arg: any) => boolean; |
17 | // render | 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 | export interface DescOptions extends DescriptionsProps { | 24 | export interface DescOptions extends DescriptionsProps { |
@@ -30,7 +33,7 @@ export interface DescOptions extends DescriptionsProps { | @@ -30,7 +33,7 @@ export interface DescOptions extends DescriptionsProps { | ||
30 | * 数据 | 33 | * 数据 |
31 | * @type object | 34 | * @type object |
32 | */ | 35 | */ |
33 | - data: any; | 36 | + data: Recordable; |
34 | /** | 37 | /** |
35 | * Built-in CollapseContainer component configuration | 38 | * Built-in CollapseContainer component configuration |
36 | * @type CollapseContainerOptions | 39 | * @type CollapseContainerOptions |
src/components/Description/src/useDescription.ts
@@ -19,7 +19,7 @@ export function useDescription(props?: Partial<DescOptions>): UseDescReturnType | @@ -19,7 +19,7 @@ export function useDescription(props?: Partial<DescOptions>): UseDescReturnType | ||
19 | 19 | ||
20 | const methods: DescInstance = { | 20 | const methods: DescInstance = { |
21 | setDescProps: (descProps: Partial<DescOptions>): void => { | 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
1 | import { withInstall } from '../util'; | 1 | import { withInstall } from '../util'; |
2 | 2 | ||
3 | -import BasicDrawer from './src/BasicDrawer'; | 3 | +import BasicDrawer from './src/BasicDrawer.vue'; |
4 | 4 | ||
5 | export { BasicDrawer }; | 5 | export { BasicDrawer }; |
6 | export * from './src/types'; | 6 | export * from './src/types'; |
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,13 +10,13 @@ export const footerProps = { | ||
10 | * @description: Show close button | 10 | * @description: Show close button |
11 | */ | 11 | */ |
12 | showCancelBtn: propTypes.bool.def(true), | 12 | showCancelBtn: propTypes.bool.def(true), |
13 | - cancelButtonProps: Object as PropType<any>, | 13 | + cancelButtonProps: Object as PropType<Recordable>, |
14 | cancelText: propTypes.string.def(t('component.drawer.cancelText')), | 14 | cancelText: propTypes.string.def(t('component.drawer.cancelText')), |
15 | /** | 15 | /** |
16 | * @description: Show confirmation button | 16 | * @description: Show confirmation button |
17 | */ | 17 | */ |
18 | showOkBtn: propTypes.bool.def(true), | 18 | showOkBtn: propTypes.bool.def(true), |
19 | - okButtonProps: propTypes.any, | 19 | + okButtonProps: Object as PropType<Recordable>, |
20 | okText: propTypes.string.def(t('component.drawer.okText')), | 20 | okText: propTypes.string.def(t('component.drawer.okText')), |
21 | okType: propTypes.string.def('primary'), | 21 | okType: propTypes.string.def('primary'), |
22 | showFooter: propTypes.bool, | 22 | showFooter: propTypes.bool, |
@@ -28,6 +28,7 @@ export const footerProps = { | @@ -28,6 +28,7 @@ export const footerProps = { | ||
28 | export const basicProps = { | 28 | export const basicProps = { |
29 | isDetail: propTypes.bool, | 29 | isDetail: propTypes.bool, |
30 | title: propTypes.string.def(''), | 30 | title: propTypes.string.def(''), |
31 | + loadingText: propTypes.string, | ||
31 | showDetailBack: propTypes.bool.def(true), | 32 | showDetailBack: propTypes.bool.def(true), |
32 | visible: propTypes.bool, | 33 | visible: propTypes.bool, |
33 | loading: propTypes.bool, | 34 | loading: propTypes.bool, |
src/components/Drawer/src/types.ts
1 | import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; | 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 | import type { ScrollContainerOptions } from '/@/components/Container/index'; | 3 | import type { ScrollContainerOptions } from '/@/components/Container/index'; |
4 | 4 | ||
5 | export interface DrawerInstance { | 5 | export interface DrawerInstance { |
6 | setDrawerProps: (props: Partial<DrawerProps> | boolean) => void; | 6 | setDrawerProps: (props: Partial<DrawerProps> | boolean) => void; |
7 | + emitVisible?: (visible: boolean, uid: number) => void; | ||
7 | } | 8 | } |
8 | 9 | ||
9 | export interface ReturnMethods extends DrawerInstance { | 10 | export interface ReturnMethods extends DrawerInstance { |
10 | openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void; | 11 | openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void; |
12 | + getVisible?: ComputedRef<boolean>; | ||
11 | } | 13 | } |
12 | 14 | ||
13 | export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void; | 15 | export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void; |
@@ -16,6 +18,7 @@ export interface ReturnInnerMethods extends DrawerInstance { | @@ -16,6 +18,7 @@ export interface ReturnInnerMethods extends DrawerInstance { | ||
16 | closeDrawer: () => void; | 18 | closeDrawer: () => void; |
17 | changeLoading: (loading: boolean) => void; | 19 | changeLoading: (loading: boolean) => void; |
18 | changeOkLoading: (loading: boolean) => void; | 20 | changeOkLoading: (loading: boolean) => void; |
21 | + getVisible?: ComputedRef<boolean>; | ||
19 | } | 22 | } |
20 | 23 | ||
21 | export type UseDrawerReturnType = [RegisterFn, ReturnMethods]; | 24 | export type UseDrawerReturnType = [RegisterFn, ReturnMethods]; |
src/components/Drawer/src/useDrawer.ts
@@ -6,22 +6,32 @@ import type { | @@ -6,22 +6,32 @@ import type { | ||
6 | UseDrawerInnerReturnType, | 6 | UseDrawerInnerReturnType, |
7 | } from './types'; | 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 | import { isProdMode } from '/@/utils/env'; | 20 | import { isProdMode } from '/@/utils/env'; |
12 | import { isFunction } from '/@/utils/is'; | 21 | import { isFunction } from '/@/utils/is'; |
13 | -import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; | 22 | +import { tryOnUnmounted, isInSetup } from '/@/utils/helper/vueHelper'; |
14 | import { isEqual } from 'lodash-es'; | 23 | import { isEqual } from 'lodash-es'; |
24 | +import { error } from '/@/utils/log'; | ||
15 | 25 | ||
16 | const dataTransferRef = reactive<any>({}); | 26 | const dataTransferRef = reactive<any>({}); |
17 | 27 | ||
28 | +const visibleData = reactive<{ [key: number]: boolean }>({}); | ||
29 | + | ||
18 | /** | 30 | /** |
19 | * @description: Applicable to separate drawer and call outside | 31 | * @description: Applicable to separate drawer and call outside |
20 | */ | 32 | */ |
21 | export function useDrawer(): UseDrawerReturnType { | 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 | const drawerRef = ref<DrawerInstance | null>(null); | 36 | const drawerRef = ref<DrawerInstance | null>(null); |
27 | const loadedRef = ref<Nullable<boolean>>(false); | 37 | const loadedRef = ref<Nullable<boolean>>(false); |
@@ -41,23 +51,31 @@ export function useDrawer(): UseDrawerReturnType { | @@ -41,23 +51,31 @@ export function useDrawer(): UseDrawerReturnType { | ||
41 | uidRef.value = uuid; | 51 | uidRef.value = uuid; |
42 | drawerRef.value = drawerInstance; | 52 | drawerRef.value = drawerInstance; |
43 | loadedRef.value = true; | 53 | loadedRef.value = true; |
54 | + | ||
55 | + drawerInstance.emitVisible = (visible: boolean, uid: number) => { | ||
56 | + visibleData[uid] = visible; | ||
57 | + }; | ||
44 | } | 58 | } |
45 | 59 | ||
46 | const getInstance = () => { | 60 | const getInstance = () => { |
47 | const instance = unref(drawerRef); | 61 | const instance = unref(drawerRef); |
48 | if (!instance) { | 62 | if (!instance) { |
49 | - throw new Error('instance is undefined!'); | 63 | + error('useDrawer instance is undefined!'); |
50 | } | 64 | } |
51 | return instance; | 65 | return instance; |
52 | }; | 66 | }; |
53 | 67 | ||
54 | const methods: ReturnMethods = { | 68 | const methods: ReturnMethods = { |
55 | setDrawerProps: (props: Partial<DrawerProps>): void => { | 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 | openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => { | 77 | openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => { |
60 | - getInstance().setDrawerProps({ | 78 | + getInstance()?.setDrawerProps({ |
61 | visible: visible, | 79 | visible: visible, |
62 | }); | 80 | }); |
63 | if (!data) return; | 81 | if (!data) return; |
@@ -79,17 +97,18 @@ export function useDrawer(): UseDrawerReturnType { | @@ -79,17 +97,18 @@ export function useDrawer(): UseDrawerReturnType { | ||
79 | 97 | ||
80 | export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { | 98 | export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { |
81 | const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null); | 99 | const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null); |
82 | - const currentInstall = getCurrentInstance(); | 100 | + const currentInstance = getCurrentInstance(); |
83 | const uidRef = ref<string>(''); | 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 | const getInstance = () => { | 107 | const getInstance = () => { |
90 | const instance = unref(drawerInstanceRef); | 108 | const instance = unref(drawerInstanceRef); |
91 | if (!instance) { | 109 | if (!instance) { |
92 | - throw new Error('useDrawerInner instance is undefined!'); | 110 | + error('useDrawerInner instance is undefined!'); |
111 | + return; | ||
93 | } | 112 | } |
94 | return instance; | 113 | return instance; |
95 | }; | 114 | }; |
@@ -102,7 +121,7 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { | @@ -102,7 +121,7 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { | ||
102 | 121 | ||
103 | uidRef.value = uuid; | 122 | uidRef.value = uuid; |
104 | drawerInstanceRef.value = modalInstance; | 123 | drawerInstanceRef.value = modalInstance; |
105 | - currentInstall.emit('register', modalInstance, uuid); | 124 | + currentInstance?.emit('register', modalInstance, uuid); |
106 | }; | 125 | }; |
107 | 126 | ||
108 | watchEffect(() => { | 127 | watchEffect(() => { |
@@ -118,19 +137,22 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { | @@ -118,19 +137,22 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { | ||
118 | register, | 137 | register, |
119 | { | 138 | { |
120 | changeLoading: (loading = true) => { | 139 | changeLoading: (loading = true) => { |
121 | - getInstance().setDrawerProps({ loading }); | 140 | + getInstance()?.setDrawerProps({ loading }); |
122 | }, | 141 | }, |
123 | 142 | ||
124 | changeOkLoading: (loading = true) => { | 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 | closeDrawer: () => { | 150 | closeDrawer: () => { |
129 | - getInstance().setDrawerProps({ visible: false }); | 151 | + getInstance()?.setDrawerProps({ visible: false }); |
130 | }, | 152 | }, |
131 | 153 | ||
132 | setDrawerProps: (props: Partial<DrawerProps>) => { | 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 | <template> | 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 | <FormItem> | 3 | <FormItem> |
9 | <slot name="resetBefore" /> | 4 | <slot name="resetBefore" /> |
10 | <Button | 5 | <Button |
src/components/Modal/index.ts
1 | import './src/index.less'; | 1 | import './src/index.less'; |
2 | import { withInstall } from '../util'; | 2 | import { withInstall } from '../util'; |
3 | -import BasicModal from './src/BasicModal'; | 3 | +import BasicModal from './src/BasicModal.vue'; |
4 | 4 | ||
5 | withInstall(BasicModal); | 5 | withInstall(BasicModal); |
6 | 6 | ||
7 | export { BasicModal }; | 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 | export * from './src/types'; | 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 | import { Modal } from 'ant-design-vue'; | 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 | import { extendSlots } from '/@/utils/helper/tsxHelper'; | 6 | import { extendSlots } from '/@/utils/helper/tsxHelper'; |
6 | 7 | ||
7 | export default defineComponent({ | 8 | export default defineComponent({ |
8 | name: 'Modal', | 9 | name: 'Modal', |
9 | inheritAttrs: false, | 10 | inheritAttrs: false, |
10 | props: basicProps, | 11 | props: basicProps, |
11 | - setup(props, { attrs, slots }) { | 12 | + setup(props, { slots }) { |
12 | const { visible, draggable, destroyOnClose } = toRefs(props); | 13 | const { visible, draggable, destroyOnClose } = toRefs(props); |
13 | - | 14 | + const attrs = useAttrs(); |
14 | useModalDragMove({ | 15 | useModalDragMove({ |
15 | visible, | 16 | visible, |
16 | destroyOnClose, | 17 | destroyOnClose, |
@@ -18,7 +19,8 @@ export default defineComponent({ | @@ -18,7 +19,8 @@ export default defineComponent({ | ||
18 | }); | 19 | }); |
19 | 20 | ||
20 | return () => { | 21 | return () => { |
21 | - const propsData = { ...attrs, ...props } as any; | 22 | + const propsData = { ...unref(attrs), ...props } as Recordable; |
23 | + | ||
22 | return <Modal {...propsData}>{extendSlots(slots)}</Modal>; | 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,7 +4,7 @@ import type { | ||
4 | ModalProps, | 4 | ModalProps, |
5 | ReturnMethods, | 5 | ReturnMethods, |
6 | UseModalInnerReturnType, | 6 | UseModalInnerReturnType, |
7 | -} from './types'; | 7 | +} from '../types'; |
8 | 8 | ||
9 | import { | 9 | import { |
10 | ref, | 10 | ref, |
@@ -19,16 +19,18 @@ import { | @@ -19,16 +19,18 @@ import { | ||
19 | import { isProdMode } from '/@/utils/env'; | 19 | import { isProdMode } from '/@/utils/env'; |
20 | import { isFunction } from '/@/utils/is'; | 20 | import { isFunction } from '/@/utils/is'; |
21 | import { isEqual } from 'lodash-es'; | 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 | const dataTransferRef = reactive<any>({}); | 25 | const dataTransferRef = reactive<any>({}); |
24 | 26 | ||
27 | +const visibleData = reactive<{ [key: number]: boolean }>({}); | ||
28 | + | ||
25 | /** | 29 | /** |
26 | * @description: Applicable to independent modal and call outside | 30 | * @description: Applicable to independent modal and call outside |
27 | */ | 31 | */ |
28 | export function useModal(): UseModalReturnType { | 32 | export function useModal(): UseModalReturnType { |
29 | - if (!getCurrentInstance()) { | ||
30 | - throw new Error('Please put useModal function in the setup function!'); | ||
31 | - } | 33 | + isInSetup(); |
32 | const modalRef = ref<Nullable<ModalMethods>>(null); | 34 | const modalRef = ref<Nullable<ModalMethods>>(null); |
33 | const loadedRef = ref<Nullable<boolean>>(false); | 35 | const loadedRef = ref<Nullable<boolean>>(false); |
34 | const uidRef = ref<string>(''); | 36 | const uidRef = ref<string>(''); |
@@ -45,23 +47,29 @@ export function useModal(): UseModalReturnType { | @@ -45,23 +47,29 @@ export function useModal(): UseModalReturnType { | ||
45 | if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return; | 47 | if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return; |
46 | 48 | ||
47 | modalRef.value = modalMethod; | 49 | modalRef.value = modalMethod; |
50 | + modalMethod.emitVisible = (visible: boolean, uid: number) => { | ||
51 | + visibleData[uid] = visible; | ||
52 | + }; | ||
48 | } | 53 | } |
49 | 54 | ||
50 | const getInstance = () => { | 55 | const getInstance = () => { |
51 | const instance = unref(modalRef); | 56 | const instance = unref(modalRef); |
52 | if (!instance) { | 57 | if (!instance) { |
53 | - throw new Error('instance is undefined!'); | 58 | + error('useModal instance is undefined!'); |
54 | } | 59 | } |
55 | return instance; | 60 | return instance; |
56 | }; | 61 | }; |
57 | 62 | ||
58 | const methods: ReturnMethods = { | 63 | const methods: ReturnMethods = { |
59 | setModalProps: (props: Partial<ModalProps>): void => { | 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 | openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => { | 71 | openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => { |
64 | - getInstance().setModalProps({ | 72 | + getInstance()?.setModalProps({ |
65 | visible: visible, | 73 | visible: visible, |
66 | }); | 74 | }); |
67 | 75 | ||
@@ -83,20 +91,16 @@ export function useModal(): UseModalReturnType { | @@ -83,20 +91,16 @@ export function useModal(): UseModalReturnType { | ||
83 | 91 | ||
84 | export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { | 92 | export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { |
85 | const modalInstanceRef = ref<Nullable<ModalMethods>>(null); | 93 | const modalInstanceRef = ref<Nullable<ModalMethods>>(null); |
86 | - const currentInstall = getCurrentInstance(); | 94 | + const currentInstance = getCurrentInstance(); |
87 | const uidRef = ref<string>(''); | 95 | const uidRef = ref<string>(''); |
88 | 96 | ||
89 | - if (!currentInstall) { | ||
90 | - throw new Error('instance is undefined!'); | ||
91 | - } | ||
92 | - | ||
93 | // currentInstall.type.emits = [...currentInstall.type.emits, 'register']; | 97 | // currentInstall.type.emits = [...currentInstall.type.emits, 'register']; |
94 | // Object.assign(currentInstall.type.emits, ['register']); | 98 | // Object.assign(currentInstall.type.emits, ['register']); |
95 | 99 | ||
96 | const getInstance = () => { | 100 | const getInstance = () => { |
97 | const instance = unref(modalInstanceRef); | 101 | const instance = unref(modalInstanceRef); |
98 | if (!instance) { | 102 | if (!instance) { |
99 | - throw new Error('instance is undefined!'); | 103 | + error('useModalInner instance is undefined!'); |
100 | } | 104 | } |
101 | return instance; | 105 | return instance; |
102 | }; | 106 | }; |
@@ -108,7 +112,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { | @@ -108,7 +112,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { | ||
108 | }); | 112 | }); |
109 | uidRef.value = uuid; | 113 | uidRef.value = uuid; |
110 | modalInstanceRef.value = modalInstance; | 114 | modalInstanceRef.value = modalInstance; |
111 | - currentInstall.emit('register', modalInstance, uuid); | 115 | + currentInstance?.emit('register', modalInstance, uuid); |
112 | }; | 116 | }; |
113 | 117 | ||
114 | watchEffect(() => { | 118 | watchEffect(() => { |
@@ -124,19 +128,22 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { | @@ -124,19 +128,22 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { | ||
124 | register, | 128 | register, |
125 | { | 129 | { |
126 | changeLoading: (loading = true) => { | 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 | changeOkLoading: (loading = true) => { | 137 | changeOkLoading: (loading = true) => { |
131 | - getInstance().setModalProps({ confirmLoading: loading }); | 138 | + getInstance()?.setModalProps({ confirmLoading: loading }); |
132 | }, | 139 | }, |
133 | 140 | ||
134 | closeModal: () => { | 141 | closeModal: () => { |
135 | - getInstance().setModalProps({ visible: false }); | 142 | + getInstance()?.setModalProps({ visible: false }); |
136 | }, | 143 | }, |
137 | 144 | ||
138 | setModalProps: (props: Partial<ModalProps>) => { | 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,9 +21,12 @@ | ||
21 | width: 520px; | 21 | width: 520px; |
22 | padding-bottom: 0; | 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 | &-title { | 31 | &-title { |
29 | font-size: 16px; | 32 | font-size: 16px; |
@@ -35,46 +38,6 @@ | @@ -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 | .ant-modal-body { | 41 | .ant-modal-body { |
79 | padding: 0; | 42 | padding: 0; |
80 | } | 43 | } |
@@ -96,8 +59,6 @@ | @@ -96,8 +59,6 @@ | ||
96 | } | 59 | } |
97 | 60 | ||
98 | &-footer { | 61 | &-footer { |
99 | - // padding: 10px 26px 26px 16px; | ||
100 | - | ||
101 | button + button { | 62 | button + button { |
102 | margin-left: 10px; | 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 | 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 | +import { propTypes, VueNode } from '/@/utils/propTypes'; |
6 | +import type { ModalWrapperProps } from './types'; | ||
6 | const { t } = useI18n(); | 7 | const { t } = useI18n(); |
7 | 8 | ||
8 | export const modalProps = { | 9 | export const modalProps = { |
@@ -26,6 +27,7 @@ export const basicProps = Object.assign({}, modalProps, { | @@ -26,6 +27,7 @@ export const basicProps = Object.assign({}, modalProps, { | ||
26 | // Whether to setting wrapper | 27 | // Whether to setting wrapper |
27 | useWrapper: propTypes.bool.def(true), | 28 | useWrapper: propTypes.bool.def(true), |
28 | loading: propTypes.bool, | 29 | loading: propTypes.bool, |
30 | + loadingTip: propTypes.string, | ||
29 | /** | 31 | /** |
30 | * @description: Show close button | 32 | * @description: Show close button |
31 | */ | 33 | */ |
@@ -35,65 +37,44 @@ export const basicProps = Object.assign({}, modalProps, { | @@ -35,65 +37,44 @@ export const basicProps = Object.assign({}, modalProps, { | ||
35 | */ | 37 | */ |
36 | showOkBtn: propTypes.bool.def(true), | 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 | getContainer: Function as PropType<() => any>, | 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 | okButtonProps: Object as PropType<ButtonProps>, | 67 | okButtonProps: Object as PropType<ButtonProps>, |
81 | 68 | ||
82 | cancelButtonProps: Object as PropType<ButtonProps>, | 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 | width: [String, Number] as PropType<string | number>, | 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 | import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; | 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 | * @description: 弹窗对外暴露的方法 | 4 | * @description: 弹窗对外暴露的方法 |
5 | */ | 5 | */ |
6 | export interface ModalMethods { | 6 | export interface ModalMethods { |
7 | setModalProps: (props: Partial<ModalProps>) => void; | 7 | setModalProps: (props: Partial<ModalProps>) => void; |
8 | + emitVisible?: (visible: boolean, uid: number) => void; | ||
8 | } | 9 | } |
9 | 10 | ||
10 | export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void; | 11 | export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void; |
11 | 12 | ||
12 | export interface ReturnMethods extends ModalMethods { | 13 | export interface ReturnMethods extends ModalMethods { |
13 | openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void; | 14 | openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void; |
15 | + getVisible?: ComputedRef<boolean>; | ||
14 | } | 16 | } |
15 | 17 | ||
16 | export type UseModalReturnType = [RegisterFn, ReturnMethods]; | 18 | export type UseModalReturnType = [RegisterFn, ReturnMethods]; |
@@ -19,6 +21,7 @@ export interface ReturnInnerMethods extends ModalMethods { | @@ -19,6 +21,7 @@ export interface ReturnInnerMethods extends ModalMethods { | ||
19 | closeModal: () => void; | 21 | closeModal: () => void; |
20 | changeLoading: (loading: boolean) => void; | 22 | changeLoading: (loading: boolean) => void; |
21 | changeOkLoading: (loading: boolean) => void; | 23 | changeOkLoading: (loading: boolean) => void; |
24 | + getVisible?: ComputedRef<boolean>; | ||
22 | } | 25 | } |
23 | 26 | ||
24 | export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]; | 27 | export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]; |
@@ -38,6 +41,7 @@ export interface ModalProps { | @@ -38,6 +41,7 @@ export interface ModalProps { | ||
38 | useWrapper: boolean; | 41 | useWrapper: boolean; |
39 | 42 | ||
40 | loading: boolean; | 43 | loading: boolean; |
44 | + loadingTip?: string; | ||
41 | 45 | ||
42 | wrapperProps: Omit<ModalWrapperProps, 'loading'>; | 46 | wrapperProps: Omit<ModalWrapperProps, 'loading'>; |
43 | 47 | ||
@@ -193,4 +197,5 @@ export interface ModalWrapperProps { | @@ -193,4 +197,5 @@ export interface ModalWrapperProps { | ||
193 | minHeight: number; | 197 | minHeight: number; |
194 | visible: boolean; | 198 | visible: boolean; |
195 | fullScreen: boolean; | 199 | fullScreen: boolean; |
200 | + useWrapper: boolean; | ||
196 | } | 201 | } |
src/hooks/core/useAttrs.ts
1 | import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'; | 1 | import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'; |
2 | - | 2 | +import type { Ref } from 'vue'; |
3 | interface Params { | 3 | interface Params { |
4 | excludeListeners?: boolean; | 4 | excludeListeners?: boolean; |
5 | excludeKeys?: string[]; | 5 | excludeKeys?: string[]; |
@@ -12,7 +12,7 @@ export function entries<T>(obj: Hash<T>): [string, T][] { | @@ -12,7 +12,7 @@ export function entries<T>(obj: Hash<T>): [string, T][] { | ||
12 | return Object.keys(obj).map((key: string) => [key, obj[key]]); | 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 | const instance = getCurrentInstance(); | 16 | const instance = getCurrentInstance(); |
17 | if (!instance) return {}; | 17 | if (!instance) return {}; |
18 | 18 |
src/settings/projectSetting.ts
@@ -37,7 +37,7 @@ const setting: ProjectConfig = { | @@ -37,7 +37,7 @@ const setting: ProjectConfig = { | ||
37 | showLogo: true, | 37 | showLogo: true, |
38 | 38 | ||
39 | // Whether to show footer | 39 | // Whether to show footer |
40 | - showFooter: true, | 40 | + showFooter: false, |
41 | 41 | ||
42 | // locale setting | 42 | // locale setting |
43 | locale: { | 43 | locale: { |
src/utils/propTypes.ts
1 | import { CSSProperties, VNodeChild } from 'vue'; | 1 | import { CSSProperties, VNodeChild } from 'vue'; |
2 | import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; | 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 | type PropTypes = VueTypesInterface & { | 6 | type PropTypes = VueTypesInterface & { |
7 | readonly style: VueTypeValidableDef<CSSProperties>; | 7 | readonly style: VueTypeValidableDef<CSSProperties>; |
src/views/demo/comp/drawer/Drawer3.vue
1 | <template> | 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 | <p class="h-20" v-for="index in 40" :key="index">根据屏幕高度自适应</p> | 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 | </BasicDrawer> | 18 | </BasicDrawer> |
5 | </template> | 19 | </template> |
6 | <script lang="ts"> | 20 | <script lang="ts"> |
@@ -9,7 +23,13 @@ | @@ -9,7 +23,13 @@ | ||
9 | export default defineComponent({ | 23 | export default defineComponent({ |
10 | components: { BasicDrawer }, | 24 | components: { BasicDrawer }, |
11 | setup() { | 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 | </script> | 35 | </script> |
src/views/demo/comp/drawer/Drawer5.vue
1 | <template> | 1 | <template> |
2 | <BasicDrawer v-bind="$attrs" :isDetail="true" title="Drawer Title5"> | 2 | <BasicDrawer v-bind="$attrs" :isDetail="true" title="Drawer Title5"> |
3 | <p class="h-20">Content Message</p> | 3 | <p class="h-20">Content Message</p> |
4 | + <template #titleToolbar> toolbar </template> | ||
4 | </BasicDrawer> | 5 | </BasicDrawer> |
5 | </template> | 6 | </template> |
6 | <script lang="ts"> | 7 | <script lang="ts"> |
@@ -8,6 +9,5 @@ | @@ -8,6 +9,5 @@ | ||
8 | import { BasicDrawer } from '/@/components/Drawer'; | 9 | import { BasicDrawer } from '/@/components/Drawer'; |
9 | export default defineComponent({ | 10 | export default defineComponent({ |
10 | components: { BasicDrawer }, | 11 | components: { BasicDrawer }, |
11 | - setup() {}, | ||
12 | }); | 12 | }); |
13 | </script> | 13 | </script> |
src/views/demo/comp/drawer/index.vue
@@ -3,10 +3,10 @@ | @@ -3,10 +3,10 @@ | ||
3 | <Alert message="使用 useDrawer 进行抽屉操作" show-icon /> | 3 | <Alert message="使用 useDrawer 进行抽屉操作" show-icon /> |
4 | <a-button type="primary" class="my-4" @click="openDrawerLoading">打开Drawer</a-button> | 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 | <Alert message="自适应高度/显示footer" show-icon /> | 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 | <Alert | 11 | <Alert |
12 | message="内外数据交互,外部通过 transferModalData 发送,内部通过 receiveDrawerDataRef 接收。该数据具有响应式" | 12 | message="内外数据交互,外部通过 transferModalData 发送,内部通过 receiveDrawerDataRef 接收。该数据具有响应式" |
@@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
14 | /> | 14 | /> |
15 | <a-button type="primary" class="my-4" @click="send">打开Drawer并传递数据</a-button> | 15 | <a-button type="primary" class="my-4" @click="send">打开Drawer并传递数据</a-button> |
16 | <Alert message="详情页模式" show-icon /> | 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 | <Drawer1 @register="register1" /> | 18 | <Drawer1 @register="register1" /> |
19 | <Drawer2 @register="register2" /> | 19 | <Drawer2 @register="register2" /> |
20 | <Drawer3 @register="register3" /> | 20 | <Drawer3 @register="register3" /> |
src/views/demo/feat/tabs/index.vue
@@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
4 | <a-input placeholder="请输入" /> | 4 | <a-input placeholder="请输入" /> |
5 | </CollapseContainer> | 5 | </CollapseContainer> |
6 | 6 | ||
7 | - <CollapseContainer class="mt-4 px-4" title="标签页操作"> | 7 | + <CollapseContainer class="mt-4" title="标签页操作"> |
8 | <a-button class="mr-2" @click="closeAll">关闭所有</a-button> | 8 | <a-button class="mr-2" @click="closeAll">关闭所有</a-button> |
9 | <a-button class="mr-2" @click="closeLeft">关闭左侧</a-button> | 9 | <a-button class="mr-2" @click="closeLeft">关闭左侧</a-button> |
10 | <a-button class="mr-2" @click="closeRight">关闭右侧</a-button> | 10 | <a-button class="mr-2" @click="closeRight">关闭右侧</a-button> |