Explorar el Código

perf(modal-drawer): replace the scrollbar assembly

vben hace 4 años
padre
commit
ebf7c8aa53
Se han modificado 41 ficheros con 1100 adiciones y 874 borrados
  1. 5 0
      CHANGELOG.zh_CN.md
  2. 9 2
      src/components/Container/src/LazyContainer.vue
  3. 12 8
      src/components/Container/src/collapse/CollapseContainer.vue
  4. 5 2
      src/components/Container/src/collapse/CollapseHeader.vue
  5. 1 1
      src/components/Description/src/index.tsx
  6. 1 1
      src/components/Description/src/props.ts
  7. 5 2
      src/components/Description/src/types.ts
  8. 1 1
      src/components/Description/src/useDescription.ts
  9. 1 1
      src/components/Drawer/index.ts
  10. 0 246
      src/components/Drawer/src/BasicDrawer.tsx
  11. 259 0
      src/components/Drawer/src/BasicDrawer.vue
  12. 85 0
      src/components/Drawer/src/components/DrawerFooter.vue
  13. 73 0
      src/components/Drawer/src/components/DrawerHeader.vue
  14. 0 66
      src/components/Drawer/src/index.less
  15. 3 2
      src/components/Drawer/src/props.ts
  16. 4 1
      src/components/Drawer/src/types.ts
  17. 39 17
      src/components/Drawer/src/useDrawer.ts
  18. 1 6
      src/components/Form/src/components/FormAction.vue
  19. 3 3
      src/components/Modal/index.ts
  20. 0 232
      src/components/Modal/src/BasicModal.tsx
  21. 184 0
      src/components/Modal/src/BasicModal.vue
  22. 0 161
      src/components/Modal/src/ModalWrapper.tsx
  23. 8 6
      src/components/Modal/src/components/Modal.tsx
  24. 98 0
      src/components/Modal/src/components/ModalClose.vue
  25. 39 0
      src/components/Modal/src/components/ModalFooter.vue
  26. 22 0
      src/components/Modal/src/components/ModalHeader.vue
  27. 152 0
      src/components/Modal/src/components/ModalWrapper.vue
  28. 26 19
      src/components/Modal/src/hooks/useModal.ts
  29. 0 0
      src/components/Modal/src/hooks/useModalContext.ts
  30. 0 0
      src/components/Modal/src/hooks/useModalDrag.ts
  31. 0 0
      src/components/Modal/src/hooks/useModalFullScreen.ts
  32. 5 44
      src/components/Modal/src/index.less
  33. 21 40
      src/components/Modal/src/props.ts
  34. 6 1
      src/components/Modal/src/types.ts
  35. 2 2
      src/hooks/core/useAttrs.ts
  36. 1 1
      src/settings/projectSetting.ts
  37. 1 1
      src/utils/propTypes.ts
  38. 22 2
      src/views/demo/comp/drawer/Drawer3.vue
  39. 1 1
      src/views/demo/comp/drawer/Drawer5.vue
  40. 4 4
      src/views/demo/comp/drawer/index.vue
  41. 1 1
      src/views/demo/feat/tabs/index.vue

+ 5 - 0
CHANGELOG.zh_CN.md

@@ -12,6 +12,10 @@
 - form: 新增远程下拉`ApiSelect`及示例
 - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
 
+### ⚡ Performance Improvements
+
+- 优化`modal`与`drawer`滚动条组件
+
 ### 🐛 Bug Fixes
 
 - 修复混合模式下滚动条丢失问题
@@ -21,6 +25,7 @@
 - 修复路由类型错误
 - 修复菜单分割时权限失效问题
 - 关闭多标签页时 iframe 提前加载
+- 修复`modal`与`drawer`已知问题
 
 ## 2.0.0-rc.14 (2020-12-15)
 

+ 9 - 2
src/components/Container/src/LazyContainer.vue

@@ -1,6 +1,6 @@
 <template>
   <transition-group
-    class="lazy-container"
+    :class="prefixCls"
     v-bind="$attrs"
     ref="elRef"
     :name="transitionName"
@@ -25,6 +25,7 @@
   import { useTimeoutFn } from '/@/hooks/core/useTimeout';
   import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
   import { propTypes } from '/@/utils/propTypes';
+  import { useDesign } from '/@/hooks/web/useDesign';
 
   interface State {
     isInit: boolean;
@@ -70,6 +71,8 @@
         intersectionObserverInstance: null,
       });
 
+      const { prefixCls } = useDesign('lazy-container');
+
       onMounted(() => {
         immediateInit();
         initIntersectionObserver();
@@ -129,13 +132,17 @@
       }
       return {
         elRef,
+        prefixCls,
         ...toRefs(state),
       };
     },
   });
 </script>
 <style lang="less">
-  .lazy-container {
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-lazy-container';
+
+  .@{prefix-cls} {
     width: 100%;
     height: 100%;
   }

+ 12 - 8
src/components/Container/src/collapse/CollapseContainer.vue

@@ -1,6 +1,6 @@
 <template>
-  <div class="collapse-container p-2">
-    <CollapseHeader v-bind="$props" :show="show" @expand="handleExpand">
+  <div :class="['p-2', prefixCls]">
+    <CollapseHeader v-bind="$props" :prefixCls="prefixCls" :show="show" @expand="handleExpand">
       <template #title>
         <slot name="title" />
       </template>
@@ -8,7 +8,7 @@
 
     <CollapseTransition :enable="canExpan">
       <Skeleton v-if="loading" />
-      <div class="collapse-container__body" v-else v-show="show">
+      <div :class="`${prefixCls}__body`" v-else v-show="show">
         <LazyContainer :timeout="lazyTime" v-if="lazy">
           <slot />
           <template #skeleton>
@@ -35,6 +35,7 @@
   // hook
   import { useTimeoutFn } from '/@/hooks/core/useTimeout';
   import { propTypes } from '/@/utils/propTypes';
+  import { useDesign } from '/@/hooks/web/useDesign';
 
   export default defineComponent({
     name: 'CollapseContainer',
@@ -64,6 +65,9 @@
     },
     setup(props) {
       const show = ref(true);
+
+      const { prefixCls } = useDesign('collapse-container');
+
       /**
        * @description: Handling development events
        */
@@ -77,20 +81,20 @@
       return {
         show,
         handleExpand,
+        prefixCls,
       };
     },
   });
 </script>
 <style lang="less">
-  .collapse-container {
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-collapse-container';
+
+  .@{prefix-cls} {
     background: #fff;
     border-radius: 2px;
     transition: all 0.3s ease-in-out;
 
-    &.no-shadow {
-      box-shadow: none;
-    }
-
     &__header {
       display: flex;
       height: 32px;

+ 5 - 2
src/components/Container/src/collapse/CollapseHeader.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="collapse-container__header">
+  <div :class="`${prefixCls}__header`">
     <BasicTitle :helpMessage="$attrs.helpMessage">
       <template v-if="$attrs.title">
         {{ $attrs.title }}
@@ -9,7 +9,7 @@
       </template>
     </BasicTitle>
 
-    <div class="collapse-container__action">
+    <div :class="`${prefixCls}__action`">
       <slot name="action" />
       <BasicArrow v-if="$attrs.canExpan" top :expand="$attrs.show" @click="$emit('expand')" />
     </div>
@@ -21,5 +21,8 @@
   export default defineComponent({
     inheritAttrs: false,
     components: { BasicArrow, BasicTitle },
+    props: {
+      prefixCls: String,
+    },
   });
 </script>

+ 1 - 1
src/components/Description/src/index.tsx

@@ -24,7 +24,7 @@ export default defineComponent({
     const getMergeProps = computed(() => {
       return {
         ...props,
-        ...(unref(propsRef) as any),
+        ...(unref(propsRef) as Recordable),
       } as DescOptions;
     });
 

+ 1 - 1
src/components/Description/src/props.ts

@@ -13,7 +13,7 @@ export default {
   bordered: propTypes.bool.def(true),
 
   column: {
-    type: [Number, Object] as PropType<number | any>,
+    type: [Number, Object] as PropType<number | Recordable>,
     default: () => {
       return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 };
     },

+ 5 - 2
src/components/Description/src/types.ts

@@ -15,7 +15,10 @@ export interface DescItem {
   span?: number;
   show?: (...arg: any) => boolean;
   // render
-  render?: (val: string, data: any) => VNode | undefined | JSX.Element | Element | string | number;
+  render?: (
+    val: string,
+    data: Recordable
+  ) => VNode | undefined | JSX.Element | Element | string | number;
 }
 
 export interface DescOptions extends DescriptionsProps {
@@ -30,7 +33,7 @@ export interface DescOptions extends DescriptionsProps {
    * 数据
    * @type object
    */
-  data: any;
+  data: Recordable;
   /**
    * Built-in CollapseContainer component configuration
    * @type CollapseContainerOptions

+ 1 - 1
src/components/Description/src/useDescription.ts

@@ -19,7 +19,7 @@ export function useDescription(props?: Partial<DescOptions>): UseDescReturnType
 
   const methods: DescInstance = {
     setDescProps: (descProps: Partial<DescOptions>): void => {
-      unref(descRef)!.setDescProps(descProps);
+      unref(descRef)?.setDescProps(descProps);
     },
   };
 

+ 1 - 1
src/components/Drawer/index.ts

@@ -1,6 +1,6 @@
 import { withInstall } from '../util';
 
-import BasicDrawer from './src/BasicDrawer';
+import BasicDrawer from './src/BasicDrawer.vue';
 
 export { BasicDrawer };
 export * from './src/types';

+ 0 - 246
src/components/Drawer/src/BasicDrawer.tsx

@@ -1,246 +0,0 @@
-import './index.less';
-
-import type { DrawerInstance, DrawerProps } from './types';
-import type { CSSProperties } from 'vue';
-
-import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw } from 'vue';
-import { Drawer, Row, Col, Button } from 'ant-design-vue';
-
-import { BasicTitle } from '/@/components/Basic';
-import { Loading } from '/@/components/Loading';
-import { LeftOutlined } from '@ant-design/icons-vue';
-
-import { useI18n } from '/@/hooks/web/useI18n';
-
-import { getSlot } from '/@/utils/helper/tsxHelper';
-import { isFunction, isNumber } from '/@/utils/is';
-import { deepMerge } from '/@/utils';
-import { tryTsxEmit } from '/@/utils/helper/vueHelper';
-
-import { basicProps } from './props';
-
-const prefixCls = 'basic-drawer';
-export default defineComponent({
-  inheritAttrs: false,
-  props: basicProps,
-  emits: ['visible-change', 'ok', 'close', 'register'],
-  setup(props, { slots, emit, attrs }) {
-    const scrollRef = ref<ElRef>(null);
-    const visibleRef = ref(false);
-    const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
-
-    const { t } = useI18n();
-
-    const getMergeProps = computed(
-      (): DrawerProps => {
-        return deepMerge(toRaw(props), unref(propsRef));
-      }
-    );
-
-    const getProps = computed(
-      (): DrawerProps => {
-        const opt = {
-          placement: 'right',
-          ...attrs,
-          ...unref(getMergeProps),
-          visible: unref(visibleRef),
-        };
-        opt.title = undefined;
-        const { isDetail, width, wrapClassName, getContainer } = opt;
-        if (isDetail) {
-          if (!width) {
-            opt.width = '100%';
-          }
-          const detailCls = `${prefixCls}__detail`;
-
-          opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
-
-          if (!getContainer) {
-            // TODO type error?
-            opt.getContainer = '.layout-content' as any;
-          }
-        }
-        return opt as DrawerProps;
-      }
-    );
-
-    const getBindValues = computed(
-      (): DrawerProps => {
-        return {
-          ...attrs,
-          ...unref(getProps),
-        };
-      }
-    );
-
-    // Custom implementation of the bottom button,
-    const getFooterHeight = computed(() => {
-      const { footerHeight, showFooter } = unref(getProps);
-
-      if (showFooter && footerHeight) {
-        return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
-      }
-      return `0px`;
-    });
-
-    const getScrollContentStyle = computed(
-      (): CSSProperties => {
-        const footerHeight = unref(getFooterHeight);
-        return {
-          position: 'relative',
-          height: `calc(100% - ${footerHeight})`,
-          overflow: 'auto',
-          padding: '16px',
-          paddingBottom: '30px',
-        };
-      }
-    );
-
-    const getLoading = computed(() => {
-      return !!unref(getProps)?.loading;
-    });
-
-    watchEffect(() => {
-      visibleRef.value = props.visible;
-    });
-
-    watch(
-      () => visibleRef.value,
-      (visible) => {
-        nextTick(() => {
-          emit('visible-change', visible);
-        });
-      },
-      {
-        immediate: false,
-      }
-    );
-
-    // Cancel event
-    async function onClose(e: ChangeEvent) {
-      const { closeFunc } = unref(getProps);
-      emit('close', e);
-      if (closeFunc && isFunction(closeFunc)) {
-        const res = await closeFunc();
-        visibleRef.value = !res;
-        return;
-      }
-      visibleRef.value = false;
-    }
-
-    function setDrawerProps(props: Partial<DrawerProps>): void {
-      // Keep the last setDrawerProps
-      propsRef.value = deepMerge(unref(propsRef) || {}, props);
-
-      if (Reflect.has(props, 'visible')) {
-        visibleRef.value = !!props.visible;
-      }
-    }
-
-    function renderFooter() {
-      if (slots?.footer) {
-        return getSlot(slots, 'footer');
-      }
-      const {
-        showCancelBtn,
-        cancelButtonProps,
-        cancelText,
-        showOkBtn,
-        okType,
-        okText,
-        okButtonProps,
-        confirmLoading,
-        showFooter,
-      } = unref(getProps);
-      if (!showFooter) {
-        return null;
-      }
-
-      return (
-        <div class={`${prefixCls}__footer`}>
-          {getSlot(slots, 'insertFooter')}
-          {showCancelBtn && (
-            <Button {...cancelButtonProps} onClick={onClose} class="mr-2">
-              {() => cancelText}
-            </Button>
-          )}
-          {getSlot(slots, 'centerFooter')}
-          {showOkBtn && (
-            <Button
-              type={okType}
-              onClick={() => {
-                emit('ok');
-              }}
-              {...okButtonProps}
-              loading={confirmLoading}
-            >
-              {() => okText}
-            </Button>
-          )}
-          {getSlot(slots, 'appendFooter')}
-        </div>
-      );
-    }
-
-    function renderHeader() {
-      if (slots?.title) {
-        return getSlot(slots, 'title');
-      }
-      const { title } = unref(getMergeProps);
-
-      if (!props.isDetail) {
-        return <BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>;
-      }
-      return (
-        <Row type="flex" align="middle" class={`${prefixCls}__detail-header`}>
-          {() => (
-            <>
-              {props.showDetailBack && (
-                <Button size="small" type="link" onClick={onClose}>
-                  {() => <LeftOutlined />}
-                </Button>
-              )}
-              {title && (
-                <Col style="flex:1" class={[`${prefixCls}__detail-title`, 'ellipsis', 'px-2']}>
-                  {() => title}
-                </Col>
-              )}
-              {getSlot(slots, 'titleToolbar')}
-            </>
-          )}
-        </Row>
-      );
-    }
-
-    const drawerInstance: DrawerInstance = {
-      setDrawerProps: setDrawerProps,
-    };
-
-    tryTsxEmit((instance) => {
-      emit('register', drawerInstance, instance.uid);
-    });
-
-    return () => {
-      return (
-        <Drawer class={prefixCls} onClose={onClose} {...unref(getBindValues)}>
-          {{
-            title: () => renderHeader(),
-            default: () => (
-              <>
-                <div ref={scrollRef} style={unref(getScrollContentStyle)}>
-                  <Loading
-                    absolute
-                    tip={t('component.drawer.loadingText')}
-                    loading={unref(getLoading)}
-                  />
-                  {getSlot(slots)}
-                </div>
-                {renderFooter()}
-              </>
-            ),
-          }}
-        </Drawer>
-      );
-    };
-  },
-});

+ 259 - 0
src/components/Drawer/src/BasicDrawer.vue

@@ -0,0 +1,259 @@
+<template>
+  <Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
+    <template #title v-if="!$slots.title">
+      <DrawerHeader
+        :title="getMergeProps.title"
+        :isDetail="isDetail"
+        :showDetailBack="showDetailBack"
+        @close="onClose"
+      >
+        <template #titleToolbar>
+          <slot name="titleToolbar" />
+        </template>
+      </DrawerHeader>
+    </template>
+
+    <ScrollContainer
+      :style="getScrollContentStyle"
+      v-loading="getLoading"
+      :loading-tip="loadingText || t('component.drawer.loadingText')"
+    >
+      <slot />
+    </ScrollContainer>
+    <DrawerFooter v-bind="getProps" @close="onClose" @ok="handleOk" :height="getFooterHeight">
+      <template #[item]="data" v-for="item in Object.keys($slots)">
+        <slot :name="item" v-bind="data" />
+      </template>
+    </DrawerFooter>
+  </Drawer>
+</template>
+<script lang="ts">
+  import type { DrawerInstance, DrawerProps } from './types';
+  import type { CSSProperties } from 'vue';
+
+  import {
+    defineComponent,
+    ref,
+    computed,
+    watchEffect,
+    watch,
+    unref,
+    nextTick,
+    toRaw,
+    getCurrentInstance,
+  } from 'vue';
+  import { Drawer } from 'ant-design-vue';
+
+  import { useI18n } from '/@/hooks/web/useI18n';
+
+  import { isFunction, isNumber } from '/@/utils/is';
+  import { deepMerge } from '/@/utils';
+  import DrawerFooter from './components/DrawerFooter.vue';
+  import DrawerHeader from './components/DrawerHeader.vue';
+  import { ScrollContainer } from '/@/components/Container';
+
+  import { basicProps } from './props';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useAttrs } from '/@/hooks/core/useAttrs';
+
+  export default defineComponent({
+    inheritAttrs: false,
+    components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
+    props: basicProps,
+    emits: ['visible-change', 'ok', 'close', 'register'],
+    setup(props, { emit }) {
+      const visibleRef = ref(false);
+      const attrs = useAttrs();
+      const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
+
+      const { t } = useI18n();
+      const { prefixVar, prefixCls } = useDesign('basic-drawer');
+
+      const drawerInstance: DrawerInstance = {
+        setDrawerProps: setDrawerProps,
+        emitVisible: undefined,
+      };
+
+      const instance = getCurrentInstance();
+
+      instance && emit('register', drawerInstance, instance.uid);
+
+      const getMergeProps = computed(
+        (): DrawerProps => {
+          return deepMerge(toRaw(props), unref(propsRef));
+        }
+      );
+
+      const getProps = computed(
+        (): DrawerProps => {
+          const opt = {
+            placement: 'right',
+            ...unref(attrs),
+            ...unref(getMergeProps),
+            visible: unref(visibleRef),
+          };
+          opt.title = undefined;
+          const { isDetail, width, wrapClassName, getContainer } = opt;
+          if (isDetail) {
+            if (!width) {
+              opt.width = '100%';
+            }
+            const detailCls = `${prefixCls}__detail`;
+            opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
+
+            if (!getContainer) {
+              // TODO type error?
+              opt.getContainer = `.${prefixVar}-layout-content` as any;
+            }
+          }
+          return opt as DrawerProps;
+        }
+      );
+
+      const getBindValues = computed(
+        (): DrawerProps => {
+          return {
+            ...attrs,
+            ...unref(getProps),
+          };
+        }
+      );
+
+      // Custom implementation of the bottom button,
+      const getFooterHeight = computed(() => {
+        const { footerHeight, showFooter } = unref(getProps);
+        if (showFooter && footerHeight) {
+          return isNumber(footerHeight)
+            ? `${footerHeight}px`
+            : `${footerHeight.replace('px', '')}px`;
+        }
+        return `0px`;
+      });
+
+      const getScrollContentStyle = computed(
+        (): CSSProperties => {
+          const footerHeight = unref(getFooterHeight);
+          return {
+            position: 'relative',
+            height: `calc(100% - ${footerHeight})`,
+          };
+        }
+      );
+
+      const getLoading = computed(() => {
+        return !!unref(getProps)?.loading;
+      });
+
+      watchEffect(() => {
+        visibleRef.value = props.visible;
+      });
+
+      watch(
+        () => visibleRef.value,
+        (visible) => {
+          nextTick(() => {
+            emit('visible-change', visible);
+            instance && drawerInstance.emitVisible?.(visible, instance.uid);
+          });
+        }
+      );
+
+      // Cancel event
+      async function onClose(e: Recordable) {
+        const { closeFunc } = unref(getProps);
+        emit('close', e);
+        if (closeFunc && isFunction(closeFunc)) {
+          const res = await closeFunc();
+          visibleRef.value = !res;
+          return;
+        }
+        visibleRef.value = false;
+      }
+
+      function setDrawerProps(props: Partial<DrawerProps>): void {
+        // Keep the last setDrawerProps
+        propsRef.value = deepMerge(unref(propsRef) || {}, props);
+
+        if (Reflect.has(props, 'visible')) {
+          visibleRef.value = !!props.visible;
+        }
+      }
+
+      function handleOk() {
+        emit('ok');
+      }
+
+      return {
+        onClose,
+        t,
+        prefixCls,
+        getMergeProps,
+        getScrollContentStyle,
+        getProps,
+        getLoading,
+        getBindValues,
+        getFooterHeight,
+        handleOk,
+      };
+    },
+  });
+</script>
+<style lang="less">
+  @import (reference) '../../../design/index.less';
+  @header-height: 60px;
+  @detail-header-height: 40px;
+  @prefix-cls: ~'@{namespace}-basic-drawer';
+  @prefix-cls-detail: ~'@{namespace}-basic-drawer__detail';
+
+  .@{prefix-cls} {
+    .ant-drawer-wrapper-body {
+      overflow: hidden;
+    }
+
+    .ant-drawer-close {
+      &:hover {
+        color: @error-color;
+      }
+    }
+
+    .ant-drawer-body {
+      height: calc(100% - @header-height);
+      padding: 0;
+      background-color: @background-color-dark;
+
+      .scrollbar__wrap {
+        padding: 16px !important;
+        margin-bottom: 0 !important;
+      }
+    }
+  }
+
+  .@{prefix-cls-detail} {
+    position: absolute;
+
+    .ant-drawer-header {
+      width: 100%;
+      height: @detail-header-height;
+      padding: 0;
+      border-top: 1px solid @border-color-base;
+      box-sizing: border-box;
+    }
+
+    .ant-drawer-title {
+      height: 100%;
+    }
+
+    .ant-drawer-close {
+      height: @detail-header-height;
+      line-height: @detail-header-height;
+    }
+
+    .scrollbar__wrap {
+      padding: 0 !important;
+    }
+
+    .ant-drawer-body {
+      height: calc(100% - @detail-header-height);
+    }
+  }
+</style>

+ 85 - 0
src/components/Drawer/src/components/DrawerFooter.vue

@@ -0,0 +1,85 @@
+<template>
+  <div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer">
+    <template v-if="!$slots.footer">
+      <slot name="insertFooter" />
+      <a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn">
+        {{ cancelText }}
+      </a-button>
+      <slot name="centerFooter" />
+      <a-button
+        :type="okType"
+        @click="handleOk"
+        v-bind="okButtonProps"
+        class="mr-2"
+        :loading="confirmLoading"
+        v-if="showOkBtn"
+      >
+        {{ okText }}
+      </a-button>
+      <slot name="appendFooter" />
+    </template>
+
+    <template v-else>
+      <slot name="footer" />
+    </template>
+  </div>
+</template>
+<script lang="ts">
+  import type { CSSProperties } from 'vue';
+  import { defineComponent, computed } from 'vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { footerProps } from '../props';
+  export default defineComponent({
+    name: 'BasicDrawerFooter',
+    props: {
+      ...footerProps,
+      height: {
+        type: String,
+        default: '60px',
+      },
+    },
+    emits: ['ok', 'close'],
+    setup(props, { emit }) {
+      const { prefixCls } = useDesign('basic-drawer-footer');
+
+      const getStyle = computed(
+        (): CSSProperties => {
+          const heightStr = `${props.height}`;
+          return {
+            height: heightStr,
+            lineHeight: heightStr,
+          };
+        }
+      );
+
+      function handleOk() {
+        emit('ok');
+      }
+
+      function handleClose() {
+        emit('close');
+      }
+      return { handleOk, prefixCls, handleClose, getStyle };
+    },
+  });
+</script>
+
+<style lang="less">
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-basic-drawer-footer';
+  @footer-height: 60px;
+  .@{prefix-cls} {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    padding: 0 12px 0 20px;
+    text-align: right;
+    background: #fff;
+    border-top: 1px solid @border-color-base;
+
+    > * {
+      margin-right: 8px;
+    }
+  }
+</style>

+ 73 - 0
src/components/Drawer/src/components/DrawerHeader.vue

@@ -0,0 +1,73 @@
+<template>
+  <BasicTitle v-if="!isDetail" :class="prefixCls">
+    <slot name="title" />
+    {{ !$slots.title ? title : '' }}
+  </BasicTitle>
+
+  <div :class="[prefixCls, `${prefixCls}--detail`]" v-else>
+    <span :class="`${prefixCls}__twrap`">
+      <span @click="handleClose" v-if="showDetailBack">
+        <ArrowLeftOutlined :class="`${prefixCls}__back`" />
+      </span>
+      <span v-if="title">{{ title }}</span>
+    </span>
+
+    <span :class="`${prefixCls}__toolbar`">
+      <slot name="titleToolbar" />
+    </span>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTitle } from '/@/components/Basic';
+  import { ArrowLeftOutlined } from '@ant-design/icons-vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { propTypes } from '/@/utils/propTypes';
+  export default defineComponent({
+    name: 'BasicDrawerHeader',
+    components: { BasicTitle, ArrowLeftOutlined },
+    props: {
+      isDetail: propTypes.bool,
+      showDetailBack: propTypes.bool,
+      title: propTypes.string,
+    },
+    setup(_, { emit }) {
+      const { prefixCls } = useDesign('basic-drawer-header');
+
+      function handleClose() {
+        emit('close');
+      }
+      return { prefixCls, handleClose };
+    },
+  });
+</script>
+
+<style lang="less">
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-basic-drawer-header';
+  @footer-height: 60px;
+  .@{prefix-cls} {
+    display: flex;
+    height: 100%;
+    align-items: center;
+
+    &__back {
+      padding: 0 12px;
+      cursor: pointer;
+
+      &:hover {
+        color: @primary-color;
+      }
+    }
+
+    &__twrap {
+      flex: 1;
+    }
+
+    &__toolbar {
+      padding-right: 50px;
+    }
+  }
+</style>

+ 0 - 66
src/components/Drawer/src/index.less

@@ -1,66 +0,0 @@
-@import (reference) '../../../design/index.less';
-@header-height: 40px;
-@footer-height: 60px;
-
-.basic-drawer {
-  .ant-drawer-wrapper-body {
-    overflow: hidden;
-  }
-
-  .ant-drawer-close {
-    &:hover {
-      color: @error-color;
-    }
-  }
-
-  .ant-drawer-body {
-    height: calc(100% - @header-height);
-    padding: 0;
-    background-color: @background-color-dark;
-
-    .scrollbar__wrap {
-      padding: 16px;
-    }
-  }
-
-  &__detail {
-    position: absolute;
-
-    &-header {
-      height: 100%;
-    }
-
-    .ant-drawer-header {
-      width: 100%;
-      height: @header-height;
-      padding: 0;
-      border-top: 1px solid @border-color-base;
-      box-sizing: border-box;
-    }
-
-    .ant-drawer-title {
-      height: 100%;
-    }
-
-    .ant-drawer-close {
-      height: @header-height;
-      line-height: @header-height;
-    }
-
-    .scrollbar__wrap {
-      padding: 0;
-    }
-  }
-
-  &__footer {
-    position: absolute;
-    bottom: 0;
-    width: 100%;
-    height: @footer-height;
-    padding: 0 26px;
-    line-height: @footer-height;
-    text-align: right;
-    background: #fff;
-    border-top: 1px solid @border-color-base;
-  }
-}

+ 3 - 2
src/components/Drawer/src/props.ts

@@ -10,13 +10,13 @@ export const footerProps = {
    * @description: Show close button
    */
   showCancelBtn: propTypes.bool.def(true),
-  cancelButtonProps: Object as PropType<any>,
+  cancelButtonProps: Object as PropType<Recordable>,
   cancelText: propTypes.string.def(t('component.drawer.cancelText')),
   /**
    * @description: Show confirmation button
    */
   showOkBtn: propTypes.bool.def(true),
-  okButtonProps: propTypes.any,
+  okButtonProps: Object as PropType<Recordable>,
   okText: propTypes.string.def(t('component.drawer.okText')),
   okType: propTypes.string.def('primary'),
   showFooter: propTypes.bool,
@@ -28,6 +28,7 @@ export const footerProps = {
 export const basicProps = {
   isDetail: propTypes.bool,
   title: propTypes.string.def(''),
+  loadingText: propTypes.string,
   showDetailBack: propTypes.bool.def(true),
   visible: propTypes.bool,
   loading: propTypes.bool,

+ 4 - 1
src/components/Drawer/src/types.ts

@@ -1,13 +1,15 @@
 import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
-import type { CSSProperties, VNodeChild } from 'vue';
+import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
 import type { ScrollContainerOptions } from '/@/components/Container/index';
 
 export interface DrawerInstance {
   setDrawerProps: (props: Partial<DrawerProps> | boolean) => void;
+  emitVisible?: (visible: boolean, uid: number) => void;
 }
 
 export interface ReturnMethods extends DrawerInstance {
   openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void;
+  getVisible?: ComputedRef<boolean>;
 }
 
 export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void;
@@ -16,6 +18,7 @@ export interface ReturnInnerMethods extends DrawerInstance {
   closeDrawer: () => void;
   changeLoading: (loading: boolean) => void;
   changeOkLoading: (loading: boolean) => void;
+  getVisible?: ComputedRef<boolean>;
 }
 
 export type UseDrawerReturnType = [RegisterFn, ReturnMethods];

+ 39 - 17
src/components/Drawer/src/useDrawer.ts

@@ -6,22 +6,32 @@ import type {
   UseDrawerInnerReturnType,
 } from './types';
 
-import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw } from 'vue';
+import {
+  ref,
+  getCurrentInstance,
+  unref,
+  reactive,
+  watchEffect,
+  nextTick,
+  toRaw,
+  computed,
+} from 'vue';
 
 import { isProdMode } from '/@/utils/env';
 import { isFunction } from '/@/utils/is';
-import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
+import { tryOnUnmounted, isInSetup } from '/@/utils/helper/vueHelper';
 import { isEqual } from 'lodash-es';
+import { error } from '/@/utils/log';
 
 const dataTransferRef = reactive<any>({});
 
+const visibleData = reactive<{ [key: number]: boolean }>({});
+
 /**
  * @description: Applicable to separate drawer and call outside
  */
 export function useDrawer(): UseDrawerReturnType {
-  if (!getCurrentInstance()) {
-    throw new Error('Please put useDrawer function in the setup function!');
-  }
+  isInSetup();
 
   const drawerRef = ref<DrawerInstance | null>(null);
   const loadedRef = ref<Nullable<boolean>>(false);
@@ -41,23 +51,31 @@ export function useDrawer(): UseDrawerReturnType {
     uidRef.value = uuid;
     drawerRef.value = drawerInstance;
     loadedRef.value = true;
+
+    drawerInstance.emitVisible = (visible: boolean, uid: number) => {
+      visibleData[uid] = visible;
+    };
   }
 
   const getInstance = () => {
     const instance = unref(drawerRef);
     if (!instance) {
-      throw new Error('instance is undefined!');
+      error('useDrawer instance is undefined!');
     }
     return instance;
   };
 
   const methods: ReturnMethods = {
     setDrawerProps: (props: Partial<DrawerProps>): void => {
-      getInstance().setDrawerProps(props);
+      getInstance()?.setDrawerProps(props);
     },
 
+    getVisible: computed((): boolean => {
+      return visibleData[~~unref(uidRef)];
+    }),
+
     openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
-      getInstance().setDrawerProps({
+      getInstance()?.setDrawerProps({
         visible: visible,
       });
       if (!data) return;
@@ -79,17 +97,18 @@ export function useDrawer(): UseDrawerReturnType {
 
 export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
   const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
-  const currentInstall = getCurrentInstance();
+  const currentInstance = getCurrentInstance();
   const uidRef = ref<string>('');
 
-  if (!currentInstall) {
-    throw new Error('useDrawerInner instance is undefined!');
+  if (!currentInstance) {
+    error('useDrawerInner instance is undefined!');
   }
 
   const getInstance = () => {
     const instance = unref(drawerInstanceRef);
     if (!instance) {
-      throw new Error('useDrawerInner instance is undefined!');
+      error('useDrawerInner instance is undefined!');
+      return;
     }
     return instance;
   };
@@ -102,7 +121,7 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
 
     uidRef.value = uuid;
     drawerInstanceRef.value = modalInstance;
-    currentInstall.emit('register', modalInstance, uuid);
+    currentInstance?.emit('register', modalInstance, uuid);
   };
 
   watchEffect(() => {
@@ -118,19 +137,22 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
     register,
     {
       changeLoading: (loading = true) => {
-        getInstance().setDrawerProps({ loading });
+        getInstance()?.setDrawerProps({ loading });
       },
 
       changeOkLoading: (loading = true) => {
-        getInstance().setDrawerProps({ confirmLoading: loading });
+        getInstance()?.setDrawerProps({ confirmLoading: loading });
       },
+      getVisible: computed((): boolean => {
+        return visibleData[~~unref(uidRef)];
+      }),
 
       closeDrawer: () => {
-        getInstance().setDrawerProps({ visible: false });
+        getInstance()?.setDrawerProps({ visible: false });
       },
 
       setDrawerProps: (props: Partial<DrawerProps>) => {
-        getInstance().setDrawerProps(props);
+        getInstance()?.setDrawerProps(props);
       },
     },
   ];

+ 1 - 6
src/components/Form/src/components/FormAction.vue

@@ -1,10 +1,5 @@
 <template>
-  <a-col
-    v-bind="actionColOpt"
-    class="mb-2"
-    :style="{ textAlign: 'right' }"
-    v-if="showActionButtonGroup"
-  >
+  <a-col v-bind="actionColOpt" :style="{ textAlign: 'right' }" v-if="showActionButtonGroup">
     <FormItem>
       <slot name="resetBefore" />
       <Button

+ 3 - 3
src/components/Modal/index.ts

@@ -1,10 +1,10 @@
 import './src/index.less';
 import { withInstall } from '../util';
-import BasicModal from './src/BasicModal';
+import BasicModal from './src/BasicModal.vue';
 
 withInstall(BasicModal);
 
 export { BasicModal };
-export { useModalContext } from './src/useModalContext';
-export { useModal, useModalInner } from './src/useModal';
+export { useModalContext } from './src/hooks/useModalContext';
+export { useModal, useModalInner } from './src/hooks/useModal';
 export * from './src/types';

+ 0 - 232
src/components/Modal/src/BasicModal.tsx

@@ -1,232 +0,0 @@
-import type { ModalProps, ModalMethods } from './types';
-
-import { defineComponent, computed, ref, watch, unref, watchEffect, toRef } from 'vue';
-
-import Modal from './Modal';
-import { Button } from '/@/components/Button';
-import ModalWrapper from './ModalWrapper';
-import { BasicTitle } from '/@/components/Basic';
-import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
-
-import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper';
-import { isFunction } from '/@/utils/is';
-import { deepMerge } from '/@/utils';
-import { tryTsxEmit } from '/@/utils/helper/vueHelper';
-
-import { basicProps } from './props';
-import { useFullScreen } from './useFullScreen';
-export default defineComponent({
-  name: 'BasicModal',
-  props: basicProps,
-  emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register'],
-  setup(props, { slots, emit, attrs }) {
-    const visibleRef = ref(false);
-    const propsRef = ref<Partial<ModalProps> | null>(null);
-    const modalWrapperRef = ref<ComponentRef>(null);
-    // modal   Bottom and top height
-    const extHeightRef = ref(0);
-    // Unexpanded height of the popup
-
-    // Custom title component: get title
-    const getMergeProps = computed(
-      (): ModalProps => {
-        return {
-          ...props,
-          ...(unref(propsRef) as any),
-        };
-      }
-    );
-
-    const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
-      modalWrapperRef,
-      extHeightRef,
-      wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
-    });
-
-    // modal component does not need title
-    const getProps = computed(
-      (): ModalProps => {
-        const opt = {
-          ...unref(getMergeProps),
-          visible: unref(visibleRef),
-          title: undefined,
-        };
-
-        return {
-          ...opt,
-          wrapClassName: unref(getWrapClassName),
-        };
-      }
-    );
-
-    const getModalBindValue = computed((): any => {
-      return { ...attrs, ...unref(getProps) };
-    });
-
-    watchEffect(() => {
-      visibleRef.value = !!props.visible;
-    });
-
-    watch(
-      () => unref(visibleRef),
-      (v) => {
-        emit('visible-change', v);
-      },
-      {
-        immediate: false,
-      }
-    );
-
-    /**
-     * @description: 渲染标题
-     */
-    function renderTitle() {
-      const { helpMessage } = unref(getProps);
-      const { title } = unref(getMergeProps);
-      return (
-        <BasicTitle helpMessage={helpMessage}>
-          {() => (slots.title ? getSlot(slots, 'title') : title)}
-        </BasicTitle>
-      );
-    }
-
-    // 取消事件
-    async function handleCancel(e: Event) {
-      e?.stopPropagation();
-
-      if (props.closeFunc && isFunction(props.closeFunc)) {
-        const isClose: boolean = await props.closeFunc();
-        visibleRef.value = !isClose;
-        return;
-      }
-
-      visibleRef.value = false;
-      emit('cancel');
-    }
-
-    /**
-     * @description: 设置modal参数
-     */
-    function setModalProps(props: Partial<ModalProps>): void {
-      // Keep the last setModalProps
-      propsRef.value = deepMerge(unref(propsRef) || {}, props);
-      if (!Reflect.has(props, 'visible')) return;
-      visibleRef.value = !!props.visible;
-    }
-
-    function renderContent() {
-      type OmitWrapperType = Omit<
-        ModalProps,
-        'fullScreen' | 'modalFooterHeight' | 'visible' | 'loading'
-      >;
-      const { useWrapper, loading, wrapperProps } = unref(getProps);
-      if (!useWrapper) return getSlot(slots);
-
-      const showFooter = props.footer !== undefined && !props.footer ? 0 : undefined;
-      return (
-        <ModalWrapper
-          footerOffset={props.wrapperFooterOffset}
-          fullScreen={unref(fullScreenRef)}
-          ref={modalWrapperRef}
-          loading={loading}
-          visible={unref(visibleRef)}
-          modalFooterHeight={showFooter}
-          {...((wrapperProps as unknown) as OmitWrapperType)}
-          onGetExtHeight={(height: number) => {
-            extHeightRef.value = height;
-          }}
-          onHeightChange={(height: string) => {
-            emit('height-change', height);
-          }}
-        >
-          {() => getSlot(slots)}
-        </ModalWrapper>
-      );
-    }
-
-    // 底部按钮自定义实现,
-    function renderFooter() {
-      const {
-        showCancelBtn,
-        cancelButtonProps,
-        cancelText,
-        showOkBtn,
-        okType,
-        okText,
-        okButtonProps,
-        confirmLoading,
-      } = unref(getProps);
-
-      return (
-        <>
-          {getSlot(slots, 'insertFooter')}
-          {showCancelBtn && (
-            <Button {...cancelButtonProps} onClick={handleCancel}>
-              {() => cancelText}
-            </Button>
-          )}
-          {getSlot(slots, 'centerdFooter')}
-          {showOkBtn && (
-            <Button
-              type={okType as any}
-              loading={confirmLoading}
-              onClick={() => {
-                emit('ok');
-              }}
-              {...okButtonProps}
-            >
-              {() => okText}
-            </Button>
-          )}
-          {getSlot(slots, 'appendFooter')}
-        </>
-      );
-    }
-
-    /**
-     * @description: 关闭按钮
-     */
-    function renderClose() {
-      const { canFullscreen } = unref(getProps);
-
-      const fullScreen = unref(fullScreenRef) ? (
-        <FullscreenExitOutlined role="full" onClick={handleFullScreen} />
-      ) : (
-        <FullscreenOutlined role="close" onClick={handleFullScreen} />
-      );
-
-      const cls = [
-        'custom-close-icon',
-        {
-          'can-full': canFullscreen,
-        },
-      ];
-
-      return (
-        <div class={cls}>
-          {canFullscreen && fullScreen}
-          <CloseOutlined onClick={handleCancel} />
-        </div>
-      );
-    }
-
-    const modalMethods: ModalMethods = {
-      setModalProps,
-    };
-
-    tryTsxEmit((instance) => {
-      emit('register', modalMethods, instance.uid);
-    });
-    return () => (
-      <Modal onCancel={handleCancel} {...unref(getModalBindValue)}>
-        {{
-          footer: () => renderFooter(),
-          closeIcon: () => renderClose(),
-          title: () => renderTitle(),
-          ...extendSlots(slots, ['default']),
-          default: () => renderContent(),
-        }}
-      </Modal>
-    );
-  },
-});

+ 184 - 0
src/components/Modal/src/BasicModal.vue

@@ -0,0 +1,184 @@
+<template>
+  <Modal @cancel="handleCancel" v-bind="getBindValue">
+    <template #closeIcon v-if="!$slots.closeIcon">
+      <ModalClose
+        :canFullscreen="getProps.canFullscreen"
+        :fullScreen="fullScreenRef"
+        @cancel="handleCancel"
+        @fullscreen="handleFullScreen"
+      />
+    </template>
+
+    <template #title v-if="!$slots.title">
+      <ModalHeader :helpMessage="getProps.helpMessage" :title="getMergeProps.title" />
+    </template>
+
+    <template #footer v-if="!$slots.footer">
+      <ModalFooter v-bind="getProps" @ok="handleOk" @cancel="handleCancel" />
+    </template>
+    <ModalWrapper
+      :useWrapper="getProps.useWrapper"
+      :footerOffset="wrapperFooterOffset"
+      :fullScreen="fullScreenRef"
+      ref="modalWrapperRef"
+      :loading="getProps.loading"
+      :visible="visibleRef"
+      :modalFooterHeight="footer !== undefined && !footer ? 0 : undefined"
+      v-bind="omit(getProps.wrapperProps, 'visible')"
+      @ext-height="handleExtHeight"
+      @height-change="handleHeightChange"
+    >
+      <slot />
+    </ModalWrapper>
+  </Modal>
+</template>
+<script lang="ts">
+  import type { ModalProps, ModalMethods } from './types';
+
+  import {
+    defineComponent,
+    computed,
+    ref,
+    watch,
+    unref,
+    watchEffect,
+    toRef,
+    getCurrentInstance,
+  } from 'vue';
+
+  import Modal from './components/Modal';
+  import ModalWrapper from './components/ModalWrapper.vue';
+  import ModalClose from './components/ModalClose.vue';
+  import ModalFooter from './components/ModalFooter.vue';
+  import ModalHeader from './components/ModalHeader.vue';
+
+  import { isFunction } from '/@/utils/is';
+  import { deepMerge } from '/@/utils';
+
+  import { basicProps } from './props';
+  import { useFullScreen } from './hooks/useModalFullScreen';
+  import { omit } from 'lodash-es';
+  export default defineComponent({
+    name: 'BasicModal',
+    components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader },
+    props: basicProps,
+    emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register'],
+    setup(props, { emit, attrs }) {
+      const visibleRef = ref(false);
+      const propsRef = ref<Partial<ModalProps> | null>(null);
+      const modalWrapperRef = ref<ComponentRef>(null);
+      // modal   Bottom and top height
+      const extHeightRef = ref(0);
+      const modalMethods: ModalMethods = {
+        setModalProps,
+        emitVisible: undefined,
+      };
+      const instance = getCurrentInstance();
+      if (instance) {
+        emit('register', modalMethods, instance.uid);
+      }
+
+      // Custom title component: get title
+      const getMergeProps = computed(
+        (): ModalProps => {
+          return {
+            ...props,
+            ...(unref(propsRef) as any),
+          };
+        }
+      );
+
+      const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
+        modalWrapperRef,
+        extHeightRef,
+        wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
+      });
+
+      // modal component does not need title
+      const getProps = computed(
+        (): ModalProps => {
+          const opt = {
+            ...unref(getMergeProps),
+            visible: unref(visibleRef),
+            title: undefined,
+          };
+
+          return {
+            ...opt,
+            wrapClassName: unref(getWrapClassName),
+          };
+        }
+      );
+
+      const getBindValue = computed((): any => {
+        return { ...attrs, ...unref(getProps) };
+      });
+
+      watchEffect(() => {
+        visibleRef.value = !!props.visible;
+      });
+
+      watch(
+        () => unref(visibleRef),
+        (v) => {
+          emit('visible-change', v);
+          instance && modalMethods.emitVisible?.(v, instance.uid);
+        },
+        {
+          immediate: false,
+        }
+      );
+
+      // 取消事件
+      async function handleCancel(e: Event) {
+        e?.stopPropagation();
+
+        if (props.closeFunc && isFunction(props.closeFunc)) {
+          const isClose: boolean = await props.closeFunc();
+          visibleRef.value = !isClose;
+          return;
+        }
+
+        visibleRef.value = false;
+        emit('cancel');
+      }
+
+      /**
+       * @description: 设置modal参数
+       */
+      function setModalProps(props: Partial<ModalProps>): void {
+        // Keep the last setModalProps
+        propsRef.value = deepMerge(unref(propsRef) || {}, props);
+        if (!Reflect.has(props, 'visible')) return;
+        visibleRef.value = !!props.visible;
+      }
+
+      function handleOk() {
+        emit('ok');
+      }
+
+      function handleHeightChange(height: string) {
+        emit('height-change', height);
+      }
+
+      function handleExtHeight(height: number) {
+        extHeightRef.value = height;
+      }
+
+      return {
+        handleCancel,
+        getBindValue,
+        getProps,
+        handleFullScreen,
+        fullScreenRef,
+        getMergeProps,
+        handleOk,
+        visibleRef,
+        omit,
+        modalWrapperRef,
+        handleExtHeight,
+        handleHeightChange,
+      };
+    },
+  });
+</script>

+ 0 - 161
src/components/Modal/src/ModalWrapper.tsx

@@ -1,161 +0,0 @@
-import type { ModalWrapperProps } from './types';
-import type { CSSProperties } from 'vue';
-
-import {
-  defineComponent,
-  computed,
-  ref,
-  watchEffect,
-  unref,
-  watch,
-  onMounted,
-  nextTick,
-  onUnmounted,
-} from 'vue';
-import { Spin } from 'ant-design-vue';
-
-import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
-
-import { getSlot } from '/@/utils/helper/tsxHelper';
-import { useElResize } from '/@/hooks/event/useElResize';
-import { propTypes } from '/@/utils/propTypes';
-import { createModalContext } from './useModalContext';
-
-export default defineComponent({
-  name: 'ModalWrapper',
-  props: {
-    loading: propTypes.bool,
-    modalHeaderHeight: propTypes.number.def(50),
-    modalFooterHeight: propTypes.number.def(54),
-    minHeight: propTypes.number.def(200),
-    footerOffset: propTypes.number.def(0),
-    visible: propTypes.bool,
-    fullScreen: propTypes.bool,
-  },
-  emits: ['heightChange', 'getExtHeight'],
-  setup(props: ModalWrapperProps, { slots, emit }) {
-    const wrapperRef = ref<ElRef>(null);
-    const spinRef = ref<ComponentRef>(null);
-    const realHeightRef = ref(0);
-
-    let stopElResizeFn: Fn = () => {};
-
-    useWindowSizeFn(setModalHeight);
-
-    createModalContext({
-      redoModalHeight: setModalHeight,
-    });
-
-    const wrapStyle = computed(
-      (): CSSProperties => {
-        return {
-          minHeight: `${props.minHeight}px`,
-          height: `${unref(realHeightRef)}px`,
-          overflow: 'auto',
-        };
-      }
-    );
-
-    watchEffect(() => {
-      setModalHeight();
-    });
-
-    watch(
-      () => props.fullScreen,
-      (v) => {
-        !v && setModalHeight();
-      }
-    );
-
-    onMounted(() => {
-      const { modalHeaderHeight, modalFooterHeight } = props;
-      emit('getExtHeight', modalHeaderHeight + modalFooterHeight);
-      listenElResize();
-    });
-
-    onUnmounted(() => {
-      stopElResizeFn && stopElResizeFn();
-    });
-
-    async function setModalHeight() {
-      // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
-      // 加上这个,就必须在使用的时候传递父级的visible
-      if (!props.visible) return;
-      const wrapperRefDom = unref(wrapperRef);
-      if (!wrapperRefDom) return;
-      const bodyDom = wrapperRefDom.parentElement;
-      if (!bodyDom) return;
-      bodyDom.style.padding = '0';
-      await nextTick();
-
-      try {
-        const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
-        if (!modalDom) return;
-
-        const modalRect = getComputedStyle(modalDom).top;
-        const modalTop = Number.parseInt(modalRect);
-        let maxHeight =
-          window.innerHeight -
-          modalTop * 2 +
-          (props.footerOffset! || 0) -
-          props.modalFooterHeight -
-          props.modalHeaderHeight;
-
-        // 距离顶部过进会出现滚动条
-        if (modalTop < 40) {
-          maxHeight -= 26;
-        }
-        await nextTick();
-        const spinEl = unref(spinRef);
-
-        if (!spinEl) return;
-
-        const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement;
-        if (!spinContainerEl) return;
-
-        const realHeight = spinContainerEl.scrollHeight;
-
-        if (props.fullScreen) {
-          realHeightRef.value =
-            window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight;
-        } else {
-          realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30;
-        }
-        emit('heightChange', unref(realHeightRef));
-
-        nextTick(() => {
-          const el = spinEl.$el;
-          if (el) {
-            el.style.height = `${unref(realHeightRef)}px`;
-          }
-        });
-      } catch (error) {
-        console.log(error);
-      }
-    }
-
-    function listenElResize() {
-      const wrapper = unref(wrapperRef);
-      if (!wrapper) return;
-
-      const container = wrapper.querySelector('.ant-spin-container');
-      if (!container) return;
-
-      const [start, stop] = useElResize(container, () => {
-        setModalHeight();
-      });
-      stopElResizeFn = stop;
-      start();
-    }
-
-    return () => {
-      return (
-        <div ref={wrapperRef} style={unref(wrapStyle)}>
-          <Spin ref={spinRef} spinning={props.loading}>
-            {() => getSlot(slots)}
-          </Spin>
-        </div>
-      );
-    };
-  },
-});

+ 8 - 6
src/components/Modal/src/Modal.tsx → src/components/Modal/src/components/Modal.tsx

@@ -1,16 +1,17 @@
 import { Modal } from 'ant-design-vue';
-import { defineComponent, toRefs } from 'vue';
-import { basicProps } from './props';
-import { useModalDragMove } from './useModalDrag';
+import { defineComponent, toRefs, unref } from 'vue';
+import { basicProps } from '../props';
+import { useModalDragMove } from '../hooks/useModalDrag';
+import { useAttrs } from '/@/hooks/core/useAttrs';
 import { extendSlots } from '/@/utils/helper/tsxHelper';
 
 export default defineComponent({
   name: 'Modal',
   inheritAttrs: false,
   props: basicProps,
-  setup(props, { attrs, slots }) {
+  setup(props, { slots }) {
     const { visible, draggable, destroyOnClose } = toRefs(props);
-
+    const attrs = useAttrs();
     useModalDragMove({
       visible,
       destroyOnClose,
@@ -18,7 +19,8 @@ export default defineComponent({
     });
 
     return () => {
-      const propsData = { ...attrs, ...props } as any;
+      const propsData = { ...unref(attrs), ...props } as Recordable;
+
       return <Modal {...propsData}>{extendSlots(slots)}</Modal>;
     };
   },

+ 98 - 0
src/components/Modal/src/components/ModalClose.vue

@@ -0,0 +1,98 @@
+<template>
+  <div :class="getClass">
+    <template v-if="canFullscreen">
+      <FullscreenExitOutlined role="full" @click="handleFullScreen" v-if="fullScreen" />
+
+      <FullscreenOutlined role="close" @click="handleFullScreen" v-else />
+    </template>
+    <CloseOutlined @click="handleCancel" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, computed } from 'vue';
+  import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { propTypes } from '/@/utils/propTypes';
+
+  export default defineComponent({
+    name: 'ModalClose',
+    components: { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined },
+    props: {
+      canFullscreen: propTypes.bool.def(true),
+      fullScreen: propTypes.bool,
+    },
+    emits: ['cancel', 'fullscreen'],
+    setup(props, { emit }) {
+      const { prefixCls } = useDesign('basic-modal-close');
+
+      const getClass = computed(() => {
+        return [
+          prefixCls,
+          `${prefixCls}--custom`,
+          {
+            [`${prefixCls}--can-full`]: props.canFullscreen,
+          },
+        ];
+      });
+
+      function handleCancel() {
+        emit('cancel');
+      }
+      function handleFullScreen(e: Event) {
+        e?.stopPropagation();
+        e?.preventDefault();
+        emit('fullscreen');
+      }
+
+      return {
+        getClass,
+        prefixCls,
+        handleCancel,
+        handleFullScreen,
+      };
+    },
+  });
+</script>
+<style lang="less">
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-basic-modal-close';
+  .@{prefix-cls} {
+    display: flex;
+    height: 95%;
+    align-items: center;
+
+    > span {
+      margin-left: 48px;
+      font-size: 16px;
+    }
+
+    &--can-full {
+      > span {
+        margin-left: 12px;
+      }
+    }
+
+    &:not(&--can-full) {
+      > span:nth-child(1) {
+        &:hover {
+          font-weight: 700;
+        }
+      }
+    }
+
+    & span:nth-child(1) {
+      display: inline-block;
+      padding: 10px;
+
+      &:hover {
+        color: @primary-color;
+      }
+    }
+
+    & span:nth-child(2) {
+      &:hover {
+        color: @error-color;
+      }
+    }
+  }
+</style>

+ 39 - 0
src/components/Modal/src/components/ModalFooter.vue

@@ -0,0 +1,39 @@
+<template>
+  <div>
+    <slot name="insertFooter" />
+    <a-button v-bind="cancelButtonProps" @click="handleCancel" v-if="showCancelBtn">
+      {{ cancelText }}
+    </a-button>
+    <slot name="centerFooter" />
+    <a-button
+      :type="okType"
+      @click="handleOk"
+      :loading="confirmLoading"
+      v-bind="okButtonProps"
+      v-if="showOkBtn"
+    >
+      {{ okText }}
+    </a-button>
+    <slot name="appendFooter" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+
+  import { basicProps } from '../props';
+  export default defineComponent({
+    name: 'BasicModalFooter',
+    props: basicProps,
+    emits: ['ok', 'cancel'],
+    setup(_, { emit }) {
+      function handleOk() {
+        emit('ok');
+      }
+
+      function handleCancel() {
+        emit('cancel');
+      }
+      return { handleOk, handleCancel };
+    },
+  });
+</script>

+ 22 - 0
src/components/Modal/src/components/ModalHeader.vue

@@ -0,0 +1,22 @@
+<template>
+  <BasicTitle :helpMessage="helpMessage">
+    {{ title }}
+  </BasicTitle>
+</template>
+<script lang="ts">
+  import type { PropType } from 'vue';
+  import { defineComponent } from 'vue';
+  import { BasicTitle } from '/@/components/Basic';
+
+  import { propTypes } from '/@/utils/propTypes';
+  export default defineComponent({
+    name: 'BasicModalHeader',
+    components: { BasicTitle },
+    props: {
+      helpMessage: {
+        type: [String, Array] as PropType<string | string[]>,
+      },
+      title: propTypes.string,
+    },
+  });
+</script>

+ 152 - 0
src/components/Modal/src/components/ModalWrapper.vue

@@ -0,0 +1,152 @@
+<template>
+  <ScrollContainer ref="wrapperRef" :style="wrapStyle">
+    <div ref="spinRef" :style="spinStyle" v-loading="loading" :loading-tip="loadingTip">
+      <slot />
+    </div>
+  </ScrollContainer>
+</template>
+<script lang="ts">
+  import type { ModalWrapperProps } from '../types';
+  import type { CSSProperties } from 'vue';
+
+  import {
+    defineComponent,
+    computed,
+    ref,
+    watchEffect,
+    unref,
+    watch,
+    onMounted,
+    nextTick,
+    onUnmounted,
+  } from 'vue';
+  import { Spin } from 'ant-design-vue';
+
+  import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
+  import { ScrollContainer } from '/@/components/Container';
+
+  // import { useElResize } from '/@/hooks/event/useElResize';
+  import { propTypes } from '/@/utils/propTypes';
+  import { createModalContext } from '../hooks/useModalContext';
+
+  export default defineComponent({
+    name: 'ModalWrapper',
+    components: { Spin, ScrollContainer },
+    props: {
+      loading: propTypes.bool,
+      useWrapper: propTypes.bool.def(true),
+      modalHeaderHeight: propTypes.number.def(50),
+      modalFooterHeight: propTypes.number.def(54),
+      minHeight: propTypes.number.def(200),
+      footerOffset: propTypes.number.def(0),
+      visible: propTypes.bool,
+      fullScreen: propTypes.bool,
+      loadingTip: propTypes.string,
+    },
+    emits: ['height-change', 'ext-height'],
+    setup(props: ModalWrapperProps, { emit }) {
+      const wrapperRef = ref<ComponentRef>(null);
+      const spinRef = ref<ElRef>(null);
+      const realHeightRef = ref(0);
+
+      let stopElResizeFn: Fn = () => {};
+
+      useWindowSizeFn(setModalHeight);
+
+      createModalContext({
+        redoModalHeight: setModalHeight,
+      });
+
+      const wrapStyle = computed(
+        (): CSSProperties => {
+          return {
+            minHeight: `${props.minHeight}px`,
+            height: `${unref(realHeightRef)}px`,
+            // overflow: 'auto',
+          };
+        }
+      );
+
+      const spinStyle = computed(
+        (): CSSProperties => {
+          return {
+            // padding 28
+            height: `${unref(realHeightRef) - 28}px`,
+          };
+        }
+      );
+
+      watchEffect(() => {
+        props.useWrapper && setModalHeight();
+      });
+
+      watch(
+        () => props.fullScreen,
+        () => {
+          setTimeout(() => {
+            setModalHeight();
+          }, 0);
+        }
+      );
+
+      onMounted(() => {
+        const { modalHeaderHeight, modalFooterHeight } = props;
+        emit('ext-height', modalHeaderHeight + modalFooterHeight);
+        // listenElResize();
+      });
+
+      onUnmounted(() => {
+        stopElResizeFn && stopElResizeFn();
+      });
+
+      async function setModalHeight() {
+        // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
+        // 加上这个,就必须在使用的时候传递父级的visible
+        if (!props.visible) return;
+        const wrapperRefDom = unref(wrapperRef);
+        if (!wrapperRefDom) return;
+        const bodyDom = wrapperRefDom.$el.parentElement;
+        if (!bodyDom) return;
+        bodyDom.style.padding = '0';
+        await nextTick();
+
+        try {
+          const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
+          if (!modalDom) return;
+
+          const modalRect = getComputedStyle(modalDom).top;
+          const modalTop = Number.parseInt(modalRect);
+          let maxHeight =
+            window.innerHeight -
+            modalTop * 2 +
+            (props.footerOffset! || 0) -
+            props.modalFooterHeight -
+            props.modalHeaderHeight;
+
+          // 距离顶部过进会出现滚动条
+          if (modalTop < 40) {
+            maxHeight -= 26;
+          }
+          await nextTick();
+          const spinEl = unref(spinRef);
+
+          if (!spinEl) return;
+
+          const realHeight = spinEl.scrollHeight;
+
+          if (props.fullScreen) {
+            realHeightRef.value =
+              window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight;
+          } else {
+            realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30;
+          }
+          emit('height-change', unref(realHeightRef));
+        } catch (error) {
+          console.log(error);
+        }
+      }
+
+      return { wrapStyle, wrapperRef, spinRef, spinStyle };
+    },
+  });
+</script>

+ 26 - 19
src/components/Modal/src/useModal.ts → src/components/Modal/src/hooks/useModal.ts

@@ -4,7 +4,7 @@ import type {
   ModalProps,
   ReturnMethods,
   UseModalInnerReturnType,
-} from './types';
+} from '../types';
 
 import {
   ref,
@@ -19,16 +19,18 @@ import {
 import { isProdMode } from '/@/utils/env';
 import { isFunction } from '/@/utils/is';
 import { isEqual } from 'lodash-es';
-import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
+import { tryOnUnmounted, isInSetup } from '/@/utils/helper/vueHelper';
+import { error } from '/@/utils/log';
+import { computed } from 'vue';
 const dataTransferRef = reactive<any>({});
 
+const visibleData = reactive<{ [key: number]: boolean }>({});
+
 /**
  * @description: Applicable to independent modal and call outside
  */
 export function useModal(): UseModalReturnType {
-  if (!getCurrentInstance()) {
-    throw new Error('Please put useModal function in the setup function!');
-  }
+  isInSetup();
   const modalRef = ref<Nullable<ModalMethods>>(null);
   const loadedRef = ref<Nullable<boolean>>(false);
   const uidRef = ref<string>('');
@@ -45,23 +47,29 @@ export function useModal(): UseModalReturnType {
     if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return;
 
     modalRef.value = modalMethod;
+    modalMethod.emitVisible = (visible: boolean, uid: number) => {
+      visibleData[uid] = visible;
+    };
   }
 
   const getInstance = () => {
     const instance = unref(modalRef);
     if (!instance) {
-      throw new Error('instance is undefined!');
+      error('useModal instance is undefined!');
     }
     return instance;
   };
 
   const methods: ReturnMethods = {
     setModalProps: (props: Partial<ModalProps>): void => {
-      getInstance().setModalProps(props);
+      getInstance()?.setModalProps(props);
     },
+    getVisible: computed((): boolean => {
+      return visibleData[~~unref(uidRef)];
+    }),
 
     openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => {
-      getInstance().setModalProps({
+      getInstance()?.setModalProps({
         visible: visible,
       });
 
@@ -83,20 +91,16 @@ export function useModal(): UseModalReturnType {
 
 export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
   const modalInstanceRef = ref<Nullable<ModalMethods>>(null);
-  const currentInstall = getCurrentInstance();
+  const currentInstance = getCurrentInstance();
   const uidRef = ref<string>('');
 
-  if (!currentInstall) {
-    throw new Error('instance is undefined!');
-  }
-
   // currentInstall.type.emits = [...currentInstall.type.emits, 'register'];
   // Object.assign(currentInstall.type.emits, ['register']);
 
   const getInstance = () => {
     const instance = unref(modalInstanceRef);
     if (!instance) {
-      throw new Error('instance is undefined!');
+      error('useModalInner instance is undefined!');
     }
     return instance;
   };
@@ -108,7 +112,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
       });
     uidRef.value = uuid;
     modalInstanceRef.value = modalInstance;
-    currentInstall.emit('register', modalInstance, uuid);
+    currentInstance?.emit('register', modalInstance, uuid);
   };
 
   watchEffect(() => {
@@ -124,19 +128,22 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
     register,
     {
       changeLoading: (loading = true) => {
-        getInstance().setModalProps({ loading });
+        getInstance()?.setModalProps({ loading });
       },
+      getVisible: computed((): boolean => {
+        return visibleData[~~unref(uidRef)];
+      }),
 
       changeOkLoading: (loading = true) => {
-        getInstance().setModalProps({ confirmLoading: loading });
+        getInstance()?.setModalProps({ confirmLoading: loading });
       },
 
       closeModal: () => {
-        getInstance().setModalProps({ visible: false });
+        getInstance()?.setModalProps({ visible: false });
       },
 
       setModalProps: (props: Partial<ModalProps>) => {
-        getInstance().setModalProps(props);
+        getInstance()?.setModalProps(props);
       },
     },
   ];

+ 0 - 0
src/components/Modal/src/useModalContext.ts → src/components/Modal/src/hooks/useModalContext.ts


+ 0 - 0
src/components/Modal/src/useModalDrag.ts → src/components/Modal/src/hooks/useModalDrag.ts


+ 0 - 0
src/components/Modal/src/useFullScreen.ts → src/components/Modal/src/hooks/useModalFullScreen.ts


+ 5 - 44
src/components/Modal/src/index.less

@@ -21,9 +21,12 @@
   width: 520px;
   padding-bottom: 0;
 
-  .ant-spin-nested-loading {
-    padding: 16px;
+  .scroll-container {
+    padding: 14px;
   }
+  // .ant-spin-nested-loading {
+  //   padding: 16px;
+  // }
 
   &-title {
     font-size: 16px;
@@ -35,46 +38,6 @@
     }
   }
 
-  .custom-close-icon {
-    display: flex;
-    height: 95%;
-    align-items: center;
-
-    > span {
-      margin-left: 48px;
-      font-size: 16px;
-    }
-
-    &.can-full {
-      > span {
-        margin-left: 12px;
-      }
-    }
-
-    &:not(.can-full) {
-      > span:nth-child(1) {
-        &:hover {
-          font-weight: 700;
-        }
-      }
-    }
-
-    & span:nth-child(1) {
-      display: inline-block;
-      padding: 10px;
-
-      &:hover {
-        color: @primary-color;
-      }
-    }
-
-    & span:nth-child(2) {
-      &:hover {
-        color: @error-color;
-      }
-    }
-  }
-
   .ant-modal-body {
     padding: 0;
   }
@@ -96,8 +59,6 @@
   }
 
   &-footer {
-    // padding: 10px 26px 26px 16px;
-
     button + button {
       margin-left: 10px;
     }

+ 21 - 40
src/components/Modal/src/props.ts

@@ -1,8 +1,9 @@
-import type { PropType } from 'vue';
+import type { PropType, CSSProperties } from 'vue';
 import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
 
 import { useI18n } from '/@/hooks/web/useI18n';
-import { propTypes } from '/@/utils/propTypes';
+import { propTypes, VueNode } from '/@/utils/propTypes';
+import type { ModalWrapperProps } from './types';
 const { t } = useI18n();
 
 export const modalProps = {
@@ -26,6 +27,7 @@ export const basicProps = Object.assign({}, modalProps, {
   // Whether to setting wrapper
   useWrapper: propTypes.bool.def(true),
   loading: propTypes.bool,
+  loadingTip: propTypes.string,
   /**
    * @description: Show close button
    */
@@ -35,65 +37,44 @@ export const basicProps = Object.assign({}, modalProps, {
    */
   showOkBtn: propTypes.bool.def(true),
 
-  wrapperProps: Object as PropType<any>,
+  wrapperProps: Object as PropType<Partial<ModalWrapperProps>>,
 
-  afterClose: Function as PropType<() => Promise<any>>,
+  afterClose: Function as PropType<() => Promise<VueNode>>,
 
-  bodyStyle: Object as PropType<any>,
+  bodyStyle: Object as PropType<CSSProperties>,
 
-  closable: {
-    type: Boolean as PropType<boolean>,
-    default: true,
-  },
+  closable: propTypes.bool.def(true),
 
-  closeIcon: Object as PropType<any>,
+  closeIcon: Object as PropType<VueNode>,
 
-  confirmLoading: Boolean as PropType<boolean>,
+  confirmLoading: propTypes.bool,
 
-  destroyOnClose: Boolean as PropType<boolean>,
+  destroyOnClose: propTypes.bool,
 
-  footer: Object as PropType<any>,
+  footer: Object as PropType<VueNode>,
 
   getContainer: Function as PropType<() => any>,
 
-  mask: {
-    type: Boolean as PropType<boolean>,
-    default: true,
-  },
+  mask: propTypes.bool.def(true),
 
-  maskClosable: {
-    type: Boolean as PropType<boolean>,
-    default: true,
-  },
-  keyboard: {
-    type: Boolean as PropType<boolean>,
-    default: true,
-  },
+  maskClosable: propTypes.bool.def(true),
+  keyboard: propTypes.bool.def(true),
 
-  maskStyle: Object as PropType<any>,
+  maskStyle: Object as PropType<CSSProperties>,
 
-  okType: {
-    type: String as PropType<string>,
-    default: 'primary',
-  },
+  okType: propTypes.string.def('primary'),
 
   okButtonProps: Object as PropType<ButtonProps>,
 
   cancelButtonProps: Object as PropType<ButtonProps>,
 
-  title: {
-    type: String as PropType<string>,
-  },
+  title: propTypes.string,
 
-  visible: Boolean as PropType<boolean>,
+  visible: propTypes.bool,
 
   width: [String, Number] as PropType<string | number>,
 
-  wrapClassName: {
-    type: String as PropType<string>,
-  },
+  wrapClassName: propTypes.string,
 
-  zIndex: {
-    type: Number as PropType<number>,
-  },
+  zIndex: propTypes.number,
 });

+ 6 - 1
src/components/Modal/src/types.ts

@@ -1,16 +1,18 @@
 import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
-import type { CSSProperties, VNodeChild } from 'vue';
+import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
 /**
  * @description: 弹窗对外暴露的方法
  */
 export interface ModalMethods {
   setModalProps: (props: Partial<ModalProps>) => void;
+  emitVisible?: (visible: boolean, uid: number) => void;
 }
 
 export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void;
 
 export interface ReturnMethods extends ModalMethods {
   openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void;
+  getVisible?: ComputedRef<boolean>;
 }
 
 export type UseModalReturnType = [RegisterFn, ReturnMethods];
@@ -19,6 +21,7 @@ export interface ReturnInnerMethods extends ModalMethods {
   closeModal: () => void;
   changeLoading: (loading: boolean) => void;
   changeOkLoading: (loading: boolean) => void;
+  getVisible?: ComputedRef<boolean>;
 }
 
 export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods];
@@ -38,6 +41,7 @@ export interface ModalProps {
   useWrapper: boolean;
 
   loading: boolean;
+  loadingTip?: string;
 
   wrapperProps: Omit<ModalWrapperProps, 'loading'>;
 
@@ -193,4 +197,5 @@ export interface ModalWrapperProps {
   minHeight: number;
   visible: boolean;
   fullScreen: boolean;
+  useWrapper: boolean;
 }

+ 2 - 2
src/hooks/core/useAttrs.ts

@@ -1,5 +1,5 @@
 import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue';
-
+import type { Ref } from 'vue';
 interface Params {
   excludeListeners?: boolean;
   excludeKeys?: string[];
@@ -12,7 +12,7 @@ export function entries<T>(obj: Hash<T>): [string, T][] {
   return Object.keys(obj).map((key: string) => [key, obj[key]]);
 }
 
-export function useAttrs(params: Params = {}) {
+export function useAttrs(params: Params = {}): Ref<Recordable> | {} {
   const instance = getCurrentInstance();
   if (!instance) return {};
 

+ 1 - 1
src/settings/projectSetting.ts

@@ -37,7 +37,7 @@ const setting: ProjectConfig = {
   showLogo: true,
 
   // Whether to show footer
-  showFooter: true,
+  showFooter: false,
 
   // locale setting
   locale: {

+ 1 - 1
src/utils/propTypes.ts

@@ -1,7 +1,7 @@
 import { CSSProperties, VNodeChild } from 'vue';
 import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types';
 
-type VueNode = VNodeChild | JSX.Element;
+export type VueNode = VNodeChild | JSX.Element;
 
 type PropTypes = VueTypesInterface & {
   readonly style: VueTypeValidableDef<CSSProperties>;

+ 22 - 2
src/views/demo/comp/drawer/Drawer3.vue

@@ -1,6 +1,20 @@
 <template>
-  <BasicDrawer v-bind="$attrs" title="Modal Title" width="50%" showFooter>
+  <BasicDrawer v-bind="$attrs" title="Modal Title" width="50%" showFooter @ok="handleOk">
     <p class="h-20" v-for="index in 40" :key="index">根据屏幕高度自适应</p>
+    <template #insertFooter>
+      <a-button> btn</a-button>
+    </template>
+    <template #centerFooter>
+      <a-button> btn2</a-button>
+    </template>
+
+    <template #appendFooter>
+      <a-button> btn3</a-button>
+    </template>
+
+    <!-- <template #footer>
+      <a-button> customerFooter</a-button>
+    </template> -->
   </BasicDrawer>
 </template>
 <script lang="ts">
@@ -9,7 +23,13 @@
   export default defineComponent({
     components: { BasicDrawer },
     setup() {
-      return {};
+      return {
+        handleOk: () => {
+          console.log('=====================');
+          console.log('ok');
+          console.log('======================');
+        },
+      };
     },
   });
 </script>

+ 1 - 1
src/views/demo/comp/drawer/Drawer5.vue

@@ -1,6 +1,7 @@
 <template>
   <BasicDrawer v-bind="$attrs" :isDetail="true" title="Drawer Title5">
     <p class="h-20">Content Message</p>
+    <template #titleToolbar> toolbar </template>
   </BasicDrawer>
 </template>
 <script lang="ts">
@@ -8,6 +9,5 @@
   import { BasicDrawer } from '/@/components/Drawer';
   export default defineComponent({
     components: { BasicDrawer },
-    setup() {},
   });
 </script>

+ 4 - 4
src/views/demo/comp/drawer/index.vue

@@ -3,10 +3,10 @@
     <Alert message="使用 useDrawer 进行抽屉操作" show-icon />
     <a-button type="primary" class="my-4" @click="openDrawerLoading">打开Drawer</a-button>
 
-    <Alert message="内外同时同时显示隐藏" show-icon />
-    <a-button type="primary" class="my-4" @click="openDrawer2">打开Drawer</a-button>
+    <Alert message="内外同时控制显示隐藏" show-icon />
+    <a-button type="primary" class="my-4" @click="openDrawer2(true)">打开Drawer</a-button>
     <Alert message="自适应高度/显示footer" show-icon />
-    <a-button type="primary" class="my-4" @click="openDrawer3">打开Drawer</a-button>
+    <a-button type="primary" class="my-4" @click="openDrawer3(true)">打开Drawer</a-button>
 
     <Alert
       message="内外数据交互,外部通过 transferModalData 发送,内部通过 receiveDrawerDataRef 接收。该数据具有响应式"
@@ -14,7 +14,7 @@
     />
     <a-button type="primary" class="my-4" @click="send">打开Drawer并传递数据</a-button>
     <Alert message="详情页模式" show-icon />
-    <a-button type="primary" class="my-4" @click="openDrawer5">打开详情Drawer</a-button>
+    <a-button type="primary" class="my-4" @click="openDrawer5(true)">打开详情Drawer</a-button>
     <Drawer1 @register="register1" />
     <Drawer2 @register="register2" />
     <Drawer3 @register="register3" />

+ 1 - 1
src/views/demo/feat/tabs/index.vue

@@ -4,7 +4,7 @@
       <a-input placeholder="请输入" />
     </CollapseContainer>
 
-    <CollapseContainer class="mt-4 px-4" title="标签页操作">
+    <CollapseContainer class="mt-4" title="标签页操作">
       <a-button class="mr-2" @click="closeAll">关闭所有</a-button>
       <a-button class="mr-2" @click="closeLeft">关闭左侧</a-button>
       <a-button class="mr-2" @click="closeRight">关闭右侧</a-button>