支持复制媒体消息

This commit is contained in:
bob
2025-04-28 14:59:59 +08:00
parent 81fdfda734
commit 0fd2a4d4cb
5 changed files with 98 additions and 12 deletions

View File

@@ -1,10 +1,15 @@
<script setup>
import { msgContentType } from '@/const/msgConst'
import MsgBoxDocument from '@/views/message/components/MsgBoxDocument.vue'
import { watch, onUnmounted } from 'vue'
const props = defineProps(['isShow', 'target', 'contentType', 'fileName', 'fileSize', 'src'])
const emit = defineEmits(['update:isShow', 'confirm'])
onUnmounted(() => {
document.removeEventListener('keydown', handleKeyPress)
})
const handleConfirm = () => {
emit('confirm')
emit('update:isShow', false)
@@ -13,6 +18,24 @@ const handleConfirm = () => {
const handleClose = () => {
emit('update:isShow', false)
}
const handleKeyPress = (event) => {
if (event.key === 'Enter' && props.isShow) {
handleConfirm()
event.preventDefault()
}
}
watch(
() => props.isShow,
(newVal) => {
if (newVal) {
document.addEventListener('keydown', handleKeyPress)
} else {
document.removeEventListener('keydown', handleKeyPress)
}
}
)
</script>
<template>

View File

@@ -3,7 +3,13 @@ import { QuillEditor, Delta, Quill } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import { computed, onMounted, onUnmounted, onBeforeUnmount, ref, watch, nextTick } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import { useMessageStore, useImageStore } from '@/stores'
import {
useMessageStore,
useImageStore,
useAudioStore,
useDocumentStore,
useVideoStore
} from '@/stores'
import { ElMessage } from 'element-plus'
import { emojis } from '@/js/utils/emojis'
import { base64ToFile, jsonParseSafe } from '@/js/utils/common'
@@ -13,6 +19,7 @@ import { getMd5 } from '@/js/utils/file'
import { prehandleImage } from '@/js/utils/image'
import { MsgType } from '@/proto/msg'
import AtList from '@/views/message/components/AtList.vue'
import AgreeBeforeSend from '@/views/message/components/AgreeBeforeSend.vue'
/**
* 处理粘贴格式问题
@@ -67,6 +74,9 @@ const props = defineProps(['sessionId', 'draft'])
const emit = defineEmits(['saveLocalMsg', 'sendMessage'])
const messageData = useMessageStore()
const imageData = useImageStore()
const audioData = useAudioStore()
const documentData = useDocumentStore()
const videoData = useVideoStore()
const inputEditorRef = ref()
const editorRef = ref()
const isShowAtList = ref(false)
@@ -75,11 +85,22 @@ const atKey = ref('')
const atListOffsetX = ref(0)
const atListOffsetY = ref(0)
const toSendAtList = ref([])
const showAgreeDialog = ref(false)
const session = computed(() => {
return messageData.sessionList[props.sessionId]
})
const remoteName = computed(() => {
if (session.value.sessionType === MsgType.CHAT) {
return session.value.objectInfo.nickName
} else if (session.value.sessionType === MsgType.GROUP_CHAT) {
return session.value.objectInfo.groupName
} else {
return ''
}
})
const quill = computed(() => {
return editorRef.value?.getQuill()
})
@@ -377,6 +398,12 @@ watch(
{ deep: true }
)
let pasteContent
let pasteContentType
let pasteFileName
let pasteFileSize
let pasteUrl
/**
* 把输入框的字符串内容渲染成富媒体内容
* @param content 字符串内容
@@ -386,14 +413,33 @@ const renderContent = (content) => {
quill.value.setText('')
return
}
pasteContent = content
const jsonContent = jsonParseSafe(content)
if (jsonContent && jsonContent['type'] && jsonContent['value']) {
// TODO 暂时直接渲染成文本
const range = quill.value.getSelection()
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)
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 = []
//匹配内容中的图片
@@ -456,7 +502,9 @@ const handleEnter = async () => {
allUploadedSuccessFn: () => {}
}
const contentObj = await parseContent(callbacks)
const contentObj = pasteContent
? { contentFromLocal: [pasteContent], contentFromServer: [pasteContent] }
: await parseContent(callbacks)
const content = contentObj.contentFromLocal.join('').trim()
if (!content) {
@@ -513,6 +561,7 @@ const handleEnter = async () => {
emit('sendMessage', { msg, atTargets })
}
pasteContent = ''
quill.value.setText('') // 编辑窗口置空
toSendAtList.value = []
}
@@ -595,6 +644,15 @@ defineExpose({
:atKey="atKey"
@selected="onSelectedAtTarget"
></AtList>
<AgreeBeforeSend
v-model:isShow="showAgreeDialog"
:target="remoteName"
:contentType="pasteContentType"
:fileName="pasteFileName"
:fileSize="pasteFileSize"
:src="pasteUrl"
@confirm="handleEnter"
></AgreeBeforeSend>
</div>
</template>

View File

@@ -129,7 +129,7 @@ const onConfirmSendFile = () => {
requestApi(requestBody, files)
.then((res) => {
if (res.data.code === 0) {
setStoreData(contentType, res.data.data)
setStoreData(res.data.data)
messageData.updateMsg(msg.sessionId, msg.msgId, {
uploadStatus: msgFileUploadStatus.UPLOAD_SUCCESS,
uploadProgress: 100

View File

@@ -178,6 +178,7 @@ const renderVideo = (content) => {
const url = videoData.video[videoId]?.downloadUrl
if (url) {
return h(MsgBoxVideo, {
msgId: msg.value.msgId,
videoId,
url,
fileName: videoData.video[videoId].fileName,

View File

@@ -5,7 +5,7 @@ import 'xgplayer/dist/index.min.css'
import { formatFileSize } from '@/js/utils/common'
import VideoloadfailedIcon from '@/assets/svg/videoloadfailed.svg'
const props = defineProps(['videoId', 'url', 'fileName', 'size', 'width', 'height'])
const props = defineProps(['msgId', 'videoId', 'url', 'fileName', 'size', 'width', 'height'])
const emits = defineEmits(['load'])
const isLoaded = ref(0) // 0未加载1加载成功2加载失败
@@ -38,7 +38,7 @@ const renderHeight = computed(() => {
onMounted(() => {
const player = new Player({
id: `msg-xgplayer-${props.videoId}`,
id: `msg-xgplayer-${props.msgId}-${props.videoId}`,
url: props.url,
fluid: true,
autoplay: false,
@@ -78,7 +78,11 @@ onMounted(() => {
:class="{ loading: isLoaded === 0 }"
:style="{ width: `${renderWidth}px`, height: `${renderHeight}px` }"
>
<div v-show="isLoaded === 1" ref="videoWrapperRef" :id="`msg-xgplayer-${props.videoId}`"></div>
<div
v-show="isLoaded === 1"
ref="videoWrapperRef"
:id="`msg-xgplayer-${props.msgId}-${props.videoId}`"
></div>
<div v-show="isLoaded === 2" class="error">
<VideoloadfailedIcon style="width: 48px; height: 48px; fill: #fff" />
<span style="color: #fff">视频加载失败</span>