Skip to content

Commit

Permalink
feat: 编程式 Dialog 初步实现
Browse files Browse the repository at this point in the history
  • Loading branch information
nonhana committed Nov 24, 2024
1 parent 75de521 commit a74cf87
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 28 deletions.
53 changes: 38 additions & 15 deletions components/hana/Dialog.vue → components/hana/Dialog/index.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
<script setup lang="ts">
import type { TransitionProps } from 'vue'
import type { DialogOptions } from './useDialog'
withDefaults(defineProps<{
title?: string
overlayOpacity?: number
hideHeader?: boolean
width?: string
height?: string
}>(), {
withDefaults(defineProps<DialogOptions>(), {
title: '默认标题',
overlayOpacity: 0.5,
hideHeader: false,
width: '400px',
height: 'auto',
programmatic: false,
})
const visible = defineModel<boolean>()
const emits = defineEmits<{
(e: 'Ok'): void
(e: 'Cancel'): void
(e: 'destroy'): void
(e: 'update:modelValue', value: boolean): void
}>()
function handleClose() {
visible.value = false
emits('update:modelValue', false)
}
const transitionClasses: TransitionProps = {
Expand All @@ -29,12 +30,16 @@ const transitionClasses: TransitionProps = {
leaveFromClass: 'opacity-100',
leaveToClass: 'opacity-0 scale-95',
}
function handleAfterLeave() {
emits('destroy')
}
</script>

<template>
<transition v-bind="transitionClasses">
<transition v-bind="transitionClasses" @after-leave="handleAfterLeave">
<div
v-if="visible"
v-if="modelValue"
class="fixed left-1/2 top-1/2 z-50 max-w-[90%] -translate-x-1/2 -translate-y-1/2 rounded-2xl bg-white p-5 shadow"
:style="{ width }"
>
Expand All @@ -49,18 +54,36 @@ const transitionClasses: TransitionProps = {
</div>
</slot>
</div>
<div :style="{ height }">
<div v-if="programmatic">
<span>{{ content }}</span>
</div>
<div v-else :style="{ height }">
<slot />
</div>
<div v-if="$slots.footer" class="mt-5">
<div v-if="programmatic" class="mt-5 flex justify-end gap-4">
<HanaButton
v-if="showCancelButton || false"
@click="emits('Cancel')"
>
{{ cancelText || '取消' }}
</HanaButton>
<HanaButton
v-if="showOkButton || true"
dark-mode
@click="emits('Ok')"
>
{{ okText || '确定' }}
</HanaButton>
</div>
<div v-else-if="$slots.footer" class="mt-5">
<slot name="footer" />
</div>
</div>
</transition>
<div
class="fixed inset-0 z-40 bg-black transition-opacity duration-300"
:class="{ 'pointer-events-none': !visible }"
:style="{ opacity: visible ? overlayOpacity : 0 }"
:class="{ 'pointer-events-none': !modelValue }"
:style="{ opacity: modelValue ? overlayOpacity : 0 }"
@click="handleClose"
/>
</template>
69 changes: 69 additions & 0 deletions components/hana/Dialog/useDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createVNode, render } from 'vue'
import Dialog from './index.vue'

// 声明式调用 Dialog 的选项
// 如 <HanaDialog ... />
interface DialogDeclarativeOptions {
title?: string
overlayOpacity?: number
hideHeader?: boolean
width?: string
height?: string
modelValue?: boolean
}

// 编程式调用 Dialog 的选项
// 如 HanaDialog({ ... })
interface DialogProgrammaticOptions {
title?: string
content?: string
showOkButton?: boolean
showCancelButton?: boolean
okText?: string
cancelText?: string
onOk?: () => void
onCancel?: () => void
}

export type DialogOptions = DialogDeclarativeOptions & DialogProgrammaticOptions & { programmatic?: boolean }

export default function useDialog() {
const HanaDialog = (options: DialogOptions) => {
if (typeof window === 'undefined') {
return
}

const container = document.createElement('div')
document.appendChild(container)

const dialogVNode = createVNode(Dialog, {
...options,
'programmatic': true,
'modelValue': true,
'onDestroy': () => {
render(null, container) // 1. 把 Dialog 组件从 container 中移除
document.removeChild(container) // 2. 把 container 从 document.body 中移除
},
'onOk': () => {
options.onOk?.()
render(null, container)
document.removeChild(container)
},
'onCancel': () => {
options.onCancel?.()
render(null, container)
document.removeChild(container)
},
'onUpdate:modelValue': (value: boolean) => {
if (!value) {
render(null, container)
document.removeChild(container)
}
},
})

render(dialogVNode, container)
}

return { HanaDialog }
}
2 changes: 1 addition & 1 deletion components/hana/Message/Container.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { MessageOptions } from './useMessage'
import Message from './index.vue'
import Message from './Item.vue'
interface MessageItem extends MessageOptions {
key: number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const props = withDefaults(defineProps<MessageOptions>(), {
message: '默认消息',
type: 'info',
timeout: 3000,
position: 'top',
})
const visible = ref(false)
Expand Down
19 changes: 12 additions & 7 deletions components/hana/Message/useMessage.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import type { ComponentPublicInstance } from 'vue'
import { createVNode, render } from 'vue'
import Container from './Container.vue'

export interface MessageOptions {
message?: string
type?: 'info' | 'success' | 'warning' | 'error'
position?: 'top' | 'bottom'
timeout?: number
[key: string]: any
}

let containerInstance: any = null
type ContainerInstance = ComponentPublicInstance<{
addMessage: (options: MessageOptions) => void
}>

let containerInstance: ContainerInstance | null = null

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

const messageContainerVNode = createVNode(Container)
render(messageContainerVNode, container)

containerInstance = messageContainerVNode.component?.exposed as ContainerInstance
}

containerInstance?.addMessage(options)
containerInstance.addMessage(options)
}

return { HanaMessage }
Expand Down
30 changes: 26 additions & 4 deletions components/main/Header/index.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<script setup lang="ts">
import useDialog from '~/components/hana/Dialog/useDialog'
import useMessage from '~/components/hana/Message/useMessage'
import { useStore } from '~/store'
const { t } = useI18n()
const { routesMap } = useRoutesMap()
const { userStore } = useStore()
const { HanaMessage } = useMessage()
const { HanaDialog } = useDialog()
const notLoggedInMap = [{
text: t('header.user.notLoggedIn.login'),
Expand Down Expand Up @@ -59,6 +61,22 @@ function handleUserCommand(command: string | number | object) {
case t('header.user.notLoggedIn.register'):
registerWindowVisible.value = true
break
case t('header.user.loggedIn.logout'):
HanaDialog({
title: '提示',
content: '确定要退出登录吗?',
showCancelButton: true,
onOk: () => {
// localStorage.removeItem('token')
// userStore.logout()
// HanaMessage({
// message: '已退出登录。',
// type: 'success',
// })
console.log('已退出登录。')
},
})
break
default:
HanaMessage({
message: '功能开发中...',
Expand All @@ -78,6 +96,10 @@ async function handleSubmit(type: 'login' | 'register', e: Event) {
localStorage.setItem('token', data.value.payload!.token)
userStore.setUserInfo(data.value.payload!.userInfo)
loginWindowVisible.value = false
HanaMessage({
message: `欢迎回来,${userStore.userInfo?.username}。`,
type: 'success',
})
}
}
break
Expand Down Expand Up @@ -199,7 +221,7 @@ onUnmounted(() => {
</div>
</div>
</div>
<HanaDialog v-model="loginWindowVisible" title="欢迎来到...花园。">
<!-- <HanaDialog v-model="loginWindowVisible" title="欢迎来到...花园。">
<form @submit.prevent="(e) => handleSubmit('login', e)">
<div class="flex flex-col gap-4">
<HanaInput name="account" prefix-icon="lucide:user-round" shape="rounded" placeholder="用户名 / 邮箱" />
Expand All @@ -214,8 +236,8 @@ onUnmounted(() => {
</HanaButton>
</div>
</form>
</HanaDialog>
<HanaDialog v-model="registerWindowVisible" title="这里有你想找的花吗?">
</HanaDialog> -->
<!-- <HanaDialog v-model="registerWindowVisible" title="这里有你想找的花吗?">
<form @submit.prevent="(e) => handleSubmit('register', e)">
<div class="flex flex-col gap-4">
<HanaInput name="username" prefix-icon="lucide:user-round" shape="rounded" placeholder="用户名" />
Expand All @@ -232,7 +254,7 @@ onUnmounted(() => {
</HanaButton>
</div>
</form>
</HanaDialog>
</HanaDialog> -->
<div class="block md:hidden">
<MainHeaderDrawer v-model="drawerVisible" :active-status="activeStatus" />
</div>
Expand Down

0 comments on commit a74cf87

Please sign in to comment.