alert.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <script lang="ts" setup>
  2. import type { Component } from 'vue';
  3. import type { AlertProps } from './alert';
  4. import { computed, h, nextTick, ref } from 'vue';
  5. import { useSimpleLocale } from '@vben-core/composables';
  6. import {
  7. CircleAlert,
  8. CircleCheckBig,
  9. CircleHelp,
  10. CircleX,
  11. Info,
  12. X,
  13. } from '@vben-core/icons';
  14. import {
  15. AlertDialog,
  16. AlertDialogAction,
  17. AlertDialogCancel,
  18. AlertDialogContent,
  19. AlertDialogDescription,
  20. AlertDialogTitle,
  21. VbenButton,
  22. VbenLoading,
  23. VbenRenderContent,
  24. } from '@vben-core/shadcn-ui';
  25. import { globalShareState } from '@vben-core/shared/global-state';
  26. import { cn } from '@vben-core/shared/utils';
  27. import { provideAlertContext } from './alert';
  28. const props = withDefaults(defineProps<AlertProps>(), {
  29. bordered: true,
  30. buttonAlign: 'end',
  31. centered: true,
  32. });
  33. const emits = defineEmits(['closed', 'confirm', 'opened']);
  34. const open = defineModel<boolean>('open', { default: false });
  35. const { $t } = useSimpleLocale();
  36. const components = globalShareState.getComponents();
  37. const isConfirm = ref(false);
  38. function onAlertClosed() {
  39. emits('closed', isConfirm.value);
  40. isConfirm.value = false;
  41. }
  42. function onEscapeKeyDown() {
  43. isConfirm.value = false;
  44. }
  45. const getIconRender = computed(() => {
  46. let iconRender: Component | null = null;
  47. if (props.icon) {
  48. if (typeof props.icon === 'string') {
  49. switch (props.icon) {
  50. case 'error': {
  51. iconRender = h(CircleX, {
  52. style: { color: 'hsl(var(--destructive))' },
  53. });
  54. break;
  55. }
  56. case 'info': {
  57. iconRender = h(Info, { style: { color: 'hsl(var(--info))' } });
  58. break;
  59. }
  60. case 'question': {
  61. iconRender = CircleHelp;
  62. break;
  63. }
  64. case 'success': {
  65. iconRender = h(CircleCheckBig, {
  66. style: { color: 'hsl(var(--success))' },
  67. });
  68. break;
  69. }
  70. case 'warning': {
  71. iconRender = h(CircleAlert, {
  72. style: { color: 'hsl(var(--warning))' },
  73. });
  74. break;
  75. }
  76. default: {
  77. iconRender = null;
  78. break;
  79. }
  80. }
  81. }
  82. } else {
  83. iconRender = props.icon ?? null;
  84. }
  85. return iconRender;
  86. });
  87. function doCancel() {
  88. handleCancel();
  89. handleOpenChange(false);
  90. }
  91. function doConfirm() {
  92. handleConfirm();
  93. handleOpenChange(false);
  94. }
  95. provideAlertContext({
  96. doCancel,
  97. doConfirm,
  98. });
  99. function handleConfirm() {
  100. isConfirm.value = true;
  101. emits('confirm');
  102. }
  103. function handleCancel() {
  104. isConfirm.value = false;
  105. }
  106. const loading = ref(false);
  107. async function handleOpenChange(val: boolean) {
  108. await nextTick(); // 等待标记isConfirm状态
  109. if (!val && props.beforeClose) {
  110. loading.value = true;
  111. try {
  112. const res = await props.beforeClose({ isConfirm: isConfirm.value });
  113. if (res !== false) {
  114. open.value = false;
  115. }
  116. } finally {
  117. loading.value = false;
  118. }
  119. } else {
  120. open.value = val;
  121. }
  122. }
  123. </script>
  124. <template>
  125. <AlertDialog :open="open" @update:open="handleOpenChange">
  126. <AlertDialogContent
  127. :open="open"
  128. :centered="centered"
  129. :overlay-blur="overlayBlur"
  130. @opened="emits('opened')"
  131. @closed="onAlertClosed"
  132. @escape-key-down="onEscapeKeyDown"
  133. :class="
  134. cn(
  135. containerClass,
  136. 'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:w-[520px] sm:max-w-[80%] sm:rounded-[var(--radius)]',
  137. {
  138. 'border-border border': bordered,
  139. 'shadow-3xl': !bordered,
  140. },
  141. )
  142. "
  143. >
  144. <div :class="cn('relative flex-1 overflow-y-auto p-3', contentClass)">
  145. <AlertDialogTitle v-if="title">
  146. <div class="flex items-center">
  147. <component :is="getIconRender" class="mr-2" />
  148. <span class="flex-auto">{{ $t(title) }}</span>
  149. <AlertDialogCancel v-if="showCancel" as-child>
  150. <VbenButton
  151. variant="ghost"
  152. size="icon"
  153. class="rounded-full"
  154. :disabled="loading"
  155. @click="handleCancel"
  156. >
  157. <X class="text-muted-foreground size-4" />
  158. </VbenButton>
  159. </AlertDialogCancel>
  160. </div>
  161. </AlertDialogTitle>
  162. <AlertDialogDescription>
  163. <div class="m-4 min-h-[30px]">
  164. <VbenRenderContent :content="content" render-br />
  165. </div>
  166. <VbenLoading v-if="loading && contentMasking" :spinning="loading" />
  167. </AlertDialogDescription>
  168. <div
  169. class="flex items-center justify-end gap-x-2"
  170. :class="`justify-${buttonAlign}`"
  171. >
  172. <VbenRenderContent :content="footer" />
  173. <AlertDialogCancel v-if="showCancel" as-child>
  174. <component
  175. :is="components.DefaultButton || VbenButton"
  176. :disabled="loading"
  177. variant="ghost"
  178. @click="handleCancel"
  179. >
  180. {{ cancelText || $t('cancel') }}
  181. </component>
  182. </AlertDialogCancel>
  183. <AlertDialogAction as-child>
  184. <component
  185. :is="components.PrimaryButton || VbenButton"
  186. :loading="loading"
  187. @click="handleConfirm"
  188. >
  189. {{ confirmText || $t('confirm') }}
  190. </component>
  191. </AlertDialogAction>
  192. </div>
  193. </div>
  194. </AlertDialogContent>
  195. </AlertDialog>
  196. </template>