|
|
|
@ -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()} |
|
|
|
</> |
|
|
|
|