合并消息转发重构

This commit is contained in:
bob
2025-05-13 15:01:31 +08:00
parent fd5a645bba
commit 83376a2a6a
5 changed files with 185 additions and 76 deletions

View File

@@ -32,6 +32,10 @@ export const msgChatQuerySessionService = (obj) => {
return request.get('/chat/querySession', { params: obj }) return request.get('/chat/querySession', { params: obj })
} }
export const msgChatQueryMessagesService = (obj) => {
return request.get('/chat/queryMessages', { params: obj })
}
export const msgChatCloseSessionService = (obj) => { export const msgChatCloseSessionService = (obj) => {
return request.post('/chat/closeSession', obj) return request.post('/chat/closeSession', obj)
} }

View File

@@ -21,7 +21,7 @@ export const msgContentType = {
EMOJI: 5, // 表情 EMOJI: 5, // 表情
VIDEO: 6, // 视频 VIDEO: 6, // 视频
DOCUMENT: 7, // 文档 DOCUMENT: 7, // 文档
FORWARD_TOGETHER: 8 // 合并转发消息 FORWARD_TOGETHER: 10 // 合并转发消息
} }
// 消息发送状态 // 消息发送状态

View File

@@ -1158,22 +1158,22 @@ const onSelectOprMenu = (label) => {
const inputMultiSelectRef = ref(null) const inputMultiSelectRef = ref(null)
const isMultiSelect = ref(false) const isMultiSelect = ref(false)
const multiSelectedMsgIds = ref(new Set()) const multiSelectedMsgKeys = ref(new Set())
const handleMsgItemSelect = (msgKey, selected) => { const handleMsgItemSelect = (msgKey, selected) => {
if (!isMultiSelect.value) { if (!isMultiSelect.value) {
isMultiSelect.value = true isMultiSelect.value = true
} }
if (selected) { if (selected) {
multiSelectedMsgIds.value.add(msgKey) multiSelectedMsgKeys.value.add(msgKey)
} else { } else {
multiSelectedMsgIds.value.delete(msgKey) multiSelectedMsgKeys.value.delete(msgKey)
} }
} }
const handleCancleMultiSelect = () => { const handleCancleMultiSelect = () => {
isMultiSelect.value = false isMultiSelect.value = false
multiSelectedMsgIds.value.clear() multiSelectedMsgKeys.value.clear()
} }
const handleForwardTogether = () => { const handleForwardTogether = () => {
@@ -1187,14 +1187,17 @@ const handleForwardOneByOne = () => {
} }
const handleBatchDeleteMsg = () => { const handleBatchDeleteMsg = () => {
const deleteMsgIds = [...multiSelectedMsgKeys.value].map((item) => {
return messageData.getMsg(selectedSessionId.value, item).msgId
})
msgChatDeleteMsgService({ msgChatDeleteMsgService({
sessionId: selectedSessionId.value, sessionId: selectedSessionId.value,
deleteMsgIds: [...multiSelectedMsgIds.value] deleteMsgIds: [...deleteMsgIds]
}) })
.then((res) => { .then((res) => {
if (res.data.code === 0) { if (res.data.code === 0) {
multiSelectedMsgIds.value.forEach((item) => { multiSelectedMsgKeys.value.forEach((msgKey) => {
messageData.removeMsgRecord(selectedSessionId.value, item) messageData.removeMsgRecord(selectedSessionId.value, msgKey)
}) })
handleCancleMultiSelect() handleCancleMultiSelect()
ElMessage.success('消息已删除') ElMessage.success('消息已删除')
@@ -1326,10 +1329,10 @@ const handleGlobalMouseUp = (e) => {
isMultiSelect.value = true isMultiSelect.value = true
} }
const msgId = el.dataset.msgId const msgKey = el.dataset.msgKey
const disabled = el.dataset.disabled const disabled = el.dataset.disabled
if (disabled !== 'true' && !multiSelectedMsgIds.value.has(msgId)) { if (disabled !== 'true' && !multiSelectedMsgKeys.value.has(msgKey)) {
multiSelectedMsgIds.value.add(msgId) multiSelectedMsgKeys.value.add(msgKey)
} }
const cancelClick = (e) => { const cancelClick = (e) => {
@@ -1346,40 +1349,6 @@ const handleGlobalMouseUp = (e) => {
const isShowForwardMsgDialog = ref(false) const isShowForwardMsgDialog = ref(false)
const showForwardMsgDialogTitle = ref('') const showForwardMsgDialogTitle = ref('')
// 待转发的消息
const forwardMsgs = computed(() => {
let msgs = []
multiSelectedMsgIds.value.forEach((item) => {
const msg = messageData.getMsg(selectedSessionId.value, item)
let nickName = ''
if (msg.msgType === MsgType.CHAT) {
if (myAccount.value === msg.fromId) {
nickName = userData.user.nickName
} else {
nickName = messageData.sessionList[msg.sessionId].objectInfo.nickName
}
} else if (msg.msgType === MsgType.GROUP_CHAT) {
const groupId = messageData.sessionList[msg.sessionId].remoteId
const members = groupData.groupMembersList[groupId]
nickName = members[msg.fromId].nickName
}
msgs.push({
...msg,
nickName
})
})
if (showForwardMsgDialogTitle.value === '合并转发') {
return [
{
type: msgContentType.FORWARD_TOGETHER,
value: msgs
}
]
} else {
return msgs
}
})
const sessionListSortedKey = computed(() => { const sessionListSortedKey = computed(() => {
return sessionListSorted.value return sessionListSorted.value
@@ -1389,11 +1358,11 @@ const sessionListSortedKey = computed(() => {
.map((item) => item.sessionId) .map((item) => item.sessionId)
}) })
const showForwardMsgDialog = (msgId) => { const showForwardMsgDialog = (msgKey) => {
multiSelectedMsgIds.value.clear() multiSelectedMsgKeys.value.clear()
multiSelectedMsgIds.value.add(msgId) multiSelectedMsgKeys.value.add(msgKey)
isShowForwardMsgDialog.value = true isShowForwardMsgDialog.value = true
showForwardMsgDialogTitle.value = '转发消息' showForwardMsgDialogTitle.value = '逐条转发'
} }
const handleConfirmForwardMsg = async (sessions) => { const handleConfirmForwardMsg = async (sessions) => {
@@ -1412,14 +1381,43 @@ const handleConfirmForwardMsg = async (sessions) => {
messageData.addSession(res.data.data.session) messageData.addSession(res.data.data.session)
} }
for (const forwardMsg of forwardMsgs.value) { if (showForwardMsgDialogTitle.value === '逐条转发') {
const content = for (const msgKey of multiSelectedMsgKeys.value) {
showForwardMsgDialogTitle.value !== '合并转发' const msg = messageData.getMsg(selectedSessionId.value, msgKey)
? forwardMsg.content await handleSendForwardMsg({
: JSON.stringify(forwardMsg) session: item,
content: msg.content
})
}
} else if (showForwardMsgDialogTitle.value === '合并转发') {
const msgs = [...multiSelectedMsgKeys.value].map((item) => {
const msg = messageData.getMsg(selectedSessionId.value, item)
let nickName = ''
if (selectedSession.value.sessionType === MsgType.CHAT) {
if (myAccount.value === msg.fromId) {
nickName = userData.user.nickName
} else {
nickName = selectedSession.value.objectInfo.nickName
}
} else if (selectedSession.value.sessionType === MsgType.GROUP_CHAT) {
const groupId = selectedSession.value.remoteId
const members = groupData.groupMembersList[groupId]
nickName = members[msg.fromId].nickName
}
return {
nickName,
msgId: msg.msgId
}
})
await handleSendForwardMsg({ await handleSendForwardMsg({
session: item, session: item,
content: content content: JSON.stringify({
type: msgContentType.FORWARD_TOGETHER,
value: {
sessionId: selectedSessionId.value,
data: [...msgs]
}
})
}) })
} }
} }
@@ -1675,7 +1673,7 @@ const onShowRecorder = () => {
:isLoadMoreLoading="selectedSessionCache[selectedSessionId]?.isLoadMoreLoading" :isLoadMoreLoading="selectedSessionCache[selectedSessionId]?.isLoadMoreLoading"
:inputEditorRef="inputEditorRef" :inputEditorRef="inputEditorRef"
:isMultiSelect="isMultiSelect" :isMultiSelect="isMultiSelect"
:isSelected="multiSelectedMsgIds.has(item)" :isSelected="multiSelectedMsgKeys.has(item)"
@loadMore="onLoadMore" @loadMore="onLoadMore"
@showUserCard="onShowUserCard" @showUserCard="onShowUserCard"
@showGroupCard="onShowGroupCard" @showGroupCard="onShowGroupCard"
@@ -1728,7 +1726,7 @@ const onShowRecorder = () => {
<el-container v-if="isMultiSelect"> <el-container v-if="isMultiSelect">
<InputMultiSelect <InputMultiSelect
ref="inputMultiSelectRef" ref="inputMultiSelectRef"
:selectedCount="multiSelectedMsgIds.size" :selectedCount="multiSelectedMsgKeys.size"
@exit="handleCancleMultiSelect" @exit="handleCancleMultiSelect"
@forwardTogether="handleForwardTogether" @forwardTogether="handleForwardTogether"
@forwardOneByOne="handleForwardOneByOne" @forwardOneByOne="handleForwardOneByOne"

View File

@@ -1,5 +1,5 @@
<script setup lang="jsx"> <script setup lang="jsx">
import { onMounted, computed, watch, createApp, h } from 'vue' import { ref, onMounted, computed, watch, createApp, h } from 'vue'
import { ElDialog, ElLoading } from 'element-plus' import { ElDialog, ElLoading } from 'element-plus'
import { import {
useUserStore, useUserStore,
@@ -24,6 +24,7 @@ import MsgBoxDocument from '@/views/message/components/MsgBoxDocument.vue'
import DialogForMsgList from '@/views/message/components/DialogForMsgList.vue' import DialogForMsgList from '@/views/message/components/DialogForMsgList.vue'
import { emojis } from '@/js/utils/emojis' import { emojis } from '@/js/utils/emojis'
import { MsgType } from '@/proto/msg' import { MsgType } from '@/proto/msg'
import { msgChatQueryMessagesService } from '@/api/message'
const props = defineProps(['isShow', 'title', 'sessionId', 'msgs', 'tier']) const props = defineProps(['isShow', 'title', 'sessionId', 'msgs', 'tier'])
const emit = defineEmits(['update:isShow', 'showUserCard', 'close']) const emit = defineEmits(['update:isShow', 'showUserCard', 'close'])
@@ -36,8 +37,16 @@ const audioData = useAudioStore()
const videoData = useVideoStore() const videoData = useVideoStore()
const documentData = useDocumentStore() const documentData = useDocumentStore()
const msgsFromServer = ref({})
onMounted(async () => { onMounted(async () => {
await messageData.preloadResource(props.msgs) const loadingInstance = ElLoading.service(el_loading_options)
try {
await messageData.preloadResource(props.msgs)
await loadForwardTogetherMsgs()
} finally {
loadingInstance.close()
}
}) })
/** /**
@@ -50,6 +59,65 @@ watch(
} }
) )
const loadForwardTogetherMsgs = async () => {
for (const msg of props.msgs) {
const content = msg.content
const contentJson = jsonParseSafe(content)
if (!contentJson) {
return
}
const type = contentJson['type']
const value = contentJson['value']
if (!type || !value) {
return
} else {
if (type === msgContentType.FORWARD_TOGETHER) {
let res
try {
const msgIds = value.data
.map((item) => {
return item.msgId
})
.join(',')
res = await msgChatQueryMessagesService({
sessionId: value.sessionId,
msgIds
})
} catch (error) {
console.error(error)
return
}
const msgs = res.data.data
if (!res.data.data || res.data.data.length == 0) {
return
}
// value.data(取里面的nickName) 和 msgs合一
const newMsgs = {}
msgs.forEach((item) => {
newMsgs[item.msgId] = item
})
value.data.forEach((item) => {
if (item.msgId in newMsgs) {
newMsgs[item.msgId] = {
...newMsgs[item.msgId],
...item
}
}
})
msgsFromServer.value[msg.msgId] = Object.values(newMsgs).sort((a, b) => {
const timeA = new Date(a.sendTime || a.msgTime).getTime()
const timeB = new Date(b.sendTime || b.msgTime).getTime()
return timeA - timeB
})
}
}
}
}
const myAccount = computed(() => { const myAccount = computed(() => {
return userData.user.account return userData.user.account
}) })
@@ -91,7 +159,7 @@ const renderContent = ({ msg }) => {
case msgContentType.DOCUMENT: case msgContentType.DOCUMENT:
return renderDocument(value) return renderDocument(value)
case msgContentType.FORWARD_TOGETHER: case msgContentType.FORWARD_TOGETHER:
return renderForwardTogether(value) return renderForwardTogether(msgId)
default: default:
return <span>{content}</span> return <span>{content}</span>
} }
@@ -232,13 +300,13 @@ const renderDocument = (content) => {
} }
} }
const renderForwardTogether = (msgs) => { const renderForwardTogether = (msgId) => {
const title = '聊天记录' const title = '聊天记录'
const msgsSorted = msgs.sort((a, b) => { const msgsSorted = msgsFromServer.value[msgId]
const timeA = new Date(a.sendTime || a.msgTime).getTime()
const timeB = new Date(b.sendTime || b.msgTime).getTime() if (!msgsSorted) {
return timeA - timeB return <div class={'forward-together'}></div>
}) }
return ( return (
<div <div

View File

@@ -23,7 +23,11 @@ import MsgBoxVideo from '@/views/message/components/MsgBoxVideo.vue'
import MsgBoxDocument from '@/views/message/components/MsgBoxDocument.vue' import MsgBoxDocument from '@/views/message/components/MsgBoxDocument.vue'
import MenuMsgItem from '@/views/message/components/MenuMsgItem.vue' import MenuMsgItem from '@/views/message/components/MenuMsgItem.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { msgChatDeleteMsgService, msgChatRevokeMsgService } from '@/api/message' import {
msgChatDeleteMsgService,
msgChatQueryMessagesService,
msgChatRevokeMsgService
} from '@/api/message'
import DialogForMsgList from '@/views/message/components/DialogForMsgList.vue' import DialogForMsgList from '@/views/message/components/DialogForMsgList.vue'
const props = defineProps([ const props = defineProps([
@@ -61,18 +65,18 @@ const audioData = useAudioStore()
const videoData = useVideoStore() const videoData = useVideoStore()
const documentData = useDocumentStore() const documentData = useDocumentStore()
onMounted(() => { onMounted(async () => {
rendering() await rendering()
}) })
let app = null let app = null
const rendering = () => { const rendering = async () => {
const msgContent = document.querySelector(`#div-content-${msg.value.msgId}`) const msgContent = document.querySelector(`#div-content-${msg.value.msgId}`)
if (msgContent) { if (msgContent) {
if (app) { if (app) {
app.unmount() app.unmount()
} }
const vnode = renderComponent(msg.value.content) const vnode = await renderComponent(msg.value.content)
app = createApp({ app = createApp({
render: () => vnode render: () => vnode
}) })
@@ -84,7 +88,7 @@ const rendering = () => {
* 动态渲染消息内容 * 动态渲染消息内容
* @param content 消息内容 * @param content 消息内容
*/ */
const renderComponent = (content) => { const renderComponent = async (content) => {
const contentJson = jsonParseSafe(content) const contentJson = jsonParseSafe(content)
if (!contentJson) { if (!contentJson) {
return renderMix(content) return renderMix(content)
@@ -114,7 +118,7 @@ const renderComponent = (content) => {
case msgContentType.DOCUMENT: case msgContentType.DOCUMENT:
return renderDocument(value) return renderDocument(value)
case msgContentType.FORWARD_TOGETHER: case msgContentType.FORWARD_TOGETHER:
return renderForwardTogether(value) return await renderForwardTogether(value)
default: default:
return h('span', content) return h('span', content)
} }
@@ -263,11 +267,46 @@ const showMsgContentInForwardTogether = (content) => {
} }
} }
const renderForwardTogether = (msgs) => { const renderForwardTogether = async (content) => {
let res
try {
const msgIds = content.data
.map((item) => {
return item.msgId
})
.join(',')
res = await msgChatQueryMessagesService({
sessionId: content.sessionId,
msgIds
})
} catch (error) {
console.error(error)
return h('span', content)
}
const msgs = res.data.data
if (!res.data.data || res.data.data.length == 0) {
return h('span', content)
}
// 把content.data(取里面的nickName) 和 msgs合一
const newMsgs = {}
msgs.forEach((item) => {
newMsgs[item.msgId] = item
})
content.data.forEach((item) => {
if (item.msgId in newMsgs) {
newMsgs[item.msgId] = {
...newMsgs[item.msgId],
...item
}
}
})
const title = const title =
(msgs[0].msgType === MsgType.GROUP_CHAT ? '群聊' : nickNameFromMsg.value) + '的聊天记录' (msgs[0].msgType === MsgType.GROUP_CHAT ? '群聊' : nickNameFromMsg.value) + '的聊天记录'
const msgsSorted = msgs.sort((a, b) => { const msgsSorted = Object.values(newMsgs).sort((a, b) => {
const timeA = new Date(a.sendTime || a.msgTime).getTime() const timeA = new Date(a.sendTime || a.msgTime).getTime()
const timeB = new Date(b.sendTime || b.msgTime).getTime() const timeB = new Date(b.sendTime || b.msgTime).getTime()
return timeA - timeB return timeA - timeB
@@ -1046,7 +1085,7 @@ const handleItemClick = () => {
</div> </div>
<div <div
class="message-item" class="message-item"
:data-msg-id="props.msgKey" :data-msg-key="props.msgKey"
:data-disabled="multiSelectOptionDisabled" :data-disabled="multiSelectOptionDisabled"
:class="{ unreadMsg: isUnreadMsg }" :class="{ unreadMsg: isUnreadMsg }"
> >