朋友圈

This commit is contained in:
2025-12-08 13:08:00 +08:00
parent bc2d4dd466
commit a7b5179976
4 changed files with 107 additions and 13 deletions

View File

@@ -21,6 +21,9 @@ class WebSocketManager {
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private reconnectDelay = 3000
// 用于通知去重的缓存存储最近处理过的通知ID
private recentMomentNotifs: Set<string> = new Set()
private notifCacheTimeout = 5000 // 5秒内相同通知视为重复
/**
* 连接WebSocket
@@ -119,7 +122,10 @@ class WebSocketManager {
* 添加普通消息处理器
*/
onMessage(handler: MessageHandler) {
this.messageHandlers.push(handler)
// 防止重复注册
if (!this.messageHandlers.includes(handler)) {
this.messageHandlers.push(handler)
}
}
/**
@@ -136,7 +142,10 @@ class WebSocketManager {
* 添加信令处理器 (WebRTC用)
*/
onSignal(handler: SignalHandler) {
this.signalHandlers.push(handler)
// 防止重复注册
if (!this.signalHandlers.includes(handler)) {
this.signalHandlers.push(handler)
}
}
/**
@@ -153,7 +162,10 @@ class WebSocketManager {
* 添加朋友圈通知处理器
*/
onMomentNotification(handler: MomentNotifHandler) {
this.momentNotifHandlers.push(handler)
// 防止重复注册
if (!this.momentNotifHandlers.includes(handler)) {
this.momentNotifHandlers.push(handler)
}
}
/**
@@ -167,9 +179,27 @@ class WebSocketManager {
}
/**
* 内部处理朋友圈通知
* 内部处理朋友圈通知(带去重)
*/
private handleMomentNotification(payload: MomentNotifPayload) {
// 生成通知唯一标识(基于 moment_id + type + from_user_id
const notifKey = `${payload.moment_id}_${payload.type}_${payload.from_user?.id || ''}_${payload.comment_id || ''}`
// 检查是否为重复通知
if (this.recentMomentNotifs.has(notifKey)) {
console.log('🔄 跳过重复朋友圈通知:', notifKey)
return
}
// 添加到缓存
this.recentMomentNotifs.add(notifKey)
// 设置定时器清除缓存
setTimeout(() => {
this.recentMomentNotifs.delete(notifKey)
}, this.notifCacheTimeout)
// 分发给所有处理器
this.momentNotifHandlers.forEach(handler => handler(payload))
}

View File

@@ -1,9 +1,9 @@
<template>
<div>
<img
<img
:src="fullImageUrl"
class="max-w-full cursor-zoom-in hover:brightness-90 transition duration-300 block max-h-[300px] object-cover rounded-lg"
loading="lazy"
loading="lazy"
@click="showPreview = true"
/>
@@ -12,7 +12,7 @@
:show="showPreview"
:images="[fullImageUrl]"
@close="showPreview = false"
/>
/>
</div>
</template>

View File

@@ -64,7 +64,8 @@
:name="moment.user?.name || ''"
:avatar="moment.user?.avatar"
size="xl"
class="shrink-0"
class="shrink-0 cursor-pointer hover:opacity-80 transition"
@click="showUserInfo(moment.user)"
/>
<div class="flex-1 min-w-0">
<!-- 名字和时间 -->
@@ -142,8 +143,9 @@
:name="like.user?.name || ''"
:avatar="like.user?.avatar"
size="sm"
class="shrink-0"
class="shrink-0 cursor-pointer hover:opacity-80 transition"
:title="like.user?.name"
@click="showUserInfo(like.user)"
/>
</template>
<span v-if="moment.likes.length > 8" class="text-gray-500 text-xs ml-1">
@@ -158,21 +160,35 @@
<div
v-for="comment in moment.comments.slice(0, expandedMoments.has(moment.id) ? undefined : 3)"
:key="comment.id"
class="flex items-start gap-2 p-2 bg-white/5 rounded-lg text-sm"
class="flex items-start gap-2 p-2 bg-white/5 rounded-lg text-sm group/comment"
>
<Avatar
:name="comment.user?.name || ''"
:avatar="comment.user?.avatar"
size="sm"
class="shrink-0"
class="shrink-0 cursor-pointer hover:opacity-80 transition"
@click="showUserInfo(comment.user)"
/>
<div class="flex-1 min-w-0">
<span class="font-medium text-gray-300">{{ comment.user?.name }}</span>
<span
class="font-medium text-gray-300 cursor-pointer hover:text-primary transition"
@click="showUserInfo(comment.user)"
>{{ comment.user?.name }}</span>
<span v-if="comment.reply_to_user" class="text-gray-500">
回复 <span class="text-gray-400">{{ comment.reply_to_user.name }}</span>
回复 <span
class="text-gray-400 cursor-pointer hover:text-primary transition"
@click="showUserInfo(comment.reply_to_user)"
>{{ comment.reply_to_user.name }}</span>
</span>
<span class="text-gray-500">: </span>
<span class="text-gray-400 break-words">{{ comment.content }}</span>
<!-- 回复按钮 -->
<button
@click.stop="openCommentInput(moment, comment)"
class="ml-2 text-xs text-gray-500 hover:text-primary transition opacity-0 group-hover/comment:opacity-100"
>
回复
</button>
</div>
</div>
<!-- 展开更多评论 -->
@@ -254,29 +270,48 @@
:initial-index="previewIndex"
@close="showImagePreview = false"
/>
<!-- 用户信息卡片 -->
<UserInfoCard
:show="showUserCard"
:user="selectedUser"
@close="showUserCard = false"
@send-message="handleSendMessage"
/>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { useMomentStore } from '@/stores/moment'
import { useAuthStore } from '@/stores/auth'
import { useChatStore } from '@/stores/chat'
import Avatar from '@/components/common/Avatar.vue'
import ImagePreview from '@/components/common/ImagePreview.vue'
import UserInfoCard from '@/components/common/UserInfoCard.vue'
import MomentPublisherDark from './MomentPublisherDark.vue'
import MomentNotificationPanel from './MomentNotificationPanel.vue'
import { parseMediaUrls } from '@/types/moment'
import { formatRelativeTime } from '@/utils/format'
import { storage } from '@/utils/storage'
import type { Moment, MomentComment } from '@/types/moment'
import type { User } from '@/types/api'
const router = useRouter()
const momentStore = useMomentStore()
const authStore = useAuthStore()
const chatStore = useChatStore()
// 状态
const showPublisher = ref(false)
const showNotifications = ref(false)
const expandedMoments = reactive(new Set<number>())
// 用户信息卡片相关
const showUserCard = ref(false)
const selectedUser = ref<User | null>(null)
// 评论相关
const commentingMoment = ref<Moment | null>(null)
const commentText = ref('')
@@ -374,6 +409,34 @@ async function handleComment() {
function onPublished() {
showPublisher.value = false
}
// 显示用户信息卡片
function showUserInfo(user?: User | null) {
if (!user) return
selectedUser.value = user
showUserCard.value = true
}
// 从用户信息卡片发起聊天
async function handleSendMessage() {
if (!selectedUser.value) return
// 关闭用户信息卡片
showUserCard.value = false
// 切换到聊天 Tab
storage.setCurrentTab('chat')
// 查找或创建与该用户的会话
const contact = chatStore.contacts.find(c => c.user_id === selectedUser.value?.id || c.id === selectedUser.value?.id)
if (contact) {
chatStore.setCurrentTarget(contact)
storage.setSelectedConversation(contact.id)
if (contact.room_id) {
storage.setSelectedRoomId(contact.room_id)
}
}
}
</script>
<style scoped>

View File

@@ -129,3 +129,4 @@