|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
@@ -9,53 +10,96 @@ import { BasicTitle } from '/@/components/Basic';
|
|
|
import { FullLoading } from '/@/components/Loading/index';
|
|
|
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 { useI18n } from '/@/hooks/web/useI18n';
|
|
|
+import { tryTsxEmit } from '/@/utils/helper/vueHelper';
|
|
|
+
|
|
|
+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<ElRef>(null);
|
|
|
-
|
|
|
const visibleRef = ref(false);
|
|
|
- const propsRef = ref<Partial<DrawerProps> | null>(null);
|
|
|
+ const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
|
|
|
|
|
|
const { t } = useI18n('component.drawer');
|
|
|
|
|
|
- const getMergeProps = computed((): any => {
|
|
|
- return deepMerge(toRaw(props), unref(propsRef));
|
|
|
- });
|
|
|
-
|
|
|
- const getProps = computed(() => {
|
|
|
- const opt: any = {
|
|
|
- placement: 'right',
|
|
|
- ...attrs,
|
|
|
- ...props,
|
|
|
- ...(unref(propsRef) as any),
|
|
|
- visible: unref(visibleRef),
|
|
|
- };
|
|
|
- opt.title = undefined;
|
|
|
+ const getMergeProps = computed(
|
|
|
+ (): DrawerProps => {
|
|
|
+ return deepMerge(toRaw(props), unref(propsRef));
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- if (opt.isDetail) {
|
|
|
- if (!opt.width) {
|
|
|
- opt.width = '100%';
|
|
|
- }
|
|
|
- opt.wrapClassName = opt.wrapClassName
|
|
|
- ? `${opt.wrapClassName} ${prefixCls}__detail`
|
|
|
- : `${prefixCls}__detail`;
|
|
|
- if (!opt.getContainer) {
|
|
|
- opt.getContainer = '.layout-content';
|
|
|
+ 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;
|
|
|
}
|
|
|
- return opt;
|
|
|
+ );
|
|
|
+
|
|
|
+ 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 {
|
|
|
+ hidden: !unref(getProps).loading,
|
|
|
+ };
|
|
|
});
|
|
|
|
|
|
watchEffect(() => {
|
|
@@ -74,22 +118,13 @@ export default defineComponent({
|
|
|
}
|
|
|
);
|
|
|
|
|
|
- // Custom implementation of the bottom button,
|
|
|
- const getFooterHeight = computed(() => {
|
|
|
- const { footerHeight, showFooter }: DrawerProps = unref(getProps);
|
|
|
- if (showFooter && footerHeight) {
|
|
|
- return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
|
|
|
- }
|
|
|
- return `0px`;
|
|
|
- });
|
|
|
-
|
|
|
// Cancel event
|
|
|
- async function onClose(e: any) {
|
|
|
+ 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;
|
|
@@ -98,12 +133,16 @@ export default defineComponent({
|
|
|
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,
|
|
@@ -114,65 +153,64 @@ export default defineComponent({
|
|
|
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>
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -180,41 +218,20 @@ export default defineComponent({
|
|
|
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={t('loadingText')}
|
|
|
- class={[!unref(getProps).loading ? 'hidden' : '']}
|
|
|
- />
|
|
|
- {getSlot(slots, 'default')}
|
|
|
+ <div ref={scrollRef} style={unref(getScrollContentStyle)}>
|
|
|
+ <FullLoading absolute tip={t('loadingText')} class={unref(getLoading)} />
|
|
|
+ {getSlot(slots)}
|
|
|
</div>
|
|
|
{renderFooter()}
|
|
|
</>
|