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,162 +1,165 @@
<template>
<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"
@touchend="maskHandler.close"
@click="maskHandler.close"
class="fixed inset-0 bg-black/20 backdrop-blur-sm z-[999] transition-all duration-3000 ease-in-out opacity-100"></div>
<AutoFixHeightPage>
<template #container>
<div class="flex flex-col overflow-auto h-full relative">
<!-- 页面蒙板 -->
<div
v-if="showMask"
@touchend="maskHandler.close"
@click="maskHandler.close"
class="fixed inset-0 bg-black/20 backdrop-blur-sm z-[999] transition-all duration-3000 ease-in-out opacity-100"></div>
<!-- 导航条 -->
<NavBar>
<template #center>
<n-text>{{ t('mobile_contact.title') }}</n-text>
</template>
<template #right>
<n-dropdown
@on-clickoutside="addIconHandler.clickOutside"
@select="addIconHandler.select"
trigger="click"
:show-arrow="true"
:options="uiViewsData.addOptions">
<n-button round strong secondary @click="addIconHandler.open">
<template #icon>
<n-icon>
<svg><use href="#plus"></use></svg>
</n-icon>
</template>
</n-button>
</n-dropdown>
</template>
</NavBar>
<!-- 输入框 -->
<div class="px-16px mt-2 mb-12px z-1">
<n-input
id="search"
class="rounded-6px w-full relative text-12px"
:maxlength="20"
clearable
spellCheck="false"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
:placeholder="t('mobile_contact.input.search')">
<template #prefix>
<svg class="w-12px h-12px"><use href="#search"></use></svg>
</template>
</n-input>
</div>
<n-card
:segmented="{ content: true, footer: 'soft' }"
:bordered="false"
class="custom-rounded flex-1"
header-class="py-15px! px-16px! text-14px!"
:title="t('mobile_contact.my_chat')">
<template #header-extra>
<div @click="toMessage" class="h-full flex items-center justify-end">
<span
v-if="contactUnreadCount > 0"
class="px-4px py-4px rounded-999px bg-#c14053 text-white text-12px font-600 min-w-20px text-center">
{{ contactUnreadCount > 99 ? '99+' : contactUnreadCount }}
</span>
</div>
<div @click="toMessage" class="h-full flex justify-end items-center">
<img src="@/assets/mobile/friend/right-arrow.webp" class="block h-20px dark:invert" alt="" />
</div>
</template>
<n-tabs type="segment" animated class="mt-4px p-[4px_10px_0px_8px]">
<n-tab-pane name="1" :tab="t('mobile_contact.tab.contacts')">
<n-collapse :display-directive="'show'" accordion :default-expanded-names="['1']">
<ContextMenu @contextmenu="showMenu($event)" @select="handleSelect($event.label)" :menu="menuList">
<n-collapse-item :title="t('mobile_contact.friend.title')" name="1">
<template #header-extra>
<span class="text-(10px #707070)">{{ onlineCount }}/{{ contactStore.contactsList.length }}</span>
<!-- 导航条 -->
<NavBar>
<template #center>
<n-text>{{ t('mobile_contact.title') }}</n-text>
</template>
<template #right>
<n-dropdown
@on-clickoutside="addIconHandler.clickOutside"
@select="addIconHandler.select"
trigger="click"
:show-arrow="true"
:options="uiViewsData.addOptions">
<n-button round strong secondary @click="addIconHandler.open">
<template #icon>
<n-icon>
<svg><use href="#plus"></use></svg>
</n-icon>
</template>
<n-scrollbar style="max-height: calc(100vh - (340px + var(--safe-area-inset-top)))">
<!-- 用户框 多套一层div来移除默认的右键事件然后覆盖掉因为margin空隙而导致右键可用 -->
<div @contextmenu.stop="$event.preventDefault()">
<n-flex
:size="10"
@click="handleClick(item.uid, RoomTypeEnum.SINGLE)"
:class="{ active: activeItem === item.uid }"
</n-button>
</n-dropdown>
</template>
</NavBar>
<!-- 输入框 -->
<div class="px-16px mt-2 mb-12px z-1">
<n-input
id="search"
class="rounded-6px w-full relative text-12px"
:maxlength="20"
clearable
spellCheck="false"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
:placeholder="t('mobile_contact.input.search')">
<template #prefix>
<svg class="w-12px h-12px"><use href="#search"></use></svg>
</template>
</n-input>
</div>
<n-card
:segmented="{ content: true, footer: 'soft' }"
:bordered="false"
class="custom-rounded flex-1"
header-class="py-15px! px-16px! text-14px!"
:title="t('mobile_contact.my_chat')">
<template #header-extra>
<div @click="toMessage" class="h-full flex items-center justify-end">
<span
v-if="contactUnreadCount > 0"
class="px-4px py-4px rounded-999px bg-#c14053 text-white text-12px font-600 min-w-20px text-center">
{{ contactUnreadCount > 99 ? '99+' : contactUnreadCount }}
</span>
</div>
<div @click="toMessage" class="h-full flex justify-end items-center">
<img src="@/assets/mobile/friend/right-arrow.webp" class="block h-20px dark:invert" alt="" />
</div>
</template>
<n-tabs type="segment" animated class="mt-4px p-[4px_10px_0px_8px]">
<n-tab-pane name="1" :tab="t('mobile_contact.tab.contacts')">
<n-collapse :display-directive="'show'" accordion :default-expanded-names="['1']">
<ContextMenu @contextmenu="showMenu($event)" @select="handleSelect($event.label)" :menu="menuList">
<n-collapse-item :title="t('mobile_contact.friend.title')" name="1">
<template #header-extra>
<span class="text-(10px #707070)">{{ onlineCount }}/{{ contactStore.contactsList.length }}</span>
</template>
<n-scrollbar style="max-height: calc(100vh - (340px + var(--safe-area-inset-top)))">
<!-- 用户框 多套一层div来移除默认的右键事件然后覆盖掉因为margin空隙而导致右键可用 -->
<div @contextmenu.stop="$event.preventDefault()">
<n-flex
:size="10"
@click="handleClick(item.uid, RoomTypeEnum.SINGLE)"
:class="{ active: activeItem === item.uid }"
class="item-box w-full h-75px mb-5px"
v-for="item in sortedContacts"
:key="item.uid">
<n-flex align="center" :size="10" class="h-75px pl-6px pr-8px flex-1 truncate">
<n-avatar
round
style="border: 1px solid var(--avatar-border-color)"
:size="44"
class="grayscale"
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
:src="AvatarUtils.getAvatarUrl(groupStore.getUserInfo(item.uid)?.avatar!)"
fallback-src="/logo.png" />
<n-flex vertical justify="space-between" class="h-fit flex-1 truncate">
<span class="text-14px leading-tight flex-1 truncate">
{{ groupStore.getUserInfo(item.uid)?.name }}
</span>
<div class="text leading-tight text-12px flex-y-center gap-4px flex-1 truncate">
[
<template v-if="isBotUser(item.uid)">助手</template>
<template v-else-if="getUserState(item.uid)">
<img class="size-12px rounded-50%" :src="getUserState(item.uid)?.url" alt="" />
{{ getUserState(item.uid)?.title }}
</template>
<template v-else>
<n-badge
:color="item.activeStatus === OnlineEnum.ONLINE ? '#1ab292' : '#909090'"
dot />
{{ item.activeStatus === OnlineEnum.ONLINE ? '在线' : '离线' }}
</template>
]
</div>
</n-flex>
</n-flex>
</n-flex>
</div>
</n-scrollbar>
</n-collapse-item>
</ContextMenu>
</n-collapse>
</n-tab-pane>
<n-tab-pane name="2" :tab="t('mobile_contact.tab.group')">
<n-collapse :display-directive="'show'" accordion :default-expanded-names="['1']">
<n-collapse-item :title="t('mobile_contact.group.title')" name="1">
<template #header-extra>
<span class="text-(10px #707070)">{{ groupChatList.length }}</span>
</template>
<n-scrollbar style="max-height: calc(100vh - (340px + var(--safe-area-inset-top)))">
<div
@click="handleClick(item.roomId, RoomTypeEnum.GROUP)"
:class="{ active: activeItem === item.roomId }"
class="item-box w-full h-75px mb-5px"
v-for="item in sortedContacts"
:key="item.uid">
v-for="item in groupChatList"
:key="item.roomId">
<n-flex align="center" :size="10" class="h-75px pl-6px pr-8px flex-1 truncate">
<n-avatar
round
style="border: 1px solid var(--avatar-border-color)"
bordered
:size="44"
class="grayscale"
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
:src="AvatarUtils.getAvatarUrl(groupStore.getUserInfo(item.uid)?.avatar!)"
:src="AvatarUtils.getAvatarUrl(item.avatar)"
fallback-src="/logo.png" />
<n-flex vertical justify="space-between" class="h-fit flex-1 truncate">
<span class="text-14px leading-tight flex-1 truncate">
{{ groupStore.getUserInfo(item.uid)?.name }}
</span>
<div class="text leading-tight text-12px flex-y-center gap-4px flex-1 truncate">
[
<template v-if="isBotUser(item.uid)">助手</template>
<template v-else-if="getUserState(item.uid)">
<img class="size-12px rounded-50%" :src="getUserState(item.uid)?.url" alt="" />
{{ getUserState(item.uid)?.title }}
</template>
<template v-else>
<n-badge :color="item.activeStatus === OnlineEnum.ONLINE ? '#1ab292' : '#909090'" dot />
{{ item.activeStatus === OnlineEnum.ONLINE ? '在线' : '离线' }}
</template>
]
</div>
</n-flex>
<span class="text-14px leading-tight flex-1 truncate">{{ item.remark || item.groupName }}</span>
</n-flex>
</n-flex>
</div>
</n-scrollbar>
</n-collapse-item>
</ContextMenu>
</n-collapse>
</n-tab-pane>
<n-tab-pane name="2" :tab="t('mobile_contact.tab.group')">
<n-collapse :display-directive="'show'" accordion :default-expanded-names="['1']">
<n-collapse-item :title="t('mobile_contact.group.title')" name="1">
<template #header-extra>
<span class="text-(10px #707070)">{{ groupChatList.length }}</span>
</template>
<n-scrollbar style="max-height: calc(100vh - (340px + var(--safe-area-inset-top)))">
<div
@click="handleClick(item.roomId, RoomTypeEnum.GROUP)"
:class="{ active: activeItem === item.roomId }"
class="item-box w-full h-75px mb-5px"
v-for="item in groupChatList"
:key="item.roomId">
<n-flex align="center" :size="10" class="h-75px pl-6px pr-8px flex-1 truncate">
<n-avatar
round
style="border: 1px solid var(--avatar-border-color)"
bordered
:size="44"
:src="AvatarUtils.getAvatarUrl(item.avatar)"
fallback-src="/logo.png" />
<span class="text-14px leading-tight flex-1 truncate">{{ item.remark || item.groupName }}</span>
</n-flex>
</div>
</n-scrollbar>
</n-collapse-item>
</n-collapse>
</n-tab-pane>
</n-tabs>
</n-card>
</div>
</div>
</n-scrollbar>
</n-collapse-item>
</n-collapse>
</n-tab-pane>
</n-tabs>
</n-card>
</div>
</template>
</AutoFixHeightPage>
</template>
<style scoped>
.custom-rounded {

View File

@@ -1,207 +1,215 @@
<template>
<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" />
<AutoFixHeightPage>
<template #container>
<div class="flex flex-col overflow-auto h-full relative">
<!-- 页面蒙板 -->
<div
v-if="showMask"
@touchend="maskHandler.close"
@mouseup="maskHandler.close"
:class="[
longPressState.longPressActive
? ''
: 'bg-black/20 backdrop-blur-sm transition-all duration-3000 ease-in-out opacity-100'
]"
class="fixed inset-0 z-[999]"></div>
<!-- 页面蒙板 -->
<div
v-if="showMask"
@touchend="maskHandler.close"
@mouseup="maskHandler.close"
:class="[
longPressState.longPressActive
? ''
: 'bg-black/20 backdrop-blur-sm transition-all duration-3000 ease-in-out opacity-100'
]"
class="fixed inset-0 z-[999]"></div>
<NavBar>
<template #left>
<n-flex @click="toSimpleBio" align="center" :size="6" class="w-full">
<n-avatar
:size="38"
:src="AvatarUtils.getAvatarUrl(userStore.userInfo?.avatar ? userStore.userInfo.avatar : '/logoD.png')"
fallback-src="/logo.png"
round />
<NavBar>
<template #left>
<n-flex @click="toSimpleBio" align="center" :size="6" class="w-full">
<n-avatar
:size="38"
:src="AvatarUtils.getAvatarUrl(userStore.userInfo?.avatar ? userStore.userInfo.avatar : '/logoD.png')"
fallback-src="/logo.png"
round />
<n-flex vertical justify="center" :size="6">
<p
style="
font-weight: bold !important;
font-family:
system-ui,
-apple-system,
sans-serif;
"
class="text-(16px [--text-color])">
{{ userStore.userInfo?.name ? userStore.userInfo.name : t('mobile_home.noname') }}
</p>
<p class="text-(10px [--text-color])">
{{
userStore.userInfo?.uid
? groupStore.getUserInfo(userStore.userInfo!.uid)?.locPlace || t('mobile_home.china')
: t('mobile_home.china')
}}
</p>
</n-flex>
</n-flex>
</template>
<template #right>
<n-dropdown
@on-clickoutside="addIconHandler.clickOutside"
@select="addIconHandler.select"
trigger="click"
:show-arrow="true"
:options="uiViewsData.addOptions">
<n-button round strong secondary @click="addIconHandler.open">
<template #icon>
<n-icon>
<svg><use href="#plus"></use></svg>
</n-icon>
</template>
</n-button>
<!-- <svg @click="addIconHandler.open" class="size-22px p-5px rounded-8px">
<use href="#plus"></use>
</svg> -->
</n-dropdown>
</template>
</NavBar>
<div class="px-16px mt-5px">
<div class="py-5px shrink-0">
<n-input
id="search"
class="rounded-6px w-full relative text-12px"
:maxlength="20"
clearable
spellCheck="false"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
:placeholder="t('mobile_home.input.search')"
@focus="lockScroll"
@blur="unlockScroll">
<template #prefix>
<svg class="w-12px h-12px">
<use href="#search"></use>
</svg>
<n-flex vertical justify="center" :size="6">
<p
style="
font-weight: bold !important;
font-family:
system-ui,
-apple-system,
sans-serif;
"
class="text-(16px [--text-color])">
{{ userStore.userInfo?.name ? userStore.userInfo.name : t('mobile_home.noname') }}
</p>
<p class="text-(10px [--text-color])">
{{
userStore.userInfo?.uid
? groupStore.getUserInfo(userStore.userInfo!.uid)?.locPlace || t('mobile_home.china')
: t('mobile_home.china')
}}
</p>
</n-flex>
</n-flex>
</template>
</n-input>
</div>
<n-divider class="m-0! p-0! mt-10px!" />
</div>
<van-pull-refresh
class="flex-1"
:pull-distance="100"
:disabled="!isEnablePullRefresh"
v-model="loading"
@refresh="onRefresh">
<div class="flex flex-col h-full">
<div class="flex-1 overflow-y-auto overflow-x-hidden min-h-0" @scroll="onScroll" ref="scrollContainer">
<van-swipe-cell
@open="handleSwipeOpen"
@close="handleSwipeClose"
v-for="(item, idx) in sessionList"
v-on-long-press="[(e: PointerEvent) => handleLongPress(e, item), longPressOption]"
:key="`${item.id}-${idx}`"
class="text-black"
:class="item.top ? 'w-full bg-#64A29C18' : ''">
<!-- 长按项 -->
<div
@click.stop="intoRoom(item)"
class="grid grid-cols-[2.2rem_1fr_max-content] items-start px-4 py-3 gap-1">
<div class="flex-shrink-0">
<n-badge
:offset="[-6, 6]"
: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-badge>
</div>
<!-- 中间两行内容 -->
<div class="truncate pl-7 flex pt-5px gap-10px leading-tight flex-col">
<n-text class="text-16px font-bold flex-1 truncate">{{ item.name }}</n-text>
<div class="text-13px text-gray-600 dark:text-gray-400 truncate">
{{ item.text }}
</div>
</div>
<template #right>
<n-dropdown
@on-clickoutside="addIconHandler.clickOutside"
@select="addIconHandler.select"
trigger="click"
:show-arrow="true"
:options="uiViewsData.addOptions">
<n-button round strong secondary @click="addIconHandler.open">
<template #icon>
<n-icon>
<svg><use href="#plus"></use></svg>
</n-icon>
</template>
</n-button>
<!-- <svg @click="addIconHandler.open" class="size-22px p-5px rounded-8px">
<use href="#plus"></use>
</svg> -->
</n-dropdown>
</template>
</NavBar>
<!-- 时间靠顶 -->
<div class="text-12px pt-9px text-right flex flex-col gap-1 items-end justify-center">
<div class="flex items-center gap-1">
<span v-if="item.hotFlag === IsAllUserEnum.Yes">
<svg class="size-22px select-none outline-none cursor-pointer color-#13987f">
<use href="#auth"></use>
</svg>
</span>
<span class="text-gray-600 whitespace-nowrap">
{{ formatTimestamp(item?.activeTime) }}
</span>
</div>
<div v-if="item.muteNotification === NotificationTypeEnum.NOT_DISTURB">
<svg class="size-14px z-100 color-gray-500/90">
<use href="#close-remind"></use>
</svg>
<div class="px-16px mt-5px">
<div class="py-5px shrink-0">
<n-input
id="search"
class="rounded-6px w-full relative text-12px"
:maxlength="20"
clearable
spellCheck="false"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
:placeholder="t('mobile_home.input.search')"
@focus="lockScroll"
@blur="unlockScroll">
<template #prefix>
<svg class="w-12px h-12px">
<use href="#search"></use>
</svg>
</template>
</n-input>
</div>
<n-divider class="m-0! p-0! mt-10px!" />
</div>
<van-pull-refresh
class="flex-1"
:pull-distance="100"
:disabled="!isEnablePullRefresh"
v-model="loading"
@refresh="onRefresh">
<div class="flex flex-col h-full">
<div class="flex-1 overflow-y-auto overflow-x-hidden min-h-0" @scroll="onScroll" ref="scrollContainer">
<van-swipe-cell
@open="handleSwipeOpen"
@close="handleSwipeClose"
v-for="(item, idx) in sessionList"
v-on-long-press="[(e: PointerEvent) => handleLongPress(e, item), longPressOption]"
:key="`${item.id}-${idx}`"
class="text-black"
:class="item.top ? 'w-full bg-#64A29C18' : ''">
<!-- 长按项 -->
<div
@click.stop="intoRoom(item)"
class="grid grid-cols-[2.2rem_1fr_max-content] items-start px-4 py-3 gap-1">
<div class="flex-shrink-0">
<n-badge
:offset="[-6, 6]"
: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-badge>
</div>
<!-- 中间两行内容 -->
<div class="truncate pl-7 flex pt-5px gap-10px leading-tight flex-col">
<n-text class="text-16px font-bold flex-1 truncate">{{ item.name }}</n-text>
<div class="text-13px text-gray-600 dark:text-gray-400 truncate">
{{ item.text }}
</div>
</div>
<!-- 时间靠顶 -->
<div class="text-12px pt-9px text-right flex flex-col gap-1 items-end justify-center">
<div class="flex items-center gap-1">
<span v-if="item.hotFlag === IsAllUserEnum.Yes">
<svg class="size-22px select-none outline-none cursor-pointer color-#13987f">
<use href="#auth"></use>
</svg>
</span>
<span class="text-gray-600 whitespace-nowrap">
{{ formatTimestamp(item?.activeTime) }}
</span>
</div>
<div v-if="item.muteNotification === NotificationTypeEnum.NOT_DISTURB">
<svg class="size-14px z-100 color-gray-500/90">
<use href="#close-remind"></use>
</svg>
</div>
</div>
</div>
<template #right>
<div class="flex w-auto flex-wrap h-full">
<div
class="h-full text-14px w-80px bg-#13987f text-white flex items-center justify-center"
@click="handleToggleTop(item)">
{{ item.top ? t('mobile_home.chat.unpin') : t('mobile_home.chat.pintop') }}
</div>
<div
:class="(item?.unreadCount ?? 0) > 0 ? 'bg-#909090' : 'bg-#fbb160'"
class="h-full text-14px w-80px text-white flex items-center justify-center"
@click="handleToggleReadStatus((item?.unreadCount ?? 0) > 0, item)">
{{
(item?.unreadCount ?? 0) > 0
? t('mobile_home.chat.mark_as_read')
: t('mobile_home.chat.mark_as_unread')
}}
</div>
<div
class="h-full text-14px w-80px bg-#d5304f text-white flex items-center justify-center"
@click="handleDelete(item)">
{{ t('mobile_home.chat.delete') }}
</div>
</div>
</template>
</van-swipe-cell>
</div>
</div>
</van-pull-refresh>
<teleport to="body">
<div
v-if="longPressState.showLongPressMenu"
: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="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')
}}
</div>
</div>
<template #right>
<div class="flex w-auto flex-wrap h-full">
<div
class="h-full text-14px w-80px bg-#13987f text-white flex items-center justify-center"
@click="handleToggleTop(item)">
{{ item.top ? t('mobile_home.chat.unpin') : t('mobile_home.chat.pintop') }}
</div>
<div
:class="(item?.unreadCount ?? 0) > 0 ? 'bg-#909090' : 'bg-#fbb160'"
class="h-full text-14px w-80px text-white flex items-center justify-center"
@click="handleToggleReadStatus((item?.unreadCount ?? 0) > 0, item)">
{{
(item?.unreadCount ?? 0) > 0
? t('mobile_home.chat.mark_as_read')
: t('mobile_home.chat.mark_as_unread')
}}
</div>
<div
class="h-full text-14px w-80px bg-#d5304f text-white flex items-center justify-center"
@click="handleDelete(item)">
{{ t('mobile_home.chat.delete') }}
</div>
</div>
</template>
</van-swipe-cell>
</div>
</div>
</van-pull-refresh>
<teleport to="body">
<div
v-if="longPressState.showLongPressMenu"
: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="handleToggleTop(currentLongPressItem)">
{{ currentLongPressItem?.top ? t('mobile_home.menu.unpin') : t('mobile_home.menu.pintop') }}
<div class="flex w-full justify-center h-15px">
<svg width="34" height="13" viewBox="0 0 35 13">
<path d="M0 0 L35 0 L17.5 13 Z" fill="#4e4e4e" />
</svg>
</div>
</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')
}}
</div>
</div>
<div class="flex w-full justify-center h-15px">
<svg width="34" height="13" viewBox="0 0 35 13">
<path d="M0 0 L35 0 L17.5 13 Z" fill="#4e4e4e" />
</svg>
</div>
</teleport>
</div>
</teleport>
</div>
</template>
</AutoFixHeightPage>
</template>
<script setup lang="ts">

View File

@@ -1,49 +1,54 @@
<template>
<div class="flex flex-col overflow-auto h-full">
<!-- 设置区 -->
<Settings />
<AutoFixHeightPage :show-footer="false">
<template #container>
<div class="flex flex-col overflow-auto h-full">
<!-- 设置区 -->
<Settings />
<PersonalInfo :is-show="isShow"></PersonalInfo>
<PersonalInfo :is-show="isShow"></PersonalInfo>
<div class="relative top-0 flex-1 flex">
<div ref="measureRef" class="h-full w-full absolute top-0 z-0"></div>
<!-- 动态内容 -->
<div
ref="scrollContainer"
:style="{ height: tabHeight + 'px' }"
class="z-1 overflow-hidden mt-2 absolute z-3 w-full">
<n-card class="custom-rounded" content-class="flex flex-col gap-4 z-1 p-10px mt-4 p-15px!">
<n-scrollbar
ref="scrollbarRef"
<!-- FIX: 内容消失问题 待确定 -->
<div class="relative top-0 flex-1 flex">
<div ref="measureRef" class="h-full w-full absolute top-0 z-0"></div>
<!-- 动态内容 -->
<div
ref="scrollContainer"
:style="{ height: tabHeight + 'px' }"
:content-style="{ overflowX: 'hidden' }"
class="overflow-x-hidden"
@scroll="handleScroll">
<!-- 动态内容区域 -->
<div class="py-12px">
<DynamicList
mode="mobile"
@preview-image="previewImage"
@video-play="handleVideoPlay"
@load-more="loadMore"
@item-click="handleItemClick" />
</div>
</n-scrollbar>
</n-card>
</div>
</div>
class="z-1 overflow-hidden mt-2 absolute z-3 w-full">
<n-card class="custom-rounded" content-class="flex flex-col gap-4 z-1 p-10px mt-4 p-15px!">
<n-scrollbar
ref="scrollbarRef"
:style="{ height: tabHeight + 'px' }"
:content-style="{ overflowX: 'hidden' }"
class="overflow-x-hidden"
@scroll="handleScroll">
<!-- 动态内容区域 -->
<div class="py-12px">
<DynamicList
mode="mobile"
@preview-image="previewImage"
@video-play="handleVideoPlay"
@load-more="loadMore"
@item-click="handleItemClick" />
</div>
</n-scrollbar>
</n-card>
</div>
</div>
<div
@click="toPublishCommunity"
class="w-52px h-52px rounded-full absolute bottom-120px right-20px z-3 flex items-center justify-center bg-[linear-gradient(145deg,#ACD7DA,#13987F)] shadow-[0_4px_12px_rgba(0,0,0,0.25),0_0_12px_rgba(172,215,218,0.8)]">
<div class="relative w-20px h-20px">
<!-- 竖线 -->
<div class="absolute left-1/2 top-0 h-full w-2px bg-white -translate-x-1/2"></div>
<!-- 横线 -->
<div class="absolute top-1/2 left-0 w-full h-2px bg-white -translate-y-1/2"></div>
<div
@click="toPublishCommunity"
class="w-52px h-52px rounded-full absolute bottom-120px right-20px z-3 flex items-center justify-center bg-[linear-gradient(145deg,#ACD7DA,#13987F)] shadow-[0_4px_12px_rgba(0,0,0,0.25),0_0_12px_rgba(172,215,218,0.8)]">
<div class="relative w-20px h-20px">
<!-- 竖线 -->
<div class="absolute left-1/2 top-0 h-full w-2px bg-white -translate-x-1/2"></div>
<!-- 横线 -->
<div class="absolute top-1/2 left-0 w-full h-2px bg-white -translate-y-1/2"></div>
</div>
</div>
</div>
</div>
</div>
</template>
</AutoFixHeightPage>
</template>
<script setup lang="ts">
import PersonalInfo from '#/components/my/PersonalInfo.vue'