Skip to content

Commit

Permalink
feat: 完成 Message 编程式组件
Browse files Browse the repository at this point in the history
  • Loading branch information
nonhana committed Nov 23, 2024
1 parent a0b11c8 commit 75de521
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 53 deletions.
59 changes: 59 additions & 0 deletions components/hana/Message/Container.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script setup lang="ts">
import type { MessageOptions } from './useMessage'
import Message from './index.vue'
interface MessageItem extends MessageOptions {
key: number
}
const messages = ref<MessageItem[]>([])
let keyCounter = 0
function addMessage(options: MessageOptions) {
const key = keyCounter++
messages.value.push({ ...options, key })
setTimeout(() => {
removeMessage(key)
}, options.timeout || 3000)
}
function removeMessage(key: number) {
messages.value = messages.value.filter(msg => msg.key !== key)
}
defineExpose({
addMessage,
})
</script>

<template>
<div class="fixed left-1/2 top-4 z-50 flex w-full -translate-x-1/2 flex-col gap-4">
<transition-group name="message_container">
<div
v-for="msg in messages"
:key="msg.key"
class="flex w-full justify-center"
>
<Message v-bind="msg" />
</div>
</transition-group>
</div>
</template>

<style scoped>
.message_container-move,
.message_container-enter-active,
.message_container-leave-active {
transition: all 0.3s ease;
}
.message_container-enter-from,
.message_container-leave-to {
transform: translateY(calc(-100% - 1rem));
}
.message_container-leave-active {
position: absolute;
}
</style>
24 changes: 24 additions & 0 deletions components/hana/Message/Icon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
defineProps<{
type: 'info' | 'success' | 'warning' | 'error'
}>()
</script>

<template>
<svg v-if="type === 'info'" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<circle cx="12" cy="12" r="10" /><path d="M12 16v-4m0-4h.01" />
</g>
</svg>
<svg v-else-if="type === 'success'" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6L9 17l-5-5" />
</svg>
<svg v-else-if="type === 'warning'" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<circle cx="12" cy="12" r="10" /><path d="M12 8v4m0 4h.01" />
</g>
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 6L6 18M6 6l12 12" />
</svg>
</template>
51 changes: 15 additions & 36 deletions components/hana/Message/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import type { TransitionProps } from 'vue'
import type { MessageOptions } from './useMessage'
const props = withDefaults(defineProps<MessageOptions>(), {
Expand All @@ -9,52 +8,32 @@ const props = withDefaults(defineProps<MessageOptions>(), {
position: 'top',
})
const emits = defineEmits<{
(e: 'destroy'): void
}>()
const visible = ref(false)
const transitionClasses: TransitionProps = {
enterActiveClass: 'transition-all duration-300',
enterFromClass: 'translate-y-[calc(-100%-1rem)]',
enterToClass: 'translate-y-0',
leaveActiveClass: 'transition-all duration-300',
leaveFromClass: 'translate-y-0',
leaveToClass: 'translate-y-[calc(-100%-1rem)]',
}
onMounted(() => {
visible.value = true
setTimeout(() => {
visible.value = false
}, props.timeout)
})
function handleAfterLeave() {
emits('destroy')
type NonUndefined<T> = T extends undefined ? never : T
const messageClasses: Record<NonUndefined<MessageOptions['type']>, string> = {
info: 'bg-hana-blue-100 text-hana-blue border-hana-blue border-2',
success: 'bg-green-100 text-green-600 border-green-600 border-2',
warning: 'bg-yellow-100 text-yellow-600 border-yellow-600 border-2',
error: 'bg-red-100 text-red-600 border-red-600 border-2',
}
</script>

<template>
<transition v-bind="transitionClasses" @after-leave="handleAfterLeave">
<div
v-if="visible"
class="fixed left-1/2 z-50 -translate-x-1/2 rounded-lg p-3 text-sm shadow-lg"
:class="{
'bg-blue-100': props.type === 'info',
'bg-green-100': props.type === 'success',
'bg-yellow-100': props.type === 'warning',
'bg-red-100': props.type === 'error',
'text-blue-800': props.type === 'info',
'text-green-800': props.type === 'success',
'text-yellow-800': props.type === 'warning',
'text-red-800': props.type === 'error',
'top-4': props.position === 'top',
'bottom-4': props.position === 'bottom',
}"
>
{{ props.message }}
</div>
</transition>
<div
v-if="visible"
class="flex shrink-0 items-center gap-2 text-nowrap rounded-lg p-3 text-sm shadow-lg"
:class="messageClasses[props.type]"
>
<HanaMessageIcon :type="type" />
<span>{{ props.message }}</span>
</div>
</template>
27 changes: 13 additions & 14 deletions components/hana/Message/useMessage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createVNode, render } from 'vue'
import Message from './index.vue'
import Container from './Container.vue'

export interface MessageOptions {
message?: string
Expand All @@ -9,21 +9,20 @@ export interface MessageOptions {
[key: string]: any
}

export default function useMessage() {
const open = (options: MessageOptions) => {
const container = document.createElement('div')
document.body.appendChild(container)
let containerInstance: any = null

const messageVNode = createVNode(Message, {
...options,
onDestroy: () => {
render(null, container)
document.body.removeChild(container)
},
})
export default function useMessage() {
const HanaMessage = (options: MessageOptions) => {
if (!containerInstance) {
const container = document.createElement('div')
document.body.appendChild(container)
const messageContainer = createVNode(Container)
render(messageContainer, container)
containerInstance = messageContainer.component?.exposed
}

render(messageVNode, container)
containerInstance?.addMessage(options)
}

return { open }
return { HanaMessage }
}
6 changes: 3 additions & 3 deletions components/main/Header/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useStore } from '~/store'
const { t } = useI18n()
const { routesMap } = useRoutesMap()
const { userStore } = useStore()
const { open } = useMessage()
const { HanaMessage } = useMessage()
const notLoggedInMap = [{
text: t('header.user.notLoggedIn.login'),
Expand Down Expand Up @@ -60,9 +60,9 @@ function handleUserCommand(command: string | number | object) {
registerWindowVisible.value = true
break
default:
open({
HanaMessage({
message: '功能开发中...',
type: 'info',
type: 'error',
})
break
}
Expand Down

0 comments on commit 75de521

Please sign in to comment.