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 a74cf87 commit 0dbacf6
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 52 deletions.
49 changes: 39 additions & 10 deletions components/hana/Dialog/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { TransitionProps } from 'vue'
import type { DialogOptions } from './useDialog'
withDefaults(defineProps<DialogOptions>(), {
const props = withDefaults(defineProps<DialogOptions>(), {
title: '默认标题',
overlayOpacity: 0.5,
hideHeader: false,
Expand All @@ -12,14 +12,34 @@ withDefaults(defineProps<DialogOptions>(), {
})
const emits = defineEmits<{
(e: 'Ok'): void
(e: 'Cancel'): void
(e: 'ok'): void
(e: 'cancel'): void
(e: 'destroy'): void
(e: 'removeOverlay'): void
(e: 'update:modelValue', value: boolean): void
}>()
const programmaticVisible = ref(false)
watch(programmaticVisible, (newV) => {
if (!newV) {
emits('removeOverlay')
}
})
const visible = computed(() => props.programmatic ? programmaticVisible.value : props.modelValue)
onMounted(() => {
if (props.programmatic) {
programmaticVisible.value = true
}
})
function handleClose() {
emits('update:modelValue', false)
if (props.programmatic) {
programmaticVisible.value = false
}
else {
emits('update:modelValue', false)
}
}
const transitionClasses: TransitionProps = {
Expand All @@ -34,12 +54,16 @@ const transitionClasses: TransitionProps = {
function handleAfterLeave() {
emits('destroy')
}
defineExpose({
handleClose,
})
</script>

<template>
<transition v-bind="transitionClasses" @after-leave="handleAfterLeave">
<div
v-if="modelValue"
v-if="visible"
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 @@ -50,7 +74,7 @@ function handleAfterLeave() {
>
<div v-if="!hideHeader" class="flex items-center">
<span v-if="title" class="text-2xl font-bold">{{ title }}</span>
<HanaButton icon="lucide:x" class="relative -right-2 -top-2 ml-auto" icon-button @click="handleClose" />
<HanaButton v-if="!programmatic" icon="lucide:x" class="relative -right-2 -top-2 ml-auto" icon-button @click="handleClose" />
</div>
</slot>
</div>
Expand All @@ -63,14 +87,18 @@ function handleAfterLeave() {
<div v-if="programmatic" class="mt-5 flex justify-end gap-4">
<HanaButton
v-if="showCancelButton || false"
@click="emits('Cancel')"
shape="square"
class="w-20"
@click="emits('cancel')"
>
{{ cancelText || '取消' }}
</HanaButton>
<HanaButton
v-if="showOkButton || true"
dark-mode
@click="emits('Ok')"
shape="square"
class="w-20"
@click="emits('ok')"
>
{{ okText || '确定' }}
</HanaButton>
Expand All @@ -81,9 +109,10 @@ function handleAfterLeave() {
</div>
</transition>
<div
v-if="!programmatic"
class="fixed inset-0 z-40 bg-black transition-opacity duration-300"
:class="{ 'pointer-events-none': !modelValue }"
:style="{ opacity: modelValue ? overlayOpacity : 0 }"
:class="{ 'pointer-events-none': !visible }"
:style="{ opacity: visible ? overlayOpacity : 0 }"
@click="handleClose"
/>
</template>
74 changes: 50 additions & 24 deletions components/hana/Dialog/useDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface DialogDeclarativeOptions {
}

// 编程式调用 Dialog 的选项
// 如 HanaDialog({ ... })
// 如 callHanaDialog({ ... })
interface DialogProgrammaticOptions {
title?: string
content?: string
Expand All @@ -28,42 +28,68 @@ interface DialogProgrammaticOptions {
export type DialogOptions = DialogDeclarativeOptions & DialogProgrammaticOptions & { programmatic?: boolean }

export default function useDialog() {
const HanaDialog = (options: DialogOptions) => {
if (typeof window === 'undefined') {
return
// 编程式调用 Dialog 手动管理遮罩层以正确触发过渡效果
function createOverlay(overlayOpacity: number = 0.5) {
const overlay = document.createElement('div')
overlay.className = 'fixed inset-0 z-40 bg-black transition-opacity duration-300'
overlay.style.opacity = '0'
document.body.appendChild(overlay)

requestAnimationFrame(() => {
overlay.style.opacity = String(overlayOpacity)
})

const removeOverlay = () => {
overlay.style.opacity = '0'
setTimeout(() => {
document.body.removeChild(overlay)
}, 300)
}

overlay.addEventListener('click', () => {
removeOverlay()
})
return { removeOverlay }
}

// 编程式调用 Dialog
const callHanaDialog = (options: DialogOptions) => {
// 创建容器
const container = document.createElement('div')
document.appendChild(container)
document.body.appendChild(container)

// 创建遮罩层
const { removeOverlay } = createOverlay(options.overlayOpacity)

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

render(dialogVNode, container)
}

return { HanaDialog }
return { callHanaDialog }
}
4 changes: 2 additions & 2 deletions components/hana/Message/useMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type ContainerInstance = ComponentPublicInstance<{
let containerInstance: ContainerInstance | null = null

export default function useMessage() {
const HanaMessage = (options: MessageOptions) => {
const callHanaMessage = (options: MessageOptions) => {
if (!containerInstance) {
const container = document.createElement('div')
document.body.appendChild(container)
Expand All @@ -29,5 +29,5 @@ export default function useMessage() {
containerInstance.addMessage(options)
}

return { HanaMessage }
return { callHanaMessage }
}
31 changes: 15 additions & 16 deletions components/main/Header/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { useStore } from '~/store'
const { t } = useI18n()
const { routesMap } = useRoutesMap()
const { userStore } = useStore()
const { HanaMessage } = useMessage()
const { HanaDialog } = useDialog()
const { callHanaMessage } = useMessage()
const { callHanaDialog } = useDialog()
const notLoggedInMap = [{
text: t('header.user.notLoggedIn.login'),
Expand Down Expand Up @@ -62,23 +62,22 @@ function handleUserCommand(command: string | number | object) {
registerWindowVisible.value = true
break
case t('header.user.loggedIn.logout'):
HanaDialog({
callHanaDialog({
title: '提示',
content: '确定要退出登录吗?',
showCancelButton: true,
onOk: () => {
// localStorage.removeItem('token')
// userStore.logout()
// HanaMessage({
// message: '已退出登录。',
// type: 'success',
// })
console.log('已退出登录。')
localStorage.removeItem('token')
userStore.logout()
callHanaMessage({
message: '已退出登录。',
type: 'success',
})
},
})
break
default:
HanaMessage({
callHanaMessage({
message: '功能开发中...',
type: 'error',
})
Expand All @@ -96,7 +95,7 @@ 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({
callHanaMessage({
message: `欢迎回来,${userStore.userInfo?.username}。`,
type: 'success',
})
Expand Down Expand Up @@ -221,7 +220,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 @@ -236,8 +235,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 @@ -254,7 +253,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 0dbacf6

Please sign in to comment.