vben
2020-12-01 5db3ce773793d06fa17336aca42470d2aae93fb8
src/components/Drawer/src/BasicDrawer.tsx
@@ -1,58 +1,103 @@
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 { FullLoading } from '/@/components/Loading/index';
import { Loading } from '/@/components/Loading';
import { LeftOutlined } from '@ant-design/icons-vue';
import { basicProps } from './props';
import { useI18n } from '/@/hooks/web/useI18n';
import { getSlot } from '/@/utils/helper/tsxHelper';
import { isFunction, isNumber } from '/@/utils/is';
import { buildUUID } from '/@/utils/uuid';
import { deepMerge } from '/@/utils';
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
import './index.less';
import { basicProps } from './props';
const prefixCls = 'basic-drawer';
export default defineComponent({
  // inheritAttrs: false,
  inheritAttrs: false,
  props: basicProps,
  emits: ['visible-change', 'ok', 'close', 'register'],
  setup(props, { slots, emit, attrs }) {
    const scrollRef = ref<any>(null);
    const scrollRef = ref<ElRef>(null);
    const visibleRef = ref(false);
    const propsRef = ref<Partial<DrawerProps> | null>(null);
    const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
    const getMergeProps = computed((): any => {
      return deepMerge(toRaw(props), unref(propsRef));
    const { t } = useI18n('component.drawer');
    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 getProps = computed(() => {
      const opt: any = {
        placement: 'right',
        ...attrs,
        ...props,
        ...(unref(propsRef) as any),
        visible: unref(visibleRef),
      };
      opt.title = undefined;
      if (opt.isDetail) {
        if (!opt.width) {
          opt.width = '100%';
        }
        opt.wrapClassName = opt.wrapClassName
          ? `${opt.wrapClassName} ${prefixCls}__detail`
          : `${prefixCls}__detail`;
        if (!opt.getContainer) {
          opt.getContainer = `.default-layout__main`;
        }
    const getScrollContentStyle = computed(
      (): CSSProperties => {
        const footerHeight = unref(getFooterHeight);
        return {
          position: 'relative',
          height: `calc(100% - ${footerHeight})`,
          overflow: 'auto',
          padding: '16px',
          paddingBottom: '30px',
        };
      }
      return opt;
    );
    const getLoading = computed(() => {
      return !!unref(getProps)?.loading;
    });
    watchEffect(() => {
@@ -71,36 +116,31 @@
      }
    );
    // 底部按钮自定义实现,
    const getFooterHeight = computed(() => {
      const { footerHeight, showFooter }: DrawerProps = unref(getProps);
      if (showFooter && footerHeight) {
        return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
      }
      return `0px`;
    });
    // 取消事件
    async function onClose(e: any) {
    // Cancel event
    async function onClose(e: ChangeEvent) {
      const { closeFunc } = unref(getProps);
      emit('close', e);
      if (closeFunc && isFunction(closeFunc)) {
        const res = await closeFunc();
        res && (visibleRef.value = false);
        visibleRef.value = !res;
        return;
      }
      visibleRef.value = false;
    }
    function setDrawerProps(props: Partial<DrawerProps>): void {
      // 保留上一次的setDrawerProps
      // 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,
@@ -111,65 +151,64 @@
        okButtonProps,
        confirmLoading,
        showFooter,
      }: DrawerProps = unref(getProps);
      } = unref(getProps);
      if (!showFooter) {
        return null;
      }
      return (
        getSlot(slots, 'footer') ||
        (showFooter && (
          <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>
        ))
        <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);
      return props.isDetail ? (
        getSlot(slots, 'title') || (
          <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>
        )
      ) : (
        <BasicTitle>{() => title || getSlot(slots, 'title')}</BasicTitle>
      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>
      );
    }
@@ -177,41 +216,20 @@
      setDrawerProps: setDrawerProps,
    };
    const uuid = buildUUID();
    emit('register', drawerInstance, uuid);
    tryTsxEmit((instance) => {
      emit('register', drawerInstance, instance.uid);
    });
    return () => {
      const footerHeight = unref(getFooterHeight);
      return (
        <Drawer
          class={prefixCls}
          onClose={onClose}
          {...{
            ...attrs,
            ...unref(getProps),
          }}
        >
        <Drawer class={prefixCls} onClose={onClose} {...unref(getBindValues)}>
          {{
            title: () => renderHeader(),
            default: () => (
              <>
                <div
                  ref={scrollRef}
                  {...attrs}
                  style={{
                    position: 'relative',
                    height: `calc(100% - ${footerHeight})`,
                    overflow: 'auto',
                    padding: '16px',
                    paddingBottom: '30px',
                  }}
                >
                  <FullLoading
                    absolute
                    tip="加载中..."
                    class={[!unref(getProps).loading ? 'hidden' : '']}
                  />
                  {getSlot(slots, 'default')}
                <div ref={scrollRef} style={unref(getScrollContentStyle)}>
                  <Loading absolute tip={t('loadingText')} loading={unref(getLoading)} />
                  {getSlot(slots)}
                </div>
                {renderFooter()}
              </>