完成新版已读回执规则接入,处理相应的socket消息监听、相关已读未读数量统计和详情列表展示
This commit is contained in:
parent
bdfd604fd9
commit
331ca65db6
@ -94,3 +94,8 @@ export const ServeConfirmVoteHandle = (data = {}) => {
|
||||
export const ServeEmptyMessage = (data) => {
|
||||
return post('/api/v1/talk/message/empty', data)
|
||||
}
|
||||
|
||||
//获取消息已读未读详情
|
||||
export const ServeMessageReadDetail = (data) => {
|
||||
return post('/api/v1/talk/my-records/read/condition', data)
|
||||
}
|
||||
|
@ -327,7 +327,7 @@ import { parseTime } from '@/utils/datetime'
|
||||
import { fileFormatSize, fileSuffix } from '@/utils/strings'
|
||||
import { NImage, NInfiniteScroll, NScrollbar, NIcon, NDatePicker } from 'naive-ui'
|
||||
|
||||
const emits = defineEmits(['clearSearchMemberByAlphabet', 'getDisabledDateArray'])
|
||||
const emits = defineEmits(['clearSearchMemberByAlphabet', 'getDisabledDateArray', 'hideSearchResultModal'])
|
||||
|
||||
const dialogueStore = useDialogueStore()
|
||||
// 当前对话参数
|
||||
@ -736,15 +736,29 @@ const downloadAndOpenFile = (item) => {
|
||||
|
||||
//跳转到对应的记录位置
|
||||
const toDialogueByMember = async (msgInfo) => {
|
||||
const sessionId = await getSessionId(dialogueParams.talk_type, dialogueParams.receiver_id)
|
||||
uni.navigateTo({
|
||||
url:
|
||||
'/pages/dialog/index?sessionId=' +
|
||||
sessionId +
|
||||
'&keepDialogInfo=1' +
|
||||
'&msgInfo=' +
|
||||
encodeURIComponent(JSON.stringify(msgInfo))
|
||||
console.error('====跳转到对应的记录位置====', msgInfo)
|
||||
// 根据搜索结果, 指定用于查询指定消息上下文的sequence
|
||||
dialogueStore.specifiedMsg = encodeURIComponent(
|
||||
JSON.stringify({
|
||||
talk_type: msgInfo.talk_type,
|
||||
receiver_id: msgInfo.receiver_id,
|
||||
msg_id: msgInfo.msg_id,
|
||||
cursor: msgInfo.sequence - 15 > 0 ? msgInfo.sequence - 15 : 0,
|
||||
direction: 'down',
|
||||
sort_sequence: 'asc',
|
||||
create_time: msgInfo.created_at
|
||||
})
|
||||
)
|
||||
emits('hideSearchResultModal')
|
||||
// const sessionId = await getSessionId(dialogueParams.talk_type, dialogueParams.receiver_id)
|
||||
// uni.navigateTo({
|
||||
// url:
|
||||
// '/pages/dialog/index?sessionId=' +
|
||||
// sessionId +
|
||||
// '&keepDialogInfo=1' +
|
||||
// '&msgInfo=' +
|
||||
// encodeURIComponent(JSON.stringify(msgInfo))
|
||||
// })
|
||||
}
|
||||
|
||||
//重置部分搜索条件
|
||||
|
@ -7,6 +7,7 @@ import EventTalk from './event/talk'
|
||||
import EventKeyboard from './event/keyboard'
|
||||
import EventLogin from './event/login'
|
||||
import EventRevoke from './event/revoke'
|
||||
import EventRead from './event/read'
|
||||
import { getAccessToken, isLoggedIn } from './utils/auth'
|
||||
|
||||
const urlCallback = () => {
|
||||
@ -85,6 +86,7 @@ class Connect {
|
||||
this.onImContactStatus()
|
||||
this.onImMessageRevoke()
|
||||
this.onImMessageKeyboard()
|
||||
this.onImMessageListenRead()
|
||||
this.onImMessageListenReadIncr()
|
||||
}
|
||||
|
||||
@ -132,11 +134,14 @@ class Connect {
|
||||
this.conn.on('im.message.revoke', (data: any) => new EventRevoke(data))
|
||||
}
|
||||
|
||||
onImMessageListenRead() {
|
||||
// 消息已读回执监听事件(全量)
|
||||
this.conn.on('im.message.listen.read', (data: any) => new EventRead(data, 'total'))
|
||||
}
|
||||
|
||||
onImMessageListenReadIncr() {
|
||||
// 消息已读回执监听事件
|
||||
this.conn.on('im.message.listen.read.incr', (data: any) => {
|
||||
console.error('====接收到了新版已读回执增量=====', data)
|
||||
})
|
||||
// 消息已读回执监听事件(增量)
|
||||
this.conn.on('im.message.listen.read.incr', (data: any) => new EventRead(data, 'incr'))
|
||||
}
|
||||
|
||||
onImContactApply() {
|
||||
|
54
src/event/read.js
Normal file
54
src/event/read.js
Normal file
@ -0,0 +1,54 @@
|
||||
import Base from './base'
|
||||
import { useTalkStore, useDialogueStore } from '@/store'
|
||||
import ws from '@/connect'
|
||||
import { bus } from '@/utils/event-bus'
|
||||
|
||||
/**
|
||||
* 已读回执事件
|
||||
*/
|
||||
class Read extends Base {
|
||||
/**
|
||||
* @var resource 资源
|
||||
*/
|
||||
resource
|
||||
|
||||
/**
|
||||
* 场景类型
|
||||
*/
|
||||
type
|
||||
|
||||
/**
|
||||
* 初始化构造方法
|
||||
*
|
||||
* @param {Object} resource Socket消息
|
||||
*/
|
||||
constructor(resource, type) {
|
||||
super()
|
||||
|
||||
this.resource = resource
|
||||
this.type = type
|
||||
this.handle()
|
||||
}
|
||||
|
||||
handle() {
|
||||
if (this.type == 'total') {
|
||||
console.error('====接收到了新版已读回执全量=====', this.resource)
|
||||
const readList = this.resource.result
|
||||
if (readList.length > 0) {
|
||||
readList.forEach((item) => {
|
||||
useDialogueStore().updateDialogueRecord({
|
||||
msg_id: item.msg_id,
|
||||
read_total_num: item.read_total_num
|
||||
})
|
||||
})
|
||||
}
|
||||
} else if (this.type == 'incr') {
|
||||
console.error('====接收到了新版已读回执增量=====', this.resource)
|
||||
// 由于直接使用增量的数值,会导致消息列表的已读回执数量不准确,可能多可能少
|
||||
// 所以收到增量消息后,直接手动触发一次查询全量
|
||||
bus.emit('check-visible-out-elements', 'incr')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Read
|
@ -49,8 +49,8 @@ export interface ITalkRecord {
|
||||
send_status: number
|
||||
float: string,
|
||||
is_convert_text?:number//语音记录的 是否是在展示转文本状态 1:是 0:否,
|
||||
erp_user_id:number
|
||||
|
||||
erp_user_id:number,
|
||||
read_total_num:number
|
||||
}
|
||||
|
||||
export interface ITalkRecordExtraText {
|
||||
|
@ -481,6 +481,12 @@ const onDatePickShow = (show) => {
|
||||
// state.nowDateTime = new Date()
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏搜索结果模态框
|
||||
const hideSearchResultModal = () => {
|
||||
handleSearchRecordByConditionModalClose()
|
||||
state.isShowGroupAside = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -506,6 +512,7 @@ const onDatePickShow = (show) => {
|
||||
:receiver_id="talkParams.receiver_id"
|
||||
:index_name="talkParams.index_name"
|
||||
:specifiedMsg="talkParams.specifiedMsg"
|
||||
:num="talkParams.num"
|
||||
/>
|
||||
</main>
|
||||
|
||||
@ -706,6 +713,7 @@ const onDatePickShow = (show) => {
|
||||
@getDisabledDateArray="getDisabledDateArray"
|
||||
:selectedDateTime="state.selectedDateTime"
|
||||
:nowDateTime="state.nowDateTime"
|
||||
@hideSearchResultModal="hideSearchResultModal"
|
||||
/>
|
||||
</div>
|
||||
</n-card>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { watch, onMounted, ref, nextTick, onUnmounted } from 'vue'
|
||||
import { NDropdown, NCheckbox } from 'naive-ui'
|
||||
import { NDropdown, NCheckbox, NPopover, NInfiniteScroll } from 'naive-ui'
|
||||
import { Loading, MoreThree, ToTop } from '@icon-park/vue-next'
|
||||
import { bus } from '@/utils/event-bus'
|
||||
import { useDialogueStore } from '@/store'
|
||||
@ -16,16 +16,17 @@ import { useInject, useTalkRecord, useUtil } from '@/hooks'
|
||||
import { ExclamationCircleFilled } from '@ant-design/icons-vue'
|
||||
import { useUserStore, useUploadsStore } from '@/store'
|
||||
import RevokeMessage from '@/components/talk/message/RevokeMessage.vue'
|
||||
import { voiceToText } from '@/api/chat.js'
|
||||
import { voiceToText, ServeMessageReadDetail } from '@/api/chat.js'
|
||||
import { confirmBox } from '@/components/confirm-box/service.js'
|
||||
import ws from '@/connect'
|
||||
import avatarModule from '@/components/avatar-module/index.vue'
|
||||
|
||||
// 定义消息已读状态接口
|
||||
interface ReadStatus {
|
||||
msg_ids: string[]
|
||||
talk_type: number
|
||||
receiver_id: number,
|
||||
user_id: number
|
||||
receiver_id: number
|
||||
user_id?: number
|
||||
}
|
||||
|
||||
// 定义状态接口
|
||||
@ -40,6 +41,12 @@ interface State {
|
||||
isScrolling: boolean
|
||||
scrollTimer: number | null
|
||||
lastTriggerTime: number
|
||||
talkReadListDetail: any[]
|
||||
readDetailIsUnread: number
|
||||
currentMsgReadDetail: any | null
|
||||
currentReadDetailPage: number
|
||||
hasMoreReadListDetail: boolean
|
||||
loadingReadListDetail: boolean
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
@ -135,7 +142,7 @@ const onPanelScroll = (e: any) => {
|
||||
checkVisibleOutElements()
|
||||
// 重置节流时间戳,保证下一次变化能立刻触发
|
||||
lastVisibleOutTriggerTime = Date.now()
|
||||
}, 150) // 150ms 的防抖时间
|
||||
}, 300) // 300ms 的防抖时间
|
||||
|
||||
// 检测是否到达底部
|
||||
if (skipBottom.value == false) {
|
||||
@ -387,47 +394,18 @@ const state = ref<State>({
|
||||
lastUpdateTime: 0,
|
||||
isScrolling: false,
|
||||
scrollTimer: null,
|
||||
lastTriggerTime: 0
|
||||
lastTriggerTime: 0,
|
||||
talkReadListDetail: [],
|
||||
readDetailIsUnread: 1,
|
||||
currentMsgReadDetail: null,
|
||||
currentReadDetailPage: 1,
|
||||
hasMoreReadListDetail: true,
|
||||
loadingReadListDetail: false
|
||||
})
|
||||
|
||||
// 创建一个响应式的 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) {
|
||||
@ -512,36 +490,8 @@ const checkVisibleOutElements = () => {
|
||||
})
|
||||
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))
|
||||
@ -552,17 +502,21 @@ const checkVisibleOutElements = () => {
|
||||
let lastVisibleOutTriggerTime = 0
|
||||
|
||||
//新版采用socket监听已读回执,不轮询接口
|
||||
watch(() => state.value.visibleOutElements, (newVal) => {
|
||||
watch(
|
||||
() => state.value.visibleOutElements,
|
||||
(newVal) => {
|
||||
const now = Date.now()
|
||||
if (now - lastVisibleOutTriggerTime < 1000) {
|
||||
return
|
||||
}
|
||||
lastVisibleOutTriggerTime = now
|
||||
checkVisibleOutElements()
|
||||
}, {
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// 观察者函数
|
||||
const handleIntersection = (entries) => {
|
||||
@ -617,7 +571,20 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 事件总线防抖处理
|
||||
let eventBusDebounceTimer: number | null = null
|
||||
|
||||
onMounted(() => {
|
||||
// 事件总线监听
|
||||
bus.subscribe('check-visible-out-elements', (type) => {
|
||||
if (eventBusDebounceTimer) {
|
||||
clearTimeout(eventBusDebounceTimer)
|
||||
}
|
||||
eventBusDebounceTimer = window.setTimeout(() => {
|
||||
checkVisibleOutElements()
|
||||
eventBusDebounceTimer = null
|
||||
}, 500)
|
||||
})
|
||||
//设置观察者前设置定时器
|
||||
if (state.value.setMessageReadInterval) {
|
||||
clearInterval(state.value.setMessageReadInterval)
|
||||
@ -669,7 +636,77 @@ onUnmounted(() => {
|
||||
state.value.setOutMessageReadInterval = null
|
||||
checkVisibleOutElements()
|
||||
}
|
||||
// 清理事件总线防抖定时器
|
||||
if (eventBusDebounceTimer) {
|
||||
clearTimeout(eventBusDebounceTimer)
|
||||
eventBusDebounceTimer = null
|
||||
}
|
||||
// 事件总线移除监听
|
||||
bus.unsubscribe('check-visible-out-elements', checkVisibleOutElements)
|
||||
})
|
||||
|
||||
//点击显示对应消息的已读回执详情
|
||||
const toShowMessageReadDetail = async (item?: ITalkRecord) => {
|
||||
if (item) {
|
||||
state.value.currentMsgReadDetail = item
|
||||
onReadTabChange('unread-tab')
|
||||
return
|
||||
}
|
||||
let params = {
|
||||
page: state.value.currentReadDetailPage,
|
||||
pageSize: 10,
|
||||
type: 'detail', //list是列表,detail是详情
|
||||
talkType: state?.value?.currentMsgReadDetail?.talk_type, //1私聊2群聊
|
||||
receiverId: state?.value?.currentMsgReadDetail?.receiver_id, //私聊的时候是对方用户id,群聊的时候是对方群id
|
||||
msgId: state?.value?.currentMsgReadDetail?.msg_id,
|
||||
isUnread: state?.value?.readDetailIsUnread //不送或者送0代表看已读,送1看未读
|
||||
}
|
||||
console.log(params)
|
||||
const resp = await ServeMessageReadDetail(params)
|
||||
console.log(resp)
|
||||
if (resp.code === 200) {
|
||||
console.log(resp?.data?.data?.length)
|
||||
if (resp?.data?.data?.length > 0) {
|
||||
state.value.hasMoreReadListDetail = true
|
||||
if (state.value.currentReadDetailPage === 1) {
|
||||
state.value.talkReadListDetail = resp.data.data
|
||||
} else {
|
||||
state.value.talkReadListDetail = [...state.value.talkReadListDetail, ...resp.data.data]
|
||||
}
|
||||
} else {
|
||||
if (state.value.currentReadDetailPage === 1) {
|
||||
state.value.talkReadListDetail = []
|
||||
}
|
||||
state.value.hasMoreReadListDetail = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//已读回执tab切换
|
||||
const onReadTabChange = (value) => {
|
||||
if (value === 'unread-tab') {
|
||||
//未读
|
||||
state.value.readDetailIsUnread = 1
|
||||
} else if (value === 'read-tab') {
|
||||
//已读
|
||||
state.value.readDetailIsUnread = 0
|
||||
}
|
||||
state.value.currentReadDetailPage = 1
|
||||
toShowMessageReadDetail()
|
||||
}
|
||||
|
||||
//加载更多已读回执
|
||||
const loadMoreReadListDetail = () => {
|
||||
console.log('loadMoreReadListDetail')
|
||||
if (!state.value.hasMoreReadListDetail || state.value.loadingReadListDetail) {
|
||||
return
|
||||
}
|
||||
state.value.loadingReadListDetail = true
|
||||
state.value.currentReadDetailPage++
|
||||
toShowMessageReadDetail().finally(() => {
|
||||
state.value.loadingReadListDetail = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -821,6 +858,72 @@ onUnmounted(() => {
|
||||
{{ item.extra?.reply?.content }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 已读回执 -->
|
||||
<div class="talk_read_num" v-if="item.user_id === props.uid">
|
||||
<n-popover trigger="click" placement="bottom-end" style="height: 382px; padding: 0;">
|
||||
<template #trigger>
|
||||
<span v-if="props.talk_type === 1">{{
|
||||
item.read_total_num > 0 ? '已读' : '未读'
|
||||
}}</span>
|
||||
<span v-if="props.talk_type === 2" @click="toShowMessageReadDetail(item)">
|
||||
已读 ({{ item?.read_total_num || 0 }}/{{
|
||||
props.num - 1 > 0 ? props.num - 1 : 0
|
||||
}})
|
||||
</span>
|
||||
</template>
|
||||
<div class="talk-read-list-detail">
|
||||
<n-tabs
|
||||
type="line"
|
||||
animated
|
||||
justify-content="space-around"
|
||||
@update:value="onReadTabChange"
|
||||
>
|
||||
<n-tab name="unread-tab">
|
||||
{{ `未读(${props.num - 1 - (item.read_total_num || 0) || 0})` }}
|
||||
</n-tab>
|
||||
<n-tab name="read-tab">
|
||||
{{ `已读(${item.read_total_num || 0})` }}
|
||||
</n-tab>
|
||||
</n-tabs>
|
||||
<div class="talk-read-list">
|
||||
<n-infinite-scroll style="height: 340px;" @load="loadMoreReadListDetail">
|
||||
<div
|
||||
class="talk-read-list-item"
|
||||
v-for="(talkReadDetailItem,
|
||||
talkReadDetailIndex) in state.talkReadListDetail"
|
||||
:key="talkReadDetailIndex"
|
||||
>
|
||||
<avatarModule
|
||||
:mode="1"
|
||||
:avatar="talkReadDetailItem.avatar"
|
||||
:userName="talkReadDetailItem.nickName"
|
||||
:groupType="0"
|
||||
:customStyle="{
|
||||
width: '36px',
|
||||
height: '36px'
|
||||
}"
|
||||
:customTextStyle="{
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold',
|
||||
color: '#fff',
|
||||
lineHeight: '17px'
|
||||
}"
|
||||
></avatarModule>
|
||||
<div class="talk-read-list-item-info">
|
||||
<span style="font-size: 12px; font-weight: 600; line-height: 17px;">{{
|
||||
talkReadDetailItem.nickName
|
||||
}}</span>
|
||||
<span style="font-size: 12px; color: #999; line-height: 14px;">{{
|
||||
talkReadDetailItem.jobNum
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</n-infinite-scroll>
|
||||
</div>
|
||||
</div>
|
||||
</n-popover>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -1019,6 +1122,18 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.talk_read_num {
|
||||
text-align: right;
|
||||
color: #7a58de;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 17px;
|
||||
margin: 5px 0 0;
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.talk-title {
|
||||
opacity: 1;
|
||||
@ -1067,4 +1182,31 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.talk-read-list-detail {
|
||||
width: 341px;
|
||||
padding: 0 14px;
|
||||
|
||||
.talk-read-list {
|
||||
.talk-read-list-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
|
||||
.talk-read-list-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
|
||||
span {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user