alert.vue 5.4 KB

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