AlertBuilder.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import type { Component, VNode } from 'vue';
  2. import type { Recordable } from '@vben-core/typings';
  3. import type { AlertProps, BeforeCloseScope, PromptProps } from './alert';
  4. import { h, nextTick, ref, render } from 'vue';
  5. import { useSimpleLocale } from '@vben-core/composables';
  6. import { Input, VbenRenderContent } from '@vben-core/shadcn-ui';
  7. import { isFunction, isString } from '@vben-core/shared/utils';
  8. import Alert from './alert.vue';
  9. const alerts = ref<Array<{ container: HTMLElement; instance: Component }>>([]);
  10. const { $t } = useSimpleLocale();
  11. export function vbenAlert(options: AlertProps): Promise<void>;
  12. export function vbenAlert(
  13. message: string,
  14. options?: Partial<AlertProps>,
  15. ): Promise<void>;
  16. export function vbenAlert(
  17. message: string,
  18. title?: string,
  19. options?: Partial<AlertProps>,
  20. ): Promise<void>;
  21. export function vbenAlert(
  22. arg0: AlertProps | string,
  23. arg1?: Partial<AlertProps> | string,
  24. arg2?: Partial<AlertProps>,
  25. ): Promise<void> {
  26. return new Promise((resolve, reject) => {
  27. const options: AlertProps = isString(arg0)
  28. ? {
  29. content: arg0,
  30. }
  31. : { ...arg0 };
  32. if (arg1) {
  33. if (isString(arg1)) {
  34. options.title = arg1;
  35. } else if (!isString(arg1)) {
  36. // 如果第二个参数是对象,则合并到选项中
  37. Object.assign(options, arg1);
  38. }
  39. }
  40. if (arg2 && !isString(arg2)) {
  41. Object.assign(options, arg2);
  42. }
  43. // 创建容器元素
  44. const container = document.createElement('div');
  45. document.body.append(container);
  46. // 创建一个引用,用于在回调中访问实例
  47. const alertRef = { container, instance: null as any };
  48. const props: AlertProps & Recordable<any> = {
  49. onClosed: (isConfirm: boolean) => {
  50. // 移除组件实例以及创建的所有dom(恢复页面到打开前的状态)
  51. // 从alerts数组中移除该实例
  52. alerts.value = alerts.value.filter((item) => item !== alertRef);
  53. // 从DOM中移除容器
  54. render(null, container);
  55. if (container.parentNode) {
  56. container.remove();
  57. }
  58. // 解析 Promise,传递用户操作结果
  59. if (isConfirm) {
  60. resolve();
  61. } else {
  62. reject(new Error('dialog cancelled'));
  63. }
  64. },
  65. ...options,
  66. open: true,
  67. title: options.title ?? $t.value('prompt'),
  68. };
  69. // 创建Alert组件的VNode
  70. const vnode = h(Alert, props);
  71. // 渲染组件到容器
  72. render(vnode, container);
  73. // 保存组件实例引用
  74. alertRef.instance = vnode.component?.proxy as Component;
  75. // 将实例和容器添加到alerts数组中
  76. alerts.value.push(alertRef);
  77. });
  78. }
  79. export function vbenConfirm(options: AlertProps): Promise<void>;
  80. export function vbenConfirm(
  81. message: string,
  82. options?: Partial<AlertProps>,
  83. ): Promise<void>;
  84. export function vbenConfirm(
  85. message: string,
  86. title?: string,
  87. options?: Partial<AlertProps>,
  88. ): Promise<void>;
  89. export function vbenConfirm(
  90. arg0: AlertProps | string,
  91. arg1?: Partial<AlertProps> | string,
  92. arg2?: Partial<AlertProps>,
  93. ): Promise<void> {
  94. const defaultProps: Partial<AlertProps> = {
  95. showCancel: true,
  96. };
  97. if (!arg1) {
  98. return isString(arg0)
  99. ? vbenAlert(arg0, defaultProps)
  100. : vbenAlert({ ...defaultProps, ...arg0 });
  101. } else if (!arg2) {
  102. return isString(arg1)
  103. ? vbenAlert(arg0 as string, arg1, defaultProps)
  104. : vbenAlert(arg0 as string, { ...defaultProps, ...arg1 });
  105. }
  106. return vbenAlert(arg0 as string, arg1 as string, {
  107. ...defaultProps,
  108. ...arg2,
  109. });
  110. }
  111. export async function vbenPrompt<T = any>(
  112. options: PromptProps<T>,
  113. ): Promise<T | undefined> {
  114. const {
  115. component: _component,
  116. componentProps: _componentProps,
  117. componentSlots,
  118. content,
  119. defaultValue,
  120. modelPropName: _modelPropName,
  121. ...delegated
  122. } = options;
  123. const modelValue = ref<T | undefined>(defaultValue);
  124. const inputComponentRef = ref<null | VNode>(null);
  125. const staticContents: Component[] = [];
  126. staticContents.push(h(VbenRenderContent, { content, renderBr: true }));
  127. const modelPropName = _modelPropName || 'modelValue';
  128. const componentProps = { ..._componentProps };
  129. // 每次渲染时都会重新计算的内容函数
  130. const contentRenderer = () => {
  131. const currentProps = { ...componentProps };
  132. // 设置当前值
  133. currentProps[modelPropName] = modelValue.value;
  134. // 设置更新处理函数
  135. currentProps[`onUpdate:${modelPropName}`] = (val: T) => {
  136. modelValue.value = val;
  137. };
  138. // 创建输入组件
  139. inputComponentRef.value = h(
  140. _component || Input,
  141. currentProps,
  142. componentSlots,
  143. );
  144. // 返回包含静态内容和输入组件的数组
  145. return h(
  146. 'div',
  147. { class: 'flex flex-col gap-2' },
  148. { default: () => [...staticContents, inputComponentRef.value] },
  149. );
  150. };
  151. const props: AlertProps & Recordable<any> = {
  152. ...delegated,
  153. async beforeClose(scope: BeforeCloseScope) {
  154. if (delegated.beforeClose) {
  155. return await delegated.beforeClose({
  156. ...scope,
  157. value: modelValue.value,
  158. });
  159. }
  160. },
  161. // 使用函数形式,每次渲染都会重新计算内容
  162. content: contentRenderer,
  163. contentMasking: true,
  164. async onOpened() {
  165. await nextTick();
  166. const componentRef: null | VNode = inputComponentRef.value;
  167. if (componentRef) {
  168. if (
  169. componentRef.component?.exposed &&
  170. isFunction(componentRef.component.exposed.focus)
  171. ) {
  172. componentRef.component.exposed.focus();
  173. } else {
  174. if (componentRef.el) {
  175. if (
  176. isFunction(componentRef.el.focus) &&
  177. ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(
  178. componentRef.el.tagName,
  179. )
  180. ) {
  181. componentRef.el.focus();
  182. } else if (isFunction(componentRef.el.querySelector)) {
  183. const focusableElement = componentRef.el.querySelector(
  184. 'input, select, textarea, button',
  185. );
  186. if (focusableElement && isFunction(focusableElement.focus)) {
  187. focusableElement.focus();
  188. }
  189. } else if (
  190. componentRef.el.nextElementSibling &&
  191. isFunction(componentRef.el.nextElementSibling.focus)
  192. ) {
  193. componentRef.el.nextElementSibling.focus();
  194. }
  195. }
  196. }
  197. }
  198. },
  199. };
  200. await vbenConfirm(props);
  201. return modelValue.value;
  202. }
  203. export function clearAllAlerts() {
  204. alerts.value.forEach((alert) => {
  205. // 从DOM中移除容器
  206. render(null, alert.container);
  207. if (alert.container.parentNode) {
  208. alert.container.remove();
  209. }
  210. });
  211. alerts.value = [];
  212. }