接入会话置顶、会话免打扰功能到聊天设置页面;接入退出群聊、解散群聊功能,并解决历史遗留问题:群主不能退群(普通群);接入新版socket消息监听用于处理消息已读回执,读消息视角依然沿用旧版轮询方案防止丢失既有数据;查视角采用新版监听socket消息方案代替轮询接口实现
This commit is contained in:
parent
8ce7d143ce
commit
bdfd604fd9
@ -11,13 +11,14 @@ import {
|
|||||||
ServeSecedeGroup,
|
ServeSecedeGroup,
|
||||||
ServeUpdateGroupCard,
|
ServeUpdateGroupCard,
|
||||||
ServeGetGroupNotices,
|
ServeGetGroupNotices,
|
||||||
ServeEditGroup
|
ServeEditGroup,
|
||||||
|
ServeDismissGroup
|
||||||
} from '@/api/group'
|
} from '@/api/group'
|
||||||
import { useInject } from '@/hooks'
|
import { useInject } from '@/hooks'
|
||||||
import customModal from '@/components/common/customModal.vue'
|
import customModal from '@/components/common/customModal.vue'
|
||||||
import avatarModule from '@/components/avatar-module/index.vue'
|
import avatarModule from '@/components/avatar-module/index.vue'
|
||||||
import UserCardModal from '@/components/user/UserCardModal.vue'
|
import UserCardModal from '@/components/user/UserCardModal.vue'
|
||||||
import { ServeEmptyMessage } from '@/api/chat'
|
import { ServeEmptyMessage, ServeTopTalkList, ServeSetNotDisturb } from '@/api/chat'
|
||||||
import { parseTime } from '@/utils/datetime'
|
import { parseTime } from '@/utils/datetime'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@ -48,6 +49,12 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const talkParams = reactive({
|
||||||
|
isTop: computed(() => talkStore.findItem(props.talkType + '_' + props.gid)?.is_top),
|
||||||
|
isDisturb: computed(() => talkStore.findItem(props.talkType + '_' + props.gid)?.is_disturb),
|
||||||
|
sessionId: computed(() => talkStore.findItem(props.talkType + '_' + props.gid)?.id)
|
||||||
|
})
|
||||||
|
|
||||||
watch(props, () => {
|
watch(props, () => {
|
||||||
if (props.talkType === 2) {
|
if (props.talkType === 2) {
|
||||||
loadDetail()
|
loadDetail()
|
||||||
@ -98,7 +105,8 @@ const state = reactive({
|
|||||||
}, //群公告信息
|
}, //群公告信息
|
||||||
editGroupName: false, //是否编辑群名称
|
editGroupName: false, //是否编辑群名称
|
||||||
editGroupNameValue: '', //编辑中的群名称
|
editGroupNameValue: '', //编辑中的群名称
|
||||||
chatSettingOperateType: '' //聊天设置操作类型
|
chatSettingOperateType: '', //聊天设置操作类型
|
||||||
|
isLastAdmin: false //是否是最后一个管理员
|
||||||
})
|
})
|
||||||
|
|
||||||
const members = ref<any[]>([])
|
const members = ref<any[]>([])
|
||||||
@ -177,17 +185,36 @@ const onClose = () => {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSignOut = () => {
|
const onSignOut = (closeLoading) => {
|
||||||
ServeSecedeGroup({
|
ServeSecedeGroup({
|
||||||
group_id: props.gid
|
group_id: props.gid
|
||||||
}).then((res) => {
|
})
|
||||||
|
.then((res) => {
|
||||||
|
closeLoading()
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
window['$message'].success('已退出群聊')
|
window['$message'].success('已退出群聊')
|
||||||
|
state.isShowModal = false
|
||||||
onClose()
|
onClose()
|
||||||
} else {
|
} else {
|
||||||
window['$message'].error(res.message)
|
window['$message'].error(res.message || res.msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
closeLoading()
|
||||||
|
window['$message'].error(err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDismiss = async (closeLoading) => {
|
||||||
|
const { code, message } = await ServeDismissGroup({ group_id: props.gid })
|
||||||
|
closeLoading()
|
||||||
|
if (code === 200) {
|
||||||
|
onClose()
|
||||||
|
state.isShowModal = false
|
||||||
|
window['$message'].success('群聊已解散')
|
||||||
|
} else {
|
||||||
|
window['$message'].info(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeRemark = () => {
|
const onChangeRemark = () => {
|
||||||
@ -238,6 +265,16 @@ const handleModalConfirm = (closeLoading) => {
|
|||||||
})
|
})
|
||||||
} else if (state.chatSettingOperateType == 'quit') {
|
} else if (state.chatSettingOperateType == 'quit') {
|
||||||
//退出群聊
|
//退出群聊
|
||||||
|
if (state.isLastAdmin) {
|
||||||
|
//如果是最后一个管理员,则退出同时解散群聊
|
||||||
|
onDismiss(closeLoading)
|
||||||
|
} else {
|
||||||
|
//如果不是最后一个管理员,则退出群聊
|
||||||
|
onSignOut(closeLoading)
|
||||||
|
}
|
||||||
|
} else if (state.chatSettingOperateType == 'dismiss') {
|
||||||
|
//解散群聊
|
||||||
|
onDismiss(closeLoading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,12 +293,15 @@ const showChatSettingOperateModal = (type: string) => {
|
|||||||
break
|
break
|
||||||
case 'quit':
|
case 'quit':
|
||||||
state.chatSettingOperateHint = '确定退出群聊'
|
state.chatSettingOperateHint = '确定退出群聊'
|
||||||
const findAdmin = groupMemberList.value.find((item) => item.leader === 2 || item.leader === 1)
|
const findOtherAdmin = groupMemberList.value.find(
|
||||||
const isLastAdmin = findAdmin && findAdmin.user_id === userStore.uid
|
(item) => (item.leader === 2 || item.leader === 1) && item.user_id !== userStore.uid
|
||||||
if (isLastAdmin) {
|
)
|
||||||
state.chatSettingOperateSubHint = '退出后,本群将被解散'
|
if (findOtherAdmin) {
|
||||||
} else {
|
state.isLastAdmin = false
|
||||||
state.chatSettingOperateSubHint = '退出后,聊天记录将被清空'
|
state.chatSettingOperateSubHint = '退出后,聊天记录将被清空'
|
||||||
|
} else {
|
||||||
|
state.isLastAdmin = true
|
||||||
|
state.chatSettingOperateSubHint = '退出后,本群将被解散'
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -380,6 +420,42 @@ const handleEditGroupNameConfirm = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//设置会话置顶
|
||||||
|
const onTopChange = (value: boolean) => {
|
||||||
|
ServeTopTalkList({
|
||||||
|
list_id: talkParams.sessionId,
|
||||||
|
type: value ? 1 : 2
|
||||||
|
}).then(({ code, message }) => {
|
||||||
|
if (code == 200) {
|
||||||
|
talkStore.updateItem({
|
||||||
|
index_name: props.talkType + '_' + props.gid,
|
||||||
|
is_top: talkParams.isTop == 0 ? 1 : 0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
window['$message'].error(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置会话免打扰
|
||||||
|
const onDisturbChange = (value: boolean) => {
|
||||||
|
ServeSetNotDisturb({
|
||||||
|
talk_type: props.talkType,
|
||||||
|
receiver_id: props.gid,
|
||||||
|
is_disturb: value ? 1 : 0
|
||||||
|
}).then(({ code, message }) => {
|
||||||
|
if (code == 200) {
|
||||||
|
window['$message'].success('设置成功!')
|
||||||
|
talkStore.updateItem({
|
||||||
|
index_name: props.talkType + '_' + props.gid,
|
||||||
|
is_disturb: value ? 1 : 0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
window['$message'].error(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<section class="el-container is-vertical section">
|
<section class="el-container is-vertical section">
|
||||||
@ -602,14 +678,14 @@ const handleEditGroupNameConfirm = () => {
|
|||||||
<div class="b-box" style="margin: 16px 0 32px;">
|
<div class="b-box" style="margin: 16px 0 32px;">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="title">置顶会话</div>
|
<div class="title">置顶会话</div>
|
||||||
<n-switch />
|
<n-switch :value="talkParams.isTop === 1" @update:value="onTopChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="b-box" style="margin: 32px 0 20px;">
|
<div class="b-box" style="margin: 32px 0 20px;">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="title">消息免打扰</div>
|
<div class="title">消息免打扰</div>
|
||||||
<n-switch />
|
<n-switch :value="talkParams.isDisturb === 1" @update:value="onDisturbChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -733,9 +809,11 @@ const handleEditGroupNameConfirm = () => {
|
|||||||
<template #content>
|
<template #content>
|
||||||
<div class="custom-modal-content">
|
<div class="custom-modal-content">
|
||||||
<text>{{ state.chatSettingOperateHint }}</text>
|
<text>{{ state.chatSettingOperateHint }}</text>
|
||||||
<text style="font-size: 16px; color: #999999; margin: 0; line-height: 22px;">{{
|
<text
|
||||||
state.chatSettingOperateSubHint
|
style="font-size: 16px; color: #999999; margin: 0; line-height: 22px;"
|
||||||
}}</text>
|
:style="{ color: state.isLastAdmin ? '#CF3050' : '' }"
|
||||||
|
>{{ state.chatSettingOperateSubHint }}</text
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</customModal>
|
</customModal>
|
||||||
|
@ -24,7 +24,7 @@ const { showUserInfoModal } = useInject()
|
|||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<span>踢出群聊</span>
|
<span>移出群聊</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -85,6 +85,7 @@ class Connect {
|
|||||||
this.onImContactStatus()
|
this.onImContactStatus()
|
||||||
this.onImMessageRevoke()
|
this.onImMessageRevoke()
|
||||||
this.onImMessageKeyboard()
|
this.onImMessageKeyboard()
|
||||||
|
this.onImMessageListenReadIncr()
|
||||||
}
|
}
|
||||||
|
|
||||||
onPing() {
|
onPing() {
|
||||||
@ -131,6 +132,13 @@ class Connect {
|
|||||||
this.conn.on('im.message.revoke', (data: any) => new EventRevoke(data))
|
this.conn.on('im.message.revoke', (data: any) => new EventRevoke(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onImMessageListenReadIncr() {
|
||||||
|
// 消息已读回执监听事件
|
||||||
|
this.conn.on('im.message.listen.read.incr', (data: any) => {
|
||||||
|
console.error('====接收到了新版已读回执增量=====', data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onImContactApply() {
|
onImContactApply() {
|
||||||
// 好友申请事件
|
// 好友申请事件
|
||||||
this.conn.on('im.contact.apply', (data: any) => {
|
this.conn.on('im.contact.apply', (data: any) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, onMounted, ref, nextTick } from 'vue'
|
import { watch, onMounted, ref, nextTick, onUnmounted } from 'vue'
|
||||||
import { NDropdown, NCheckbox } from 'naive-ui'
|
import { NDropdown, NCheckbox } from 'naive-ui'
|
||||||
import { Loading, MoreThree, ToTop } from '@icon-park/vue-next'
|
import { Loading, MoreThree, ToTop } from '@icon-park/vue-next'
|
||||||
import { bus } from '@/utils/event-bus'
|
import { bus } from '@/utils/event-bus'
|
||||||
@ -18,6 +18,30 @@ import { useUserStore ,useUploadsStore} from '@/store'
|
|||||||
import RevokeMessage from '@/components/talk/message/RevokeMessage.vue'
|
import RevokeMessage from '@/components/talk/message/RevokeMessage.vue'
|
||||||
import { voiceToText } from '@/api/chat.js'
|
import { voiceToText } from '@/api/chat.js'
|
||||||
import { confirmBox } from '@/components/confirm-box/service.js'
|
import { confirmBox } from '@/components/confirm-box/service.js'
|
||||||
|
import ws from '@/connect'
|
||||||
|
|
||||||
|
// 定义消息已读状态接口
|
||||||
|
interface ReadStatus {
|
||||||
|
msg_ids: string[]
|
||||||
|
talk_type: number
|
||||||
|
receiver_id: number,
|
||||||
|
user_id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义状态接口
|
||||||
|
interface State {
|
||||||
|
visibleElements: Set<HTMLElement>
|
||||||
|
visibleOutElements: Set<HTMLElement>
|
||||||
|
tempWaitDoRead: ReadStatus[]
|
||||||
|
tempWaitDoCheck: ReadStatus[]
|
||||||
|
setMessageReadInterval: number | null
|
||||||
|
setOutMessageReadInterval: number | null
|
||||||
|
lastUpdateTime: number
|
||||||
|
isScrolling: boolean
|
||||||
|
scrollTimer: number | null
|
||||||
|
lastTriggerTime: number
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
uid: {
|
uid: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -38,6 +62,10 @@ const props = defineProps({
|
|||||||
specifiedMsg: {
|
specifiedMsg: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
num: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -96,6 +124,19 @@ const onPanelScroll = (e: any) => {
|
|||||||
dialogueStore.setUnreadBubble(0)
|
dialogueStore.setUnreadBubble(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置滚动状态
|
||||||
|
state.value.isScrolling = true
|
||||||
|
if (state.value.scrollTimer) {
|
||||||
|
clearTimeout(state.value.scrollTimer)
|
||||||
|
}
|
||||||
|
state.value.scrollTimer = window.setTimeout(() => {
|
||||||
|
state.value.isScrolling = false
|
||||||
|
// 滚动停止,强制触发一次
|
||||||
|
checkVisibleOutElements()
|
||||||
|
// 重置节流时间戳,保证下一次变化能立刻触发
|
||||||
|
lastVisibleOutTriggerTime = Date.now()
|
||||||
|
}, 150) // 150ms 的防抖时间
|
||||||
|
|
||||||
// 检测是否到达底部
|
// 检测是否到达底部
|
||||||
if (skipBottom.value == false) {
|
if (skipBottom.value == false) {
|
||||||
let len = dialogueStore.records.length
|
let len = dialogueStore.records.length
|
||||||
@ -295,7 +336,10 @@ watch(
|
|||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(decodeURIComponent(newProps.specifiedMsg))
|
const parsed = JSON.parse(decodeURIComponent(newProps.specifiedMsg))
|
||||||
// 只有会话id和参数都匹配才进入特殊模式
|
// 只有会话id和参数都匹配才进入特殊模式
|
||||||
if (parsed.talk_type === newProps.talk_type && parsed.receiver_id === newProps.receiver_id) {
|
if (
|
||||||
|
parsed.talk_type === newProps.talk_type &&
|
||||||
|
parsed.receiver_id === newProps.receiver_id
|
||||||
|
) {
|
||||||
specialParams = parsed
|
specialParams = parsed
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@ -333,6 +377,299 @@ const onContextMenuAvatar=(e:any,item:any)=>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const state = ref<State>({
|
||||||
|
visibleElements: new Set(),
|
||||||
|
visibleOutElements: new Set(),
|
||||||
|
tempWaitDoRead: [],
|
||||||
|
tempWaitDoCheck: [],
|
||||||
|
setMessageReadInterval: null,
|
||||||
|
setOutMessageReadInterval: null,
|
||||||
|
lastUpdateTime: 0,
|
||||||
|
isScrolling: false,
|
||||||
|
scrollTimer: null,
|
||||||
|
lastTriggerTime: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建一个响应式的 Map 来维护已读数量
|
||||||
|
const recordReadsMap = ref<Map<string, number>>(new Map())
|
||||||
|
|
||||||
|
// 定义观察者变量
|
||||||
|
let observer: IntersectionObserver | null = null
|
||||||
|
|
||||||
|
// 监听 Map 的变化,更新 UI
|
||||||
|
watch(
|
||||||
|
recordReadsMap,
|
||||||
|
(newMap) => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
newMap.forEach((readNum, msgId) => {
|
||||||
|
const element = document.getElementById(msgId)
|
||||||
|
if (element) {
|
||||||
|
element.dataset.readNum = String(readNum)
|
||||||
|
const readNumElement = element.querySelector('.have_read_num')
|
||||||
|
if (readNumElement) {
|
||||||
|
if (props.talk_type === 1) {
|
||||||
|
readNumElement.textContent = readNum > 0 ? '已读' : '未读'
|
||||||
|
} else {
|
||||||
|
readNumElement.textContent =
|
||||||
|
'已读 (' +
|
||||||
|
readNum +
|
||||||
|
'/' +
|
||||||
|
(Number(props.num) - 1 > 0 ? Number(props.num) - 1 : 0) +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 检查需要发送已读回执的元素
|
||||||
|
const checkVisibleElements = () => {
|
||||||
|
if (state.value.visibleElements.size > 0) {
|
||||||
|
let waitDoRead: ReadStatus[] = []
|
||||||
|
state.value.visibleElements.forEach((el: HTMLElement) => {
|
||||||
|
const msgId = el.dataset.msgid
|
||||||
|
const talkType = Number(el.dataset.talktype)
|
||||||
|
const receiverId = Number(el.dataset.receiverid)
|
||||||
|
|
||||||
|
if (!msgId) return
|
||||||
|
|
||||||
|
if (waitDoRead.length === 0) {
|
||||||
|
waitDoRead.push({
|
||||||
|
msg_ids: [msgId],
|
||||||
|
talk_type: talkType,
|
||||||
|
receiver_id: receiverId
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const existingItem = waitDoRead.find(
|
||||||
|
(item) => item.talk_type === talkType && item.receiver_id === receiverId
|
||||||
|
)
|
||||||
|
if (existingItem) {
|
||||||
|
existingItem.msg_ids.push(msgId)
|
||||||
|
} else {
|
||||||
|
waitDoRead.push({
|
||||||
|
msg_ids: [msgId],
|
||||||
|
talk_type: talkType,
|
||||||
|
receiver_id: receiverId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (waitDoRead.length > 0) {
|
||||||
|
waitDoRead.forEach((doReadItem) => {
|
||||||
|
const prevItem = state.value.tempWaitDoRead.find(
|
||||||
|
(prev) =>
|
||||||
|
prev.talk_type === doReadItem.talk_type && prev.receiver_id === doReadItem.receiver_id
|
||||||
|
)
|
||||||
|
if (!prevItem || !doReadItem.msg_ids.every((id) => prevItem.msg_ids.includes(id))) {
|
||||||
|
console.error('====发送了新版已读回执=====', doReadItem)
|
||||||
|
ws.emit('im.message.new.read', doReadItem)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
state.value.tempWaitDoRead = JSON.parse(JSON.stringify(waitDoRead))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查需要获取别人发送的已读回执列表的元素
|
||||||
|
const checkVisibleOutElements = () => {
|
||||||
|
if (state.value.visibleOutElements.size > 0) {
|
||||||
|
let waitDoCheck: ReadStatus[] = []
|
||||||
|
state.value.visibleOutElements.forEach((el: HTMLElement) => {
|
||||||
|
const msgId = el.dataset.msgid
|
||||||
|
const talkType = Number(el.dataset.talktype)
|
||||||
|
const receiverId = Number(el.dataset.receiverid)
|
||||||
|
|
||||||
|
if (!msgId) return
|
||||||
|
|
||||||
|
if (waitDoCheck.length === 0) {
|
||||||
|
waitDoCheck.push({
|
||||||
|
msg_ids: [msgId],
|
||||||
|
talk_type: talkType,
|
||||||
|
receiver_id: receiverId,
|
||||||
|
user_id: props.uid
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const existingItem = waitDoCheck.find(
|
||||||
|
(item) => item.talk_type === talkType && item.receiver_id === receiverId
|
||||||
|
)
|
||||||
|
if (existingItem) {
|
||||||
|
existingItem.msg_ids.push(msgId)
|
||||||
|
} else {
|
||||||
|
waitDoCheck.push({
|
||||||
|
msg_ids: [msgId],
|
||||||
|
talk_type: talkType,
|
||||||
|
receiver_id: receiverId,
|
||||||
|
user_id: props.uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (waitDoCheck.length > 0) {
|
||||||
|
waitDoCheck.forEach((doCheckItem) => {
|
||||||
|
// console.error('====调用了已读回执查询接口=====', doCheckItem)
|
||||||
|
console.error('====组装了新版已读回执参数,需要发送socket=====', doCheckItem)
|
||||||
|
ws.emit('im.message.listen.read', doCheckItem)
|
||||||
|
// let params = Object.assign({}, doCheckItem, {
|
||||||
|
// talkType: doCheckItem.talk_type,
|
||||||
|
// receiverId: doCheckItem.talk_type === 1 ? props.uid : props.receiver_id,
|
||||||
|
// msgIds: doCheckItem.msg_ids,
|
||||||
|
// type: 'list'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const resp = ServeReadConditionList(params)
|
||||||
|
// resp
|
||||||
|
// .then(({ code, data }) => {
|
||||||
|
// if (code == 200) {
|
||||||
|
// if (Array.isArray(data.data)) {
|
||||||
|
// console.error('处理批量更新', data.data)
|
||||||
|
// data.data.forEach((item) => {
|
||||||
|
// if (item.msgId && item.readNum !== undefined) {
|
||||||
|
// recordReadsMap.value.set(item.msgId, item.readNum)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// } else if (data.data && data.data.readNum !== undefined) {
|
||||||
|
// console.error('处理单个更新', data.data)
|
||||||
|
// doCheckItem.msg_ids.forEach((msgId) => {
|
||||||
|
// recordReadsMap.value.set(msgId, data.data.readNum)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .catch(() => {})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
state.value.tempWaitDoCheck = JSON.parse(JSON.stringify(waitDoCheck))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义节流时间戳
|
||||||
|
let lastVisibleOutTriggerTime = 0
|
||||||
|
|
||||||
|
//新版采用socket监听已读回执,不轮询接口
|
||||||
|
watch(() => state.value.visibleOutElements, (newVal) => {
|
||||||
|
const now = Date.now()
|
||||||
|
if (now - lastVisibleOutTriggerTime < 1000) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastVisibleOutTriggerTime = now
|
||||||
|
checkVisibleOutElements()
|
||||||
|
}, {
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 观察者函数
|
||||||
|
const handleIntersection = (entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
||||||
|
let elData = entry.target.dataset
|
||||||
|
const msgType = elData.msgtype
|
||||||
|
const userId = elData.userid
|
||||||
|
if (Number(msgType) < 1000 && Number(userId) !== Number(props.uid)) {
|
||||||
|
//我读别人发的消息,需要发送已读回执
|
||||||
|
state.value.visibleElements.add(entry.target)
|
||||||
|
}
|
||||||
|
if (Number(msgType) < 1000 && Number(userId) === Number(props.uid)) {
|
||||||
|
//我发的消息,需要获取别人发送的已读回执列表
|
||||||
|
state.value.visibleOutElements.add(entry.target)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 元素离开视口,从集合中移除
|
||||||
|
state.value.visibleElements.delete(entry.target)
|
||||||
|
state.value.visibleOutElements.delete(entry.target)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听消息列表变化
|
||||||
|
watch(
|
||||||
|
() => records.value,
|
||||||
|
() => {
|
||||||
|
nextTick(() => {
|
||||||
|
// 断开旧的观察者
|
||||||
|
if (observer) {
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新初始化观察者
|
||||||
|
const options = {
|
||||||
|
root: null,
|
||||||
|
threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||||
|
rootMargin: '50px 0px'
|
||||||
|
}
|
||||||
|
observer = new IntersectionObserver(handleIntersection, options)
|
||||||
|
|
||||||
|
// 重新观察所有消息元素
|
||||||
|
const messageElements = document.querySelectorAll('.message-item')
|
||||||
|
messageElements.forEach((el) => {
|
||||||
|
if (observer) {
|
||||||
|
observer.observe(el)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
//设置观察者前设置定时器
|
||||||
|
if (state.value.setMessageReadInterval) {
|
||||||
|
clearInterval(state.value.setMessageReadInterval)
|
||||||
|
state.value.setMessageReadInterval = null
|
||||||
|
}
|
||||||
|
state.value.setMessageReadInterval = setInterval(() => {
|
||||||
|
checkVisibleElements()
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
if (state.value.setOutMessageReadInterval) {
|
||||||
|
clearInterval(state.value.setOutMessageReadInterval)
|
||||||
|
state.value.setOutMessageReadInterval = null
|
||||||
|
}
|
||||||
|
// 旧版采用定时器来轮询已读回执,新版采用socket监听已读回执
|
||||||
|
// state.value.setOutMessageReadInterval = setInterval(() => {
|
||||||
|
// checkVisibleOutElements()
|
||||||
|
// }, 2000)
|
||||||
|
|
||||||
|
//初始化设置观察者
|
||||||
|
const options = {
|
||||||
|
root: null,
|
||||||
|
threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||||
|
rootMargin: '50px 0px'
|
||||||
|
}
|
||||||
|
observer = new IntersectionObserver(handleIntersection, options)
|
||||||
|
|
||||||
|
// 观察所有消息元素
|
||||||
|
nextTick(() => {
|
||||||
|
const messageElements = document.querySelectorAll('.message-item')
|
||||||
|
messageElements.forEach((el) => {
|
||||||
|
if (observer) {
|
||||||
|
observer.observe(el)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (observer) {
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
if (state.value.setMessageReadInterval) {
|
||||||
|
clearInterval(state.value.setMessageReadInterval)
|
||||||
|
state.value.setMessageReadInterval = null
|
||||||
|
checkVisibleElements()
|
||||||
|
}
|
||||||
|
if (state.value.setOutMessageReadInterval) {
|
||||||
|
clearInterval(state.value.setOutMessageReadInterval)
|
||||||
|
state.value.setOutMessageReadInterval = null
|
||||||
|
checkVisibleOutElements()
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -354,6 +691,11 @@ const onContextMenuAvatar=(e:any,item:any)=>{
|
|||||||
v-for="(item, index) in records"
|
v-for="(item, index) in records"
|
||||||
:key="item.msg_id"
|
:key="item.msg_id"
|
||||||
:id="item.msg_id"
|
:id="item.msg_id"
|
||||||
|
:data-msgid="item.msg_id"
|
||||||
|
:data-msgtype="item.msg_type"
|
||||||
|
:data-userid="item.user_id"
|
||||||
|
:data-talktype="props?.talk_type"
|
||||||
|
:data-receiverid="props?.receiver_id"
|
||||||
>
|
>
|
||||||
<!-- 系统消息 -->
|
<!-- 系统消息 -->
|
||||||
<div v-if="item.msg_type >= 1000" class="message-box">
|
<div v-if="item.msg_type >= 1000" class="message-box">
|
||||||
@ -387,7 +729,11 @@ const onContextMenuAvatar=(e:any,item:any)=>{
|
|||||||
>
|
>
|
||||||
<!-- 多选按钮 -->
|
<!-- 多选按钮 -->
|
||||||
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column shrink-0">
|
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column shrink-0">
|
||||||
<n-checkbox size="small" :checked="item.isCheck" @update:checked="item.isCheck = !item.isCheck" />
|
<n-checkbox
|
||||||
|
size="small"
|
||||||
|
:checked="item.isCheck"
|
||||||
|
@update:checked="item.isCheck = !item.isCheck"
|
||||||
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
<!-- 头像信息 -->
|
<!-- 头像信息 -->
|
||||||
|
|
||||||
@ -415,9 +761,7 @@ const onContextMenuAvatar=(e:any,item:any)=>{
|
|||||||
<span>{{ parseTime(item.created_at, '{y}/{m}/{d} {h}:{i}') }}</span>
|
<span>{{ parseTime(item.created_at, '{y}/{m}/{d} {h}:{i}') }}</span>
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="talk-title">
|
<div class="talk-title">
|
||||||
<span class="mr-7px"
|
<span class="mr-7px" v-show="talk_type == 2 && item.float == 'left'"
|
||||||
v-show="talk_type == 2 && item.float == 'left'"
|
|
||||||
|
|
||||||
>{{ item.nickname }}
|
>{{ item.nickname }}
|
||||||
</span>
|
</span>
|
||||||
<span>{{ parseTime(item.created_at, '{y}/{m}/{d} {h}:{i}') }}</span>
|
<span>{{ parseTime(item.created_at, '{y}/{m}/{d} {h}:{i}') }}</span>
|
||||||
|
@ -60,11 +60,11 @@ const onSendMessage = (data = {}, callBack: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServePublishMessage(message)
|
ServePublishMessage(message)
|
||||||
.then(({ code, message }) => {
|
.then(({ code, message, msg }) => {
|
||||||
if (code == 200) {
|
if (code == 200) {
|
||||||
callBack(true)
|
callBack(true)
|
||||||
} else {
|
} else {
|
||||||
window['$message'].warning(message)
|
window['$message'].warning(message || msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user