From af7ef14ba2e9643769d086afd6bb61eaa510e5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= Date: Mon, 14 Jul 2025 11:04:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=92=A4=E5=9B=9E=E6=B6=88=E6=81=AF=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 27 ++ .../chating/components/MessageItem/index.vue | 329 ++++++++++++++---- store/modules/message.js | 6 +- util/imCommon.js | 28 ++ util/revokeMessage.js | 82 +++++ 5 files changed, 397 insertions(+), 75 deletions(-) create mode 100644 util/revokeMessage.js diff --git a/App.vue b/App.vue index 19c83d6..e4871be 100644 --- a/App.vue +++ b/App.vue @@ -365,6 +365,33 @@ export default { IMSDK.IMEvents.OnConversationChanged, conversationChangedHandler ); + + // 撤回消息回调 + const messageRevokedHandler = ({ data }) => { + if (this.storeIsSyncing) { + return; + } + // 兼容:如果没有 contentType,补充为撤回类型 + if (!data.contentType) { + data.contentType = MessageType.RevokeMessage; + data.notificationElem = { + detail: JSON.stringify({ + revokerID: data.revokerID, + revokerName: data.revokerNickname || data.revokerID + }) + }; + } + // 更新被撤回的消息为撤回通知消息 + this.updateOneMessage({ + message: data, + type: UpdateMessageTypes.Overall, + }); + }; + + IMSDK.subscribe( + IMSDK.IMEvents.OnNewRecvMessageRevoked, + messageRevokedHandler + ); }, tryLogin() { diff --git a/pages/conversation/chating/components/MessageItem/index.vue b/pages/conversation/chating/components/MessageItem/index.vue index 2d05631..a5c0103 100644 --- a/pages/conversation/chating/components/MessageItem/index.vue +++ b/pages/conversation/chating/components/MessageItem/index.vue @@ -1,83 +1,100 @@ @@ -94,8 +111,11 @@ import MediaMessageRender from "./MediaMessageRender.vue"; import ErrorMessageRender from "./ErrorMessageRender.vue"; import VoiceMessageRender from './VoiceMessageRender.vue' import FileMessageRender from './FileMessageRender.vue' +import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'; + import { noticeMessageTypes } from "@/constant"; import { tipMessaggeFormat, formatMessageTime } from "@/util/imCommon"; +import { revokeMessage, canRevokeMessage } from "@/util/revokeMessage"; const textRenderTypes = [MessageType.TextMessage]; @@ -108,7 +128,8 @@ export default { MediaMessageRender, ErrorMessageRender, VoiceMessageRender, - FileMessageRender + FileMessageRender, + uniPopup, }, props: { source: Object, @@ -120,12 +141,17 @@ export default { isActive: Boolean, }, data() { - return {}; + return { + sendingDelay: true, + showActionMenu: false, + actionMenuItems: [], + }; }, computed: { ...mapGetters([ "storeCurrentConversation", "storeSelfInfo", + "storeHistoryMessageList", ]), isSingle() { return ( @@ -169,6 +195,14 @@ export default { this.$emit("messageItemRender", this.source.clientMsgID); this.setSendingDelay(); }, + watch: { + source: { + handler(newVal, oldVal) { + console.log('MessageItem source changed:', { newVal, oldVal }); + }, + deep: true + } + }, methods: { setSendingDelay() { if (this.source && this.source.status === MessageStatus.Sending) { @@ -177,6 +211,119 @@ export default { }, 2000); } }, + handleLongPress(e) { + this.actionMenuItems = this.getActionMenuItems(); + this.showActionMenu = true; + this.$nextTick(() => { + this.$refs.actionPopup && this.$refs.actionPopup.open && this.$refs.actionPopup.open('bottom'); + }); + }, + + + getActionMenuItems() { + const items = []; + + // 复制消息(文本消息) + if (this.source.contentType === 101) { + items.push('复制'); + } + + // 撤回消息(只有自己发送的2分钟内消息才能撤回) + if (canRevokeMessage(this.source, this.storeSelfInfo.userID)) { + items.push('撤回'); + } + + return items; + }, + + handleActionClick(action) { + switch (action) { + case '复制': + this.copyMessage(); + break; + case '撤回': + this.confirmRevokeMessage(); + break; + case '转发': + this.forwardMessage(); + break; + default: + console.log('未知操作:', action); + } + this.showActionMenu = false; + this.$refs.actionPopup && this.$refs.actionPopup.close && this.$refs.actionPopup.close(); + }, + + closeActionMenu() { + this.showActionMenu = false; + this.$refs.actionPopup && this.$refs.actionPopup.close && this.$refs.actionPopup.close(); + }, + onPopupChange(e) { + if (!e.show) { + this.showActionMenu = false; + } + }, + + copyMessage() { + if (this.source.contentType === 101 && this.source.textElem && this.source.textElem.content) { + uni.setClipboardData({ + data: this.source.textElem.content, + success: () => { + uni.showToast({ + title: '已复制到剪贴板', + icon: 'success' + }); + } + }); + } + }, + + forwardMessage() { + // TODO: 实现转发消息功能 + uni.showToast({ + title: '转发功能开发中', + icon: 'none' + }); + }, + + async confirmRevokeMessage() { + // 再次检查是否可以撤回(防止状态变化) + if (!canRevokeMessage(this.source, this.storeSelfInfo.userID)) { + uni.showToast({ + title: '只能撤回自己发送的2分钟内的消息', + icon: 'none', + duration: 2000 + }); + return; + } + + try { + uni.showLoading({ + title: '撤回中...' + }); + + // 撤回消息 + const result = await revokeMessage( + this.storeCurrentConversation.conversationID, + this.source.clientMsgID + ); + + uni.hideLoading(); + + + uni.showToast({ + title: '撤回成功', + icon: 'success' + }); + } catch (error) { + uni.hideLoading(); + uni.showToast({ + title: '撤回失败', + icon: 'none' + }); + console.error('撤回消息失败:', error); + } + }, }, }; @@ -339,4 +486,38 @@ export default { .fade-enter { opacity: 0; } + +.popup-action-list { + display: flex; + flex-direction: column; + padding: 24rpx 0; + background: #f8f8f8; +} + +.popup-action-item { + margin: 0 32rpx 20rpx 32rpx; + padding: 24rpx 0; + background: #fff; + border-radius: 16rpx; + text-align: center; + font-size: 32rpx; + color: #333; + transition: background 0.2s; + box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); + + &:last-child { + margin-bottom: 0; + } + + &:active { + background: #e6f0ff; + color: #1677ff; + } +} + +.popup-action-text { + display: block; + width: 100%; + height: 100%; +} diff --git a/store/modules/message.js b/store/modules/message.js index 3b064ef..6ddc0c0 100644 --- a/store/modules/message.js +++ b/store/modules/message.js @@ -55,10 +55,11 @@ const actions = { isSuccess = false, }, ) { - const tmpList = state.historyMessageList; + const tmpList = [...state.historyMessageList]; const idx = tmpList.findIndex( (msg) => msg.clientMsgID === message.clientMsgID, ); + console.log('updateOneMessage:', { message, idx, listLength: tmpList.length }); if (idx !== -1) { if (type === UpdateMessageTypes.Overall) { tmpList[idx] = { @@ -71,6 +72,9 @@ const actions = { ); } commit("SET_HISTORY_MESSAGE_LIST", tmpList); + console.log('消息更新成功,新的消息内容:', tmpList[idx]); + } else { + console.log('未找到要更新的消息:', message.clientMsgID); } }, resetMessageState({ commit }) { diff --git a/util/imCommon.js b/util/imCommon.js index d78a363..b89d91b 100644 --- a/util/imCommon.js +++ b/util/imCommon.js @@ -188,6 +188,20 @@ export const parseMessageByType = (pmsg) => { return `${getName(groupNameUpdateUser)}修改了群名称为${ groupNameUpdateDetail.group.groupName }`; + case MessageType.RevokeMessage: + try { + const revokeDetails = JSON.parse(pmsg.notificationElem.detail); + const revokerID = revokeDetails.revokerID; + const revokerName = revokeDetails.revokerName || "未知用户"; + + if (revokerID === store.getters.storeCurrentUserID) { + return "你撤回了一条消息"; + } else { + return `${revokerName}撤回了一条消息`; + } + } catch (error) { + return "消息已被撤回"; + } default: return "[暂未支持的消息类型]"; } @@ -288,6 +302,20 @@ export const tipMessaggeFormat = (msg, currentUserID) => { const dismissDetails = JSON.parse(msg.notificationElem.detail); const dismissUser = dismissDetails.opUser; return `${getName(dismissUser)}解散了群聊`; + case MessageType.RevokeMessage: + try { + const revokeDetails = JSON.parse(msg.notificationElem.detail); + const revokerID = revokeDetails.revokerID; + const revokerName = revokeDetails.revokerName || "未知用户"; + + if (revokerID === currentUserID) { + return "你撤回了一条消息"; + } else { + return `${revokerName}撤回了一条消息`; + } + } catch (error) { + return "消息已被撤回"; + } default: return ""; } diff --git a/util/revokeMessage.js b/util/revokeMessage.js new file mode 100644 index 0000000..381ec6c --- /dev/null +++ b/util/revokeMessage.js @@ -0,0 +1,82 @@ +import IMSDK, { MessageStatus } from "openim-uniapp-polyfill"; + +/** + * 撤回消息 + * @param {string} conversationID - 会话ID + * @param {string} clientMsgID - 消息客户端ID + * @returns {Promise} 撤回结果 + */ +export const revokeMessage = async (conversationID, clientMsgID) => { + try { + const result = await IMSDK.asyncApi( + IMSDK.IMMethods.RevokeMessage, + IMSDK.uuid(), + { + conversationID, + clientMsgID, + } + ); + return result; + } catch (error) { + console.error("撤回消息失败:", error); + throw error; + } +}; + +/** + * 检查消息是否可以撤回 + * @param {Object} message - 消息对象 + * @param {string} currentUserID - 当前用户ID + * @returns {boolean} 是否可以撤回 + */ +export const canRevokeMessage = (message, currentUserID) => { + console.log('检查消息是否可以撤回:', { + messageSendID: message.sendID, + currentUserID: currentUserID, + messageStatus: message.status, + contentType: message.contentType, + sendTime: message.sendTime, + now: Date.now() + }); + + // 只能撤回自己发送的消息 + if (message.sendID !== currentUserID) { + console.log('不是自己发送的消息'); + return false; + } + + // 检查消息状态是否为成功 + if (message.status !== MessageStatus.Succeed) { + console.log('消息状态不是成功:', message.status); + return false; + } + + // 检查消息类型是否支持撤回 + const revokableTypes = [ + 101, // 文本消息 + 102, // 图片消息 + 103, // 语音消息 + 104, // 视频消息 + 105, // 文件消息 + 106, // @消息 + 110, // 自定义消息 + ]; + + if (!revokableTypes.includes(message.contentType)) { + console.log('不支持撤回的消息类型:', message.contentType); + return false; + } + + // 检查消息发送时间是否在2分钟内(可配置) + const now = Date.now(); + const messageTime = message.sendTime; + const timeLimit = 2 * 60 * 1000; // 2分钟 + + if (now - messageTime > timeLimit) { + console.log('消息发送时间超过2分钟'); + return false; + } + + console.log('消息可以撤回'); + return true; +}; \ No newline at end of file