refactor(layout): move safe-area logic to HeaderBar with fallback to scaffold

This commit is contained in:
Clover You
2026-02-25 15:59:12 +08:00
committed by Dawn
parent 90a9150ac4
commit 9d3c37b22d
6 changed files with 424 additions and 388 deletions

View File

@@ -6,8 +6,6 @@
'bg-cover bg-center bg-no-repeat': props.backgroundImage
}"
:style="mergedStyle">
<!-- 顶部安全区域 -->
<div :class="[{ 'safe-area-top': safeAreaTop }, props.topSafeAreaClass]" />
<!-- 内容区域 -->
<div class="flex-1 min-h-0">
@@ -234,9 +232,6 @@ useMitt.on(WsResponseMessageType.RECEIVE_MESSAGE, async (data: MessageType) => {
</script>
<style scoped lang="scss">
.safe-area-top {
padding-top: var(--safe-area-inset-top);
}
.safe-area-bottom {
padding-bottom: var(--safe-area-inset-bottom);
}

View File

@@ -3,7 +3,7 @@
<img v-if="bgmURL" :src="bgmURL" class="absolute fixed top-0 left-0 w-full h-full z-0 dark:opacity-20" />
<!-- 页面容器 -->
<div class="flex w-full items-start flex-col flex-1 min-h-0 z-1">
<div class="w-full">
<div class="w-full" :class="{ 'pt-[var(--safe-area-inset-top)]': safeAreaRef }">
<slot name="header"></slot>
</div>
<!-- 消息内容区 -->
@@ -19,11 +19,31 @@
<script setup lang="ts">
import bgImg from '@/assets/mobile/chat-home/background.webp'
const { showFooter = true, background = true } = defineProps<{ showFooter?: boolean; background?: string | boolean }>()
const {
showFooter = true,
background = true,
safeArea = true
} = defineProps<{
showFooter?: boolean
background?: string | boolean
safeArea?: boolean
}>()
const bgmURL = computed(() => {
return typeof background === 'boolean' && background ? bgImg : background
})
const safeAreaRef = useSafeArea(() => safeArea)
function useSafeArea(getter: () => boolean) {
const safeArea = ref(true)
// 移除脚手架提供的安全区域样式,交由组件自己控制
const removeSafeArea = () => {
safeArea.value = false
}
provide('removeSafeArea', removeSafeArea)
return computed(() => safeArea.value && getter())
}
</script>
<style lang="scss"></style>

View File

@@ -1,5 +1,6 @@
<template>
<div class="w-full h-[56px] grid grid-cols-[100px_1fr_100px] z-2 bg-background text-foreground">
<div
class="w-full h-[56px] grid grid-cols-[100px_1fr_100px] z-2 bg-background text-foreground pt-[var(--safe-area-inset-top)]">
<div @click="handleBack" class="w-full h-full flex items-center">
<svg class="iconpark-icon w-24px h-24px ms-16px p-5px">
<use href="#fanhui" class="text-foreground"></use>
@@ -51,6 +52,10 @@ const props = withDefaults(defineProps<HeaderBarProps>(), {
border: false
})
// 移除脚手架提供的安全区域样式,交由组件自己控制
const removeScaffoldSaveArea = inject<() => void>('removeSafeArea', () => undefined)
removeScaffoldSaveArea()
const emits = defineEmits<(e: 'roomNameClick', payload: HeaderBarProps) => void>()
const handleRoomNameClick = () => {

View File

@@ -1,8 +1,7 @@
<template>
<AutoFixHeightPage>
<template #container>
<div class="flex flex-col overflow-auto h-full relative">
<img
src="@/assets/mobile/chat-home/background.webp"
class="absolute fixed top-0 left-0 w-full h-full z-0 dark:opacity-20" />
<!-- 页面蒙板 -->
<div
v-if="showMask"
@@ -111,7 +110,9 @@
{{ getUserState(item.uid)?.title }}
</template>
<template v-else>
<n-badge :color="item.activeStatus === OnlineEnum.ONLINE ? '#1ab292' : '#909090'" dot />
<n-badge
:color="item.activeStatus === OnlineEnum.ONLINE ? '#1ab292' : '#909090'"
dot />
{{ item.activeStatus === OnlineEnum.ONLINE ? '在线' : '离线' }}
</template>
]
@@ -157,6 +158,8 @@
</n-tabs>
</n-card>
</div>
</template>
</AutoFixHeightPage>
</template>
<style scoped>
.custom-rounded {

View File

@@ -1,9 +1,7 @@
<template>
<AutoFixHeightPage>
<template #container>
<div class="flex flex-col overflow-auto h-full relative">
<img
src="@/assets/mobile/chat-home/background.webp"
class="absolute fixed top-0 l-0 w-full h-full z-0 dark:opacity-20" />
<!-- 页面蒙板 -->
<div
v-if="showMask"
@@ -119,7 +117,11 @@
:color="item.muteNotification === NotificationTypeEnum.NOT_DISTURB ? 'grey' : '#c14053'"
:value="item.unreadCount"
:max="99">
<n-avatar :size="52" :src="AvatarUtils.getAvatarUrl(item.avatar)" fallback-src="/logo.png" round />
<n-avatar
:size="52"
:src="AvatarUtils.getAvatarUrl(item.avatar)"
fallback-src="/logo.png"
round />
</n-badge>
</div>
<!-- 中间两行内容 -->
@@ -184,13 +186,17 @@
:style="{ top: longPressState.longPressMenuTop + 'px' }"
class="fixed gap-10px z-999 left-1/2 transform -translate-x-1/2">
<div class="flex justify-between p-18px text-16px gap-22px rounded-16px bg-#4e4e4e whitespace-nowrap">
<div class="text-white" @click="handleDelete(currentLongPressItem)">{{ t('mobile_home.menu.delete') }}</div>
<div class="text-white" @click="handleDelete(currentLongPressItem)">
{{ t('mobile_home.menu.delete') }}
</div>
<div class="text-white" @click="handleToggleTop(currentLongPressItem)">
{{ currentLongPressItem?.top ? t('mobile_home.menu.unpin') : t('mobile_home.menu.pintop') }}
</div>
<div class="text-white" @click="handleToggleReadStatus((currentLongPressItem?.unreadCount ?? 0) > 0)">
{{
(currentLongPressItem?.unreadCount ?? 0) > 0 ? t('mobile_home.menu.read') : t('mobile_home.menu.unread')
(currentLongPressItem?.unreadCount ?? 0) > 0
? t('mobile_home.menu.read')
: t('mobile_home.menu.unread')
}}
</div>
</div>
@@ -202,6 +208,8 @@
</div>
</teleport>
</div>
</template>
</AutoFixHeightPage>
</template>
<script setup lang="ts">

View File

@@ -1,10 +1,13 @@
<template>
<AutoFixHeightPage :show-footer="false">
<template #container>
<div class="flex flex-col overflow-auto h-full">
<!-- 设置区 -->
<Settings />
<PersonalInfo :is-show="isShow"></PersonalInfo>
<!-- FIX: 内容消失问题 待确定 -->
<div class="relative top-0 flex-1 flex">
<div ref="measureRef" class="h-full w-full absolute top-0 z-0"></div>
<!-- 动态内容 -->
@@ -44,6 +47,8 @@
</div>
</div>
</div>
</template>
</AutoFixHeightPage>
</template>
<script setup lang="ts">
import PersonalInfo from '#/components/my/PersonalInfo.vue'