提交 ebf7c8aa 编写于 作者: V vben

perf(modal-drawer): replace the scrollbar assembly

上级 73cee06d
...@@ -12,6 +12,10 @@ ...@@ -12,6 +12,10 @@
- form: 新增远程下拉`ApiSelect`及示例 - form: 新增远程下拉`ApiSelect`及示例
- form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
### ⚡ Performance Improvements
- 优化`modal``drawer`滚动条组件
### 🐛 Bug Fixes ### 🐛 Bug Fixes
- 修复混合模式下滚动条丢失问题 - 修复混合模式下滚动条丢失问题
...@@ -21,6 +25,7 @@ ...@@ -21,6 +25,7 @@
- 修复路由类型错误 - 修复路由类型错误
- 修复菜单分割时权限失效问题 - 修复菜单分割时权限失效问题
- 关闭多标签页时 iframe 提前加载 - 关闭多标签页时 iframe 提前加载
- 修复`modal``drawer`已知问题
## 2.0.0-rc.14 (2020-12-15) ## 2.0.0-rc.14 (2020-12-15)
......
<template> <template>
<transition-group <transition-group
class="lazy-container" :class="prefixCls"
v-bind="$attrs" v-bind="$attrs"
ref="elRef" ref="elRef"
:name="transitionName" :name="transitionName"
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'; import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import { useDesign } from '/@/hooks/web/useDesign';
interface State { interface State {
isInit: boolean; isInit: boolean;
...@@ -70,6 +71,8 @@ ...@@ -70,6 +71,8 @@
intersectionObserverInstance: null, intersectionObserverInstance: null,
}); });
const { prefixCls } = useDesign('lazy-container');
onMounted(() => { onMounted(() => {
immediateInit(); immediateInit();
initIntersectionObserver(); initIntersectionObserver();
...@@ -129,13 +132,17 @@ ...@@ -129,13 +132,17 @@
} }
return { return {
elRef, elRef,
prefixCls,
...toRefs(state), ...toRefs(state),
}; };
}, },
}); });
</script> </script>
<style lang="less"> <style lang="less">
.lazy-container { @import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-lazy-container';
.@{prefix-cls} {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
......
<template> <template>
<div class="collapse-container p-2"> <div :class="['p-2', prefixCls]">
<CollapseHeader v-bind="$props" :show="show" @expand="handleExpand"> <CollapseHeader v-bind="$props" :prefixCls="prefixCls" :show="show" @expand="handleExpand">
<template #title> <template #title>
<slot name="title" /> <slot name="title" />
</template> </template>
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<CollapseTransition :enable="canExpan"> <CollapseTransition :enable="canExpan">
<Skeleton v-if="loading" /> <Skeleton v-if="loading" />
<div class="collapse-container__body" v-else v-show="show"> <div :class="`${prefixCls}__body`" v-else v-show="show">
<LazyContainer :timeout="lazyTime" v-if="lazy"> <LazyContainer :timeout="lazyTime" v-if="lazy">
<slot /> <slot />
<template #skeleton> <template #skeleton>
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
// hook // hook
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({ export default defineComponent({
name: 'CollapseContainer', name: 'CollapseContainer',
...@@ -64,6 +65,9 @@ ...@@ -64,6 +65,9 @@
}, },
setup(props) { setup(props) {
const show = ref(true); const show = ref(true);
const { prefixCls } = useDesign('collapse-container');
/** /**
* @description: Handling development events * @description: Handling development events
*/ */
...@@ -77,20 +81,20 @@ ...@@ -77,20 +81,20 @@
return { return {
show, show,
handleExpand, handleExpand,
prefixCls,
}; };
}, },
}); });
</script> </script>
<style lang="less"> <style lang="less">
.collapse-container { @import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-collapse-container';
.@{prefix-cls} {
background: #fff; background: #fff;
border-radius: 2px; border-radius: 2px;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
&.no-shadow {
box-shadow: none;
}
&__header { &__header {
display: flex; display: flex;
height: 32px; height: 32px;
......
<template> <template>
<div class="collapse-container__header"> <div :class="`${prefixCls}__header`">
<BasicTitle :helpMessage="$attrs.helpMessage"> <BasicTitle :helpMessage="$attrs.helpMessage">
<template v-if="$attrs.title"> <template v-if="$attrs.title">
{{ $attrs.title }} {{ $attrs.title }}
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
</template> </template>
</BasicTitle> </BasicTitle>
<div class="collapse-container__action"> <div :class="`${prefixCls}__action`">
<slot name="action" /> <slot name="action" />
<BasicArrow v-if="$attrs.canExpan" top :expand="$attrs.show" @click="$emit('expand')" /> <BasicArrow v-if="$attrs.canExpan" top :expand="$attrs.show" @click="$emit('expand')" />
</div> </div>
...@@ -21,5 +21,8 @@ ...@@ -21,5 +21,8 @@
export default defineComponent({ export default defineComponent({
inheritAttrs: false, inheritAttrs: false,
components: { BasicArrow, BasicTitle }, components: { BasicArrow, BasicTitle },
props: {
prefixCls: String,
},
}); });
</script> </script>
...@@ -24,7 +24,7 @@ export default defineComponent({ ...@@ -24,7 +24,7 @@ export default defineComponent({
const getMergeProps = computed(() => { const getMergeProps = computed(() => {
return { return {
...props, ...props,
...(unref(propsRef) as any), ...(unref(propsRef) as Recordable),
} as DescOptions; } as DescOptions;
}); });
......
...@@ -13,7 +13,7 @@ export default { ...@@ -13,7 +13,7 @@ export default {
bordered: propTypes.bool.def(true), bordered: propTypes.bool.def(true),
column: { column: {
type: [Number, Object] as PropType<number | any>, type: [Number, Object] as PropType<number | Recordable>,
default: () => { default: () => {
return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }; return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 };
}, },
......
...@@ -15,7 +15,10 @@ export interface DescItem { ...@@ -15,7 +15,10 @@ export interface DescItem {
span?: number; span?: number;
show?: (...arg: any) => boolean; show?: (...arg: any) => boolean;
// render // render
render?: (val: string, data: any) => VNode | undefined | JSX.Element | Element | string | number; render?: (
val: string,
data: Recordable
) => VNode | undefined | JSX.Element | Element | string | number;
} }
export interface DescOptions extends DescriptionsProps { export interface DescOptions extends DescriptionsProps {
...@@ -30,7 +33,7 @@ export interface DescOptions extends DescriptionsProps { ...@@ -30,7 +33,7 @@ export interface DescOptions extends DescriptionsProps {
* 数据 * 数据
* @type object * @type object
*/ */
data: any; data: Recordable;
/** /**
* Built-in CollapseContainer component configuration * Built-in CollapseContainer component configuration
* @type CollapseContainerOptions * @type CollapseContainerOptions
......
...@@ -19,7 +19,7 @@ export function useDescription(props?: Partial<DescOptions>): UseDescReturnType ...@@ -19,7 +19,7 @@ export function useDescription(props?: Partial<DescOptions>): UseDescReturnType
const methods: DescInstance = { const methods: DescInstance = {
setDescProps: (descProps: Partial<DescOptions>): void => { setDescProps: (descProps: Partial<DescOptions>): void => {
unref(descRef)!.setDescProps(descProps); unref(descRef)?.setDescProps(descProps);
}, },
}; };
......
import { withInstall } from '../util'; import { withInstall } from '../util';
import BasicDrawer from './src/BasicDrawer'; import BasicDrawer from './src/BasicDrawer.vue';
export { BasicDrawer }; export { BasicDrawer };
export * from './src/types'; export * from './src/types';
......
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 { Loading } from '/@/components/Loading';
import { LeftOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { getSlot } from '/@/utils/helper/tsxHelper';
import { isFunction, isNumber } from '/@/utils/is';
import { deepMerge } from '/@/utils';
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
import { basicProps } from './props';
const prefixCls = 'basic-drawer';
export default defineComponent({
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<Nullable<DrawerProps>>>(null);
const { t } = useI18n();
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 getScrollContentStyle = computed(
(): CSSProperties => {
const footerHeight = unref(getFooterHeight);
return {
position: 'relative',
height: `calc(100% - ${footerHeight})`,
overflow: 'auto',
padding: '16px',
paddingBottom: '30px',
};
}
);
const getLoading = computed(() => {
return !!unref(getProps)?.loading;
});
watchEffect(() => {
visibleRef.value = props.visible;
});
watch(
() => visibleRef.value,
(visible) => {
nextTick(() => {
emit('visible-change', visible);
});
},
{
immediate: false,
}
);
// Cancel event
async function onClose(e: ChangeEvent) {
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) || {}, props);
if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible;
}
}
function renderFooter() {
if (slots?.footer) {
return getSlot(slots, 'footer');
}
const {
showCancelBtn,
cancelButtonProps,
cancelText,
showOkBtn,
okType,
okText,
okButtonProps,
confirmLoading,
showFooter,
} = unref(getProps);
if (!showFooter) {
return null;
}
return (
<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);
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>
);
}
const drawerInstance: DrawerInstance = {
setDrawerProps: setDrawerProps,
};
tryTsxEmit((instance) => {
emit('register', drawerInstance, instance.uid);
});
return () => {
return (
<Drawer class={prefixCls} onClose={onClose} {...unref(getBindValues)}>
{{
title: () => renderHeader(),
default: () => (
<>
<div ref={scrollRef} style={unref(getScrollContentStyle)}>
<Loading
absolute
tip={t('component.drawer.loadingText')}
loading={unref(getLoading)}
/>
{getSlot(slots)}
</div>
{renderFooter()}
</>
),
}}
</Drawer>
);
};
},
});
<template>
<Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
<template #title v-if="!$slots.title">
<DrawerHeader
:title="getMergeProps.title"
:isDetail="isDetail"
:showDetailBack="showDetailBack"
@close="onClose"
>
<template #titleToolbar>
<slot name="titleToolbar" />
</template>
</DrawerHeader>
</template>
<ScrollContainer
:style="getScrollContentStyle"
v-loading="getLoading"
:loading-tip="loadingText || t('component.drawer.loadingText')"
>
<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" />
</template>
</DrawerFooter>
</Drawer>
</template>
<script lang="ts">
import type { DrawerInstance, DrawerProps } from './types';
import type { CSSProperties } from 'vue';
import {
defineComponent,
ref,
computed,
watchEffect,
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({
inheritAttrs: false,
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
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 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.wrapClassName = 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),
};
}
);
// 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(() => {
visibleRef.value = props.visible;
});
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) || {}, props);
if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible;
}
}
function handleOk() {
emit('ok');
}
return {
onClose,
t,
prefixCls,
getMergeProps,
getScrollContentStyle,
getProps,
getLoading,
getBindValues,
getFooterHeight,
handleOk,
};
},
});
</script>
<style lang="less">
@import (reference) '../../../design/index.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: @background-color-dark;
.scrollbar__wrap {
padding: 16px !important;
margin-bottom: 0 !important;
}
}
}
.@{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>
<template>
<div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer">
<template v-if="!$slots.footer">
<slot name="insertFooter" />
<a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn">
{{ cancelText }}
</a-button>
<slot name="centerFooter" />
<a-button
:type="okType"
@click="handleOk"
v-bind="okButtonProps"
class="mr-2"
:loading="confirmLoading"
v-if="showOkBtn"
>
{{ okText }}
</a-button>
<slot name="appendFooter" />
</template>
<template v-else>
<slot name="footer" />
</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">
@import (reference) '../../../../design/index.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: #fff;
border-top: 1px solid @border-color-base;
> * {
margin-right: 8px;
}
}
</style>
<template>
<BasicTitle v-if="!isDetail" :class="prefixCls">
<slot name="title" />
{{ !$slots.title ? title : '' }}
</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" />
</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { BasicTitle } from '/@/components/Basic';
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'BasicDrawerHeader',
components: { BasicTitle, ArrowLeftOutlined },
props: {
isDetail: propTypes.bool,
showDetailBack: propTypes.bool,
title: propTypes.string,
},
setup(_, { emit }) {
const { prefixCls } = useDesign('basic-drawer-header');
function handleClose() {
emit('close');
}
return { prefixCls, handleClose };
},
});
</script>
<style lang="less">
@import (reference) '../../../../design/index.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;
}
}
</style>
@import (reference) '../../../design/index.less';
@header-height: 40px;
@footer-height: 60px;
.basic-drawer {
.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: @background-color-dark;
.scrollbar__wrap {
padding: 16px;
}
}
&__detail {
position: absolute;
&-header {
height: 100%;
}
.ant-drawer-header {
width: 100%;
height: @header-height;
padding: 0;
border-top: 1px solid @border-color-base;
box-sizing: border-box;
}
.ant-drawer-title {
height: 100%;
}
.ant-drawer-close {
height: @header-height;
line-height: @header-height;
}
.scrollbar__wrap {
padding: 0;
}
}
&__footer {
position: absolute;
bottom: 0;
width: 100%;
height: @footer-height;
padding: 0 26px;
line-height: @footer-height;
text-align: right;
background: #fff;
border-top: 1px solid @border-color-base;
}
}
...@@ -10,13 +10,13 @@ export const footerProps = { ...@@ -10,13 +10,13 @@ export const footerProps = {
* @description: Show close button * @description: Show close button
*/ */
showCancelBtn: propTypes.bool.def(true), showCancelBtn: propTypes.bool.def(true),
cancelButtonProps: Object as PropType<any>, cancelButtonProps: Object as PropType<Recordable>,
cancelText: propTypes.string.def(t('component.drawer.cancelText')), cancelText: propTypes.string.def(t('component.drawer.cancelText')),
/** /**
* @description: Show confirmation button * @description: Show confirmation button
*/ */
showOkBtn: propTypes.bool.def(true), showOkBtn: propTypes.bool.def(true),
okButtonProps: propTypes.any, okButtonProps: Object as PropType<Recordable>,
okText: propTypes.string.def(t('component.drawer.okText')), okText: propTypes.string.def(t('component.drawer.okText')),
okType: propTypes.string.def('primary'), okType: propTypes.string.def('primary'),
showFooter: propTypes.bool, showFooter: propTypes.bool,
...@@ -28,6 +28,7 @@ export const footerProps = { ...@@ -28,6 +28,7 @@ export const footerProps = {
export const basicProps = { export const basicProps = {
isDetail: propTypes.bool, isDetail: propTypes.bool,
title: propTypes.string.def(''), title: propTypes.string.def(''),
loadingText: propTypes.string,
showDetailBack: propTypes.bool.def(true), showDetailBack: propTypes.bool.def(true),
visible: propTypes.bool, visible: propTypes.bool,
loading: propTypes.bool, loading: propTypes.bool,
......
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
import type { CSSProperties, VNodeChild } from 'vue'; import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
import type { ScrollContainerOptions } from '/@/components/Container/index'; import type { ScrollContainerOptions } from '/@/components/Container/index';
export interface DrawerInstance { export interface DrawerInstance {
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void; setDrawerProps: (props: Partial<DrawerProps> | boolean) => void;
emitVisible?: (visible: boolean, uid: number) => void;
} }
export interface ReturnMethods extends DrawerInstance { export interface ReturnMethods extends DrawerInstance {
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void; openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void;
getVisible?: ComputedRef<boolean>;
} }
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void; export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void;
...@@ -16,6 +18,7 @@ export interface ReturnInnerMethods extends DrawerInstance { ...@@ -16,6 +18,7 @@ export interface ReturnInnerMethods extends DrawerInstance {
closeDrawer: () => void; closeDrawer: () => void;
changeLoading: (loading: boolean) => void; changeLoading: (loading: boolean) => void;
changeOkLoading: (loading: boolean) => void; changeOkLoading: (loading: boolean) => void;
getVisible?: ComputedRef<boolean>;
} }
export type UseDrawerReturnType = [RegisterFn, ReturnMethods]; export type UseDrawerReturnType = [RegisterFn, ReturnMethods];
......
...@@ -6,22 +6,32 @@ import type { ...@@ -6,22 +6,32 @@ import type {
UseDrawerInnerReturnType, UseDrawerInnerReturnType,
} from './types'; } from './types';
import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw } from 'vue'; import {
ref,
getCurrentInstance,
unref,
reactive,
watchEffect,
nextTick,
toRaw,
computed,
} from 'vue';
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; import { tryOnUnmounted, isInSetup } from '/@/utils/helper/vueHelper';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { error } from '/@/utils/log';
const dataTransferRef = reactive<any>({}); const dataTransferRef = reactive<any>({});
const visibleData = reactive<{ [key: number]: boolean }>({});
/** /**
* @description: Applicable to separate drawer and call outside * @description: Applicable to separate drawer and call outside
*/ */
export function useDrawer(): UseDrawerReturnType { export function useDrawer(): UseDrawerReturnType {
if (!getCurrentInstance()) { isInSetup();
throw new Error('Please put useDrawer function in the setup function!');
}
const drawerRef = ref<DrawerInstance | null>(null); const drawerRef = ref<DrawerInstance | null>(null);
const loadedRef = ref<Nullable<boolean>>(false); const loadedRef = ref<Nullable<boolean>>(false);
...@@ -41,23 +51,31 @@ export function useDrawer(): UseDrawerReturnType { ...@@ -41,23 +51,31 @@ export function useDrawer(): UseDrawerReturnType {
uidRef.value = uuid; uidRef.value = uuid;
drawerRef.value = drawerInstance; drawerRef.value = drawerInstance;
loadedRef.value = true; loadedRef.value = true;
drawerInstance.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible;
};
} }
const getInstance = () => { const getInstance = () => {
const instance = unref(drawerRef); const instance = unref(drawerRef);
if (!instance) { if (!instance) {
throw new Error('instance is undefined!'); error('useDrawer instance is undefined!');
} }
return instance; return instance;
}; };
const methods: ReturnMethods = { const methods: ReturnMethods = {
setDrawerProps: (props: Partial<DrawerProps>): void => { setDrawerProps: (props: Partial<DrawerProps>): void => {
getInstance().setDrawerProps(props); getInstance()?.setDrawerProps(props);
}, },
getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)];
}),
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => { openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
getInstance().setDrawerProps({ getInstance()?.setDrawerProps({
visible: visible, visible: visible,
}); });
if (!data) return; if (!data) return;
...@@ -79,17 +97,18 @@ export function useDrawer(): UseDrawerReturnType { ...@@ -79,17 +97,18 @@ export function useDrawer(): UseDrawerReturnType {
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null); const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
const currentInstall = getCurrentInstance(); const currentInstance = getCurrentInstance();
const uidRef = ref<string>(''); const uidRef = ref<string>('');
if (!currentInstall) { if (!currentInstance) {
throw new Error('useDrawerInner instance is undefined!'); error('useDrawerInner instance is undefined!');
} }
const getInstance = () => { const getInstance = () => {
const instance = unref(drawerInstanceRef); const instance = unref(drawerInstanceRef);
if (!instance) { if (!instance) {
throw new Error('useDrawerInner instance is undefined!'); error('useDrawerInner instance is undefined!');
return;
} }
return instance; return instance;
}; };
...@@ -102,7 +121,7 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { ...@@ -102,7 +121,7 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
uidRef.value = uuid; uidRef.value = uuid;
drawerInstanceRef.value = modalInstance; drawerInstanceRef.value = modalInstance;
currentInstall.emit('register', modalInstance, uuid); currentInstance?.emit('register', modalInstance, uuid);
}; };
watchEffect(() => { watchEffect(() => {
...@@ -118,19 +137,22 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { ...@@ -118,19 +137,22 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
register, register,
{ {
changeLoading: (loading = true) => { changeLoading: (loading = true) => {
getInstance().setDrawerProps({ loading }); getInstance()?.setDrawerProps({ loading });
}, },
changeOkLoading: (loading = true) => { changeOkLoading: (loading = true) => {
getInstance().setDrawerProps({ confirmLoading: loading }); getInstance()?.setDrawerProps({ confirmLoading: loading });
}, },
getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)];
}),
closeDrawer: () => { closeDrawer: () => {
getInstance().setDrawerProps({ visible: false }); getInstance()?.setDrawerProps({ visible: false });
}, },
setDrawerProps: (props: Partial<DrawerProps>) => { setDrawerProps: (props: Partial<DrawerProps>) => {
getInstance().setDrawerProps(props); getInstance()?.setDrawerProps(props);
}, },
}, },
]; ];
......
<template> <template>
<a-col <a-col v-bind="actionColOpt" :style="{ textAlign: 'right' }" v-if="showActionButtonGroup">
v-bind="actionColOpt"
class="mb-2"
:style="{ textAlign: 'right' }"
v-if="showActionButtonGroup"
>
<FormItem> <FormItem>
<slot name="resetBefore" /> <slot name="resetBefore" />
<Button <Button
......
import './src/index.less'; import './src/index.less';
import { withInstall } from '../util'; import { withInstall } from '../util';
import BasicModal from './src/BasicModal'; import BasicModal from './src/BasicModal.vue';
withInstall(BasicModal); withInstall(BasicModal);
export { BasicModal }; export { BasicModal };
export { useModalContext } from './src/useModalContext'; export { useModalContext } from './src/hooks/useModalContext';
export { useModal, useModalInner } from './src/useModal'; export { useModal, useModalInner } from './src/hooks/useModal';
export * from './src/types'; export * from './src/types';
import type { ModalProps, ModalMethods } from './types';
import { defineComponent, computed, ref, watch, unref, watchEffect, toRef } from 'vue';
import Modal from './Modal';
import { Button } from '/@/components/Button';
import ModalWrapper from './ModalWrapper';
import { BasicTitle } from '/@/components/Basic';
import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { getSlot, extendSlots } from '/@/utils/helper/tsxHelper';
import { isFunction } from '/@/utils/is';
import { deepMerge } from '/@/utils';
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
import { basicProps } from './props';
import { useFullScreen } from './useFullScreen';
export default defineComponent({
name: 'BasicModal',
props: basicProps,
emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register'],
setup(props, { slots, emit, attrs }) {
const visibleRef = ref(false);
const propsRef = ref<Partial<ModalProps> | null>(null);
const modalWrapperRef = ref<ComponentRef>(null);
// modal Bottom and top height
const extHeightRef = ref(0);
// Unexpanded height of the popup
// Custom title component: get title
const getMergeProps = computed(
(): ModalProps => {
return {
...props,
...(unref(propsRef) as any),
};
}
);
const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
modalWrapperRef,
extHeightRef,
wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
});
// modal component does not need title
const getProps = computed(
(): ModalProps => {
const opt = {
...unref(getMergeProps),
visible: unref(visibleRef),
title: undefined,
};
return {
...opt,
wrapClassName: unref(getWrapClassName),
};
}
);
const getModalBindValue = computed((): any => {
return { ...attrs, ...unref(getProps) };
});
watchEffect(() => {
visibleRef.value = !!props.visible;
});
watch(
() => unref(visibleRef),
(v) => {
emit('visible-change', v);
},
{
immediate: false,
}
);
/**
* @description: 渲染标题
*/
function renderTitle() {
const { helpMessage } = unref(getProps);
const { title } = unref(getMergeProps);
return (
<BasicTitle helpMessage={helpMessage}>
{() => (slots.title ? getSlot(slots, 'title') : title)}
</BasicTitle>
);
}
// 取消事件
async function handleCancel(e: Event) {
e?.stopPropagation();
if (props.closeFunc && isFunction(props.closeFunc)) {
const isClose: boolean = await props.closeFunc();
visibleRef.value = !isClose;
return;
}
visibleRef.value = false;
emit('cancel');
}
/**
* @description: 设置modal参数
*/
function setModalProps(props: Partial<ModalProps>): void {
// Keep the last setModalProps
propsRef.value = deepMerge(unref(propsRef) || {}, props);
if (!Reflect.has(props, 'visible')) return;
visibleRef.value = !!props.visible;
}
function renderContent() {
type OmitWrapperType = Omit<
ModalProps,
'fullScreen' | 'modalFooterHeight' | 'visible' | 'loading'
>;
const { useWrapper, loading, wrapperProps } = unref(getProps);
if (!useWrapper) return getSlot(slots);
const showFooter = props.footer !== undefined && !props.footer ? 0 : undefined;
return (
<ModalWrapper
footerOffset={props.wrapperFooterOffset}
fullScreen={unref(fullScreenRef)}
ref={modalWrapperRef}
loading={loading}
visible={unref(visibleRef)}
modalFooterHeight={showFooter}
{...((wrapperProps as unknown) as OmitWrapperType)}
onGetExtHeight={(height: number) => {
extHeightRef.value = height;
}}
onHeightChange={(height: string) => {
emit('height-change', height);
}}
>
{() => getSlot(slots)}
</ModalWrapper>
);
}
// 底部按钮自定义实现,
function renderFooter() {
const {
showCancelBtn,
cancelButtonProps,
cancelText,
showOkBtn,
okType,
okText,
okButtonProps,
confirmLoading,
} = unref(getProps);
return (
<>
{getSlot(slots, 'insertFooter')}
{showCancelBtn && (
<Button {...cancelButtonProps} onClick={handleCancel}>
{() => cancelText}
</Button>
)}
{getSlot(slots, 'centerdFooter')}
{showOkBtn && (
<Button
type={okType as any}
loading={confirmLoading}
onClick={() => {
emit('ok');
}}
{...okButtonProps}
>
{() => okText}
</Button>
)}
{getSlot(slots, 'appendFooter')}
</>
);
}
/**
* @description: 关闭按钮
*/
function renderClose() {
const { canFullscreen } = unref(getProps);
const fullScreen = unref(fullScreenRef) ? (
<FullscreenExitOutlined role="full" onClick={handleFullScreen} />
) : (
<FullscreenOutlined role="close" onClick={handleFullScreen} />
);
const cls = [
'custom-close-icon',
{
'can-full': canFullscreen,
},
];
return (
<div class={cls}>
{canFullscreen && fullScreen}
<CloseOutlined onClick={handleCancel} />
</div>
);
}
const modalMethods: ModalMethods = {
setModalProps,
};
tryTsxEmit((instance) => {
emit('register', modalMethods, instance.uid);
});
return () => (
<Modal onCancel={handleCancel} {...unref(getModalBindValue)}>
{{
footer: () => renderFooter(),
closeIcon: () => renderClose(),
title: () => renderTitle(),
...extendSlots(slots, ['default']),
default: () => renderContent(),
}}
</Modal>
);
},
});
<template>
<Modal @cancel="handleCancel" v-bind="getBindValue">
<template #closeIcon v-if="!$slots.closeIcon">
<ModalClose
:canFullscreen="getProps.canFullscreen"
:fullScreen="fullScreenRef"
@cancel="handleCancel"
@fullscreen="handleFullScreen"
/>
</template>
<template #title v-if="!$slots.title">
<ModalHeader :helpMessage="getProps.helpMessage" :title="getMergeProps.title" />
</template>
<template #footer v-if="!$slots.footer">
<ModalFooter v-bind="getProps" @ok="handleOk" @cancel="handleCancel" />
</template>
<ModalWrapper
:useWrapper="getProps.useWrapper"
:footerOffset="wrapperFooterOffset"
:fullScreen="fullScreenRef"
ref="modalWrapperRef"
:loading="getProps.loading"
:visible="visibleRef"
:modalFooterHeight="footer !== undefined && !footer ? 0 : undefined"
v-bind="omit(getProps.wrapperProps, 'visible')"
@ext-height="handleExtHeight"
@height-change="handleHeightChange"
>
<slot />
</ModalWrapper>
</Modal>
</template>
<script lang="ts">
import type { ModalProps, ModalMethods } from './types';
import {
defineComponent,
computed,
ref,
watch,
unref,
watchEffect,
toRef,
getCurrentInstance,
} from 'vue';
import Modal from './components/Modal';
import ModalWrapper from './components/ModalWrapper.vue';
import ModalClose from './components/ModalClose.vue';
import ModalFooter from './components/ModalFooter.vue';
import ModalHeader from './components/ModalHeader.vue';
import { isFunction } from '/@/utils/is';
import { deepMerge } from '/@/utils';
import { basicProps } from './props';
import { useFullScreen } from './hooks/useModalFullScreen';
import { omit } from 'lodash-es';
export default defineComponent({
name: 'BasicModal',
components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader },
props: basicProps,
emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register'],
setup(props, { emit, attrs }) {
const visibleRef = ref(false);
const propsRef = ref<Partial<ModalProps> | null>(null);
const modalWrapperRef = ref<ComponentRef>(null);
// modal Bottom and top height
const extHeightRef = ref(0);
const modalMethods: ModalMethods = {
setModalProps,
emitVisible: undefined,
};
const instance = getCurrentInstance();
if (instance) {
emit('register', modalMethods, instance.uid);
}
// Custom title component: get title
const getMergeProps = computed(
(): ModalProps => {
return {
...props,
...(unref(propsRef) as any),
};
}
);
const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
modalWrapperRef,
extHeightRef,
wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
});
// modal component does not need title
const getProps = computed(
(): ModalProps => {
const opt = {
...unref(getMergeProps),
visible: unref(visibleRef),
title: undefined,
};
return {
...opt,
wrapClassName: unref(getWrapClassName),
};
}
);
const getBindValue = computed((): any => {
return { ...attrs, ...unref(getProps) };
});
watchEffect(() => {
visibleRef.value = !!props.visible;
});
watch(
() => unref(visibleRef),
(v) => {
emit('visible-change', v);
instance && modalMethods.emitVisible?.(v, instance.uid);
},
{
immediate: false,
}
);
// 取消事件
async function handleCancel(e: Event) {
e?.stopPropagation();
if (props.closeFunc && isFunction(props.closeFunc)) {
const isClose: boolean = await props.closeFunc();
visibleRef.value = !isClose;
return;
}
visibleRef.value = false;
emit('cancel');
}
/**
* @description: 设置modal参数
*/
function setModalProps(props: Partial<ModalProps>): void {
// Keep the last setModalProps
propsRef.value = deepMerge(unref(propsRef) || {}, props);
if (!Reflect.has(props, 'visible')) return;
visibleRef.value = !!props.visible;
}
function handleOk() {
emit('ok');
}
function handleHeightChange(height: string) {
emit('height-change', height);
}
function handleExtHeight(height: number) {
extHeightRef.value = height;
}
return {
handleCancel,
getBindValue,
getProps,
handleFullScreen,
fullScreenRef,
getMergeProps,
handleOk,
visibleRef,
omit,
modalWrapperRef,
handleExtHeight,
handleHeightChange,
};
},
});
</script>
import type { ModalWrapperProps } from './types';
import type { CSSProperties } from 'vue';
import {
defineComponent,
computed,
ref,
watchEffect,
unref,
watch,
onMounted,
nextTick,
onUnmounted,
} from 'vue';
import { Spin } from 'ant-design-vue';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { getSlot } from '/@/utils/helper/tsxHelper';
import { useElResize } from '/@/hooks/event/useElResize';
import { propTypes } from '/@/utils/propTypes';
import { createModalContext } from './useModalContext';
export default defineComponent({
name: 'ModalWrapper',
props: {
loading: propTypes.bool,
modalHeaderHeight: propTypes.number.def(50),
modalFooterHeight: propTypes.number.def(54),
minHeight: propTypes.number.def(200),
footerOffset: propTypes.number.def(0),
visible: propTypes.bool,
fullScreen: propTypes.bool,
},
emits: ['heightChange', 'getExtHeight'],
setup(props: ModalWrapperProps, { slots, emit }) {
const wrapperRef = ref<ElRef>(null);
const spinRef = ref<ComponentRef>(null);
const realHeightRef = ref(0);
let stopElResizeFn: Fn = () => {};
useWindowSizeFn(setModalHeight);
createModalContext({
redoModalHeight: setModalHeight,
});
const wrapStyle = computed(
(): CSSProperties => {
return {
minHeight: `${props.minHeight}px`,
height: `${unref(realHeightRef)}px`,
overflow: 'auto',
};
}
);
watchEffect(() => {
setModalHeight();
});
watch(
() => props.fullScreen,
(v) => {
!v && setModalHeight();
}
);
onMounted(() => {
const { modalHeaderHeight, modalFooterHeight } = props;
emit('getExtHeight', modalHeaderHeight + modalFooterHeight);
listenElResize();
});
onUnmounted(() => {
stopElResizeFn && stopElResizeFn();
});
async function setModalHeight() {
// 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
// 加上这个,就必须在使用的时候传递父级的visible
if (!props.visible) return;
const wrapperRefDom = unref(wrapperRef);
if (!wrapperRefDom) return;
const bodyDom = wrapperRefDom.parentElement;
if (!bodyDom) return;
bodyDom.style.padding = '0';
await nextTick();
try {
const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
if (!modalDom) return;
const modalRect = getComputedStyle(modalDom).top;
const modalTop = Number.parseInt(modalRect);
let maxHeight =
window.innerHeight -
modalTop * 2 +
(props.footerOffset! || 0) -
props.modalFooterHeight -
props.modalHeaderHeight;
// 距离顶部过进会出现滚动条
if (modalTop < 40) {
maxHeight -= 26;
}
await nextTick();
const spinEl = unref(spinRef);
if (!spinEl) return;
const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement;
if (!spinContainerEl) return;
const realHeight = spinContainerEl.scrollHeight;
if (props.fullScreen) {
realHeightRef.value =
window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight;
} else {
realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30;
}
emit('heightChange', unref(realHeightRef));
nextTick(() => {
const el = spinEl.$el;
if (el) {
el.style.height = `${unref(realHeightRef)}px`;
}
});
} catch (error) {
console.log(error);
}
}
function listenElResize() {
const wrapper = unref(wrapperRef);
if (!wrapper) return;
const container = wrapper.querySelector('.ant-spin-container');
if (!container) return;
const [start, stop] = useElResize(container, () => {
setModalHeight();
});
stopElResizeFn = stop;
start();
}
return () => {
return (
<div ref={wrapperRef} style={unref(wrapStyle)}>
<Spin ref={spinRef} spinning={props.loading}>
{() => getSlot(slots)}
</Spin>
</div>
);
};
},
});
import { Modal } from 'ant-design-vue'; import { Modal } from 'ant-design-vue';
import { defineComponent, toRefs } from 'vue'; import { defineComponent, toRefs, unref } from 'vue';
import { basicProps } from './props'; import { basicProps } from '../props';
import { useModalDragMove } from './useModalDrag'; import { useModalDragMove } from '../hooks/useModalDrag';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { extendSlots } from '/@/utils/helper/tsxHelper'; import { extendSlots } from '/@/utils/helper/tsxHelper';
export default defineComponent({ export default defineComponent({
name: 'Modal', name: 'Modal',
inheritAttrs: false, inheritAttrs: false,
props: basicProps, props: basicProps,
setup(props, { attrs, slots }) { setup(props, { slots }) {
const { visible, draggable, destroyOnClose } = toRefs(props); const { visible, draggable, destroyOnClose } = toRefs(props);
const attrs = useAttrs();
useModalDragMove({ useModalDragMove({
visible, visible,
destroyOnClose, destroyOnClose,
...@@ -18,7 +19,8 @@ export default defineComponent({ ...@@ -18,7 +19,8 @@ export default defineComponent({
}); });
return () => { return () => {
const propsData = { ...attrs, ...props } as any; const propsData = { ...unref(attrs), ...props } as Recordable;
return <Modal {...propsData}>{extendSlots(slots)}</Modal>; return <Modal {...propsData}>{extendSlots(slots)}</Modal>;
}; };
}, },
......
<template>
<div :class="getClass">
<template v-if="canFullscreen">
<FullscreenExitOutlined role="full" @click="handleFullScreen" v-if="fullScreen" />
<FullscreenOutlined role="close" @click="handleFullScreen" v-else />
</template>
<CloseOutlined @click="handleCancel" />
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'ModalClose',
components: { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined },
props: {
canFullscreen: propTypes.bool.def(true),
fullScreen: propTypes.bool,
},
emits: ['cancel', 'fullscreen'],
setup(props, { emit }) {
const { prefixCls } = useDesign('basic-modal-close');
const getClass = computed(() => {
return [
prefixCls,
`${prefixCls}--custom`,
{
[`${prefixCls}--can-full`]: props.canFullscreen,
},
];
});
function handleCancel() {
emit('cancel');
}
function handleFullScreen(e: Event) {
e?.stopPropagation();
e?.preventDefault();
emit('fullscreen');
}
return {
getClass,
prefixCls,
handleCancel,
handleFullScreen,
};
},
});
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-basic-modal-close';
.@{prefix-cls} {
display: flex;
height: 95%;
align-items: center;
> span {
margin-left: 48px;
font-size: 16px;
}
&--can-full {
> span {
margin-left: 12px;
}
}
&:not(&--can-full) {
> span:nth-child(1) {
&:hover {
font-weight: 700;
}
}
}
& span:nth-child(1) {
display: inline-block;
padding: 10px;
&:hover {
color: @primary-color;
}
}
& span:nth-child(2) {
&:hover {
color: @error-color;
}
}
}
</style>
<template>
<div>
<slot name="insertFooter" />
<a-button v-bind="cancelButtonProps" @click="handleCancel" v-if="showCancelBtn">
{{ cancelText }}
</a-button>
<slot name="centerFooter" />
<a-button
:type="okType"
@click="handleOk"
:loading="confirmLoading"
v-bind="okButtonProps"
v-if="showOkBtn"
>
{{ okText }}
</a-button>
<slot name="appendFooter" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { basicProps } from '../props';
export default defineComponent({
name: 'BasicModalFooter',
props: basicProps,
emits: ['ok', 'cancel'],
setup(_, { emit }) {
function handleOk() {
emit('ok');
}
function handleCancel() {
emit('cancel');
}
return { handleOk, handleCancel };
},
});
</script>
<template>
<BasicTitle :helpMessage="helpMessage">
{{ title }}
</BasicTitle>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import { BasicTitle } from '/@/components/Basic';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'BasicModalHeader',
components: { BasicTitle },
props: {
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
},
title: propTypes.string,
},
});
</script>
<template>
<ScrollContainer ref="wrapperRef" :style="wrapStyle">
<div ref="spinRef" :style="spinStyle" v-loading="loading" :loading-tip="loadingTip">
<slot />
</div>
</ScrollContainer>
</template>
<script lang="ts">
import type { ModalWrapperProps } from '../types';
import type { CSSProperties } from 'vue';
import {
defineComponent,
computed,
ref,
watchEffect,
unref,
watch,
onMounted,
nextTick,
onUnmounted,
} from 'vue';
import { Spin } from 'ant-design-vue';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { ScrollContainer } from '/@/components/Container';
// import { useElResize } from '/@/hooks/event/useElResize';
import { propTypes } from '/@/utils/propTypes';
import { createModalContext } from '../hooks/useModalContext';
export default defineComponent({
name: 'ModalWrapper',
components: { Spin, ScrollContainer },
props: {
loading: propTypes.bool,
useWrapper: propTypes.bool.def(true),
modalHeaderHeight: propTypes.number.def(50),
modalFooterHeight: propTypes.number.def(54),
minHeight: propTypes.number.def(200),
footerOffset: propTypes.number.def(0),
visible: propTypes.bool,
fullScreen: propTypes.bool,
loadingTip: propTypes.string,
},
emits: ['height-change', 'ext-height'],
setup(props: ModalWrapperProps, { emit }) {
const wrapperRef = ref<ComponentRef>(null);
const spinRef = ref<ElRef>(null);
const realHeightRef = ref(0);
let stopElResizeFn: Fn = () => {};
useWindowSizeFn(setModalHeight);
createModalContext({
redoModalHeight: setModalHeight,
});
const wrapStyle = computed(
(): CSSProperties => {
return {
minHeight: `${props.minHeight}px`,
height: `${unref(realHeightRef)}px`,
// overflow: 'auto',
};
}
);
const spinStyle = computed(
(): CSSProperties => {
return {
// padding 28
height: `${unref(realHeightRef) - 28}px`,
};
}
);
watchEffect(() => {
props.useWrapper && setModalHeight();
});
watch(
() => props.fullScreen,
() => {
setTimeout(() => {
setModalHeight();
}, 0);
}
);
onMounted(() => {
const { modalHeaderHeight, modalFooterHeight } = props;
emit('ext-height', modalHeaderHeight + modalFooterHeight);
// listenElResize();
});
onUnmounted(() => {
stopElResizeFn && stopElResizeFn();
});
async function setModalHeight() {
// 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
// 加上这个,就必须在使用的时候传递父级的visible
if (!props.visible) return;
const wrapperRefDom = unref(wrapperRef);
if (!wrapperRefDom) return;
const bodyDom = wrapperRefDom.$el.parentElement;
if (!bodyDom) return;
bodyDom.style.padding = '0';
await nextTick();
try {
const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
if (!modalDom) return;
const modalRect = getComputedStyle(modalDom).top;
const modalTop = Number.parseInt(modalRect);
let maxHeight =
window.innerHeight -
modalTop * 2 +
(props.footerOffset! || 0) -
props.modalFooterHeight -
props.modalHeaderHeight;
// 距离顶部过进会出现滚动条
if (modalTop < 40) {
maxHeight -= 26;
}
await nextTick();
const spinEl = unref(spinRef);
if (!spinEl) return;
const realHeight = spinEl.scrollHeight;
if (props.fullScreen) {
realHeightRef.value =
window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight;
} else {
realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30;
}
emit('height-change', unref(realHeightRef));
} catch (error) {
console.log(error);
}
}
return { wrapStyle, wrapperRef, spinRef, spinStyle };
},
});
</script>
...@@ -4,7 +4,7 @@ import type { ...@@ -4,7 +4,7 @@ import type {
ModalProps, ModalProps,
ReturnMethods, ReturnMethods,
UseModalInnerReturnType, UseModalInnerReturnType,
} from './types'; } from '../types';
import { import {
ref, ref,
...@@ -19,16 +19,18 @@ import { ...@@ -19,16 +19,18 @@ import {
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; import { tryOnUnmounted, isInSetup } from '/@/utils/helper/vueHelper';
import { error } from '/@/utils/log';
import { computed } from 'vue';
const dataTransferRef = reactive<any>({}); const dataTransferRef = reactive<any>({});
const visibleData = reactive<{ [key: number]: boolean }>({});
/** /**
* @description: Applicable to independent modal and call outside * @description: Applicable to independent modal and call outside
*/ */
export function useModal(): UseModalReturnType { export function useModal(): UseModalReturnType {
if (!getCurrentInstance()) { isInSetup();
throw new Error('Please put useModal function in the setup function!');
}
const modalRef = ref<Nullable<ModalMethods>>(null); const modalRef = ref<Nullable<ModalMethods>>(null);
const loadedRef = ref<Nullable<boolean>>(false); const loadedRef = ref<Nullable<boolean>>(false);
const uidRef = ref<string>(''); const uidRef = ref<string>('');
...@@ -45,23 +47,29 @@ export function useModal(): UseModalReturnType { ...@@ -45,23 +47,29 @@ export function useModal(): UseModalReturnType {
if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return; if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return;
modalRef.value = modalMethod; modalRef.value = modalMethod;
modalMethod.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible;
};
} }
const getInstance = () => { const getInstance = () => {
const instance = unref(modalRef); const instance = unref(modalRef);
if (!instance) { if (!instance) {
throw new Error('instance is undefined!'); error('useModal instance is undefined!');
} }
return instance; return instance;
}; };
const methods: ReturnMethods = { const methods: ReturnMethods = {
setModalProps: (props: Partial<ModalProps>): void => { setModalProps: (props: Partial<ModalProps>): void => {
getInstance().setModalProps(props); getInstance()?.setModalProps(props);
}, },
getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)];
}),
openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => { openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => {
getInstance().setModalProps({ getInstance()?.setModalProps({
visible: visible, visible: visible,
}); });
...@@ -83,20 +91,16 @@ export function useModal(): UseModalReturnType { ...@@ -83,20 +91,16 @@ export function useModal(): UseModalReturnType {
export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
const modalInstanceRef = ref<Nullable<ModalMethods>>(null); const modalInstanceRef = ref<Nullable<ModalMethods>>(null);
const currentInstall = getCurrentInstance(); const currentInstance = getCurrentInstance();
const uidRef = ref<string>(''); const uidRef = ref<string>('');
if (!currentInstall) {
throw new Error('instance is undefined!');
}
// currentInstall.type.emits = [...currentInstall.type.emits, 'register']; // currentInstall.type.emits = [...currentInstall.type.emits, 'register'];
// Object.assign(currentInstall.type.emits, ['register']); // Object.assign(currentInstall.type.emits, ['register']);
const getInstance = () => { const getInstance = () => {
const instance = unref(modalInstanceRef); const instance = unref(modalInstanceRef);
if (!instance) { if (!instance) {
throw new Error('instance is undefined!'); error('useModalInner instance is undefined!');
} }
return instance; return instance;
}; };
...@@ -108,7 +112,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { ...@@ -108,7 +112,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
}); });
uidRef.value = uuid; uidRef.value = uuid;
modalInstanceRef.value = modalInstance; modalInstanceRef.value = modalInstance;
currentInstall.emit('register', modalInstance, uuid); currentInstance?.emit('register', modalInstance, uuid);
}; };
watchEffect(() => { watchEffect(() => {
...@@ -124,19 +128,22 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { ...@@ -124,19 +128,22 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
register, register,
{ {
changeLoading: (loading = true) => { changeLoading: (loading = true) => {
getInstance().setModalProps({ loading }); getInstance()?.setModalProps({ loading });
}, },
getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)];
}),
changeOkLoading: (loading = true) => { changeOkLoading: (loading = true) => {
getInstance().setModalProps({ confirmLoading: loading }); getInstance()?.setModalProps({ confirmLoading: loading });
}, },
closeModal: () => { closeModal: () => {
getInstance().setModalProps({ visible: false }); getInstance()?.setModalProps({ visible: false });
}, },
setModalProps: (props: Partial<ModalProps>) => { setModalProps: (props: Partial<ModalProps>) => {
getInstance().setModalProps(props); getInstance()?.setModalProps(props);
}, },
}, },
]; ];
......
...@@ -21,9 +21,12 @@ ...@@ -21,9 +21,12 @@
width: 520px; width: 520px;
padding-bottom: 0; padding-bottom: 0;
.ant-spin-nested-loading { .scroll-container {
padding: 16px; padding: 14px;
} }
// .ant-spin-nested-loading {
// padding: 16px;
// }
&-title { &-title {
font-size: 16px; font-size: 16px;
...@@ -35,46 +38,6 @@ ...@@ -35,46 +38,6 @@
} }
} }
.custom-close-icon {
display: flex;
height: 95%;
align-items: center;
> span {
margin-left: 48px;
font-size: 16px;
}
&.can-full {
> span {
margin-left: 12px;
}
}
&:not(.can-full) {
> span:nth-child(1) {
&:hover {
font-weight: 700;
}
}
}
& span:nth-child(1) {
display: inline-block;
padding: 10px;
&:hover {
color: @primary-color;
}
}
& span:nth-child(2) {
&:hover {
color: @error-color;
}
}
}
.ant-modal-body { .ant-modal-body {
padding: 0; padding: 0;
} }
...@@ -96,8 +59,6 @@ ...@@ -96,8 +59,6 @@
} }
&-footer { &-footer {
// padding: 10px 26px 26px 16px;
button + button { button + button {
margin-left: 10px; margin-left: 10px;
} }
......
import type { PropType } from 'vue'; import type { PropType, CSSProperties } from 'vue';
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes'; import { propTypes, VueNode } from '/@/utils/propTypes';
import type { ModalWrapperProps } from './types';
const { t } = useI18n(); const { t } = useI18n();
export const modalProps = { export const modalProps = {
...@@ -26,6 +27,7 @@ export const basicProps = Object.assign({}, modalProps, { ...@@ -26,6 +27,7 @@ export const basicProps = Object.assign({}, modalProps, {
// Whether to setting wrapper // Whether to setting wrapper
useWrapper: propTypes.bool.def(true), useWrapper: propTypes.bool.def(true),
loading: propTypes.bool, loading: propTypes.bool,
loadingTip: propTypes.string,
/** /**
* @description: Show close button * @description: Show close button
*/ */
...@@ -35,65 +37,44 @@ export const basicProps = Object.assign({}, modalProps, { ...@@ -35,65 +37,44 @@ export const basicProps = Object.assign({}, modalProps, {
*/ */
showOkBtn: propTypes.bool.def(true), showOkBtn: propTypes.bool.def(true),
wrapperProps: Object as PropType<any>, wrapperProps: Object as PropType<Partial<ModalWrapperProps>>,
afterClose: Function as PropType<() => Promise<any>>, afterClose: Function as PropType<() => Promise<VueNode>>,
bodyStyle: Object as PropType<any>, bodyStyle: Object as PropType<CSSProperties>,
closable: { closable: propTypes.bool.def(true),
type: Boolean as PropType<boolean>,
default: true,
},
closeIcon: Object as PropType<any>, closeIcon: Object as PropType<VueNode>,
confirmLoading: Boolean as PropType<boolean>, confirmLoading: propTypes.bool,
destroyOnClose: Boolean as PropType<boolean>, destroyOnClose: propTypes.bool,
footer: Object as PropType<any>, footer: Object as PropType<VueNode>,
getContainer: Function as PropType<() => any>, getContainer: Function as PropType<() => any>,
mask: { mask: propTypes.bool.def(true),
type: Boolean as PropType<boolean>,
default: true,
},
maskClosable: { maskClosable: propTypes.bool.def(true),
type: Boolean as PropType<boolean>, keyboard: propTypes.bool.def(true),
default: true,
},
keyboard: {
type: Boolean as PropType<boolean>,
default: true,
},
maskStyle: Object as PropType<any>, maskStyle: Object as PropType<CSSProperties>,
okType: { okType: propTypes.string.def('primary'),
type: String as PropType<string>,
default: 'primary',
},
okButtonProps: Object as PropType<ButtonProps>, okButtonProps: Object as PropType<ButtonProps>,
cancelButtonProps: Object as PropType<ButtonProps>, cancelButtonProps: Object as PropType<ButtonProps>,
title: { title: propTypes.string,
type: String as PropType<string>,
},
visible: Boolean as PropType<boolean>, visible: propTypes.bool,
width: [String, Number] as PropType<string | number>, width: [String, Number] as PropType<string | number>,
wrapClassName: { wrapClassName: propTypes.string,
type: String as PropType<string>,
},
zIndex: { zIndex: propTypes.number,
type: Number as PropType<number>,
},
}); });
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
import type { CSSProperties, VNodeChild } from 'vue'; import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
/** /**
* @description: 弹窗对外暴露的方法 * @description: 弹窗对外暴露的方法
*/ */
export interface ModalMethods { export interface ModalMethods {
setModalProps: (props: Partial<ModalProps>) => void; setModalProps: (props: Partial<ModalProps>) => void;
emitVisible?: (visible: boolean, uid: number) => void;
} }
export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void; export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void;
export interface ReturnMethods extends ModalMethods { export interface ReturnMethods extends ModalMethods {
openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void; openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void;
getVisible?: ComputedRef<boolean>;
} }
export type UseModalReturnType = [RegisterFn, ReturnMethods]; export type UseModalReturnType = [RegisterFn, ReturnMethods];
...@@ -19,6 +21,7 @@ export interface ReturnInnerMethods extends ModalMethods { ...@@ -19,6 +21,7 @@ export interface ReturnInnerMethods extends ModalMethods {
closeModal: () => void; closeModal: () => void;
changeLoading: (loading: boolean) => void; changeLoading: (loading: boolean) => void;
changeOkLoading: (loading: boolean) => void; changeOkLoading: (loading: boolean) => void;
getVisible?: ComputedRef<boolean>;
} }
export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]; export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods];
...@@ -38,6 +41,7 @@ export interface ModalProps { ...@@ -38,6 +41,7 @@ export interface ModalProps {
useWrapper: boolean; useWrapper: boolean;
loading: boolean; loading: boolean;
loadingTip?: string;
wrapperProps: Omit<ModalWrapperProps, 'loading'>; wrapperProps: Omit<ModalWrapperProps, 'loading'>;
...@@ -193,4 +197,5 @@ export interface ModalWrapperProps { ...@@ -193,4 +197,5 @@ export interface ModalWrapperProps {
minHeight: number; minHeight: number;
visible: boolean; visible: boolean;
fullScreen: boolean; fullScreen: boolean;
useWrapper: boolean;
} }
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'; import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue';
import type { Ref } from 'vue';
interface Params { interface Params {
excludeListeners?: boolean; excludeListeners?: boolean;
excludeKeys?: string[]; excludeKeys?: string[];
...@@ -12,7 +12,7 @@ export function entries<T>(obj: Hash<T>): [string, T][] { ...@@ -12,7 +12,7 @@ export function entries<T>(obj: Hash<T>): [string, T][] {
return Object.keys(obj).map((key: string) => [key, obj[key]]); return Object.keys(obj).map((key: string) => [key, obj[key]]);
} }
export function useAttrs(params: Params = {}) { export function useAttrs(params: Params = {}): Ref<Recordable> | {} {
const instance = getCurrentInstance(); const instance = getCurrentInstance();
if (!instance) return {}; if (!instance) return {};
......
...@@ -37,7 +37,7 @@ const setting: ProjectConfig = { ...@@ -37,7 +37,7 @@ const setting: ProjectConfig = {
showLogo: true, showLogo: true,
// Whether to show footer // Whether to show footer
showFooter: true, showFooter: false,
// locale setting // locale setting
locale: { locale: {
......
import { CSSProperties, VNodeChild } from 'vue'; import { CSSProperties, VNodeChild } from 'vue';
import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types';
type VueNode = VNodeChild | JSX.Element; export type VueNode = VNodeChild | JSX.Element;
type PropTypes = VueTypesInterface & { type PropTypes = VueTypesInterface & {
readonly style: VueTypeValidableDef<CSSProperties>; readonly style: VueTypeValidableDef<CSSProperties>;
......
<template> <template>
<BasicDrawer v-bind="$attrs" title="Modal Title" width="50%" showFooter> <BasicDrawer v-bind="$attrs" title="Modal Title" width="50%" showFooter @ok="handleOk">
<p class="h-20" v-for="index in 40" :key="index">根据屏幕高度自适应</p> <p class="h-20" v-for="index in 40" :key="index">根据屏幕高度自适应</p>
<template #insertFooter>
<a-button> btn</a-button>
</template>
<template #centerFooter>
<a-button> btn2</a-button>
</template>
<template #appendFooter>
<a-button> btn3</a-button>
</template>
<!-- <template #footer>
<a-button> customerFooter</a-button>
</template> -->
</BasicDrawer> </BasicDrawer>
</template> </template>
<script lang="ts"> <script lang="ts">
...@@ -9,7 +23,13 @@ ...@@ -9,7 +23,13 @@
export default defineComponent({ export default defineComponent({
components: { BasicDrawer }, components: { BasicDrawer },
setup() { setup() {
return {}; return {
handleOk: () => {
console.log('=====================');
console.log('ok');
console.log('======================');
},
};
}, },
}); });
</script> </script>
<template> <template>
<BasicDrawer v-bind="$attrs" :isDetail="true" title="Drawer Title5"> <BasicDrawer v-bind="$attrs" :isDetail="true" title="Drawer Title5">
<p class="h-20">Content Message</p> <p class="h-20">Content Message</p>
<template #titleToolbar> toolbar </template>
</BasicDrawer> </BasicDrawer>
</template> </template>
<script lang="ts"> <script lang="ts">
...@@ -8,6 +9,5 @@ ...@@ -8,6 +9,5 @@
import { BasicDrawer } from '/@/components/Drawer'; import { BasicDrawer } from '/@/components/Drawer';
export default defineComponent({ export default defineComponent({
components: { BasicDrawer }, components: { BasicDrawer },
setup() {},
}); });
</script> </script>
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
<Alert message="使用 useDrawer 进行抽屉操作" show-icon /> <Alert message="使用 useDrawer 进行抽屉操作" show-icon />
<a-button type="primary" class="my-4" @click="openDrawerLoading">打开Drawer</a-button> <a-button type="primary" class="my-4" @click="openDrawerLoading">打开Drawer</a-button>
<Alert message="内外同时同时显示隐藏" show-icon /> <Alert message="内外同时控制显示隐藏" show-icon />
<a-button type="primary" class="my-4" @click="openDrawer2">打开Drawer</a-button> <a-button type="primary" class="my-4" @click="openDrawer2(true)">打开Drawer</a-button>
<Alert message="自适应高度/显示footer" show-icon /> <Alert message="自适应高度/显示footer" show-icon />
<a-button type="primary" class="my-4" @click="openDrawer3">打开Drawer</a-button> <a-button type="primary" class="my-4" @click="openDrawer3(true)">打开Drawer</a-button>
<Alert <Alert
message="内外数据交互,外部通过 transferModalData 发送,内部通过 receiveDrawerDataRef 接收。该数据具有响应式" message="内外数据交互,外部通过 transferModalData 发送,内部通过 receiveDrawerDataRef 接收。该数据具有响应式"
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
/> />
<a-button type="primary" class="my-4" @click="send">打开Drawer并传递数据</a-button> <a-button type="primary" class="my-4" @click="send">打开Drawer并传递数据</a-button>
<Alert message="详情页模式" show-icon /> <Alert message="详情页模式" show-icon />
<a-button type="primary" class="my-4" @click="openDrawer5">打开详情Drawer</a-button> <a-button type="primary" class="my-4" @click="openDrawer5(true)">打开详情Drawer</a-button>
<Drawer1 @register="register1" /> <Drawer1 @register="register1" />
<Drawer2 @register="register2" /> <Drawer2 @register="register2" />
<Drawer3 @register="register3" /> <Drawer3 @register="register3" />
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<a-input placeholder="请输入" /> <a-input placeholder="请输入" />
</CollapseContainer> </CollapseContainer>
<CollapseContainer class="mt-4 px-4" title="标签页操作"> <CollapseContainer class="mt-4" title="标签页操作">
<a-button class="mr-2" @click="closeAll">关闭所有</a-button> <a-button class="mr-2" @click="closeAll">关闭所有</a-button>
<a-button class="mr-2" @click="closeLeft">关闭左侧</a-button> <a-button class="mr-2" @click="closeLeft">关闭左侧</a-button>
<a-button class="mr-2" @click="closeRight">关闭右侧</a-button> <a-button class="mr-2" @click="closeRight">关闭右侧</a-button>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册