feat: add preset alert, confirm, prompt components that can be simple called (#5843)
* feat: add preset alert, confirm, prompt components that can be simple called * fix: type definepull/78/MERGE
parent
0e3abc2b53
commit
44138f578f
|
@ -168,6 +168,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
|||
link: 'common-ui/vben-api-component',
|
||||
text: 'ApiComponent Api组件包装器',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-alert',
|
||||
text: 'Alert 轻量提示框',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-modal',
|
||||
text: 'Modal 模态框',
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben Alert 轻量提示框
|
||||
|
||||
框架提供的一些用于轻量提示的弹窗,仅使用js代码即可快速动态创建提示而不需要在template写任何代码。
|
||||
|
||||
::: info 应用场景
|
||||
|
||||
Alert提供的功能与Modal类似,但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求,请使用VbenModal
|
||||
|
||||
:::
|
||||
|
||||
::: tip README
|
||||
|
||||
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
使用 `alert` 创建只有一个确认按钮的提示框。
|
||||
|
||||
<DemoPreview dir="demos/vben-alert/alert" />
|
||||
|
||||
使用 `confirm` 创建有确认和取消按钮的提示框。
|
||||
|
||||
<DemoPreview dir="demos/vben-alert/confirm" />
|
||||
|
||||
使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。
|
||||
|
||||
<DemoPreview dir="demos/vben-alert/prompt" />
|
||||
|
||||
## 类型说明
|
||||
|
||||
```ts
|
||||
/** 预置的图标类型 */
|
||||
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||
|
||||
export type AlertProps = {
|
||||
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||
beforeClose?: () => boolean | Promise<boolean | undefined> | undefined;
|
||||
/** 边框 */
|
||||
bordered?: boolean;
|
||||
/** 取消按钮的标题 */
|
||||
cancelText?: string;
|
||||
/** 是否居中显示 */
|
||||
centered?: boolean;
|
||||
/** 确认按钮的标题 */
|
||||
confirmText?: string;
|
||||
/** 弹窗容器的额外样式 */
|
||||
containerClass?: string;
|
||||
/** 弹窗提示内容 */
|
||||
content: Component | string;
|
||||
/** 弹窗内容的额外样式 */
|
||||
contentClass?: string;
|
||||
/** 弹窗的图标(在标题的前面) */
|
||||
icon?: Component | IconType;
|
||||
/** 是否显示取消按钮 */
|
||||
showCancel?: boolean;
|
||||
/** 弹窗标题 */
|
||||
title?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 函数签名
|
||||
* alert和confirm的函数签名相同。
|
||||
* confirm默认会显示取消按钮,而alert默认只有一个按钮
|
||||
* */
|
||||
export function alert(options: AlertProps): Promise<void>;
|
||||
export function alert(
|
||||
message: string,
|
||||
options?: Partial<AlertProps>,
|
||||
): Promise<void>;
|
||||
export function alert(
|
||||
message: string,
|
||||
title?: string,
|
||||
options?: Partial<AlertProps>,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 弹出输入框的函数签名。
|
||||
* 参数beforeClose会传入用户当前输入的值
|
||||
* component指定接受用户输入的组件,默认为Input
|
||||
* componentProps 为输入组件设置的属性数据
|
||||
* defaultValue 默认的值
|
||||
* modelPropName 输入组件的值属性名称。默认为modelValue
|
||||
*/
|
||||
export async function prompt<T = any>(
|
||||
options: Omit<AlertProps, 'beforeClose'> & {
|
||||
beforeClose?: (
|
||||
val: T,
|
||||
) => boolean | Promise<boolean | undefined> | undefined;
|
||||
component?: Component;
|
||||
componentProps?: Recordable<any>;
|
||||
defaultValue?: T;
|
||||
modelPropName?: string;
|
||||
},
|
||||
): Promise<T | undefined>;
|
||||
```
|
|
@ -0,0 +1,31 @@
|
|||
<script lang="ts" setup>
|
||||
import { h } from 'vue';
|
||||
|
||||
import { alert, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import { Empty } from 'ant-design-vue';
|
||||
|
||||
function showAlert() {
|
||||
alert('This is an alert message');
|
||||
}
|
||||
|
||||
function showIconAlert() {
|
||||
alert({
|
||||
content: 'This is an alert message with icon',
|
||||
icon: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
function showCustomAlert() {
|
||||
alert({
|
||||
content: h(Empty, { description: '什么都没有' }),
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex gap-4">
|
||||
<VbenButton @click="showAlert">Alert</VbenButton>
|
||||
<VbenButton @click="showIconAlert">Alert With Icon</VbenButton>
|
||||
<VbenButton @click="showCustomAlert">Alert With Custom Content</VbenButton>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,39 @@
|
|||
<script lang="ts" setup>
|
||||
import { alert, confirm, VbenButton } from '@vben/common-ui';
|
||||
|
||||
function showConfirm() {
|
||||
confirm('This is an alert message')
|
||||
.then(() => {
|
||||
alert('Confirmed');
|
||||
})
|
||||
.catch(() => {
|
||||
alert('Canceled');
|
||||
});
|
||||
}
|
||||
|
||||
function showIconConfirm() {
|
||||
confirm({
|
||||
content: 'This is an alert message with icon',
|
||||
icon: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
function showAsyncConfirm() {
|
||||
confirm({
|
||||
beforeClose() {
|
||||
return new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
},
|
||||
content: 'This is an alert message with async confirm',
|
||||
icon: 'success',
|
||||
}).then(() => {
|
||||
alert('Confirmed');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex gap-4">
|
||||
<VbenButton @click="showConfirm">Confirm</VbenButton>
|
||||
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
|
||||
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts" setup>
|
||||
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import { VbenSelect } from '@vben-core/shadcn-ui';
|
||||
|
||||
function showPrompt() {
|
||||
prompt({
|
||||
content: '请输入一些东西',
|
||||
})
|
||||
.then((val) => {
|
||||
alert(`已收到你的输入:${val}`);
|
||||
})
|
||||
.catch(() => {
|
||||
alert('Canceled');
|
||||
});
|
||||
}
|
||||
|
||||
function showSelectPrompt() {
|
||||
prompt({
|
||||
component: VbenSelect,
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: 'Option 1', value: 'option1' },
|
||||
{ label: 'Option 2', value: 'option2' },
|
||||
{ label: 'Option 3', value: 'option3' },
|
||||
],
|
||||
placeholder: '请选择',
|
||||
},
|
||||
content: 'This is an alert message with icon',
|
||||
icon: 'question',
|
||||
}).then((val) => {
|
||||
alert(`你选择的是${val}`);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex gap-4">
|
||||
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
||||
<VbenButton @click="showSelectPrompt">Confirm With Select</VbenButton>
|
||||
</div>
|
||||
</template>
|
|
@ -15,8 +15,10 @@ export {
|
|||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
Circle,
|
||||
CircleAlert,
|
||||
CircleCheckBig,
|
||||
CircleHelp,
|
||||
CircleX,
|
||||
Copy,
|
||||
CornerDownLeft,
|
||||
Ellipsis,
|
||||
|
|
|
@ -6,6 +6,7 @@ export const messages: Record<Locale, Record<string, string>> = {
|
|||
collapse: 'Collapse',
|
||||
confirm: 'Confirm',
|
||||
expand: 'Expand',
|
||||
prompt: 'Prompt',
|
||||
reset: 'Reset',
|
||||
submit: 'Submit',
|
||||
},
|
||||
|
@ -14,6 +15,7 @@ export const messages: Record<Locale, Record<string, string>> = {
|
|||
collapse: '收起',
|
||||
confirm: '确认',
|
||||
expand: '展开',
|
||||
prompt: '提示',
|
||||
reset: '重置',
|
||||
submit: '提交',
|
||||
},
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
import type { Component } from 'vue';
|
||||
|
||||
import type { Recordable } from '@vben-core/typings';
|
||||
|
||||
import type { AlertProps } from './alert';
|
||||
|
||||
import { h, ref, render } from 'vue';
|
||||
|
||||
import { useSimpleLocale } from '@vben-core/composables';
|
||||
import { Input } from '@vben-core/shadcn-ui';
|
||||
import { isFunction, isString } from '@vben-core/shared/utils';
|
||||
|
||||
import Alert from './alert.vue';
|
||||
|
||||
const alerts = ref<Array<{ container: HTMLElement; instance: Component }>>([]);
|
||||
|
||||
const { $t } = useSimpleLocale();
|
||||
|
||||
export function vbenAlert(options: AlertProps): Promise<void>;
|
||||
export function vbenAlert(
|
||||
message: string,
|
||||
options?: Partial<AlertProps>,
|
||||
): Promise<void>;
|
||||
export function vbenAlert(
|
||||
message: string,
|
||||
title?: string,
|
||||
options?: Partial<AlertProps>,
|
||||
): Promise<void>;
|
||||
|
||||
export function vbenAlert(
|
||||
arg0: AlertProps | string,
|
||||
arg1?: Partial<AlertProps> | string,
|
||||
arg2?: Partial<AlertProps>,
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options: AlertProps = isString(arg0)
|
||||
? {
|
||||
content: arg0,
|
||||
}
|
||||
: { ...arg0 };
|
||||
if (arg1) {
|
||||
if (isString(arg1)) {
|
||||
options.title = arg1;
|
||||
} else if (!isString(arg1)) {
|
||||
// 如果第二个参数是对象,则合并到选项中
|
||||
Object.assign(options, arg1);
|
||||
}
|
||||
}
|
||||
|
||||
if (arg2 && !isString(arg2)) {
|
||||
Object.assign(options, arg2);
|
||||
}
|
||||
// 创建容器元素
|
||||
const container = document.createElement('div');
|
||||
document.body.append(container);
|
||||
|
||||
// 创建一个引用,用于在回调中访问实例
|
||||
const alertRef = { container, instance: null as any };
|
||||
|
||||
const props: AlertProps & Recordable<any> = {
|
||||
onClosed: (isConfirm: boolean) => {
|
||||
// 移除组件实例以及创建的所有dom(恢复页面到打开前的状态)
|
||||
// 从alerts数组中移除该实例
|
||||
alerts.value = alerts.value.filter((item) => item !== alertRef);
|
||||
|
||||
// 从DOM中移除容器
|
||||
render(null, container);
|
||||
if (container.parentNode) {
|
||||
container.remove();
|
||||
}
|
||||
|
||||
// 解析 Promise,传递用户操作结果
|
||||
if (isConfirm) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error('dialog cancelled'));
|
||||
}
|
||||
},
|
||||
...options,
|
||||
open: true,
|
||||
title: options.title ?? $t.value('prompt'),
|
||||
};
|
||||
|
||||
// 创建Alert组件的VNode
|
||||
const vnode = h(Alert, props);
|
||||
|
||||
// 渲染组件到容器
|
||||
render(vnode, container);
|
||||
|
||||
// 保存组件实例引用
|
||||
alertRef.instance = vnode.component?.proxy as Component;
|
||||
|
||||
// 将实例和容器添加到alerts数组中
|
||||
alerts.value.push(alertRef);
|
||||
});
|
||||
}
|
||||
|
||||
export function vbenConfirm(options: AlertProps): Promise<void>;
|
||||
export function vbenConfirm(
|
||||
message: string,
|
||||
options?: Partial<AlertProps>,
|
||||
): Promise<void>;
|
||||
export function vbenConfirm(
|
||||
message: string,
|
||||
title?: string,
|
||||
options?: Partial<AlertProps>,
|
||||
): Promise<void>;
|
||||
|
||||
export function vbenConfirm(
|
||||
arg0: AlertProps | string,
|
||||
arg1?: Partial<AlertProps> | string,
|
||||
arg2?: Partial<AlertProps>,
|
||||
): Promise<void> {
|
||||
const defaultProps: Partial<AlertProps> = {
|
||||
showCancel: true,
|
||||
};
|
||||
if (!arg1) {
|
||||
return isString(arg0)
|
||||
? vbenAlert(arg0, defaultProps)
|
||||
: vbenAlert({ ...defaultProps, ...arg0 });
|
||||
} else if (!arg2) {
|
||||
return isString(arg1)
|
||||
? vbenAlert(arg0 as string, arg1, defaultProps)
|
||||
: vbenAlert(arg0 as string, { ...defaultProps, ...arg1 });
|
||||
}
|
||||
return vbenAlert(arg0 as string, arg1 as string, {
|
||||
...defaultProps,
|
||||
...arg2,
|
||||
});
|
||||
}
|
||||
|
||||
export async function vbenPrompt<T = any>(
|
||||
options: Omit<AlertProps, 'beforeClose'> & {
|
||||
beforeClose?: (
|
||||
val: T,
|
||||
) => boolean | Promise<boolean | undefined> | undefined;
|
||||
component?: Component;
|
||||
componentProps?: Recordable<any>;
|
||||
defaultValue?: T;
|
||||
modelPropName?: string;
|
||||
},
|
||||
): Promise<T | undefined> {
|
||||
const {
|
||||
component: _component,
|
||||
componentProps: _componentProps,
|
||||
content,
|
||||
defaultValue,
|
||||
modelPropName: _modelPropName,
|
||||
...delegated
|
||||
} = options;
|
||||
const contents: Component[] = [];
|
||||
const modelValue = ref<T | undefined>(defaultValue);
|
||||
if (isString(content)) {
|
||||
contents.push(h('span', content));
|
||||
} else {
|
||||
contents.push(content);
|
||||
}
|
||||
const componentProps = _componentProps || {};
|
||||
const modelPropName = _modelPropName || 'modelValue';
|
||||
componentProps[modelPropName] = modelValue.value;
|
||||
componentProps[`onUpdate:${modelPropName}`] = (val: any) => {
|
||||
modelValue.value = val;
|
||||
};
|
||||
const componentRef = h(_component || Input, componentProps);
|
||||
contents.push(componentRef);
|
||||
const props: AlertProps & Recordable<any> = {
|
||||
...delegated,
|
||||
async beforeClose() {
|
||||
if (delegated.beforeClose) {
|
||||
return await delegated.beforeClose(modelValue.value);
|
||||
}
|
||||
},
|
||||
content: h(
|
||||
'div',
|
||||
{ class: 'flex flex-col gap-2' },
|
||||
{ default: () => contents },
|
||||
),
|
||||
onOpened() {
|
||||
// 组件挂载完成后,自动聚焦到输入组件
|
||||
if (
|
||||
componentRef.component?.exposed &&
|
||||
isFunction(componentRef.component.exposed.focus)
|
||||
) {
|
||||
componentRef.component.exposed.focus();
|
||||
} else if (componentRef.el && isFunction(componentRef.el.focus)) {
|
||||
componentRef.el.focus();
|
||||
}
|
||||
},
|
||||
};
|
||||
await vbenConfirm(props);
|
||||
return modelValue.value;
|
||||
}
|
||||
|
||||
export function clearAllAlerts() {
|
||||
alerts.value.forEach((alert) => {
|
||||
// 从DOM中移除容器
|
||||
render(null, alert.container);
|
||||
if (alert.container.parentNode) {
|
||||
alert.container.remove();
|
||||
}
|
||||
});
|
||||
alerts.value = [];
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import type { Component } from 'vue';
|
||||
|
||||
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||
|
||||
export type AlertProps = {
|
||||
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||
beforeClose?: () => boolean | Promise<boolean | undefined> | undefined;
|
||||
/** 边框 */
|
||||
bordered?: boolean;
|
||||
/** 取消按钮的标题 */
|
||||
cancelText?: string;
|
||||
/** 是否居中显示 */
|
||||
centered?: boolean;
|
||||
/** 确认按钮的标题 */
|
||||
confirmText?: string;
|
||||
/** 弹窗容器的额外样式 */
|
||||
containerClass?: string;
|
||||
/** 弹窗提示内容 */
|
||||
content: Component | string;
|
||||
/** 弹窗内容的额外样式 */
|
||||
contentClass?: string;
|
||||
/** 弹窗的图标(在标题的前面) */
|
||||
icon?: Component | IconType;
|
||||
/** 是否显示取消按钮 */
|
||||
showCancel?: boolean;
|
||||
/** 弹窗标题 */
|
||||
title?: string;
|
||||
};
|
|
@ -0,0 +1,181 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { AlertProps } from './alert';
|
||||
|
||||
import { computed, h, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { useSimpleLocale } from '@vben-core/composables';
|
||||
import {
|
||||
CircleAlert,
|
||||
CircleCheckBig,
|
||||
CircleHelp,
|
||||
CircleX,
|
||||
Info,
|
||||
X,
|
||||
} from '@vben-core/icons';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
VbenButton,
|
||||
VbenLoading,
|
||||
VbenRenderContent,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { globalShareState } from '@vben-core/shared/global-state';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
const props = withDefaults(defineProps<AlertProps>(), {
|
||||
bordered: true,
|
||||
centered: true,
|
||||
containerClass: 'w-[520px]',
|
||||
});
|
||||
const emits = defineEmits(['closed', 'confirm', 'opened']);
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
const { $t } = useSimpleLocale();
|
||||
const components = globalShareState.getComponents();
|
||||
const isConfirm = ref(false);
|
||||
watch(open, async (val) => {
|
||||
await nextTick();
|
||||
if (val) {
|
||||
isConfirm.value = false;
|
||||
} else {
|
||||
emits('closed', isConfirm.value);
|
||||
}
|
||||
});
|
||||
const getIconRender = computed(() => {
|
||||
let iconRender: Component | null = null;
|
||||
if (props.icon) {
|
||||
if (typeof props.icon === 'string') {
|
||||
switch (props.icon) {
|
||||
case 'error': {
|
||||
iconRender = h(CircleX, {
|
||||
style: { color: 'hsl(var(--destructive))' },
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'info': {
|
||||
iconRender = h(Info, { style: { color: 'hsl(var(--info))' } });
|
||||
break;
|
||||
}
|
||||
case 'question': {
|
||||
iconRender = CircleHelp;
|
||||
break;
|
||||
}
|
||||
case 'success': {
|
||||
iconRender = h(CircleCheckBig, {
|
||||
style: { color: 'hsl(var(--success))' },
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'warning': {
|
||||
iconRender = h(CircleAlert, {
|
||||
style: { color: 'hsl(var(--warning))' },
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
iconRender = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iconRender = props.icon ?? null;
|
||||
}
|
||||
return iconRender;
|
||||
});
|
||||
function handleConfirm() {
|
||||
isConfirm.value = true;
|
||||
emits('confirm');
|
||||
}
|
||||
function handleCancel() {
|
||||
open.value = false;
|
||||
}
|
||||
const loading = ref(false);
|
||||
async function handleOpenChange(val: boolean) {
|
||||
if (!val && props.beforeClose) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await props.beforeClose();
|
||||
if (res !== false) {
|
||||
open.value = false;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
open.value = val;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<AlertDialog :open="open" @update:open="handleOpenChange">
|
||||
<AlertDialogContent
|
||||
:open="open"
|
||||
:centered="centered"
|
||||
@opened="emits('opened')"
|
||||
:class="
|
||||
cn(
|
||||
containerClass,
|
||||
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
|
||||
{
|
||||
'border-border border': bordered,
|
||||
'shadow-3xl': !bordered,
|
||||
'top-1/2 !-translate-y-1/2': centered,
|
||||
},
|
||||
)
|
||||
"
|
||||
>
|
||||
<div :class="cn('relative flex-1 overflow-y-auto p-3', contentClass)">
|
||||
<AlertDialogTitle v-if="title">
|
||||
<div class="flex items-center">
|
||||
<component :is="getIconRender" class="mr-2" />
|
||||
<span class="flex-auto">{{ $t(title) }}</span>
|
||||
<AlertDialogCancel v-if="showCancel">
|
||||
<VbenButton
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-full"
|
||||
:disabled="loading"
|
||||
>
|
||||
<X class="text-muted-foreground size-4" />
|
||||
</VbenButton>
|
||||
</AlertDialogCancel>
|
||||
</div>
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<div class="m-4 mb-6 min-h-[30px]">
|
||||
<VbenRenderContent :content="content" render-br />
|
||||
</div>
|
||||
<VbenLoading v-if="loading" :spinning="loading" />
|
||||
</AlertDialogDescription>
|
||||
<div class="flex justify-end gap-x-2">
|
||||
<AlertDialogCancel
|
||||
v-if="showCancel"
|
||||
@click="handleCancel"
|
||||
:disabled="loading"
|
||||
>
|
||||
<component
|
||||
:is="components.DefaultButton || VbenButton"
|
||||
variant="ghost"
|
||||
>
|
||||
{{ cancelText || $t('cancel') }}
|
||||
</component>
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handleConfirm">
|
||||
<component
|
||||
:is="components.PrimaryButton || VbenButton"
|
||||
:loading="loading"
|
||||
>
|
||||
{{ confirmText || $t('confirm') }}
|
||||
</component>
|
||||
</AlertDialogAction>
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</template>
|
|
@ -0,0 +1,9 @@
|
|||
export * from './alert';
|
||||
|
||||
export { default as Alert } from './alert.vue';
|
||||
export {
|
||||
vbenAlert as alert,
|
||||
clearAllAlerts,
|
||||
vbenConfirm as confirm,
|
||||
vbenPrompt as prompt,
|
||||
} from './AlertBuilder';
|
|
@ -1,2 +1,3 @@
|
|||
export * from './alert';
|
||||
export * from './drawer';
|
||||
export * from './modal';
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Component, PropType } from 'vue';
|
|||
|
||||
import { defineComponent, h } from 'vue';
|
||||
|
||||
import { isFunction, isObject } from '@vben-core/shared/utils';
|
||||
import { isFunction, isObject, isString } from '@vben-core/shared/utils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RenderContent',
|
||||
|
@ -14,6 +14,10 @@ export default defineComponent({
|
|||
| undefined,
|
||||
type: [Object, String, Function],
|
||||
},
|
||||
renderBr: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup(props, { attrs, slots }) {
|
||||
return () => {
|
||||
|
@ -24,7 +28,20 @@ export default defineComponent({
|
|||
(isObject(props.content) || isFunction(props.content)) &&
|
||||
props.content !== null;
|
||||
if (!isComponent) {
|
||||
return props.content;
|
||||
if (props.renderBr && isString(props.content)) {
|
||||
const lines = props.content.split('\n');
|
||||
const result = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
result.push(h('span', { key: i }, line));
|
||||
if (i < lines.length - 1) {
|
||||
result.push(h('br'));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return props.content;
|
||||
}
|
||||
}
|
||||
return h(props.content as never, {
|
||||
...attrs,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import type { AlertDialogEmits, AlertDialogProps } from 'radix-vue';
|
||||
|
||||
import { AlertDialogRoot, useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogProps>();
|
||||
const emits = defineEmits<AlertDialogEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</AlertDialogRoot>
|
||||
</template>
|
|
@ -0,0 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import type { AlertDialogActionProps } from 'radix-vue';
|
||||
|
||||
import { AlertDialogAction } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogActionProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogAction v-bind="props">
|
||||
<slot></slot>
|
||||
</AlertDialogAction>
|
||||
</template>
|
|
@ -0,0 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import type { AlertDialogCancelProps } from 'radix-vue';
|
||||
|
||||
import { AlertDialogCancel } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogCancelProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogCancel v-bind="props">
|
||||
<slot></slot>
|
||||
</AlertDialogCancel>
|
||||
</template>
|
|
@ -0,0 +1,91 @@
|
|||
<script setup lang="ts">
|
||||
import type {
|
||||
AlertDialogContentEmits,
|
||||
AlertDialogContentProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
AlertDialogContent,
|
||||
AlertDialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
import AlertDialogOverlay from './AlertDialogOverlay.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
AlertDialogContentProps & {
|
||||
centered?: boolean;
|
||||
class?: ClassType;
|
||||
modal?: boolean;
|
||||
open?: boolean;
|
||||
overlayBlur?: number;
|
||||
zIndex?: number;
|
||||
}
|
||||
>(),
|
||||
{ modal: true },
|
||||
);
|
||||
const emits = defineEmits<
|
||||
AlertDialogContentEmits & { close: []; closed: []; opened: [] }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, modal: _modal, open: _open, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
|
||||
const contentRef = ref<InstanceType<typeof AlertDialogContent> | null>(null);
|
||||
function onAnimationEnd(event: AnimationEvent) {
|
||||
// 只有在 contentRef 的动画结束时才触发 opened/closed 事件
|
||||
if (event.target === contentRef.value?.$el) {
|
||||
if (props.open) {
|
||||
emits('opened');
|
||||
} else {
|
||||
emits('closed');
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
getContentRef: () => contentRef.value,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogPortal>
|
||||
<Transition name="fade">
|
||||
<AlertDialogOverlay
|
||||
v-if="open && modal"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position: 'fixed',
|
||||
backdropFilter:
|
||||
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||
}"
|
||||
@click="() => emits('close')"
|
||||
/>
|
||||
</Transition>
|
||||
<AlertDialogContent
|
||||
ref="contentRef"
|
||||
:style="{ ...(zIndex ? { zIndex } : {}), position: 'fixed' }"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</template>
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts" setup>
|
||||
import type { AlertDialogDescriptionProps } from 'radix-vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { AlertDialogDescription, useForwardProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogDescriptionProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogDescription
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogDescription>
|
||||
</template>
|
|
@ -0,0 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { useScrollLock } from '@vben-core/composables';
|
||||
|
||||
useScrollLock();
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-overlay z-popup inset-0"></div>
|
||||
</template>
|
|
@ -0,0 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import type { AlertDialogTitleProps } from 'radix-vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { AlertDialogTitle, useForwardProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogTitleProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogTitle
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn('text-lg font-semibold leading-none tracking-tight', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogTitle>
|
||||
</template>
|
|
@ -0,0 +1,6 @@
|
|||
export { default as AlertDialog } from './AlertDialog.vue';
|
||||
export { default as AlertDialogAction } from './AlertDialogAction.vue';
|
||||
export { default as AlertDialogCancel } from './AlertDialogCancel.vue';
|
||||
export { default as AlertDialogContent } from './AlertDialogContent.vue';
|
||||
export { default as AlertDialogDescription } from './AlertDialogDescription.vue';
|
||||
export { default as AlertDialogTitle } from './AlertDialogTitle.vue';
|
|
@ -1,4 +1,5 @@
|
|||
export * from './accordion';
|
||||
export * from './alert-dialog';
|
||||
export * from './avatar';
|
||||
export * from './badge';
|
||||
export * from './breadcrumb';
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
<script lang="ts" setup>
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { onBeforeUnmount } from 'vue';
|
||||
|
||||
import { Button, Card, Flex } from 'ant-design-vue';
|
||||
import {
|
||||
alert,
|
||||
clearAllAlerts,
|
||||
confirm,
|
||||
Page,
|
||||
prompt,
|
||||
useVbenModal,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { Button, Card, Flex, message } from 'ant-design-vue';
|
||||
|
||||
import DocButton from '../doc-button.vue';
|
||||
import AutoHeightDemo from './auto-height-demo.vue';
|
||||
|
@ -103,6 +112,61 @@ function openFormModal() {
|
|||
})
|
||||
.open();
|
||||
}
|
||||
|
||||
function openAlert() {
|
||||
alert({
|
||||
content: '这是一个弹窗',
|
||||
icon: 'success',
|
||||
}).then(() => {
|
||||
message.info('用户关闭了弹窗');
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 清除所有弹窗
|
||||
clearAllAlerts();
|
||||
});
|
||||
|
||||
function openConfirm() {
|
||||
confirm({
|
||||
beforeClose() {
|
||||
// 这里可以做一些异步操作
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, 1000);
|
||||
});
|
||||
},
|
||||
content: '这是一个确认弹窗',
|
||||
icon: 'question',
|
||||
})
|
||||
.then(() => {
|
||||
message.success('用户确认了操作');
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('用户取消了操作');
|
||||
});
|
||||
}
|
||||
|
||||
async function openPrompt() {
|
||||
prompt<string>({
|
||||
async beforeClose(val) {
|
||||
if (val === '芝士') {
|
||||
message.error('不能吃芝士');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
componentProps: { placeholder: '不能吃芝士...' },
|
||||
content: '中午吃了什么?',
|
||||
icon: 'question',
|
||||
})
|
||||
.then((res) => {
|
||||
message.success(`用户输入了:${res}`);
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('用户取消了输入');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -195,6 +259,14 @@ function openFormModal() {
|
|||
<Button type="primary" @click="openBlurModal">打开弹窗</Button>
|
||||
</template>
|
||||
</Card>
|
||||
<Card class="w-[300px]" title="轻量提示弹窗">
|
||||
<p>通过快捷方法创建动态提示弹窗,适合一些轻量的提示和确认、输入等</p>
|
||||
<template #actions>
|
||||
<Button type="primary" @click="openAlert">Alert</Button>
|
||||
<Button type="primary" @click="openConfirm">Confirm</Button>
|
||||
<Button type="primary" @click="openPrompt">Prompt</Button>
|
||||
</template>
|
||||
</Card>
|
||||
</Flex>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue