fix: 修复消息转发、上传和编辑器引用删除功能
- 添加ChatMsgTypeForward到可转发消息类型 - 修复请求拦截器中状态码判断逻辑 - 优化视频消息上传封面获取和预览显示 - 修复上传分片错误处理和进度更新 - 重构编辑器引用删除逻辑,提升代码可维护性 - 调整图片消息样式和上传蒙版显示
This commit is contained in:
parent
57e4ba69d9
commit
45e4415cec
@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted, onBeforeUnmount, nextTick, markRaw, watch } from 'vue'
|
import { ref, computed, onMounted, onBeforeUnmount, nextTick, markRaw, watch } from 'vue'
|
||||||
import { NPopover, NIcon } from 'naive-ui'
|
import { NPopover, NIcon } from 'naive-ui'
|
||||||
import {
|
import {
|
||||||
SmilingFace,
|
SmilingFace,
|
||||||
@ -1063,6 +1063,54 @@ const onSubscribeMention = async (data) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 在组件顶层作用域定义handleDeleteQuote函数
|
||||||
|
const handleDeleteQuote = function(e) {
|
||||||
|
// 如果不是删除键或退格键,直接返回
|
||||||
|
if (e.key !== 'Backspace' && e.key !== 'Delete') return;
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection.rangeCount === 0) return;
|
||||||
|
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
const editor = editorRef.value;
|
||||||
|
if (!editor) return;
|
||||||
|
|
||||||
|
const quoteElement = editor.querySelector('.editor-quote');
|
||||||
|
|
||||||
|
if (!quoteElement) {
|
||||||
|
// 如果没有引用元素,移除事件监听器
|
||||||
|
editor.removeEventListener('keydown', handleDeleteQuote);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取引用元素在编辑器子节点中的索引
|
||||||
|
const quoteIndex = Array.from(editor.childNodes).indexOf(quoteElement);
|
||||||
|
|
||||||
|
// 检查是否在引用元素前按下退格键
|
||||||
|
const isBeforeQuote = e.key === 'Backspace' &&
|
||||||
|
range.collapsed &&
|
||||||
|
range.startContainer === editor &&
|
||||||
|
quoteIndex === range.startOffset;
|
||||||
|
|
||||||
|
// 检查是否在引用元素后按下删除键
|
||||||
|
const isAfterQuote = e.key === 'Delete' &&
|
||||||
|
range.collapsed &&
|
||||||
|
range.startContainer === editor &&
|
||||||
|
quoteIndex === range.startOffset - 1;
|
||||||
|
|
||||||
|
if (isBeforeQuote || isAfterQuote) {
|
||||||
|
// 阻止默认行为
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 移除引用元素
|
||||||
|
quoteElement.remove();
|
||||||
|
quoteData.value = null;
|
||||||
|
|
||||||
|
// 触发输入事件更新编辑器内容
|
||||||
|
handleInput({ target: editor });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onSubscribeQuote = (data) => {
|
const onSubscribeQuote = (data) => {
|
||||||
|
|
||||||
quoteData.value = data
|
quoteData.value = data
|
||||||
@ -1135,58 +1183,8 @@ const onSubscribeQuote = (data) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 使用顶层作用域定义的handleDeleteQuote函数
|
||||||
|
editor.addEventListener('keydown', handleDeleteQuote);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleDeleteQuote = function(e) {
|
|
||||||
|
|
||||||
if (e.key !== 'Backspace' && e.key !== 'Delete') return;
|
|
||||||
|
|
||||||
const selection = window.getSelection();
|
|
||||||
if (selection.rangeCount === 0) return;
|
|
||||||
|
|
||||||
const range = selection.getRangeAt(0);
|
|
||||||
const quoteElement = editor.querySelector('.editor-quote');
|
|
||||||
|
|
||||||
if (!quoteElement) {
|
|
||||||
|
|
||||||
editor.removeEventListener('keydown', handleDeleteQuote);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const quoteIndex = Array.from(editor.childNodes).indexOf(quoteElement);
|
|
||||||
|
|
||||||
|
|
||||||
const isBeforeQuote = e.key === 'Backspace' &&
|
|
||||||
range.collapsed &&
|
|
||||||
range.startContainer === editor &&
|
|
||||||
quoteIndex === range.startOffset;
|
|
||||||
|
|
||||||
const isAfterQuote = e.key === 'Delete' &&
|
|
||||||
range.collapsed &&
|
|
||||||
range.startContainer === editor &&
|
|
||||||
quoteIndex === range.startOffset - 1;
|
|
||||||
|
|
||||||
if (isBeforeQuote || isAfterQuote) {
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
|
|
||||||
quoteElement.remove();
|
|
||||||
quoteData.value = null;
|
|
||||||
|
|
||||||
|
|
||||||
handleInput({ target: editor });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.addEventListener('keydown', handleDeleteQuote);
|
|
||||||
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -1955,7 +1953,6 @@ const handleEditorClick = (event) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表情样式
|
* 表情样式
|
||||||
|
@ -38,7 +38,7 @@ const img = (src: string, width = 200) => {
|
|||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<n-image class="h-149px" :src="extra.url" />
|
<n-image class="h-149px" :src="extra.url" />
|
||||||
<!-- 上传中的loading蒙版 -->
|
<!-- 上传中的loading蒙版 -->
|
||||||
<div v-if="props.extra.is_uploading" class="loading-overlay">
|
<div v-if="extra.is_uploading" class="loading-overlay">
|
||||||
<n-spin size="large" />
|
<n-spin size="large" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -53,7 +53,7 @@ const img = (src: string, width = 200) => {
|
|||||||
height:149px;
|
height:149px;
|
||||||
|
|
||||||
&.left {
|
&.left {
|
||||||
background: var(--im-message-right-bg-color);
|
background: #F4F4FC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import 'xgplayer/dist/index.min.css'
|
import 'xgplayer/dist/index.min.css'
|
||||||
import { ref, nextTick, watch } from 'vue'
|
import { ref, nextTick, watch, computed } from 'vue'
|
||||||
import { NImage, NModal, NCard, NProgress, NPopconfirm } from 'naive-ui'
|
import { NImage, NModal, NCard, NProgress, NPopconfirm } from 'naive-ui'
|
||||||
import { Play, Close, Pause, Right, Attention } from '@icon-park/vue-next'
|
import { Play, Close, Pause, Right, Attention } from '@icon-park/vue-next'
|
||||||
import { getImageInfo } from '@/utils/functions'
|
import { getImageInfo } from '@/utils/functions'
|
||||||
@ -64,6 +64,11 @@ const updatePauseStatus = () => {
|
|||||||
// 初始化时检查状态
|
// 初始化时检查状态
|
||||||
updatePauseStatus()
|
updatePauseStatus()
|
||||||
|
|
||||||
|
// 创建视频封面的URL
|
||||||
|
const videoSrc = computed(() => {
|
||||||
|
// 即使在上传过程中也返回视频URL,这样可以显示视频封面
|
||||||
|
return props.extra.url || ''
|
||||||
|
})
|
||||||
// // 监听关键道具变化
|
// // 监听关键道具变化
|
||||||
// watch(() => props.extra.percentage, (newVal: number | undefined) => {
|
// watch(() => props.extra.percentage, (newVal: number | undefined) => {
|
||||||
// // 确保进度更新时 UI 也实时更新
|
// // 确保进度更新时 UI 也实时更新
|
||||||
@ -136,7 +141,7 @@ function resumeUpload(e) {
|
|||||||
>
|
>
|
||||||
|
|
||||||
<!-- <n-image :src="extra.cover" preview-disabled /> -->
|
<!-- <n-image :src="extra.cover" preview-disabled /> -->
|
||||||
<video :src="props.extra.url" :controls="false"></video>
|
<video :src="videoSrc" :controls="false"></video>
|
||||||
<!-- 上传进度时的黑色半透明蒙层 -->
|
<!-- 上传进度时的黑色半透明蒙层 -->
|
||||||
<div v-if="extra.is_uploading && !uploadFailed" class="upload-mask"></div>
|
<div v-if="extra.is_uploading && !uploadFailed" class="upload-mask"></div>
|
||||||
<!-- 上传进度显示 -->
|
<!-- 上传进度显示 -->
|
||||||
@ -252,7 +257,7 @@ function resumeUpload(e) {
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.45);
|
background: rgba(0, 0, 0, 0.3); /* 降低不透明度,从0.45改为0.3,让视频封面能够显示 */
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ export const MessageComponents = {
|
|||||||
|
|
||||||
// 可转发的消息类型
|
// 可转发的消息类型
|
||||||
export const ForwardableMessageType = [
|
export const ForwardableMessageType = [
|
||||||
|
ChatMsgTypeForward,
|
||||||
ChatMsgTypeText,
|
ChatMsgTypeText,
|
||||||
ChatMsgTypeCode,
|
ChatMsgTypeCode,
|
||||||
ChatMsgTypeImage,
|
ChatMsgTypeImage,
|
||||||
|
@ -170,14 +170,14 @@ export const useUploadsStore = defineStore('uploads', {
|
|||||||
|
|
||||||
// 更新状态为上传中
|
// 更新状态为上传中
|
||||||
currentItem.status = 1
|
currentItem.status = 1
|
||||||
|
const updatedItem:any = this.findItem(uploadId)
|
||||||
// 上传当前分片
|
// 上传当前分片
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const res = await ServeFileSubareaUpload(form)
|
const res = await ServeFileSubareaUpload(form)
|
||||||
|
|
||||||
// 获取最新的项目状态,确保仍然存在且没有被暂停
|
// 获取最新的项目状态,确保仍然存在且没有被暂停
|
||||||
const updatedItem:any = this.findItem(uploadId)
|
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
// 当前分片上传成功,增加索引
|
// 当前分片上传成功,增加索引
|
||||||
updatedItem.uploadIndex++
|
updatedItem.uploadIndex++
|
||||||
@ -209,10 +209,12 @@ export const useUploadsStore = defineStore('uploads', {
|
|||||||
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
updatedItem.onProgress(-1)
|
||||||
console.error("分片上传错误:", error);
|
console.error("分片上传错误:", error);
|
||||||
|
|
||||||
// 获取最新的项目状态
|
// 获取最新的项目状态
|
||||||
const updatedItem = this.findItem(uploadId)
|
// 这里不应该重新定义变量,而是使用已有的updatedItem
|
||||||
|
// const updatedItem = this.findItem(uploadId)
|
||||||
if (!updatedItem) return
|
if (!updatedItem) return
|
||||||
|
|
||||||
// 如果是暂停导致的错误,不改变状态
|
// 如果是暂停导致的错误,不改变状态
|
||||||
|
@ -54,7 +54,8 @@ request.interceptors.request.use((config) => {
|
|||||||
|
|
||||||
// 响应拦截器
|
// 响应拦截器
|
||||||
request.interceptors.response.use((response) => {
|
request.interceptors.response.use((response) => {
|
||||||
if(response.data.code !==200){
|
console.log('response.data.status',response.data.status)
|
||||||
|
if(response.data.code !==200&&response.data.status!==0){
|
||||||
window['$message'].warning(response.data.msg)
|
window['$message'].warning(response.data.msg)
|
||||||
}
|
}
|
||||||
return response.data
|
return response.data
|
||||||
|
@ -330,6 +330,7 @@ const onContextMenuHandle = (key: string) => {
|
|||||||
|
|
||||||
const onRowClick = (item: ITalkRecord) => {
|
const onRowClick = (item: ITalkRecord) => {
|
||||||
if (dialogueStore.isOpenMultiSelect) {
|
if (dialogueStore.isOpenMultiSelect) {
|
||||||
|
console.log('item.msg_type',item.msg_type)
|
||||||
if (ForwardableMessageType.includes(item.msg_type)) {
|
if (ForwardableMessageType.includes(item.msg_type)) {
|
||||||
item.isCheck = !item.isCheck
|
item.isCheck = !item.isCheck
|
||||||
} else {
|
} else {
|
||||||
|
@ -101,10 +101,13 @@ const onSendImageEvent = ({ data, callBack }) => {
|
|||||||
|
|
||||||
// 发送视频消息
|
// 发送视频消息
|
||||||
const onSendVideoEvent = async ({ data }) => {
|
const onSendVideoEvent = async ({ data }) => {
|
||||||
|
|
||||||
|
|
||||||
// 获取视频首帧作为封面图
|
// 获取视频首帧作为封面图
|
||||||
// let resp = await getVideoImage(data)
|
let videoPreview = null
|
||||||
|
try {
|
||||||
|
videoPreview = await getVideoImage(data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取视频封面失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
// 先创建一个带有上传ID的临时消息对象,用于显示进度
|
// 先创建一个带有上传ID的临时消息对象,用于显示进度
|
||||||
const uploadId = `video-${Date.now()}-${Math.floor(Math.random() * 1000)}`
|
const uploadId = `video-${Date.now()}-${Math.floor(Math.random() * 1000)}`
|
||||||
@ -123,7 +126,7 @@ const onSendVideoEvent = async ({ data }) => {
|
|||||||
content: '',
|
content: '',
|
||||||
created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'),
|
created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'),
|
||||||
extra: {
|
extra: {
|
||||||
url: '',
|
url: videoPreview ? URL.createObjectURL(data) : '', // 使用本地视频URL作为预览
|
||||||
size: data.size,
|
size: data.size,
|
||||||
is_uploading: true,
|
is_uploading: true,
|
||||||
upload_id: uploadId,
|
upload_id: uploadId,
|
||||||
|
Loading…
Reference in New Issue
Block a user