diff --git a/src/api/message.js b/src/api/message.js
index 0f088ee..d657dab 100644
--- a/src/api/message.js
+++ b/src/api/message.js
@@ -12,6 +12,10 @@ export const msgChatPullMsgService = (obj) => {
return request.get('/chat/pullMsg', { params: obj })
}
+export const msgChatRevokeMsgService = (obj) => {
+ return request.post('/chat/revokeMsg', obj)
+}
+
export const msgAtService = () => {
return request.get('/chat/queryAt')
}
diff --git a/src/const/msgConst.js b/src/const/msgConst.js
index a6d7909..046890a 100644
--- a/src/const/msgConst.js
+++ b/src/const/msgConst.js
@@ -41,3 +41,13 @@ export const msgFileUploadStatus = {
UPLOAD_SUCCESS: 2, // 上传成功
UPLOAD_FAILED: 3 // 上传失败
}
+
+/**
+ * 消息撤回时间限制 10分钟
+ */
+export const MSG_REVOKE_TIME_LIMIT = 10 * 60 * 1000
+
+/**
+ * 消息撤回后能重新编辑的时间限制 2分钟
+ */
+export const MSG_REEDIT_TIME_LIMIT = 2 * 60 * 1000
diff --git a/src/js/event/index.js b/src/js/event/index.js
index bff6a7c..c762468 100644
--- a/src/js/event/index.js
+++ b/src/js/event/index.js
@@ -5,3 +5,4 @@ export * from './receiveGroupChatMsg'
export * from './receiveGroupChatReadMsg'
export * from './receiveGroupSystemMsg'
export * from './receiveAtMsg'
+export * from './receiveRevokeMsg'
diff --git a/src/js/event/receiveRevokeMsg.js b/src/js/event/receiveRevokeMsg.js
new file mode 100644
index 0000000..022de5e
--- /dev/null
+++ b/src/js/event/receiveRevokeMsg.js
@@ -0,0 +1,10 @@
+import { useMessageStore } from '@/stores'
+
+export const onReceiveRevokeMsg = () => {
+ return (msg) => {
+ const messageData = useMessageStore()
+ const sessionId = msg.body.sessionId
+ const revokeMsgId = msg.body.content
+ messageData.revokeMsgRcord(sessionId, revokeMsgId)
+ }
+}
diff --git a/src/js/websocket/wsConnect.js b/src/js/websocket/wsConnect.js
index cae761f..6bff0f8 100644
--- a/src/js/websocket/wsConnect.js
+++ b/src/js/websocket/wsConnect.js
@@ -20,7 +20,8 @@ import {
onReceiveGroupChatMsg,
onReceiveGroupChatReadMsg,
onReceiveGroupSystemMsg,
- onReceiveAtMsg
+ onReceiveAtMsg,
+ onReceiveRevokeMsg
} from '@/js/event'
class WsConnect {
@@ -121,6 +122,7 @@ class WsConnect {
[MsgType.GROUP_CHAT]: onReceiveGroupChatMsg(),
[MsgType.GROUP_CHAT_READ]: onReceiveGroupChatReadMsg(),
[MsgType.AT]: onReceiveAtMsg(),
+ [MsgType.REVOKE]: onReceiveRevokeMsg(),
[MsgType.SYS_GROUP_CREATE]: onReceiveGroupSystemMsg(),
[MsgType.SYS_GROUP_ADD_MEMBER]: onReceiveGroupSystemMsg(),
[MsgType.SYS_GROUP_DEL_MEMBER]: onReceiveGroupSystemMsg(),
diff --git a/src/proto/msg.js b/src/proto/msg.js
index 979db57..e7391d9 100644
--- a/src/proto/msg.js
+++ b/src/proto/msg.js
@@ -294,6 +294,7 @@ export const Msg = ($root.Msg = (() => {
* @property {number} STATUS_RES=9 STATUS_RES value
* @property {number} STATUS_SYNC=10 STATUS_SYNC value
* @property {number} AT=11 AT value
+ * @property {number} REVOKE=12 REVOKE value
* @property {number} SYS_GROUP_CREATE=21 SYS_GROUP_CREATE value
* @property {number} SYS_GROUP_ADD_MEMBER=22 SYS_GROUP_ADD_MEMBER value
* @property {number} SYS_GROUP_DEL_MEMBER=23 SYS_GROUP_DEL_MEMBER value
@@ -331,6 +332,7 @@ export const MsgType = ($root.MsgType = (() => {
values[(valuesById[9] = 'STATUS_RES')] = 9
values[(valuesById[10] = 'STATUS_SYNC')] = 10
values[(valuesById[11] = 'AT')] = 11
+ values[(valuesById[12] = 'REVOKE')] = 12
values[(valuesById[21] = 'SYS_GROUP_CREATE')] = 21
values[(valuesById[22] = 'SYS_GROUP_ADD_MEMBER')] = 22
values[(valuesById[23] = 'SYS_GROUP_DEL_MEMBER')] = 23
@@ -546,6 +548,7 @@ export const Header = ($root.Header = (() => {
case 9:
case 10:
case 11:
+ case 12:
case 21:
case 22:
case 23:
@@ -642,6 +645,10 @@ export const Header = ($root.Header = (() => {
case 11:
message.msgType = 11
break
+ case 'REVOKE':
+ case 12:
+ message.msgType = 12
+ break
case 'SYS_GROUP_CREATE':
case 21:
message.msgType = 21
@@ -825,18 +832,18 @@ export const Body = ($root.Body = (() => {
* | 8 | seq | - | - | M | M | M | M | O | O | M | todo | todo |
* | 9 | sessionId | - | - | M | M | M | M | M | M | M | todo | todo |
* +---+--------------+------+-----------+---------|-----------+---------------+-----------------+----------+----------------+----------+-------------------+---------------------+
- * NO filed STATUS_REQ STATUS_RES STATUS_SYNC SYS_GROUP_XXX AT(up) AT(down)
- * +---+--------------+------------+------------+-------------+------------+---------+---------+
- * | 1 | fromId | M | M | M | - | M | M |
- * | 2 | fromClient | M | M | M | - | M | M |
- * | 3 | toId | - | - | - | - | - | M |
- * | 4 | toClient | - | - | - | - | - | M |
- * | 5 | groupId | - | - | - | M | M | M |
- * | 6 | msgId | - | - | - | M | - | M |
- * | 7 | content | M | M | M | M | M | M |
- * | 8 | seq | - | - | - | - | M | M |
- * | 9 | sessionId | - | - | - | M | M | M |
- * +---+--------------+------------+------------+-------------+------------+---------+---------+
+ * NO filed STATUS_REQ STATUS_RES STATUS_SYNC SYS_GROUP_XXX AT(up) AT(down) REVOKE
+ * +---+--------------+------------+------------+-------------+------------+---------+---------+-----------+
+ * | 1 | fromId | M | M | M | - | M | M | M |
+ * | 2 | fromClient | M | M | M | - | M | M | - |
+ * | 3 | toId | - | - | - | - | - | M | o |
+ * | 4 | toClient | - | - | - | - | - | M | - |
+ * | 5 | groupId | - | - | - | M | M | M | o |
+ * | 6 | msgId | - | - | - | M | - | M | M |
+ * | 7 | content | M | M | M | M | M | M | M |
+ * | 8 | seq | - | - | - | - | M | M | - |
+ * | 9 | sessionId | - | - | - | M | M | M | M |
+ * +---+--------------+------------+------------+-------------+------------+---------+---------+-----------+
* @implements IBody
* @constructor
* @param {IBody=} [properties] Properties to set
diff --git a/src/proto/msg.proto b/src/proto/msg.proto
index 4d7bd0e..6ad3d1a 100644
--- a/src/proto/msg.proto
+++ b/src/proto/msg.proto
@@ -19,6 +19,7 @@ enum MsgType {
STATUS_RES = 9; //连接状态响应
STATUS_SYNC = 10; //端侧的连接状态同步给云端(比如在线,离开)
AT = 11; //@消息
+ REVOKE = 12; //撤回消息
SYS_GROUP_CREATE = 21; //系统消息之创建群组
SYS_GROUP_ADD_MEMBER = 22; //系统消息之添加群组成员
@@ -66,18 +67,18 @@ message Header {
| 8 | seq | - | - | M | M | M | M | O | O | M | todo | todo |
| 9 | sessionId | - | - | M | M | M | M | M | M | M | todo | todo |
+---+--------------+------+-----------+---------|-----------+---------------+-----------------+----------+----------------+----------+-------------------+---------------------+
- NO filed STATUS_REQ STATUS_RES STATUS_SYNC SYS_GROUP_XXX AT(up) AT(down)
-+---+--------------+------------+------------+-------------+------------+---------+---------+
-| 1 | fromId | M | M | M | - | M | M |
-| 2 | fromClient | M | M | M | - | M | M |
-| 3 | toId | - | - | - | - | - | M |
-| 4 | toClient | - | - | - | - | - | M |
-| 5 | groupId | - | - | - | M | M | M |
-| 6 | msgId | - | - | - | M | - | M |
-| 7 | content | M | M | M | M | M | M |
-| 8 | seq | - | - | - | - | M | M |
-| 9 | sessionId | - | - | - | M | M | M |
-+---+--------------+------------+------------+-------------+------------+---------+---------+
+ NO filed STATUS_REQ STATUS_RES STATUS_SYNC SYS_GROUP_XXX AT(up) AT(down) REVOKE
++---+--------------+------------+------------+-------------+------------+---------+---------+-----------+
+| 1 | fromId | M | M | M | - | M | M | M |
+| 2 | fromClient | M | M | M | - | M | M | - |
+| 3 | toId | - | - | - | - | - | M | o |
+| 4 | toClient | - | - | - | - | - | M | - |
+| 5 | groupId | - | - | - | M | M | M | o |
+| 6 | msgId | - | - | - | M | - | M | M |
+| 7 | content | M | M | M | M | M | M | M |
+| 8 | seq | - | - | - | - | M | M | - |
+| 9 | sessionId | - | - | - | M | M | M | M |
++---+--------------+------------+------------+-------------+------------+---------+---------+-----------+
*/
message Body {
optional string fromId = 1;
diff --git a/src/stores/message.js b/src/stores/message.js
index c63e304..21b8593 100644
--- a/src/stores/message.js
+++ b/src/stores/message.js
@@ -181,6 +181,12 @@ export const useMessageStore = defineStore('anylink-message', () => {
}
}
+ const revokeMsgRcord = (sessionId, msgKey) => {
+ if (msgRecordsList.value[sessionId] && msgKey in msgRecordsList.value[sessionId]) {
+ msgRecordsList.value[sessionId][msgKey].revoke = true
+ }
+ }
+
const getMsg = (sessionId, msgKey) => {
if (!msgRecordsList.value[sessionId] || !msgRecordsList.value[sessionId][msgKey]) {
return ref({})
@@ -333,6 +339,7 @@ export const useMessageStore = defineStore('anylink-message', () => {
updateMsgKeySort,
addMsgRecords,
removeMsgRecord,
+ revokeMsgRcord,
getMsg,
updateMsg,
diff --git a/src/views/message/MessageLayout.vue b/src/views/message/MessageLayout.vue
index a8c0c6a..2a71c3f 100644
--- a/src/views/message/MessageLayout.vue
+++ b/src/views/message/MessageLayout.vue
@@ -1296,6 +1296,7 @@ const onShowRecorder = () => {
:lastMsgId="lastMsgId"
:hasNoMoreMsg="hasNoMoreMsg"
:isLoadMoreLoading="selectedSessionCache[selectedSessionId]?.isLoadMoreLoading"
+ :inputEditorRef="inputEditorRef"
@loadMore="onLoadMore"
@showUserCard="onShowUserCard"
@showGroupCard="onShowGroupCard"
diff --git a/src/views/message/components/InputEditor.vue b/src/views/message/components/InputEditor.vue
index cf54098..f38c5a9 100644
--- a/src/views/message/components/InputEditor.vue
+++ b/src/views/message/components/InputEditor.vue
@@ -27,7 +27,7 @@ import AgreeBeforeSend from '@/views/message/components/AgreeBeforeSend.vue'
const Clipboard = Quill.import('modules/clipboard')
class PlainClipboard extends Clipboard {
onPaste(range, { text }) {
- renderContent(text)
+ handlePaste(range, text)
}
}
Quill.register(
@@ -110,7 +110,7 @@ onMounted(async () => {
// 给组件增加滚动条样式
document.querySelector('.ql-editor').classList.add('my-scrollbar')
await imageData.loadImageInfoFromContent(props.draft)
- renderContent(props.draft)
+ renderContent(props.draft) // 渲染草稿
quill.value.on('composition-start', () => {
// 当用户使用拼音输入法开始输入汉字时,这个事件就会被触发
quill.value.root.dataset.placeholder = ''
@@ -122,7 +122,7 @@ onMounted(async () => {
// 监听文本变化检测@符号
quill.value.on('text-change', (delta, oldDelta, source) => {
- if (session.value.sessionType === MsgType.GROUP_CHAT && source === 'user') {
+ if (session.value.sessionType === MsgType.GROUP_CHAT && source === Quill.sources.USER) {
const insertOps = delta.ops.filter((op) => op.insert && typeof op.insert === 'string')
const insertContent = insertOps.map((item) => item.insert).join('')
if (insertContent.length > 0) {
@@ -393,16 +393,76 @@ watch(
fn(contentObj.contentFromLocal.join('').trim())
- renderContent(messageData.sessionList[newSessionId].draft || '')
+ renderContent(messageData.sessionList[newSessionId].draft || '') // 切换session时渲染新session的草稿
},
{ deep: true }
)
-let pasteContent
-let pasteContentType
-let pasteFileName
-let pasteFileSize
-let pasteUrl
+// 实现消息复制的效果,步骤如下
+// 1. 拷贝原消息中的content内容
+// 2. 粘贴时自动调用renderContent渲染内容
+// 3. 渲染时保存复制内容
+// 4. 发送时使用保存的复制内容
+const pasteObj = {
+ content: null,
+ contentType: null,
+ fileName: null,
+ fileSize: null,
+ url: null
+}
+
+const clearPasteObj = () => {
+ pasteObj.content = null
+ pasteObj.contentType = null
+ pasteObj.fileName = null
+ pasteObj.fileSize = null
+ pasteObj.url = null
+}
+
+const handlePaste = (range, content) => {
+ if (!content) {
+ return
+ }
+
+ const jsonContent = jsonParseSafe(content)
+ if (jsonContent && jsonContent['type'] && jsonContent['value']) {
+ clearPasteObj()
+ pasteObj.content = content
+ pasteObj.contentType = jsonContent['type']
+ const fileId = jsonContent['value']
+ switch (pasteObj.contentType) {
+ case msgContentType.IMAGE:
+ pasteObj.fileName = imageData.image[fileId]?.fileName
+ pasteObj.fileSize = imageData.image[fileId]?.size
+ pasteObj.url = imageData.image[fileId]?.thumbUrl
+ break
+ case msgContentType.AUDIO:
+ pasteObj.fileName = audioData.audio[fileId]?.fileName
+ pasteObj.fileSize = audioData.audio[fileId]?.size
+ break
+ case msgContentType.VIDEO:
+ pasteObj.fileName = videoData.video[fileId]?.fileName
+ pasteObj.fileSize = videoData.video[fileId]?.size
+ break
+ case msgContentType.DOCUMENT:
+ pasteObj.fileName = documentData.document[fileId]?.fileName
+ pasteObj.fileSize = documentData.document[fileId]?.size
+ break
+ default:
+ break
+ }
+
+ // 文件确实存在才发送
+ if (pasteObj.fileName) {
+ showAgreeDialog.value = true
+ return
+ }
+ }
+
+ const delta = new Delta().retain(range.index).delete(range.length).insert(content)
+ quill.value.updateContents(delta, Quill.sources.USER)
+ quill.value.setSelection(delta.length() - range.length, Quill.sources.USER)
+}
/**
* 把输入框的字符串内容渲染成富媒体内容
@@ -413,82 +473,62 @@ const renderContent = (content) => {
quill.value.setText('')
return
}
- pasteContent = content
- const jsonContent = jsonParseSafe(content)
- if (jsonContent && jsonContent['type'] && jsonContent['value']) {
- pasteContentType = jsonContent['type']
- const fileId = jsonContent['value']
- switch (pasteContentType) {
- case msgContentType.IMAGE:
- pasteFileName = imageData.image[fileId]?.fileName
- pasteFileSize = imageData.image[fileId]?.size
- pasteUrl = imageData.image[fileId]?.thumbUrl
- break
- case msgContentType.AUDIO:
- pasteFileName = audioData.audio[fileId]?.fileName
- pasteFileSize = audioData.audio[fileId]?.size
- break
- case msgContentType.VIDEO:
- pasteFileName = videoData.video[fileId]?.fileName
- pasteFileSize = videoData.video[fileId]?.size
- break
- case msgContentType.DOCUMENT:
- pasteFileName = documentData.document[fileId]?.fileName
- pasteFileSize = documentData.document[fileId]?.size
- break
- default:
- break
- }
- showAgreeDialog.value = true
- } else {
- let contentArray = []
- //匹配内容中的图片
- content.split(/(\{.*?\})/).forEach((item) => {
- //匹配内容中的表情
- item.split(/(\[.*?\])/).forEach((item) => {
- //匹配内容中的@
- item.split(/(<.*?>)/).forEach((item) => {
- if (item) {
- contentArray.push(item)
- }
- })
+
+ let contentArray = []
+ //匹配内容中的图片
+ content.split(/(\{.*?\})/).forEach((item) => {
+ //匹配内容中的表情
+ item.split(/(\[.*?\])/).forEach((item) => {
+ //匹配内容中的@
+ item.split(/(<.*?>)/).forEach((item) => {
+ if (item) {
+ contentArray.push(item)
+ }
})
})
+ })
- // 创建一个新的 Delta 对象
- const delta = new Delta()
- contentArray.map((item) => {
- if (item.startsWith('{') && item.endsWith('}')) {
- const imageId = item.slice(1, -1)
- const imageUrl = imageData.image[imageId].originUrl
+ // 创建一个新的 Delta 对象
+ const delta = new Delta()
+ contentArray.map((item) => {
+ if (item.startsWith('{') && item.endsWith('}')) {
+ const imageId = item.slice(1, -1)
+ const imageUrl = imageData.image[imageId]?.originUrl
+ if (imageUrl) {
delta.insert({ image: imageUrl }, { alt: item })
- } else if (item.startsWith('[') && item.endsWith(']')) {
- const emojiUrl = emojis[item]
+ } else {
+ delta.insert(item)
+ }
+ } else if (item.startsWith('[') && item.endsWith(']')) {
+ const emojiUrl = emojis[item]
+ if (emojiUrl) {
delta.insert({ image: emojiUrl }, { alt: item })
- } else if (item.startsWith('<') && item.endsWith('>')) {
- const content = item.slice(1, -1)
- const index = content.indexOf('-')
- if (index !== -1) {
- const account = content.slice(0, index)
- const nickName = content.slice(index + 1)
- if (nickName) {
- toSendAtList.value.push(account)
- delta.insert({ atMention: { account, nickName } })
- } else {
- delta.insert(item)
- }
+ } else {
+ delta.insert(item)
+ }
+ } else if (item.startsWith('<') && item.endsWith('>')) {
+ const content = item.slice(1, -1)
+ const index = content.indexOf('-')
+ if (index !== -1) {
+ const account = content.slice(0, index)
+ const nickName = content.slice(index + 1)
+ if (nickName) {
+ toSendAtList.value.push(account)
+ delta.insert({ atMention: { account, nickName } })
} else {
delta.insert(item)
}
} else {
delta.insert(item)
}
- })
+ } else {
+ delta.insert(item)
+ }
+ })
- quill.value.setText('') // 清空编辑器内容
- quill.value.updateContents(delta) // 使用 Delta 对象更新编辑器内容
- quill.value.setSelection(quill.value.getLength(), 0, 'user') // 设置光标位置
- }
+ quill.value.setText('') // 清空编辑器内容
+ quill.value.updateContents(delta) // 使用 Delta 对象更新编辑器内容
+ quill.value.setSelection(quill.value.getLength(), 0, Quill.sources.USER) // 设置光标位置
}
const handleEnter = async () => {
@@ -502,8 +542,8 @@ const handleEnter = async () => {
allUploadedSuccessFn: () => {}
}
- const contentObj = pasteContent
- ? { contentFromLocal: [pasteContent], contentFromServer: [pasteContent] }
+ const contentObj = pasteObj.content
+ ? { contentFromLocal: [pasteObj.content], contentFromServer: [pasteObj.content] }
: await parseContent(callbacks)
const content = contentObj.contentFromLocal.join('').trim()
@@ -561,9 +601,10 @@ const handleEnter = async () => {
emit('sendMessage', { msg, atTargets })
}
- pasteContent = ''
- quill.value.setText('') // 编辑窗口置空
+ clearPasteObj()
toSendAtList.value = []
+ quill.value.setText('') // 编辑窗口置空
+ quill.value.setSelection(0, 0, Quill.sources.USER) // 设置光标位置
}
const options = {
@@ -600,7 +641,7 @@ const addEmoji = (key) => {
delta.retain(index)
delta.insert({ image: emojis[key] }, { alt: key })
quill.value.updateContents(delta)
- quill.value.setSelection(index + 1, 0, 'user')
+ quill.value.setSelection(index + 1, 0, Quill.sources.USER)
}
const onSelectedAtTarget = ({ account, nickName }) => {
@@ -613,18 +654,30 @@ const onSelectedAtTarget = ({ account, nickName }) => {
if (range.index >= atIndex.value) {
const delLen = range.index - atIndex.value + 1 // 删除用户输入的@符号及搜索关键字
quill.value.deleteText(atIndex.value - 1, delLen)
- quill.value.insertEmbed(atIndex.value - 1, 'atMention', { account, nickName }, 'user') // 插入Blot(占据1个位置)
- quill.value.insertText(atIndex.value, ' ', 'user') // 插入空格
- quill.value.setSelection(atIndex.value + 1, 0, 'user') // 定位光标
+ quill.value.insertEmbed(
+ atIndex.value - 1,
+ 'atMention',
+ { account, nickName },
+ Quill.sources.USER
+ ) // 插入Blot(占据1个位置)
+ quill.value.insertText(atIndex.value, ' ', Quill.sources.USER) // 插入空格
+ quill.value.setSelection(atIndex.value + 1, 0, Quill.sources.USER) // 定位光标
} else {
- quill.value.insertEmbed(range.index, 'atMention', { account, nickName }, 'user') // 插入Blot(占据1个位置)
- quill.value.insertText(range.index + 1, ' ', 'user') // 插入空格
- quill.value.setSelection(range.index + 1 + 1, 0, 'user') // 定位光标
+ quill.value.insertEmbed(range.index, 'atMention', { account, nickName }, Quill.sources.USER) // 插入Blot(占据1个位置)
+ quill.value.insertText(range.index + 1, ' ', Quill.sources.USER) // 插入空格
+ quill.value.setSelection(range.index + 1 + 1, 0, Quill.sources.USER) // 定位光标
}
}
+const reeditFromRevoke = (content) => {
+ quill.value.setText('') // 清空编辑器内容
+ quill.value.setSelection(0, 0, Quill.sources.SILENT) // 设置光标位置
+ renderContent(content)
+}
+
defineExpose({
- addEmoji
+ addEmoji,
+ reeditFromRevoke
})
@@ -647,10 +700,10 @@ defineExpose({
diff --git a/src/views/message/components/MenuMsgItem.vue b/src/views/message/components/MenuMsgItem.vue
index 1efeccd..accb19a 100644
--- a/src/views/message/components/MenuMsgItem.vue
+++ b/src/views/message/components/MenuMsgItem.vue
@@ -6,18 +6,25 @@ import DeletemsgIcon from '@/assets/svg/deletemsg.svg'
import CopyIcon from '@/assets/svg/copy.svg'
import MultiselectIcon from '@/assets/svg/multiselect.svg'
import RevokeIcon from '@/assets/svg/revoke.svg'
-import { useMenuStore } from '@/stores'
+import { useUserStore, useMenuStore } from '@/stores'
import { jsonParseSafe } from '@/js/utils/common'
-import { msgContentType } from '@/const/msgConst'
+import { MSG_REVOKE_TIME_LIMIT, msgContentType } from '@/const/msgConst'
const props = defineProps(['msg'])
const emit = defineEmits(['selectMenu'])
+const userData = useUserStore()
const menuData = useMenuStore()
+const openMenuTime = ref(null)
+
const menuName = computed(() => {
return 'MenuMsgItem-' + props.msg.msgId
})
+const myAccount = computed(() => {
+ return userData.user.account
+})
+
const contentType = computed(() => {
const contentJson = jsonParseSafe(props.msg.content)
if (!contentJson) {
@@ -37,38 +44,51 @@ const menu = computed(() => {
{
label: 'forward',
desc: '转发',
- icon: markRaw(ForwardIcon)
+ icon: markRaw(ForwardIcon),
+ index: 1
},
{
label: 'multiSelect',
desc: '多选',
- icon: markRaw(MultiselectIcon)
+ icon: markRaw(MultiselectIcon),
+ index: 2
},
{
label: 'quote',
desc: '引用',
- icon: markRaw(QuoteIcon)
- },
- {
- label: 'revoke',
- desc: '撤回',
- icon: markRaw(RevokeIcon)
+ icon: markRaw(QuoteIcon),
+ index: 3
},
{
label: 'delete',
desc: '删除',
- icon: markRaw(DeletemsgIcon)
+ icon: markRaw(DeletemsgIcon),
+ index: 5
}
]
if (contentType.value !== msgContentType.RECORDING) {
- o.unshift({
+ o.push({
label: 'copy',
desc: '复制',
- icon: markRaw(CopyIcon)
+ icon: markRaw(CopyIcon),
+ index: 0
})
}
- return o
+
+ if (
+ myAccount.value === props.msg.fromId &&
+ openMenuTime.value - new Date(props.msg.msgTime) < MSG_REVOKE_TIME_LIMIT
+ ) {
+ o.push({
+ label: 'revoke',
+ desc: '撤回',
+ icon: markRaw(RevokeIcon),
+ index: 4
+ })
+ }
+
+ return o.sort((a, b) => a.index - b.index)
})
const containerRef = ref()
@@ -106,6 +126,7 @@ const handleShowMenu = (e) => {
e.stopPropagation() // 阻止冒泡
isShowMenu.value = true
menuData.setActiveMenu(menuName.value)
+ openMenuTime.value = new Date()
nextTick(() => {
//如果发现菜单超出window.innerWidth屏幕宽度,x要修正一下,往左边弹出菜单
if (e.clientX + menuRef.value.clientWidth > window.innerWidth) {
@@ -129,6 +150,7 @@ const handleEscEvent = (event) => {
const closeMenu = () => {
isShowMenu.value = false
+ openMenuTime.value = null
}
const handleClick = (item) => {
diff --git a/src/views/message/components/MessageItem.vue b/src/views/message/components/MessageItem.vue
index ab00f50..7bfcc60 100644
--- a/src/views/message/components/MessageItem.vue
+++ b/src/views/message/components/MessageItem.vue
@@ -1,5 +1,5 @@