Browse Source
* feat: add preset alert, confirm, prompt components that can be simple called * fix: type definepull/5845/head
committed by
GitHub
23 changed files with 941 additions and 4 deletions
@ -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> |
||||
@ -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 './drawer'; |
||||
export * from './modal'; |
export * from './modal'; |
||||
|
|||||
@ -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'; |
||||
Loading…
Reference in new issue