import type { Component, VNode } from 'vue'; import type { Recordable } from '@vben-core/typings'; import type { AlertProps, BeforeCloseScope, PromptProps } from './alert'; import { h, nextTick, ref, render } from 'vue'; import { useSimpleLocale } from '@vben-core/composables'; import { Input, VbenRenderContent } from '@vben-core/shadcn-ui'; import { isFunction, isString } from '@vben-core/shared/utils'; import Alert from './alert.vue'; const alerts = ref>([]); const { $t } = useSimpleLocale(); export function vbenAlert(options: AlertProps): Promise; export function vbenAlert( message: string, options?: Partial, ): Promise; export function vbenAlert( message: string, title?: string, options?: Partial, ): Promise; export function vbenAlert( arg0: AlertProps | string, arg1?: Partial | string, arg2?: Partial, ): Promise { 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 = { 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; export function vbenConfirm( message: string, options?: Partial, ): Promise; export function vbenConfirm( message: string, title?: string, options?: Partial, ): Promise; export function vbenConfirm( arg0: AlertProps | string, arg1?: Partial | string, arg2?: Partial, ): Promise { const defaultProps: Partial = { 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( options: PromptProps, ): Promise { const { component: _component, componentProps: _componentProps, componentSlots, content, defaultValue, modelPropName: _modelPropName, ...delegated } = options; const modelValue = ref(defaultValue); const inputComponentRef = ref(null); const staticContents: Component[] = []; staticContents.push(h(VbenRenderContent, { content, renderBr: true })); const modelPropName = _modelPropName || 'modelValue'; const componentProps = { ..._componentProps }; // 每次渲染时都会重新计算的内容函数 const contentRenderer = () => { const currentProps = { ...componentProps }; // 设置当前值 currentProps[modelPropName] = modelValue.value; // 设置更新处理函数 currentProps[`onUpdate:${modelPropName}`] = (val: T) => { modelValue.value = val; }; // 创建输入组件 inputComponentRef.value = h( _component || Input, currentProps, componentSlots, ); // 返回包含静态内容和输入组件的数组 return h( 'div', { class: 'flex flex-col gap-2' }, { default: () => [...staticContents, inputComponentRef.value] }, ); }; const props: AlertProps & Recordable = { ...delegated, async beforeClose(scope: BeforeCloseScope) { if (delegated.beforeClose) { return await delegated.beforeClose({ ...scope, value: modelValue.value, }); } }, // 使用函数形式,每次渲染都会重新计算内容 content: contentRenderer, contentMasking: true, async onOpened() { await nextTick(); const componentRef: null | VNode = inputComponentRef.value; if (componentRef) { if ( componentRef.component?.exposed && isFunction(componentRef.component.exposed.focus) ) { componentRef.component.exposed.focus(); } else { if (componentRef.el) { if ( isFunction(componentRef.el.focus) && ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes( componentRef.el.tagName, ) ) { componentRef.el.focus(); } else if (isFunction(componentRef.el.querySelector)) { const focusableElement = componentRef.el.querySelector( 'input, select, textarea, button', ); if (focusableElement && isFunction(focusableElement.focus)) { focusableElement.focus(); } } else if ( componentRef.el.nextElementSibling && isFunction(componentRef.el.nextElementSibling.focus) ) { componentRef.el.nextElementSibling.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 = []; }