refactor(layout): move safe-area logic to HeaderBar with fallback to scaffold
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user