mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
183 lines
4.5 KiB
Vue
183 lines
4.5 KiB
Vue
<template>
|
|
<transition
|
|
:name="ns.b('fade')"
|
|
@before-enter="isStartTransition = true"
|
|
@before-leave="onClose"
|
|
@after-leave="$emit('destroy')"
|
|
>
|
|
<div
|
|
v-show="visible"
|
|
:id="id"
|
|
ref="messageRef"
|
|
:class="[
|
|
ns.b(),
|
|
{ [ns.m(type)]: type },
|
|
ns.is('closable', showClose),
|
|
ns.is('plain', plain),
|
|
ns.is('bottom', verticalProperty === 'bottom'),
|
|
horizontalClass,
|
|
customClass,
|
|
]"
|
|
:style="customStyle"
|
|
role="alert"
|
|
@mouseenter="clearTimer"
|
|
@mouseleave="startTimer"
|
|
>
|
|
<el-badge
|
|
v-if="repeatNum > 1"
|
|
:value="repeatNum"
|
|
:type="badgeType"
|
|
:class="ns.e('badge')"
|
|
/>
|
|
<el-icon v-if="iconComponent" :class="[ns.e('icon'), typeClass]">
|
|
<component :is="iconComponent" />
|
|
</el-icon>
|
|
<slot>
|
|
<p v-if="!dangerouslyUseHTMLString" :class="ns.e('content')">
|
|
{{ message }}
|
|
</p>
|
|
<!-- Caution here, message could've been compromised, never use user's input as message -->
|
|
<p v-else :class="ns.e('content')" v-html="message" />
|
|
</slot>
|
|
<el-icon v-if="showClose" :class="ns.e('closeBtn')" @click.stop="close">
|
|
<Close />
|
|
</el-icon>
|
|
</div>
|
|
</transition>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|
import { useEventListener, useResizeObserver, useTimeoutFn } from '@vueuse/core'
|
|
import {
|
|
TypeComponents,
|
|
TypeComponentsMap,
|
|
getEventCode,
|
|
} from '@element-plus/utils'
|
|
import { EVENT_CODE } from '@element-plus/constants'
|
|
import ElBadge from '@element-plus/components/badge'
|
|
import { useGlobalComponentSettings } from '@element-plus/components/config-provider'
|
|
import { ElIcon } from '@element-plus/components/icon'
|
|
import {
|
|
MESSAGE_DEFAULT_PLACEMENT,
|
|
messageEmits,
|
|
messageProps,
|
|
} from './message'
|
|
import { getLastOffset, getOffsetOrSpace } from './instance'
|
|
|
|
import type { BadgeProps } from '@element-plus/components/badge'
|
|
import type { CSSProperties } from 'vue'
|
|
|
|
const { Close } = TypeComponents
|
|
|
|
defineOptions({
|
|
name: 'ElMessage',
|
|
})
|
|
|
|
const props = defineProps(messageProps)
|
|
const emit = defineEmits(messageEmits)
|
|
|
|
const isStartTransition = ref(false)
|
|
|
|
const { ns, zIndex } = useGlobalComponentSettings('message')
|
|
const { currentZIndex, nextZIndex } = zIndex
|
|
|
|
const messageRef = ref<HTMLDivElement>()
|
|
const visible = ref(false)
|
|
const height = ref(0)
|
|
|
|
let stopTimer: (() => void) | undefined = undefined
|
|
|
|
const badgeType = computed<BadgeProps['type']>(() =>
|
|
props.type ? (props.type === 'error' ? 'danger' : props.type) : 'info'
|
|
)
|
|
const typeClass = computed(() => {
|
|
const type = props.type
|
|
return { [ns.bm('icon', type)]: type && TypeComponentsMap[type] }
|
|
})
|
|
const iconComponent = computed(
|
|
() => props.icon || TypeComponentsMap[props.type] || ''
|
|
)
|
|
|
|
const placement = computed(() => props.placement || MESSAGE_DEFAULT_PLACEMENT)
|
|
|
|
const lastOffset = computed(() => getLastOffset(props.id, placement.value))
|
|
const offset = computed(() => {
|
|
return (
|
|
getOffsetOrSpace(props.id, props.offset, placement.value) + lastOffset.value
|
|
)
|
|
})
|
|
const bottom = computed(() => height.value + offset.value)
|
|
const horizontalClass = computed(() => {
|
|
if (placement.value.includes('left')) return ns.is('left')
|
|
if (placement.value.includes('right')) return ns.is('right')
|
|
return ns.is('center')
|
|
})
|
|
|
|
const verticalProperty = computed(() =>
|
|
placement.value.startsWith('top') ? 'top' : 'bottom'
|
|
)
|
|
|
|
const customStyle = computed<CSSProperties>(() => ({
|
|
[verticalProperty.value]: `${offset.value}px`,
|
|
zIndex: currentZIndex.value,
|
|
}))
|
|
|
|
function startTimer() {
|
|
if (props.duration === 0) return
|
|
;({ stop: stopTimer } = useTimeoutFn(() => {
|
|
close()
|
|
}, props.duration))
|
|
}
|
|
|
|
function clearTimer() {
|
|
stopTimer?.()
|
|
}
|
|
|
|
function close() {
|
|
visible.value = false
|
|
|
|
// if the message has never started a transition, we can destroy it immediately
|
|
nextTick(() => {
|
|
if (!isStartTransition.value) {
|
|
props.onClose?.()
|
|
emit('destroy')
|
|
}
|
|
})
|
|
}
|
|
|
|
function keydown(event: KeyboardEvent) {
|
|
const code = getEventCode(event)
|
|
if (code === EVENT_CODE.esc) {
|
|
// press esc to close the message
|
|
close()
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
startTimer()
|
|
nextZIndex()
|
|
visible.value = true
|
|
})
|
|
|
|
watch(
|
|
() => props.repeatNum,
|
|
() => {
|
|
clearTimer()
|
|
startTimer()
|
|
}
|
|
)
|
|
|
|
useEventListener(document, 'keydown', keydown)
|
|
|
|
useResizeObserver(messageRef, () => {
|
|
height.value = messageRef.value!.getBoundingClientRect().height
|
|
})
|
|
|
|
defineExpose({
|
|
visible,
|
|
bottom,
|
|
close,
|
|
})
|
|
</script>
|