项目初始化
This commit is contained in:
358
web-vue/packages/core/components/Drawer/src/BasicDrawer.vue
Normal file
358
web-vue/packages/core/components/Drawer/src/BasicDrawer.vue
Normal file
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<Drawer v-bind="getBindValues" :closable="false" @close="onClose">
|
||||
<template #title v-if="!$slots.title">
|
||||
<DrawerHeader :title="getMergeProps.title" :isDetail="isDetail" :showDetailBack="showDetailBack" @close="onClose">
|
||||
<template #titleToolbar>
|
||||
<slot name="titleToolbar"></slot>
|
||||
</template>
|
||||
</DrawerHeader>
|
||||
</template>
|
||||
<template v-else #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
<template #extra>
|
||||
<Tooltip :title="t('component.drawer.cancelText')" placement="bottom">
|
||||
<Icon icon="i-ant-design:close-outlined" class="anticon-close cursor-pointer" @click="onClose" />
|
||||
</Tooltip>
|
||||
</template>
|
||||
<div v-if="widthResize" class="ew-resize" @mousedown="onMousedown"></div>
|
||||
<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" setup name="BasicDrawer">
|
||||
import type { DrawerInstance, DrawerProps } from './typing';
|
||||
import { ref, computed, watch, unref, toRaw, getCurrentInstance, CSSProperties, watchEffect } from 'vue';
|
||||
import { Drawer } from 'ant-design-vue';
|
||||
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
|
||||
import { isFunction, isNumber } from '@jeesite/core/utils/is';
|
||||
import { deepMerge } from '@jeesite/core/utils';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { Icon } from '@jeesite/core/components/Icon';
|
||||
import DrawerFooter from './components/DrawerFooter.vue';
|
||||
import DrawerHeader from './components/DrawerHeader.vue';
|
||||
import { ScrollContainer } from '@jeesite/core/components/Container';
|
||||
import { basicProps } from './props';
|
||||
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
|
||||
import { useAttrs } from '@jeesite/core/hooks/core/useAttrs';
|
||||
import { useBreakpoint } from '@jeesite/core/hooks/event/useBreakpoint';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps(basicProps);
|
||||
const emit = defineEmits(['open-change', 'ok', 'close', 'register', 'update:open']);
|
||||
const attrs = useAttrs();
|
||||
const openRef = ref(false);
|
||||
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
|
||||
|
||||
const { t } = useI18n();
|
||||
const { prefixVar, prefixCls } = useDesign('basic-drawer');
|
||||
const { realWidthRef, screenEnum } = useBreakpoint();
|
||||
|
||||
const drawerInstance: DrawerInstance = {
|
||||
getDrawerProps,
|
||||
setDrawerProps,
|
||||
emitOpen: undefined,
|
||||
};
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
if (instance) {
|
||||
emit('register', drawerInstance, instance.uid);
|
||||
}
|
||||
|
||||
const getMergeProps = computed((): DrawerProps => {
|
||||
return deepMerge(toRaw(props), unref(propsRef));
|
||||
});
|
||||
|
||||
const getWrapClassName = computed(() => {
|
||||
return `${prefixCls} ${props.wrapClassName || ''}`;
|
||||
});
|
||||
|
||||
const getProps = computed((): DrawerProps => {
|
||||
const opt = {
|
||||
placement: 'right',
|
||||
...unref(attrs),
|
||||
...deepMerge(toRaw(props), unref(propsRef)),
|
||||
open: unref(openRef),
|
||||
};
|
||||
opt.title = undefined;
|
||||
const { isDetail, width, class: 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;
|
||||
}
|
||||
} else {
|
||||
opt.class = unref(getWrapClassName);
|
||||
}
|
||||
// 小屏幕直接全屏抽屉
|
||||
if (unref(realWidthRef) < screenEnum.SM) {
|
||||
opt.width = '100%';
|
||||
}
|
||||
return opt as DrawerProps;
|
||||
});
|
||||
|
||||
const getBindValues = computed((): DrawerProps => {
|
||||
const values = {
|
||||
...attrs,
|
||||
...unref(getProps),
|
||||
open: unref(openRef),
|
||||
} as any;
|
||||
if (typeof values?.width === 'string') {
|
||||
let width = Number(values.width);
|
||||
if (!isNaN(width)) values.width = width;
|
||||
}
|
||||
delete values['wrapClassName'];
|
||||
return values;
|
||||
});
|
||||
|
||||
// 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(() => {
|
||||
openRef.value = !!props.open;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unref(openRef),
|
||||
(v) => {
|
||||
emit('open-change', v);
|
||||
emit('update:open', v);
|
||||
instance && drawerInstance.emitOpen?.(v, instance.uid);
|
||||
},
|
||||
{
|
||||
immediate: false,
|
||||
},
|
||||
);
|
||||
|
||||
// Cancel event
|
||||
async function onClose(e: Recordable) {
|
||||
const { closeFunc } = unref(getProps);
|
||||
if (closeFunc && isFunction(closeFunc)) {
|
||||
const res = await closeFunc();
|
||||
openRef.value = !res;
|
||||
return;
|
||||
}
|
||||
openRef.value = false;
|
||||
emit('close', e);
|
||||
}
|
||||
|
||||
function handleOk(e: Event) {
|
||||
emit('ok', e);
|
||||
}
|
||||
|
||||
const onMousedown = function (e) {
|
||||
const wrapper = e.target.closest('.ant-drawer-content-wrapper') as HTMLElement;
|
||||
if (!wrapper) return;
|
||||
wrapper.style.transition = 'none';
|
||||
const w = wrapper.clientWidth;
|
||||
const x = e.clientX;
|
||||
const l = e.target.offsetLeft;
|
||||
let isDown = true;
|
||||
window.onmousemove = (e) => {
|
||||
if (!isDown) {
|
||||
return;
|
||||
}
|
||||
const nl = e.clientX - (x - l);
|
||||
wrapper.style.width = w - nl + 'px';
|
||||
};
|
||||
window.onmouseup = () => {
|
||||
window.onmousemove = null;
|
||||
};
|
||||
};
|
||||
|
||||
function getDrawerProps(): Partial<DrawerProps> {
|
||||
return getProps.value;
|
||||
}
|
||||
|
||||
function setDrawerProps(props: Partial<DrawerProps>): void {
|
||||
if (typeof props.loading != 'undefined') {
|
||||
props.confirmLoading = props.loading;
|
||||
}
|
||||
// Keep the last setDrawerProps
|
||||
// propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
|
||||
propsRef.value = { ...(unref(propsRef) as Recordable), ...props } as Recordable;
|
||||
if (Reflect.has(props, 'open')) {
|
||||
openRef.value = !!props.open;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open: (loading = false) => {
|
||||
setDrawerProps({ open: true, loading });
|
||||
},
|
||||
close: () => {
|
||||
setDrawerProps({ open: false });
|
||||
},
|
||||
loading: () => {
|
||||
setDrawerProps({ loading: true });
|
||||
},
|
||||
closeLoading: () => {
|
||||
setDrawerProps({ loading: false });
|
||||
},
|
||||
confirmLoading: () => {
|
||||
setDrawerProps({ confirmLoading: true });
|
||||
},
|
||||
closeConfirmLoading: () => {
|
||||
setDrawerProps({ confirmLoading: false });
|
||||
},
|
||||
getProps: getDrawerProps,
|
||||
setProps: setDrawerProps,
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@header-height: 60px;
|
||||
@detail-header-height: 40px;
|
||||
@prefix-cls: ~'jeesite-basic-drawer';
|
||||
@prefix-cls-detail: ~'jeesite-basic-drawer__detail';
|
||||
|
||||
.ant-drawer .@{prefix-cls} {
|
||||
overflow: hidden;
|
||||
|
||||
.ew-resize {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 90%;
|
||||
width: 3px;
|
||||
z-index: 1000;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
cursor: ew-resize;
|
||||
background: @border-color-light;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-drawer {
|
||||
&-body {
|
||||
height: calc(100% - @header-height);
|
||||
padding: 0;
|
||||
background-color: @component-background;
|
||||
|
||||
> .scrollbar {
|
||||
> .scrollbar__wrap {
|
||||
> .scrollbar__view {
|
||||
margin: 16px 16px 5px;
|
||||
|
||||
> .ant-form,
|
||||
> .ant-tabs {
|
||||
margin-right: 25px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.jeesite-form-group {
|
||||
.title {
|
||||
margin-right: -12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-weight: normal;
|
||||
|
||||
.anticon {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&-extra {
|
||||
.anticon-close {
|
||||
opacity: 0.6;
|
||||
color: @text-color-base;
|
||||
|
||||
&:hover {
|
||||
color: @error-color;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefix-cls-detail} {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
|
||||
.ant-drawer {
|
||||
&-body {
|
||||
height: calc(100% - @detail-header-height);
|
||||
padding: 0 !important;
|
||||
|
||||
> .scrollbar {
|
||||
> .scrollbar__wrap {
|
||||
> .scrollbar__view {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
width: 100%;
|
||||
height: @detail-header-height;
|
||||
padding: 0;
|
||||
border-top: 1px solid @border-color-base;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&-title {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&-close {
|
||||
height: @detail-header-height;
|
||||
line-height: @detail-header-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
<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">
|
||||
<Icon icon="i-ant-design:close-outlined" />
|
||||
{{ cancelText || (getOkAuth && showOkBtn ? t('common.cancelText') : t('common.closeText')) }}
|
||||
</a-button>
|
||||
<slot name="centerFooter"></slot>
|
||||
<a-button
|
||||
:type="okType"
|
||||
@click="handleOk"
|
||||
v-bind="okButtonProps"
|
||||
class="mr-2"
|
||||
:loading="confirmLoading"
|
||||
v-if="showOkBtn && getOkAuth"
|
||||
>
|
||||
<Icon icon="i-ant-design:check-outlined" />
|
||||
{{ okText || t('common.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 '@jeesite/core/hooks/web/useDesign';
|
||||
import { usePermission } from '@jeesite/core/hooks/web/usePermission';
|
||||
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
|
||||
import { Icon } from '@jeesite/core/components/Icon';
|
||||
|
||||
import { footerProps } from '../props';
|
||||
export default defineComponent({
|
||||
name: 'BasicDrawerFooter',
|
||||
components: { Icon },
|
||||
props: {
|
||||
...footerProps,
|
||||
height: {
|
||||
type: String,
|
||||
default: '60px',
|
||||
},
|
||||
},
|
||||
emits: ['ok', 'close'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
const { hasPermission } = usePermission();
|
||||
const { prefixCls } = useDesign('basic-drawer-footer');
|
||||
|
||||
const getStyle = computed((): CSSProperties => {
|
||||
const heightStr = `${props.height}`;
|
||||
return {
|
||||
height: heightStr,
|
||||
lineHeight: heightStr,
|
||||
};
|
||||
});
|
||||
|
||||
const getOkAuth = computed(() => {
|
||||
return hasPermission(props.okAuth);
|
||||
});
|
||||
|
||||
function handleOk() {
|
||||
emit('ok');
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
return { t, prefixCls, getStyle, getOkAuth, handleOk, handleClose };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'jeesite-basic-drawer-footer';
|
||||
|
||||
.@{prefix-cls} {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
text-align: right;
|
||||
background-color: @component-background;
|
||||
// border-top: 1px solid @border-color-base;
|
||||
z-index: 100; // 设置下,否则 BasicTable 空白图标会覆盖 actions 上边框线
|
||||
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<BasicTitle v-if="!isDetail" :class="prefixCls">
|
||||
<slot name="title"></slot>
|
||||
{{ !$slots.title ? title : '' }}
|
||||
</BasicTitle>
|
||||
|
||||
<div :class="[prefixCls, `${prefixCls}--detail`]" v-else>
|
||||
<span :class="`${prefixCls}__twrap`">
|
||||
<span @click="handleClose" v-if="showDetailBack">
|
||||
<Icon icon="i-ant-design:arrow-left-outlined" :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 } from 'vue';
|
||||
import { Icon } from '@jeesite/core/components/Icon';
|
||||
import { BasicTitle } from '@jeesite/core/components/Basic';
|
||||
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
|
||||
import { propTypes } from '@jeesite/core/utils/propTypes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicDrawerHeader',
|
||||
components: { Icon, BasicTitle },
|
||||
props: {
|
||||
isDetail: propTypes.bool,
|
||||
showDetailBack: propTypes.bool,
|
||||
title: propTypes.string,
|
||||
},
|
||||
emits: ['close'],
|
||||
setup(_, { emit }) {
|
||||
const { prefixCls } = useDesign('basic-drawer-header');
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
return { prefixCls, handleClose };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'jeesite-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>
|
||||
50
web-vue/packages/core/components/Drawer/src/props.ts
Normal file
50
web-vue/packages/core/components/Drawer/src/props.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
export const footerProps = {
|
||||
confirmLoading: { type: Boolean },
|
||||
/**
|
||||
* @description: Show close button
|
||||
*/
|
||||
showCancelBtn: { type: Boolean, default: true },
|
||||
cancelButtonProps: Object as PropType<Recordable>,
|
||||
cancelText: { type: String },
|
||||
/**
|
||||
* @description: Show confirmation button
|
||||
*/
|
||||
showOkBtn: { type: Boolean, default: true },
|
||||
okButtonProps: Object as PropType<Recordable>,
|
||||
okText: { type: String },
|
||||
okType: { type: String, default: 'primary' },
|
||||
okAuth: { type: String },
|
||||
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 },
|
||||
open: { 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 },
|
||||
wrapClassName: { type: String },
|
||||
// 是否允许拖拽调整抽屉宽度
|
||||
widthResize: { type: Boolean, default: true },
|
||||
...footerProps,
|
||||
// eslint check
|
||||
width: { type: [Number, String] },
|
||||
mask: { type: Boolean, default: true },
|
||||
maskStyle: { type: Object },
|
||||
};
|
||||
198
web-vue/packages/core/components/Drawer/src/typing.ts
Normal file
198
web-vue/packages/core/components/Drawer/src/typing.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
|
||||
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
|
||||
import type { ScrollContainerOptions } from '@jeesite/core/components/Container';
|
||||
|
||||
export interface DrawerInstance {
|
||||
getDrawerProps: () => Partial<DrawerProps>;
|
||||
setDrawerProps: (props: Partial<DrawerProps>) => void;
|
||||
emitOpen?: (open: boolean, uid: number) => void;
|
||||
}
|
||||
|
||||
export interface ReturnMethods extends DrawerInstance {
|
||||
openDrawer: <T = any>(open?: boolean, data?: T, openOnSet?: boolean) => void;
|
||||
closeDrawer: () => void;
|
||||
getOpen?: ComputedRef<boolean>;
|
||||
setDrawerData: (data: any) => void;
|
||||
}
|
||||
|
||||
export type RegisterFn = (drawerInstance: DrawerInstance, uuid: number) => void;
|
||||
|
||||
export interface ReturnInnerMethods extends DrawerInstance {
|
||||
closeDrawer: () => void;
|
||||
changeLoading: (loading: boolean) => void;
|
||||
changeOkLoading: (loading: boolean) => void;
|
||||
getOpen?: 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';
|
||||
okAuth: string;
|
||||
/**
|
||||
* The ok button props, follow jsx rules
|
||||
* @type object
|
||||
*/
|
||||
okButtonProps: { props: ButtonProps; on: any };
|
||||
|
||||
/**
|
||||
* The cancel button props, follow jsx rules
|
||||
* @type object
|
||||
*/
|
||||
cancelButtonProps: { props: ButtonProps; on: any };
|
||||
/**
|
||||
* 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;
|
||||
open?: boolean;
|
||||
/**
|
||||
* Built-in ScrollContainer component configuration
|
||||
* @type ScrollContainerOptions
|
||||
*/
|
||||
scrollOptions?: ScrollContainerOptions;
|
||||
closeFunc?: () => Promise<any>;
|
||||
triggerWindowResize?: boolean;
|
||||
/**
|
||||
* Whether a close (x) button is open 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?: string | false | HTMLElement | (() => HTMLElement);
|
||||
|
||||
/**
|
||||
* 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 | any;
|
||||
|
||||
/**
|
||||
* 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';
|
||||
afterOpenChange?: (open?: 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;
|
||||
}
|
||||
export interface DrawerActionType {
|
||||
scrollBottom: () => void;
|
||||
scrollTo: (to: number) => void;
|
||||
getScrollWrap: () => Element | null;
|
||||
}
|
||||
170
web-vue/packages/core/components/Drawer/src/useDrawer.ts
Normal file
170
web-vue/packages/core/components/Drawer/src/useDrawer.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import type {
|
||||
UseDrawerReturnType,
|
||||
DrawerInstance,
|
||||
ReturnMethods,
|
||||
DrawerProps,
|
||||
UseDrawerInnerReturnType,
|
||||
} from './typing';
|
||||
|
||||
import { ref, onUnmounted, unref, getCurrentInstance, reactive, watchEffect, nextTick, toRaw, computed } from 'vue';
|
||||
import { isProdMode } from '@jeesite/core/utils/env';
|
||||
import { isFunction } from '@jeesite/core/utils/is';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
import { error } from '@jeesite/core/utils/log';
|
||||
|
||||
const dataTransfer = reactive<any>({});
|
||||
|
||||
const openData = reactive<{ [key: number]: boolean }>({});
|
||||
|
||||
/**
|
||||
* @description: Applicable to separate drawer and call outside
|
||||
*/
|
||||
export function useDrawer(): UseDrawerReturnType {
|
||||
const drawer = ref<DrawerInstance | null>(null);
|
||||
const loaded = ref<Nullable<boolean>>(false);
|
||||
const uid = ref<number>(0);
|
||||
|
||||
function register(drawerInstance: DrawerInstance, uuid: number) {
|
||||
if (!getCurrentInstance()) {
|
||||
throw new Error('useDrawer() can only be used inside setup() or functional components!');
|
||||
}
|
||||
uid.value = uuid;
|
||||
isProdMode() &&
|
||||
onUnmounted(() => {
|
||||
drawer.value = null;
|
||||
loaded.value = false;
|
||||
dataTransfer[unref(uid)] = null;
|
||||
});
|
||||
if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) return;
|
||||
|
||||
drawer.value = drawerInstance;
|
||||
loaded.value = true;
|
||||
|
||||
drawerInstance.emitOpen = (open: boolean, uid: number) => {
|
||||
openData[uid] = open;
|
||||
};
|
||||
}
|
||||
|
||||
const getInstance = () => {
|
||||
const instance = unref(drawer);
|
||||
if (!instance) {
|
||||
error('useDrawer instance is undefined!');
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
const methods: ReturnMethods = {
|
||||
getDrawerProps: (): Partial<DrawerProps> => {
|
||||
return getInstance()?.getDrawerProps() || {};
|
||||
},
|
||||
|
||||
setDrawerProps: (props: Partial<DrawerProps>): void => {
|
||||
getInstance()?.setDrawerProps(props);
|
||||
},
|
||||
|
||||
getOpen: computed((): boolean => {
|
||||
return openData[~~unref(uid)];
|
||||
}),
|
||||
|
||||
openDrawer: <T = any>(open = true, data?: T, openOnSet = true): void => {
|
||||
getInstance()?.setDrawerProps({
|
||||
open: open,
|
||||
});
|
||||
|
||||
if (!data) return;
|
||||
const id = unref(uid);
|
||||
if (openOnSet) {
|
||||
dataTransfer[id] = null;
|
||||
dataTransfer[id] = toRaw(data);
|
||||
return;
|
||||
}
|
||||
const equal = isEqual(toRaw(dataTransfer[id]), toRaw(data));
|
||||
if (!equal) {
|
||||
dataTransfer[id] = toRaw(data);
|
||||
}
|
||||
},
|
||||
|
||||
closeDrawer: () => {
|
||||
getInstance()?.setDrawerProps({ open: false });
|
||||
},
|
||||
|
||||
setDrawerData: (data: any) => {
|
||||
if (!data) return;
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const id = unref(uid);
|
||||
dataTransfer[id] = null;
|
||||
dataTransfer[id] = toRaw(data);
|
||||
return;
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
return [register, methods];
|
||||
}
|
||||
|
||||
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
|
||||
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
|
||||
const currentInstance = getCurrentInstance();
|
||||
const uidRef = ref<number>(0);
|
||||
|
||||
const getInstance = () => {
|
||||
const instance = unref(drawerInstanceRef);
|
||||
if (!instance) {
|
||||
error('useDrawerInner instance is undefined!');
|
||||
return;
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
const register = (modalInstance: DrawerInstance, uuid: number) => {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
drawerInstanceRef.value = null;
|
||||
});
|
||||
|
||||
uidRef.value = uuid;
|
||||
drawerInstanceRef.value = modalInstance;
|
||||
currentInstance?.emit('register', modalInstance, uuid);
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
const data = dataTransfer[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 });
|
||||
},
|
||||
|
||||
getOpen: computed((): boolean => {
|
||||
return openData[~~unref(uidRef)];
|
||||
}),
|
||||
|
||||
closeDrawer: () => {
|
||||
getInstance()?.setDrawerProps({ open: false });
|
||||
},
|
||||
|
||||
getDrawerProps: (): Partial<DrawerProps> => {
|
||||
return getInstance()?.getDrawerProps() || {};
|
||||
},
|
||||
|
||||
setDrawerProps: (props: Partial<DrawerProps>) => {
|
||||
getInstance()?.setDrawerProps(props);
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
Reference in New Issue
Block a user