alert.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. const props = withDefaults(defineProps<AlertProps>(), {
  28. bordered: true,
  29. buttonAlign: 'end',
  30. centered: true,
  31. containerClass: 'w-[520px]',
  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. const getIconRender = computed(() => {
  43. let iconRender: Component | null = null;
  44. if (props.icon) {
  45. if (typeof props.icon === 'string') {
  46. switch (props.icon) {
  47. case 'error': {
  48. iconRender = h(CircleX, {
  49. style: { color: 'hsl(var(--destructive))' },
  50. });
  51. break;
  52. }
  53. case 'info': {
  54. iconRender = h(Info, { style: { color: 'hsl(var(--info))' } });
  55. break;
  56. }
  57. case 'question': {
  58. iconRender = CircleHelp;
  59. break;
  60. }
  61. case 'success': {
  62. iconRender = h(CircleCheckBig, {
  63. style: { color: 'hsl(var(--success))' },
  64. });
  65. break;
  66. }
  67. case 'warning': {
  68. iconRender = h(CircleAlert, {
  69. style: { color: 'hsl(var(--warning))' },
  70. });
  71. break;
  72. }
  73. default: {
  74. iconRender = null;
  75. break;
  76. }
  77. }
  78. }
  79. } else {
  80. iconRender = props.icon ?? null;
  81. }
  82. return iconRender;
  83. });
  84. function handleConfirm() {
  85. isConfirm.value = true;
  86. emits('confirm');
  87. }
  88. function handleCancel() {
  89. isConfirm.value = false;
  90. }
  91. const loading = ref(false);
  92. async function handleOpenChange(val: boolean) {
  93. await nextTick();
  94. if (!val && props.beforeClose) {
  95. loading.value = true;
  96. try {
  97. const res = await props.beforeClose({ isConfirm: isConfirm.value });
  98. if (res !== false) {
  99. open.value = false;
  100. }
  101. } finally {
  102. loading.value = false;
  103. }
  104. } else {
  105. open.value = val;
  106. }
  107. }
  108. </script>
  109. <template>
  110. <AlertDialog :open="open" @update:open="handleOpenChange">
  111. <AlertDialogContent
  112. :open="open"
  113. :centered="centered"
  114. :overlay-blur="overlayBlur"
  115. @opened="emits('opened')"
  116. @closed="onAlertClosed"
  117. :class="
  118. cn(
  119. containerClass,
  120. 'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
  121. {
  122. 'border-border border': bordered,
  123. 'shadow-3xl': !bordered,
  124. },
  125. )
  126. "
  127. >
  128. <div :class="cn('relative flex-1 overflow-y-auto p-3', contentClass)">
  129. <AlertDialogTitle v-if="title">
  130. <div class="flex items-center">
  131. <component :is="getIconRender" class="mr-2" />
  132. <span class="flex-auto">{{ $t(title) }}</span>
  133. <AlertDialogCancel v-if="showCancel" as-child>
  134. <VbenButton
  135. variant="ghost"
  136. size="icon"
  137. class="rounded-full"
  138. :disabled="loading"
  139. @click="handleCancel"
  140. >
  141. <X class="text-muted-foreground size-4" />
  142. </VbenButton>
  143. </AlertDialogCancel>
  144. </div>
  145. </AlertDialogTitle>
  146. <AlertDialogDescription>
  147. <div class="m-4 mb-6 min-h-[30px]">
  148. <VbenRenderContent :content="content" render-br />
  149. </div>
  150. <VbenLoading v-if="loading && contentMasking" :spinning="loading" />
  151. </AlertDialogDescription>
  152. <div
  153. class="flex items-center justify-end gap-x-2"
  154. :class="`justify-${buttonAlign}`"
  155. >
  156. <VbenRenderContent :content="footer" />
  157. <AlertDialogCancel v-if="showCancel" as-child>
  158. <component
  159. :is="components.DefaultButton || VbenButton"
  160. :disabled="loading"
  161. variant="ghost"
  162. @click="handleCancel"
  163. >
  164. {{ cancelText || $t('cancel') }}
  165. </component>
  166. </AlertDialogCancel>
  167. <AlertDialogAction as-child>
  168. <component
  169. :is="components.PrimaryButton || VbenButton"
  170. :loading="loading"
  171. @click="handleConfirm"
  172. >
  173. {{ confirmText || $t('confirm') }}
  174. </component>
  175. </AlertDialogAction>
  176. </div>
  177. </div>
  178. </AlertDialogContent>
  179. </AlertDialog>
  180. </template>