alert.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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. containerClass: 'w-[520px]',
  33. });
  34. const emits = defineEmits(['closed', 'confirm', 'opened']);
  35. const open = defineModel<boolean>('open', { default: false });
  36. const { $t } = useSimpleLocale();
  37. const components = globalShareState.getComponents();
  38. const isConfirm = ref(false);
  39. function onAlertClosed() {
  40. emits('closed', isConfirm.value);
  41. isConfirm.value = false;
  42. }
  43. const getIconRender = computed(() => {
  44. let iconRender: Component | null = null;
  45. if (props.icon) {
  46. if (typeof props.icon === 'string') {
  47. switch (props.icon) {
  48. case 'error': {
  49. iconRender = h(CircleX, {
  50. style: { color: 'hsl(var(--destructive))' },
  51. });
  52. break;
  53. }
  54. case 'info': {
  55. iconRender = h(Info, { style: { color: 'hsl(var(--info))' } });
  56. break;
  57. }
  58. case 'question': {
  59. iconRender = CircleHelp;
  60. break;
  61. }
  62. case 'success': {
  63. iconRender = h(CircleCheckBig, {
  64. style: { color: 'hsl(var(--success))' },
  65. });
  66. break;
  67. }
  68. case 'warning': {
  69. iconRender = h(CircleAlert, {
  70. style: { color: 'hsl(var(--warning))' },
  71. });
  72. break;
  73. }
  74. default: {
  75. iconRender = null;
  76. break;
  77. }
  78. }
  79. }
  80. } else {
  81. iconRender = props.icon ?? null;
  82. }
  83. return iconRender;
  84. });
  85. function doCancel() {
  86. handleCancel();
  87. handleOpenChange(false);
  88. }
  89. function doConfirm() {
  90. handleConfirm();
  91. handleOpenChange(false);
  92. }
  93. provideAlertContext({
  94. doCancel,
  95. doConfirm,
  96. });
  97. function handleConfirm() {
  98. isConfirm.value = true;
  99. emits('confirm');
  100. }
  101. function handleCancel() {
  102. isConfirm.value = false;
  103. }
  104. const loading = ref(false);
  105. async function handleOpenChange(val: boolean) {
  106. const confirmState = isConfirm.value;
  107. isConfirm.value = false;
  108. await nextTick();
  109. if (!val && props.beforeClose) {
  110. loading.value = true;
  111. try {
  112. const res = await props.beforeClose({ isConfirm: confirmState });
  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. :class="
  133. cn(
  134. containerClass,
  135. '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%]',
  136. {
  137. 'border-border border': bordered,
  138. 'shadow-3xl': !bordered,
  139. },
  140. )
  141. "
  142. >
  143. <div :class="cn('relative flex-1 overflow-y-auto p-3', contentClass)">
  144. <AlertDialogTitle v-if="title">
  145. <div class="flex items-center">
  146. <component :is="getIconRender" class="mr-2" />
  147. <span class="flex-auto">{{ $t(title) }}</span>
  148. <AlertDialogCancel v-if="showCancel" as-child>
  149. <VbenButton
  150. variant="ghost"
  151. size="icon"
  152. class="rounded-full"
  153. :disabled="loading"
  154. @click="handleCancel"
  155. >
  156. <X class="text-muted-foreground size-4" />
  157. </VbenButton>
  158. </AlertDialogCancel>
  159. </div>
  160. </AlertDialogTitle>
  161. <AlertDialogDescription>
  162. <div class="m-4 min-h-[30px]">
  163. <VbenRenderContent :content="content" render-br />
  164. </div>
  165. <VbenLoading v-if="loading && contentMasking" :spinning="loading" />
  166. </AlertDialogDescription>
  167. <div
  168. class="flex items-center justify-end gap-x-2"
  169. :class="`justify-${buttonAlign}`"
  170. >
  171. <VbenRenderContent :content="footer" />
  172. <AlertDialogCancel v-if="showCancel" as-child>
  173. <component
  174. :is="components.DefaultButton || VbenButton"
  175. :disabled="loading"
  176. variant="ghost"
  177. @click="handleCancel"
  178. >
  179. {{ cancelText || $t('cancel') }}
  180. </component>
  181. </AlertDialogCancel>
  182. <AlertDialogAction as-child>
  183. <component
  184. :is="components.PrimaryButton || VbenButton"
  185. :loading="loading"
  186. @click="handleConfirm"
  187. >
  188. {{ confirmText || $t('confirm') }}
  189. </component>
  190. </AlertDialogAction>
  191. </div>
  192. </div>
  193. </AlertDialogContent>
  194. </AlertDialog>
  195. </template>