| <template> | |
| <Transition | |
| name="message-fade" | |
| appear | |
| mode="in-out" | |
| @beforeLeave="emit('close')" | |
| @afterLeave="emit('destroy')" | |
| > | |
| <div class="message" :id="id" v-if="visible"> | |
| <div class="message-container" | |
| @mouseenter="clearTimer()" | |
| @mouseleave="startTimer()" | |
| > | |
| <div class="icons"> | |
| <IconAttention theme="filled" size="18" fill="#faad14" v-if="type === 'warning'" /> | |
| <IconCheckOne theme="filled" size="18" fill="#52c41a" v-if="type === 'success'" /> | |
| <IconCloseOne theme="filled" size="18" fill="#ff4d4f" v-if="type === 'error'" /> | |
| <IconInfo theme="filled" size="18" fill="#1677ff" v-if="type === 'info'" /> | |
| </div> | |
| <div class="content"> | |
| <div class="title" v-if="title">{{ title }}</div> | |
| <div class="description">{{ message }}</div> | |
| </div> | |
| <div class="control" v-if="closable"> | |
| <span | |
| class="close-btn" | |
| @click="close()" | |
| > | |
| <IconCloseSmall /> | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| </Transition> | |
| </template> | |
| <script lang="ts" setup> | |
| import { onMounted, ref, onBeforeMount } from 'vue' | |
| import { icons } from '@/plugins/icon' | |
| const { | |
| IconAttention, | |
| IconCheckOne, | |
| IconCloseOne, | |
| IconInfo, | |
| IconCloseSmall, | |
| } = icons | |
| const props = withDefaults(defineProps<{ | |
| id: string | |
| message: string | |
| type?: string | |
| title?: string | |
| duration?: number | |
| closable?: boolean | |
| }>(), { | |
| type: 'success', | |
| title: '', | |
| duration: 3000, | |
| closable: false, | |
| }) | |
| const emit = defineEmits<{ | |
| (event: 'close'): void | |
| (event: 'destroy'): void | |
| }>() | |
| const visible = ref(true) | |
| const timer = ref<number | null>(null) | |
| const startTimer = () => { | |
| if (props.duration <= 0) return | |
| timer.value = setTimeout(close, props.duration) | |
| } | |
| const clearTimer = () => { | |
| if (timer.value) clearTimeout(timer.value) | |
| } | |
| const close = () => visible.value = false | |
| onBeforeMount(() => { | |
| clearTimer() | |
| }) | |
| onMounted(() => { | |
| startTimer() | |
| }) | |
| defineExpose({ | |
| close, | |
| }) | |
| </script> | |
| <style lang="scss" scoped> | |
| .message { | |
| max-width: 600px; | |
| & + & { | |
| margin-top: 15px; | |
| } | |
| } | |
| .message-container { | |
| min-width: 50px; | |
| display: flex; | |
| align-items: center; | |
| padding: 10px; | |
| font-size: 13px; | |
| overflow: hidden; | |
| border-radius: $borderRadius; | |
| box-shadow: 0 1px 8px rgba(0, 0, 0, .15); | |
| background: #fff; | |
| pointer-events: all; | |
| position: relative; | |
| .icons { | |
| display: flex; | |
| align-items: center; | |
| margin-right: 10px; | |
| } | |
| .title { | |
| font-size: 14px; | |
| font-weight: 700; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .content { | |
| width: 100%; | |
| } | |
| .description { | |
| line-height: 1.5; | |
| color: $textColor; | |
| } | |
| .title + .description { | |
| margin-top: 5px; | |
| } | |
| .control { | |
| position: relative; | |
| height: 100%; | |
| margin-left: 10px; | |
| } | |
| .close-btn { | |
| font-size: 15px; | |
| color: #666; | |
| display: flex; | |
| align-items: center; | |
| cursor: pointer; | |
| &:hover { | |
| color: $themeColor; | |
| } | |
| } | |
| } | |
| .message-fade-enter-active { | |
| animation: message-fade-in-down .3s; | |
| } | |
| .message-fade-leave-active { | |
| animation: message-fade-out .3s; | |
| } | |
| @keyframes message-fade-in-down { | |
| 0% { | |
| opacity: 0; | |
| transform: translateY(-20px); | |
| } | |
| 100% { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes message-fade-out { | |
| 0% { | |
| opacity: 1; | |
| margin-top: 0; | |
| } | |
| 100% { | |
| opacity: 0; | |
| margin-top: -45px; | |
| } | |
| } | |
| </style> |