初始版本提交
This commit is contained in:
6
src/components/Drawer/index.ts
Normal file
6
src/components/Drawer/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { withInstall } from '/@/utils';
|
||||
import basicDrawer from './src/BasicDrawer.vue';
|
||||
|
||||
export const BasicDrawer = withInstall(basicDrawer);
|
||||
export * from './src/typing';
|
||||
export { useDrawer, useDrawerInner } from './src/useDrawer';
|
||||
269
src/components/Drawer/src/BasicDrawer.vue
Normal file
269
src/components/Drawer/src/BasicDrawer.vue
Normal file
@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<Drawer
|
||||
:class="prefixCls"
|
||||
@close="onClose"
|
||||
v-bind="getBindValues"
|
||||
:contentWrapperStyle="contentWrapperStyle"
|
||||
>
|
||||
<template #title v-if="!$slots.title">
|
||||
<DrawerHeader
|
||||
:title="getMergeProps.title"
|
||||
:isDetail="isDetail"
|
||||
:showDetailBack="showDetailBack"
|
||||
:canFullscreen="getMergeProps.canFullscreen"
|
||||
@close="onClose"
|
||||
@fullscreen="handleFullScreen"
|
||||
>
|
||||
<template #titleToolbar>
|
||||
<slot name="titleToolbar"></slot>
|
||||
</template>
|
||||
</DrawerHeader>
|
||||
</template>
|
||||
<template v-else #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
|
||||
<ScrollContainer
|
||||
:style="getScrollContentStyle"
|
||||
v-loading="getLoading"
|
||||
:loading-tip="loadingText || t('common.loadingText')"
|
||||
>
|
||||
<slot></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 || {}"></slot>
|
||||
</template>
|
||||
</DrawerFooter>
|
||||
</Drawer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { DrawerInstance, DrawerProps } from './typing';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
computed,
|
||||
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({
|
||||
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
|
||||
inheritAttrs: false,
|
||||
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 contentWrapperStyle = ref({});
|
||||
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.class = 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),
|
||||
maskClosable: false,
|
||||
};
|
||||
});
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) visibleRef.value = newVal;
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
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) || ({} as any), props);
|
||||
|
||||
if (Reflect.has(props, 'visible')) {
|
||||
visibleRef.value = !!props.visible;
|
||||
}
|
||||
}
|
||||
|
||||
function handleFullScreen(fullScreen) {
|
||||
contentWrapperStyle.value = fullScreen ? { width: '100%' } : {};
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
emit('ok');
|
||||
}
|
||||
|
||||
return {
|
||||
onClose,
|
||||
t,
|
||||
prefixCls,
|
||||
getMergeProps: getMergeProps as any,
|
||||
getScrollContentStyle,
|
||||
getProps: getProps as any,
|
||||
getLoading,
|
||||
getBindValues,
|
||||
getFooterHeight,
|
||||
handleOk,
|
||||
handleFullScreen,
|
||||
contentWrapperStyle,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="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: @component-background;
|
||||
|
||||
.scrollbar__wrap {
|
||||
padding: 16px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
> .scrollbar > .scrollbar__bar.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{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>
|
||||
83
src/components/Drawer/src/components/DrawerFooter.vue
Normal file
83
src/components/Drawer/src/components/DrawerFooter.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer">
|
||||
<template v-if="!$slots.footer">
|
||||
<slot name="insertFooter"></slot>
|
||||
<a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn">
|
||||
{{ cancelText }}
|
||||
</a-button>
|
||||
<slot name="centerFooter"></slot>
|
||||
<a-button
|
||||
:type="okType"
|
||||
@click="handleOk"
|
||||
v-bind="okButtonProps"
|
||||
class="mr-2"
|
||||
:loading="confirmLoading"
|
||||
:disabled="confirmLoading"
|
||||
v-if="showOkBtn"
|
||||
>
|
||||
{{ okText }}
|
||||
</a-button>
|
||||
<slot name="appendFooter"></slot>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<slot name="footer"></slot>
|
||||
</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">
|
||||
@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-color: @component-background;
|
||||
border-top: 1px solid @border-color-base;
|
||||
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
107
src/components/Drawer/src/components/DrawerHeader.vue
Normal file
107
src/components/Drawer/src/components/DrawerHeader.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<BasicTitle v-if="!isDetail" :class="prefixCls">
|
||||
<slot name="title"></slot>
|
||||
{{ !$slots.title ? title : '' }}
|
||||
|
||||
<div v-if="canFullscreen" class="fullscreen-icon">
|
||||
<Tooltip :title="t('还原')" placement="bottom" v-if="fullScreen">
|
||||
<FullscreenExitOutlined role="full" @click="handleFullScreen" />
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('最大化')" placement="bottom" v-else>
|
||||
<FullscreenOutlined role="close" @click="handleFullScreen" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</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"></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { BasicTitle } from '/@/components/Basic';
|
||||
import {
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
ArrowLeftOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
export default defineComponent({
|
||||
name: 'BasicDrawerHeader',
|
||||
components: {
|
||||
BasicTitle,
|
||||
ArrowLeftOutlined,
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
Tooltip,
|
||||
},
|
||||
props: {
|
||||
isDetail: propTypes.bool,
|
||||
showDetailBack: propTypes.bool,
|
||||
title: propTypes.string,
|
||||
canFullscreen: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ['close', 'fullscreen'],
|
||||
setup(_, { emit }) {
|
||||
const { prefixCls } = useDesign('basic-drawer-header');
|
||||
const { t } = useI18n();
|
||||
const fullScreen = ref(false);
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
function handleFullScreen(e: Event) {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
fullScreen.value = !fullScreen.value;
|
||||
emit('fullscreen', fullScreen.value);
|
||||
}
|
||||
return { prefixCls, handleClose, t, fullScreen, handleFullScreen };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="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;
|
||||
}
|
||||
}
|
||||
|
||||
.fullscreen-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
44
src/components/Drawer/src/props.ts
Normal file
44
src/components/Drawer/src/props.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
export const footerProps = {
|
||||
confirmLoading: { type: Boolean },
|
||||
/**
|
||||
* @description: Show close button
|
||||
*/
|
||||
showCancelBtn: { type: Boolean, default: true },
|
||||
cancelButtonProps: Object as PropType<Recordable>,
|
||||
cancelText: { type: String, default: t('取消') },
|
||||
/**
|
||||
* @description: Show confirmation button
|
||||
*/
|
||||
showOkBtn: { type: Boolean, default: true },
|
||||
okButtonProps: Object as PropType<Recordable>,
|
||||
okText: { type: String, default: t('确认') },
|
||||
okType: { type: String, default: 'primary' },
|
||||
showFooter: { type: Boolean },
|
||||
footerHeight: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 60,
|
||||
},
|
||||
};
|
||||
export const basicProps = {
|
||||
isDetail: { type: Boolean },
|
||||
title: { type: String, default: '' },
|
||||
loadingText: { type: String },
|
||||
showDetailBack: { type: Boolean, default: true },
|
||||
visible: { type: Boolean },
|
||||
loading: { type: Boolean },
|
||||
maskClosable: { type: Boolean, default: true },
|
||||
getContainer: {
|
||||
type: [Object, String] as PropType<any>,
|
||||
},
|
||||
closeFunc: {
|
||||
type: [Function, Object] as PropType<any>,
|
||||
default: null,
|
||||
},
|
||||
destroyOnClose: { type: Boolean },
|
||||
...footerProps,
|
||||
};
|
||||
195
src/components/Drawer/src/typing.ts
Normal file
195
src/components/Drawer/src/typing.ts
Normal file
@ -0,0 +1,195 @@
|
||||
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
|
||||
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;
|
||||
closeDrawer: () => void;
|
||||
getVisible?: ComputedRef<boolean>;
|
||||
}
|
||||
|
||||
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void;
|
||||
|
||||
export interface ReturnInnerMethods extends DrawerInstance {
|
||||
closeDrawer: () => void;
|
||||
changeLoading: (loading: boolean) => void;
|
||||
changeOkLoading: (loading: boolean) => void;
|
||||
getVisible?: ComputedRef<boolean>;
|
||||
}
|
||||
|
||||
export type UseDrawerReturnType = [RegisterFn, ReturnMethods];
|
||||
|
||||
export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods];
|
||||
|
||||
export interface DrawerFooterProps {
|
||||
showOkBtn: boolean;
|
||||
showCancelBtn: boolean;
|
||||
/**
|
||||
* Text of the Cancel button
|
||||
* @default 'cancel'
|
||||
* @type string
|
||||
*/
|
||||
cancelText: string;
|
||||
/**
|
||||
* Text of the OK button
|
||||
* @default 'OK'
|
||||
* @type string
|
||||
*/
|
||||
okText: string;
|
||||
|
||||
/**
|
||||
* Button type of the OK button
|
||||
* @default 'primary'
|
||||
* @type string
|
||||
*/
|
||||
okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default';
|
||||
/**
|
||||
* The ok button props, follow jsx rules
|
||||
* @type object
|
||||
*/
|
||||
okButtonProps: { props: ButtonProps; on: {} };
|
||||
|
||||
/**
|
||||
* The cancel button props, follow jsx rules
|
||||
* @type object
|
||||
*/
|
||||
cancelButtonProps: { props: ButtonProps; on: {} };
|
||||
/**
|
||||
* Whether to apply loading visual effect for OK button or not
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
confirmLoading: boolean;
|
||||
|
||||
showFooter: boolean;
|
||||
footerHeight: string | number;
|
||||
}
|
||||
export interface DrawerProps extends DrawerFooterProps {
|
||||
isDetail?: boolean;
|
||||
loading?: boolean;
|
||||
showDetailBack?: boolean;
|
||||
visible?: boolean;
|
||||
/**
|
||||
* Built-in ScrollContainer component configuration
|
||||
* @type ScrollContainerOptions
|
||||
*/
|
||||
scrollOptions?: ScrollContainerOptions;
|
||||
closeFunc?: () => Promise<any>;
|
||||
triggerWindowResize?: boolean;
|
||||
/**
|
||||
* Whether a close (x) button is visible on top right of the Drawer dialog or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
closable?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to unmount child components on closing drawer or not.
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
destroyOnClose?: boolean;
|
||||
|
||||
/**
|
||||
* Return the mounted node for Drawer.
|
||||
* @default 'body'
|
||||
* @type any ( HTMLElement| () => HTMLElement | string)
|
||||
*/
|
||||
getContainer?: () => HTMLElement | string;
|
||||
|
||||
/**
|
||||
* Whether to show mask or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
mask?: boolean;
|
||||
|
||||
/**
|
||||
* Clicking on the mask (area outside the Drawer) to close the Drawer or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
maskClosable?: boolean;
|
||||
|
||||
/**
|
||||
* Style for Drawer's mask element.
|
||||
* @default {}
|
||||
* @type object
|
||||
*/
|
||||
maskStyle?: CSSProperties;
|
||||
|
||||
/**
|
||||
* The title for Drawer.
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
title?: VNodeChild | JSX.Element;
|
||||
/**
|
||||
* The class name of the container of the Drawer dialog.
|
||||
* @type string
|
||||
*/
|
||||
wrapClassName?: string;
|
||||
class?: string;
|
||||
/**
|
||||
* Style of wrapper element which **contains mask** compare to `drawerStyle`
|
||||
* @type object
|
||||
*/
|
||||
wrapStyle?: CSSProperties;
|
||||
|
||||
/**
|
||||
* Style of the popup layer element
|
||||
* @type object
|
||||
*/
|
||||
drawerStyle?: CSSProperties;
|
||||
|
||||
/**
|
||||
* Style of floating layer, typically used for adjusting its position.
|
||||
* @type object
|
||||
*/
|
||||
bodyStyle?: CSSProperties;
|
||||
headerStyle?: CSSProperties;
|
||||
|
||||
/**
|
||||
* Width of the Drawer dialog.
|
||||
* @default 256
|
||||
* @type string | number
|
||||
*/
|
||||
width?: string | number;
|
||||
|
||||
/**
|
||||
* placement is top or bottom, height of the Drawer dialog.
|
||||
* @type string | number
|
||||
*/
|
||||
height?: string | number;
|
||||
|
||||
/**
|
||||
* The z-index of the Drawer.
|
||||
* @default 1000
|
||||
* @type number
|
||||
*/
|
||||
zIndex?: number;
|
||||
|
||||
/**
|
||||
* The placement of the Drawer.
|
||||
* @default 'right'
|
||||
* @type string
|
||||
*/
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left';
|
||||
afterVisibleChange?: (visible?: boolean) => void;
|
||||
keyboard?: boolean;
|
||||
/**
|
||||
* Specify a callback that will be called when a user clicks mask, close button or Cancel button.
|
||||
*/
|
||||
onClose?: (e?: Event) => void;
|
||||
canFullscreen?: boolean;
|
||||
contentWrapperStyle?: any;
|
||||
}
|
||||
export interface DrawerActionType {
|
||||
scrollBottom: () => void;
|
||||
scrollTo: (to: number) => void;
|
||||
getScrollWrap: () => Element | null;
|
||||
}
|
||||
161
src/components/Drawer/src/useDrawer.ts
Normal file
161
src/components/Drawer/src/useDrawer.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import type {
|
||||
UseDrawerReturnType,
|
||||
DrawerInstance,
|
||||
ReturnMethods,
|
||||
DrawerProps,
|
||||
UseDrawerInnerReturnType,
|
||||
} from './typing';
|
||||
import {
|
||||
ref,
|
||||
getCurrentInstance,
|
||||
unref,
|
||||
reactive,
|
||||
watchEffect,
|
||||
nextTick,
|
||||
toRaw,
|
||||
computed,
|
||||
} from 'vue';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
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('useDrawer() can only be used inside setup() or functional components!');
|
||||
}
|
||||
const drawer = ref<DrawerInstance | null>(null);
|
||||
const loaded = ref<Nullable<boolean>>(false);
|
||||
const uid = ref<string>('');
|
||||
|
||||
function register(drawerInstance: DrawerInstance, uuid: string) {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
drawer.value = null;
|
||||
loaded.value = null;
|
||||
dataTransferRef[unref(uid)] = null;
|
||||
});
|
||||
|
||||
if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) {
|
||||
return;
|
||||
}
|
||||
uid.value = uuid;
|
||||
drawer.value = drawerInstance;
|
||||
loaded.value = true;
|
||||
|
||||
drawerInstance.emitVisible = (visible: boolean, uid: number) => {
|
||||
visibleData[uid] = visible;
|
||||
};
|
||||
}
|
||||
|
||||
const getInstance = () => {
|
||||
const instance = unref(drawer);
|
||||
if (!instance) {
|
||||
error('useDrawer instance is undefined!');
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
const methods: ReturnMethods = {
|
||||
setDrawerProps: (props: Partial<DrawerProps>): void => {
|
||||
getInstance()?.setDrawerProps(props);
|
||||
},
|
||||
|
||||
getVisible: computed((): boolean => {
|
||||
return visibleData[~~unref(uid)];
|
||||
}),
|
||||
|
||||
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
|
||||
getInstance()?.setDrawerProps({
|
||||
visible: visible,
|
||||
});
|
||||
if (!data) return;
|
||||
|
||||
if (openOnSet) {
|
||||
dataTransferRef[unref(uid)] = null;
|
||||
dataTransferRef[unref(uid)] = toRaw(data);
|
||||
return;
|
||||
}
|
||||
const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data));
|
||||
if (!equal) {
|
||||
dataTransferRef[unref(uid)] = toRaw(data);
|
||||
}
|
||||
},
|
||||
closeDrawer: () => {
|
||||
getInstance()?.setDrawerProps({ visible: false });
|
||||
},
|
||||
};
|
||||
|
||||
return [register, methods];
|
||||
}
|
||||
|
||||
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
|
||||
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
|
||||
const currentInstance = getCurrentInstance();
|
||||
const uidRef = ref<string>('');
|
||||
|
||||
if (!getCurrentInstance()) {
|
||||
throw new Error('useDrawerInner() can only be used inside setup() or functional components!');
|
||||
}
|
||||
|
||||
const getInstance = () => {
|
||||
const instance = unref(drawerInstanceRef);
|
||||
if (!instance) {
|
||||
error('useDrawerInner instance is undefined!');
|
||||
return;
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
const register = (modalInstance: DrawerInstance, uuid: string) => {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
drawerInstanceRef.value = null;
|
||||
});
|
||||
|
||||
uidRef.value = uuid;
|
||||
drawerInstanceRef.value = modalInstance;
|
||||
currentInstance?.emit('register', modalInstance, uuid);
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
const data = dataTransferRef[unref(uidRef)];
|
||||
if (!data) return;
|
||||
if (!callbackFn || !isFunction(callbackFn)) return;
|
||||
nextTick(() => {
|
||||
callbackFn(data);
|
||||
});
|
||||
});
|
||||
|
||||
return [
|
||||
register,
|
||||
{
|
||||
changeLoading: (loading = true) => {
|
||||
getInstance()?.setDrawerProps({ loading });
|
||||
},
|
||||
|
||||
changeOkLoading: (loading = true) => {
|
||||
getInstance()?.setDrawerProps({ confirmLoading: loading });
|
||||
},
|
||||
getVisible: computed((): boolean => {
|
||||
return visibleData[~~unref(uidRef)];
|
||||
}),
|
||||
|
||||
closeDrawer: () => {
|
||||
getInstance()?.setDrawerProps({ visible: false, confirmLoading: false });
|
||||
},
|
||||
|
||||
setDrawerProps: (props: Partial<DrawerProps>) => {
|
||||
getInstance()?.setDrawerProps(props);
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
Reference in New Issue
Block a user