AlertBuilder.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. h(VbenRenderContent, { content, renderBr: true }),
  127. ];
  128. const modelPropName = _modelPropName || 'modelValue';
  129. const componentProps = { ..._componentProps };
  130. // 每次渲染时都会重新计算的内容函数
  131. const contentRenderer = () => {
  132. const currentProps = {
  133. ...componentProps,
  134. [modelPropName]: modelValue.value,
  135. [`onUpdate:${modelPropName}`]: (val: T) => {
  136. modelValue.value = val;
  137. },
  138. };
  139. // 设置当前值
  140. // 设置更新处理函数
  141. // 创建输入组件
  142. inputComponentRef.value = h(
  143. _component || Input,
  144. currentProps,
  145. componentSlots,
  146. );
  147. // 返回包含静态内容和输入组件的数组
  148. return h(
  149. 'div',
  150. { class: 'flex flex-col gap-2' },
  151. { default: () => [...staticContents, inputComponentRef.value] },
  152. );
  153. };
  154. const props: AlertProps & Recordable<any> = {
  155. ...delegated,
  156. async beforeClose(scope: BeforeCloseScope) {
  157. if (delegated.beforeClose) {
  158. return await delegated.beforeClose({
  159. ...scope,
  160. value: modelValue.value,
  161. });
  162. }
  163. },
  164. // 使用函数形式,每次渲染都会重新计算内容
  165. content: contentRenderer,
  166. contentMasking: true,
  167. async onOpened() {
  168. await nextTick();
  169. const componentRef: null | VNode = inputComponentRef.value;
  170. if (componentRef) {
  171. if (
  172. componentRef.component?.exposed &&
  173. isFunction(componentRef.component.exposed.focus)
  174. ) {
  175. componentRef.component.exposed.focus();
  176. } else {
  177. if (componentRef.el) {
  178. if (
  179. isFunction(componentRef.el.focus) &&
  180. ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(
  181. componentRef.el.tagName,
  182. )
  183. ) {
  184. componentRef.el.focus();
  185. } else if (isFunction(componentRef.el.querySelector)) {
  186. const focusableElement = componentRef.el.querySelector(
  187. 'input, select, textarea, button',
  188. );
  189. if (focusableElement && isFunction(focusableElement.focus)) {
  190. focusableElement.focus();
  191. }
  192. } else if (
  193. componentRef.el.nextElementSibling &&
  194. isFunction(componentRef.el.nextElementSibling.focus)
  195. ) {
  196. componentRef.el.nextElementSibling.focus();
  197. }
  198. }
  199. }
  200. }
  201. },
  202. };
  203. await vbenConfirm(props);
  204. return modelValue.value;
  205. }
  206. export function clearAllAlerts() {
  207. alerts.value.forEach((alert) => {
  208. // 从DOM中移除容器
  209. render(null, alert.container);
  210. if (alert.container.parentNode) {
  211. alert.container.remove();
  212. }
  213. });
  214. alerts.value = [];
  215. }