|
|
@ -1,10 +1,10 @@ |
|
|
import type { Component } from 'vue'; |
|
|
import type { Component, VNode } from 'vue'; |
|
|
|
|
|
|
|
|
import type { Recordable } from '@vben-core/typings'; |
|
|
import type { Recordable } from '@vben-core/typings'; |
|
|
|
|
|
|
|
|
import type { AlertProps, BeforeCloseScope } from './alert'; |
|
|
import type { AlertProps, BeforeCloseScope, PromptProps } from './alert'; |
|
|
|
|
|
|
|
|
import { h, ref, render } from 'vue'; |
|
|
import { h, nextTick, ref, render } from 'vue'; |
|
|
|
|
|
|
|
|
import { useSimpleLocale } from '@vben-core/composables'; |
|
|
import { useSimpleLocale } from '@vben-core/composables'; |
|
|
import { Input } from '@vben-core/shadcn-ui'; |
|
|
import { Input } from '@vben-core/shadcn-ui'; |
|
|
@ -130,40 +130,58 @@ export function vbenConfirm( |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export async function vbenPrompt<T = any>( |
|
|
export async function vbenPrompt<T = any>( |
|
|
options: Omit<AlertProps, 'beforeClose'> & { |
|
|
options: PromptProps<T>, |
|
|
beforeClose?: (scope: { |
|
|
|
|
|
isConfirm: boolean; |
|
|
|
|
|
value: T | undefined; |
|
|
|
|
|
}) => boolean | Promise<boolean | undefined> | undefined; |
|
|
|
|
|
component?: Component; |
|
|
|
|
|
componentProps?: Recordable<any>; |
|
|
|
|
|
defaultValue?: T; |
|
|
|
|
|
modelPropName?: string; |
|
|
|
|
|
}, |
|
|
|
|
|
): Promise<T | undefined> { |
|
|
): Promise<T | undefined> { |
|
|
const { |
|
|
const { |
|
|
component: _component, |
|
|
component: _component, |
|
|
componentProps: _componentProps, |
|
|
componentProps: _componentProps, |
|
|
|
|
|
componentSlots, |
|
|
content, |
|
|
content, |
|
|
defaultValue, |
|
|
defaultValue, |
|
|
modelPropName: _modelPropName, |
|
|
modelPropName: _modelPropName, |
|
|
...delegated |
|
|
...delegated |
|
|
} = options; |
|
|
} = options; |
|
|
const contents: Component[] = []; |
|
|
|
|
|
const modelValue = ref<T | undefined>(defaultValue); |
|
|
const modelValue = ref<T | undefined>(defaultValue); |
|
|
|
|
|
const inputComponentRef = ref<null | VNode>(null); |
|
|
|
|
|
const staticContents: Component[] = []; |
|
|
|
|
|
|
|
|
if (isString(content)) { |
|
|
if (isString(content)) { |
|
|
contents.push(h('span', content)); |
|
|
staticContents.push(h('span', content)); |
|
|
} else { |
|
|
} else if (content) { |
|
|
contents.push(content); |
|
|
staticContents.push(content as Component); |
|
|
} |
|
|
} |
|
|
const componentProps = _componentProps || {}; |
|
|
|
|
|
const modelPropName = _modelPropName || 'modelValue'; |
|
|
const modelPropName = _modelPropName || 'modelValue'; |
|
|
componentProps[modelPropName] = modelValue.value; |
|
|
const componentProps = { ..._componentProps }; |
|
|
componentProps[`onUpdate:${modelPropName}`] = (val: any) => { |
|
|
|
|
|
modelValue.value = val; |
|
|
// 每次渲染时都会重新计算的内容函数
|
|
|
|
|
|
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 componentRef = h(_component || Input, componentProps); |
|
|
|
|
|
contents.push(componentRef); |
|
|
|
|
|
const props: AlertProps & Recordable<any> = { |
|
|
const props: AlertProps & Recordable<any> = { |
|
|
...delegated, |
|
|
...delegated, |
|
|
async beforeClose(scope: BeforeCloseScope) { |
|
|
async beforeClose(scope: BeforeCloseScope) { |
|
|
@ -174,23 +192,46 @@ export async function vbenPrompt<T = any>( |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
content: h( |
|
|
// 使用函数形式,每次渲染都会重新计算内容
|
|
|
'div', |
|
|
content: contentRenderer, |
|
|
{ class: 'flex flex-col gap-2' }, |
|
|
contentMasking: true, |
|
|
{ default: () => contents }, |
|
|
async onOpened() { |
|
|
), |
|
|
await nextTick(); |
|
|
onOpened() { |
|
|
const componentRef: null | VNode = inputComponentRef.value; |
|
|
// 组件挂载完成后,自动聚焦到输入组件
|
|
|
if (componentRef) { |
|
|
if ( |
|
|
if ( |
|
|
componentRef.component?.exposed && |
|
|
componentRef.component?.exposed && |
|
|
isFunction(componentRef.component.exposed.focus) |
|
|
isFunction(componentRef.component.exposed.focus) |
|
|
) { |
|
|
) { |
|
|
componentRef.component.exposed.focus(); |
|
|
componentRef.component.exposed.focus(); |
|
|
} else if (componentRef.el && isFunction(componentRef.el.focus)) { |
|
|
} else { |
|
|
componentRef.el.focus(); |
|
|
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); |
|
|
await vbenConfirm(props); |
|
|
return modelValue.value; |
|
|
return modelValue.value; |
|
|
} |
|
|
} |
|
|
|