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:
@@ -63,6 +63,12 @@
|
||||
},
|
||||
"examples": {
|
||||
"title": "Examples",
|
||||
"modal": {
|
||||
"title": "Modal"
|
||||
},
|
||||
"drawer": {
|
||||
"title": "Drawer"
|
||||
},
|
||||
"ellipsis": {
|
||||
"title": "EllipsisText"
|
||||
}
|
||||
|
@@ -63,6 +63,12 @@
|
||||
},
|
||||
"examples": {
|
||||
"title": "示例",
|
||||
"modal": {
|
||||
"title": "弹窗"
|
||||
},
|
||||
"drawer": {
|
||||
"title": "抽屉"
|
||||
},
|
||||
"ellipsis": {
|
||||
"title": "文本省略"
|
||||
}
|
||||
|
@@ -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'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
40
playground/src/views/examples/drawer/auto-height-demo.vue
Normal file
40
playground/src/views/examples/drawer/auto-height-demo.vue
Normal 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>
|
32
playground/src/views/examples/drawer/base-demo.vue
Normal file
32
playground/src/views/examples/drawer/base-demo.vue
Normal 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>
|
31
playground/src/views/examples/drawer/dynamic-demo.vue
Normal file
31
playground/src/views/examples/drawer/dynamic-demo.vue
Normal 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>
|
90
playground/src/views/examples/drawer/index.vue
Normal file
90
playground/src/views/examples/drawer/index.vue
Normal 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>
|
29
playground/src/views/examples/drawer/shared-data-demo.vue
Normal file
29
playground/src/views/examples/drawer/shared-data-demo.vue
Normal 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>
|
@@ -13,7 +13,7 @@ const text = ref(longText);
|
||||
<template>
|
||||
<Page
|
||||
description="用于多行文本省略,支持点击展开和自定义内容。"
|
||||
title="文本省略示例"
|
||||
title="文本省略组件示例"
|
||||
>
|
||||
<Card class="mb-4" title="基本使用">
|
||||
<EllipsisText :max-width="500">{{ text }}</EllipsisText>
|
||||
|
40
playground/src/views/examples/modal/auto-height-demo.vue
Normal file
40
playground/src/views/examples/modal/auto-height-demo.vue
Normal 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>
|
28
playground/src/views/examples/modal/base-demo.vue
Normal file
28
playground/src/views/examples/modal/base-demo.vue
Normal 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>
|
19
playground/src/views/examples/modal/drag-demo.vue
Normal file
19
playground/src/views/examples/modal/drag-demo.vue
Normal 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>
|
41
playground/src/views/examples/modal/dynamic-demo.vue
Normal file
41
playground/src/views/examples/modal/dynamic-demo.vue
Normal 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>
|
104
playground/src/views/examples/modal/index.vue
Normal file
104
playground/src/views/examples/modal/index.vue
Normal 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>
|
29
playground/src/views/examples/modal/shared-data-demo.vue
Normal file
29
playground/src/views/examples/modal/shared-data-demo.vue
Normal 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>
|
Reference in New Issue
Block a user