Commit ebf7c8aa53b7ed11c72734646d558a559e818473

Authored by vben
1 parent 73cee06d

perf(modal-drawer): replace the scrollbar assembly

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