feat: add modal and drawer components and examples (#4229)

* feat: add modal component

* feat: add drawer component

* feat: apply new modal and drawer components to the layout

* chore: typo

* feat: add some unit tests
This commit is contained in:
Vben
2024-08-25 23:40:52 +08:00
committed by GitHub
parent edb55b1fc0
commit 20a3868594
96 changed files with 2700 additions and 743 deletions

View File

@@ -63,6 +63,12 @@
},
"examples": {
"title": "Examples",
"modal": {
"title": "Modal"
},
"drawer": {
"title": "Drawer"
},
"ellipsis": {
"title": "EllipsisText"
}

View File

@@ -63,6 +63,12 @@
},
"examples": {
"title": "示例",
"modal": {
"title": "弹窗"
},
"drawer": {
"title": "抽屉"
},
"ellipsis": {
"title": "文本省略"
}

View File

@@ -16,13 +16,29 @@ const routes: RouteRecordRaw[] = [
path: '/examples',
children: [
{
name: 'EllipsisDemo',
path: 'ellipsis',
name: 'EllipsisExample',
path: '/examples/ellipsis',
component: () => import('#/views/examples/ellipsis/index.vue'),
meta: {
title: $t('page.examples.ellipsis.title'),
},
},
{
name: 'ModalExample',
path: '/examples/modal',
component: () => import('#/views/examples/modal/index.vue'),
meta: {
title: $t('page.examples.modal.title'),
},
},
{
name: 'DrawerExample',
path: '/examples/drawer',
component: () => import('#/views/examples/drawer/index.vue'),
meta: {
title: $t('page.examples.drawer.title'),
},
},
],
},
];

View File

@@ -0,0 +1,40 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { Button, message } from 'ant-design-vue';
const [Drawer, drawerApi] = useVbenDrawer({
onCancel() {
drawerApi.close();
},
onConfirm() {
message.info('onConfirm');
// drawerApi.close();
},
});
const list = ref<number[]>([]);
list.value = Array.from({ length: 10 }, (_v, k) => k + 1);
function handleUpdate() {
list.value = Array.from({ length: 6 }, (_v, k) => k + 1);
}
</script>
<template>
<Drawer title="自动计算高度">
<div
v-for="item in list"
:key="item"
class="even:bg-heavy bg-muted flex-center h-[220px] w-full"
>
{{ item }}
</div>
<template #prepend-footer>
<Button type="link" @click="handleUpdate">点击更新数据</Button>
</template>
</Drawer>
</template>

View File

@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { useVbenDrawer } from '@vben/common-ui';
import { message } from 'ant-design-vue';
const [Drawer, drawerApi] = useVbenDrawer({
onCancel() {
drawerApi.close();
},
onConfirm() {
message.info('onConfirm');
// drawerApi.close();
},
onOpenChange(isOpen) {
if (isOpen) {
drawerApi.setState({ loading: true });
setTimeout(() => {
drawerApi.setState({ loading: false });
}, 2000);
}
},
});
</script>
<template>
<Drawer title="基础抽屉示例" title-tooltip="标题提示内容">
<template #extra> extra </template>
base demo
<!-- <template #prepend-footer> slot </template> -->
<!-- <template #append-footer> prepend slot </template> -->
</Drawer>
</template>

View File

@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { useVbenDrawer } from '@vben/common-ui';
import { Button, message } from 'ant-design-vue';
const [Drawer, drawerApi] = useVbenDrawer({
onCancel() {
drawerApi.close();
},
onConfirm() {
message.info('onConfirm');
// drawerApi.close();
},
title: '动态修改配置示例',
});
// const state = drawerApi.useStore();
function handleUpdateTitle() {
drawerApi.setState({ title: '内部动态标题' });
}
</script>
<template>
<Drawer>
<div class="flex-col-center">
<Button class="mb-3" type="primary" @click="handleUpdateTitle()">
内部动态修改标题
</Button>
</div>
</Drawer>
</template>

View File

@@ -0,0 +1,90 @@
<script lang="ts" setup>
import { Page, useVbenDrawer } from '@vben/common-ui';
import { Button, Card } from 'ant-design-vue';
import AutoHeightDemo from './auto-height-demo.vue';
import BaseDemo from './base-demo.vue';
import DynamicDemo from './dynamic-demo.vue';
import SharedDataDemo from './shared-data-demo.vue';
const [BaseDrawer, baseDrawerApi] = useVbenDrawer({
// 链接抽离的组件
connectedComponent: BaseDemo,
});
const [AutoHeightDrawer, autoHeightDrawerApi] = useVbenDrawer({
// 链接抽离的组件
connectedComponent: AutoHeightDemo,
});
const [DynamicDrawer, dynamicDrawerApi] = useVbenDrawer({
connectedComponent: DynamicDemo,
});
const [SharedDataDrawer, sharedDrawerApi] = useVbenDrawer({
connectedComponent: SharedDataDemo,
});
function openBaseDrawer() {
baseDrawerApi.open();
}
function openAutoHeightDrawer() {
autoHeightDrawerApi.open();
}
function openDynamicDrawer() {
dynamicDrawerApi.open();
}
function handleUpdateTitle() {
dynamicDrawerApi.setState({ title: '外部动态标题' });
dynamicDrawerApi.open();
}
function openSharedDrawer() {
sharedDrawerApi.setData({
content: '外部传递的数据 content',
payload: '外部传递的数据 payload',
});
sharedDrawerApi.open();
}
</script>
<template>
<Page
description="抽屉组件通常用于在当前页面上显示一个覆盖层,用以展示重要信息或提供用户交互界面。"
title="抽屉组件示例"
>
<BaseDrawer />
<AutoHeightDrawer />
<DynamicDrawer />
<SharedDataDrawer />
<Card class="mb-4" title="基本使用">
<p class="mb-3">一个基础的抽屉示例</p>
<Button type="primary" @click="openBaseDrawer">打开抽屉</Button>
</Card>
<Card class="mb-4" title="内容高度自适应滚动">
<p class="mb-3">可根据内容自动计算滚动高度</p>
<Button type="primary" @click="openAutoHeightDrawer">打开抽屉</Button>
</Card>
<Card class="mb-4" title="动态配置示例">
<p class="mb-3">通过 setState 动态调整抽屉数据</p>
<Button type="primary" @click="openDynamicDrawer">打开抽屉</Button>
<Button class="ml-2" type="primary" @click="handleUpdateTitle">
从外部修改标题并打开
</Button>
</Card>
<Card class="mb-4" title="内外数据共享示例">
<p class="mb-3">通过共享 sharedData 来进行数据交互</p>
<Button type="primary" @click="openSharedDrawer">
打开抽屉并传递数据
</Button>
</Card>
</Page>
</template>

View File

@@ -0,0 +1,29 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { message } from 'ant-design-vue';
const data = ref();
const [Drawer, drawerApi] = useVbenDrawer({
onCancel() {
drawerApi.close();
},
onConfirm() {
message.info('onConfirm');
// drawerApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
data.value = drawerApi.getData<Record<string, any>>();
}
},
});
</script>
<template>
<Drawer title="数据共享示例">
<div class="flex-col-center">外部传递数据 {{ data }}</div>
</Drawer>
</template>

View File

@@ -13,7 +13,7 @@ const text = ref(longText);
<template>
<Page
description="用于多行文本省略,支持点击展开和自定义内容。"
title="文本省略示例"
title="文本省略组件示例"
>
<Card class="mb-4" title="基本使用">
<EllipsisText :max-width="500">{{ text }}</EllipsisText>

View File

@@ -0,0 +1,40 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Button, message } from 'ant-design-vue';
const [Modal, modalApi] = useVbenModal({
onCancel() {
modalApi.close();
},
onConfirm() {
message.info('onConfirm');
// modalApi.close();
},
});
const list = ref<number[]>([]);
list.value = Array.from({ length: 10 }, (_v, k) => k + 1);
function handleUpdate() {
list.value = Array.from({ length: 6 }, (_v, k) => k + 1);
}
</script>
<template>
<Modal title="自动计算高度">
<div
v-for="item in list"
:key="item"
class="even:bg-heavy bg-muted flex-center h-[220px] w-full"
>
{{ item }}
</div>
<template #prepend-footer>
<Button type="link" @click="handleUpdate">点击更新数据</Button>
</template>
</Modal>
</template>

View File

@@ -0,0 +1,28 @@
<script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
const [Modal, modalApi] = useVbenModal({
onCancel() {
modalApi.close();
},
onConfirm() {
message.info('onConfirm');
// modalApi.close();
},
onOpenChange(isOpen) {
if (isOpen) {
modalApi.setState({ loading: true });
setTimeout(() => {
modalApi.setState({ loading: false });
}, 2000);
}
},
});
</script>
<template>
<Modal class="w-[600px]" title="基础弹窗示例" title-tooltip="标题提示内容">
base demo
</Modal>
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
const [Modal, modalApi] = useVbenModal({
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm() {
message.info('onConfirm');
// modalApi.close();
},
});
</script>
<template>
<Modal title="可拖拽示例"> 鼠标移动到 header 可拖拽弹窗 </Modal>
</template>

View File

@@ -0,0 +1,41 @@
<script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui';
import { Button, message } from 'ant-design-vue';
const [Modal, modalApi] = useVbenModal({
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm() {
message.info('onConfirm');
// modalApi.close();
},
title: '动态修改配置示例',
});
const state = modalApi.useStore();
function handleUpdateTitle() {
modalApi.setState({ title: '内部动态标题' });
}
function handleToggleFullscreen() {
modalApi.setState((prev) => {
return { ...prev, fullscreen: !prev.fullscreen };
});
}
</script>
<template>
<Modal>
<div class="flex-col-center">
<Button class="mb-3" type="primary" @click="handleUpdateTitle()">
内部动态修改标题
</Button>
<Button class="mb-3" type="primary" @click="handleToggleFullscreen()">
{{ state.fullscreen ? '退出全屏' : '打开全屏' }}
</Button>
</div>
</Modal>
</template>

View File

@@ -0,0 +1,104 @@
<script lang="ts" setup>
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Card } from 'ant-design-vue';
import AutoHeightDemo from './auto-height-demo.vue';
import BaseDemo from './base-demo.vue';
import DragDemo from './drag-demo.vue';
import DynamicDemo from './dynamic-demo.vue';
import SharedDataDemo from './shared-data-demo.vue';
const [BaseModal, baseModalApi] = useVbenModal({
// 链接抽离的组件
connectedComponent: BaseDemo,
});
const [AutoHeightModal, autoHeightModalApi] = useVbenModal({
connectedComponent: AutoHeightDemo,
});
const [DragModal, dragModalApi] = useVbenModal({
connectedComponent: DragDemo,
});
const [DynamicModal, dynamicModalApi] = useVbenModal({
connectedComponent: DynamicDemo,
});
const [SharedDataModal, sharedModalApi] = useVbenModal({
connectedComponent: SharedDataDemo,
});
function openBaseModal() {
baseModalApi.open();
}
function openAutoHeightModal() {
autoHeightModalApi.open();
}
function openDargModal() {
dragModalApi.open();
}
function openDynamicModal() {
dynamicModalApi.open();
}
function openSharedModal() {
sharedModalApi.setData({
content: '外部传递的数据 content',
payload: '外部传递的数据 payload',
});
sharedModalApi.open();
}
function handleUpdateTitle() {
dynamicModalApi.setState({ title: '外部动态标题' });
dynamicModalApi.open();
}
</script>
<template>
<Page
description="弹窗组件常用于在不离开当前页面的情况下,显示额外的信息、表单或操作提示。"
title="弹窗组件示例"
>
<BaseModal />
<AutoHeightModal />
<DragModal />
<DynamicModal />
<SharedDataModal />
<Card class="mb-4" title="基本使用">
<p class="mb-3">一个基础的弹窗示例</p>
<Button type="primary" @click="openBaseModal">打开弹窗</Button>
</Card>
<Card class="mb-4" title="内容高度自适应">
<p class="mb-3">可根据内容并自动调整高度</p>
<Button type="primary" @click="openAutoHeightModal">打开弹窗</Button>
</Card>
<Card class="mb-4" title="可拖拽示例">
<p class="mb-3">配置 draggable 可开启拖拽功能</p>
<Button type="primary" @click="openDargModal">打开弹窗</Button>
</Card>
<Card class="mb-4" title="动态配置示例">
<p class="mb-3">通过 setState 动态调整弹窗数据</p>
<Button type="primary" @click="openDynamicModal">打开弹窗</Button>
<Button class="ml-2" type="primary" @click="handleUpdateTitle">
从外部修改标题并打开
</Button>
</Card>
<Card class="mb-4" title="内外数据共享示例">
<p class="mb-3">通过共享 sharedData 来进行数据交互</p>
<Button type="primary" @click="openSharedModal">
打开弹窗并传递数据
</Button>
</Card>
</Page>
</template>

View File

@@ -0,0 +1,29 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
const data = ref();
const [Modal, modalApi] = useVbenModal({
onCancel() {
modalApi.close();
},
onConfirm() {
message.info('onConfirm');
// modalApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
data.value = modalApi.getData<Record<string, any>>();
}
},
});
</script>
<template>
<Modal title="数据共享示例">
<div class="flex-col-center">外部传递数据 {{ data }}</div>
</Modal>
</template>