topfans/frontend/pages/support-activity/composables/useMessageRealtime.js

114 lines
4.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useStore } from 'vuex'
import { listActivityMessagesApi, createActivityMessageApi } from '@/utils/api.js'
import { getActivitySocket } from '@/utils/socket/ActivitySocket.js'
import { formatRelativeTime } from '@/utils/format.js'
const MAX_MESSAGES = 50
/**
* 活动留言实时推送 composable
* @param {import('vue').Ref<string|number>} activityId
*/
export function useMessageRealtime(activityId) {
const store = useStore()
const messages = ref([])
const loading = ref(false)
const error = ref(null)
const currentUserId = computed(() => store.state.user?.userInfo?.uid)
const socket = getActivitySocket()
// 字段映射:后端 → MessageBoard props
function toComponentShape(m) {
return {
id: m.id,
user: m.nickname || '',
avatar: m.avatar_url || '',
content: m.content,
time: formatRelativeTime(m.created_at),
isSelf: m.user_id === currentUserId.value,
}
}
async function loadHistory() {
if (!activityId.value) return
loading.value = true
error.value = null
try {
const res = await listActivityMessagesApi(activityId.value, 1, 20)
if (res.code === 0) {
messages.value = (res.data.messages || []).map(toComponentShape)
} else {
error.value = res.message || '加载留言失败'
}
} catch (e) {
error.value = e.message || '网络错误'
console.error('[useMessageRealtime] loadHistory error:', e)
} finally {
loading.value = false
}
}
function onWsMessage(payload) {
if (!payload || Number(payload.activity_id) !== Number(activityId.value)) return
if (!payload.message) return
messages.value.push(toComponentShape(payload.message))
if (messages.value.length > MAX_MESSAGES) {
messages.value.splice(0, messages.value.length - MAX_MESSAGES)
}
}
async function sendMessage(content) {
if (!content || !content.trim()) return
const trimmed = content.trim()
try {
const res = await createActivityMessageApi(activityId.value, trimmed)
if (res.code === 0) {
// WS 推回时会再次触发 onMessage → 列表会出现该留言
// 断线时本地 fallback 插入
if (!socket.isConnected && res.data?.message) {
messages.value.push(toComponentShape(res.data.message))
if (messages.value.length > MAX_MESSAGES) {
messages.value.splice(0, messages.value.length - MAX_MESSAGES)
}
}
// 无论 WS 还是 fallback都弹一个轻量 toast 提示
uni.showToast({ title: '留言成功', icon: 'success', duration: 1200 })
} else {
uni.showToast({ title: res.message || '留言失败', icon: 'none' })
}
} catch (e) {
console.error('[useMessageRealtime] sendMessage error:', e)
uni.showToast({ title: '网络错误', icon: 'none' })
}
}
onMounted(() => {
loadHistory()
// 总是调用 connect():SocketManager.connect() 内部会判断 token 是否变化。
// - 同 token + 已连接 → _doConnect 空跑,无副作用
// - 异 token(用户切换登录) → _hardReset 关闭旧连接、强制重连
// 这样即使旧用户 WS 还活着但 token 已变,也能拿到新用户自己的连接,
// 解决"另一个账户没有接收到实时推送"的问题。
const token = uni.getStorageSync('access_token')
if (token) {
socket.connect(token).catch(err => console.warn('[useMessageRealtime] connect error:', err))
}
socket.subscribe(activityId.value, ['messages'])
socket.onMessagesResponse(onWsMessage)
})
onUnmounted(() => {
socket.unsubscribe(activityId.value, ['messages'])
socket.offMessagesResponse(onWsMessage)
})
return {
messages,
loading,
error,
sendMessage,
refresh: loadHistory,
}
}