chat-pc/src/views/message/inner/panel/PanelFooter.vue
Phoenix e2e0a3ea3a fix: 修复文件上传逻辑和UI问题
- 修复文件上传暂停/恢复逻辑错误,调整播放状态与上传动作的对应关系
- 为视频上传添加半透明蒙层提升用户体验
- 移除上传管理中的冗余字段和注释代码
- 调整确认框标题的padding样式
- 添加消息重发确认功能
2025-05-26 16:43:11 +08:00

301 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import {
useTalkStore,
useDialogueStore,
useSettingsStore,
useUploadsStore,
useEditorStore,
useUserStore
} from '@/store'
import ws from '@/connect'
import { ServePublishMessage, ServeSendVote } from '@/api/chat'
import { throttle, getVideoImage } from '@/utils/common'
import { parseTime } from '@/utils/datetime'
import Editor from '@/components/editor/Editor.vue'
import MultiSelectFooter from './MultiSelectFooter.vue'
import HistoryRecord from '@/components/talk/HistoryRecord.vue'
import { uploadImg } from '@/api/upload'
const userStore = useUserStore()
const talkStore = useTalkStore()
const editorStore = useEditorStore()
const settingsStore = useSettingsStore()
const uploadsStore = useUploadsStore()
const dialogueStore = useDialogueStore()
const props = defineProps({
uid: {
type: Number,
default: 0
},
talk_type: {
type: Number,
default: 0
},
receiver_id: {
type: Number,
default: 0
},
index_name: {
type: String,
default: ''
},
online: {
type: Boolean,
default: false
},
members: {
default: () => []
}
})
const isShowHistory = ref(false)
const onSendMessage = (data = {}, callBack: any) => {
let message = {
...data,
receiver: {
receiver_id: props.receiver_id,
talk_type: props.talk_type
}
}
ServePublishMessage(message)
.then(({ code, message }) => {
if (code == 200) {
callBack(true)
} else {
window['$message'].warning(message)
}
})
.catch(() => {
window['$message'].warning('网络繁忙,请稍后重试!')
})
}
// 发送文本消息
const onSendTextEvent = throttle((value: any) => {
let { data, callBack } = value
let message = {
type: 'text',
content: data.items[0].content,
quote_id: data.quoteId,
mentions: data.mentionUids
}
onSendMessage(message, (ok: boolean) => {
if (!ok) return
let el = document.getElementById('talk-session-list')
el?.scrollTo({ top: 0, behavior: 'smooth' })
callBack(true)
})
}, 1000)
// 发送图片消息
const onSendImageEvent = ({ data, callBack }) => {
onSendMessage({ type: 'image', ...data }, callBack)
}
// 发送视频消息
const onSendVideoEvent = async ({ data }) => {
console.log('onSendVideoEvent')
// 获取视频首帧作为封面图
// let resp = await getVideoImage(data)
// 先创建一个带有上传ID的临时消息对象用于显示进度
const uploadId = `video-${Date.now()}-${Math.floor(Math.random() * 1000)}`
// 创建临时消息记录
const tempMessage = {
msg_id: uploadId,
sequence: Date.now(),
talk_type: props.talk_type,
msg_type: 5, // 视频消息类型
user_id: props.uid,
receiver_id: props.receiver_id,
is_revoke: 0,
is_mark: 0,
is_read: 1,
content: '',
created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'),
extra: {
url: '',
size: data.size,
is_uploading: true,
upload_id: uploadId,
percentage: 0
},
isCheck: false,
send_status: 1,
float: 'right' // 我发送的消息显示在右侧
}
// 直接添加到对话记录中
dialogueStore.addDialogueRecord(tempMessage)
uploadsStore.initUploadFile(
data,
props.talk_type,
props.receiver_id,
uploadId,
async (percentage) => {
dialogueStore.updateUploadProgress(uploadId, percentage)
},
async () => {
dialogueStore.batchDelDialogueRecord([uploadId])
}
)
}
// 发送代码消息
const onSendCodeEvent = ({ data, callBack }) => {
onSendMessage({ type: 'code', code: data.code, lang: data.lang }, callBack)
}
// 发送文件消息
const onSendFileEvent = ({ data }) => {
let maxsize = 200 * 1024 * 1024
if (data.size > maxsize) {
return window['$message'].warning('上传文件不能超过100M!')
}
const clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}`
const tempMessage = {
msg_id: clientUploadId,
sequence: Date.now(),
talk_type: props.talk_type,
msg_type: 6,
user_id: props.uid,
receiver_id: props.receiver_id,
nickname: dialogueStore.talk.username,
avatar: userStore.avatar,
is_revoke: 0,
is_read: 0,
created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'),
extra: {
name: data.name,
url: '',
size: data.size,
is_uploading: true,
upload_id: clientUploadId,
percentage: 0
},
float: 'right'
}
dialogueStore.addDialogueRecord(tempMessage)
uploadsStore.initUploadFile(data, props.talk_type, props.receiver_id,clientUploadId,
async (percentage) => {
dialogueStore.updateUploadProgress(clientUploadId, percentage)
},
async () => {
dialogueStore.batchDelDialogueRecord([clientUploadId])
}
)
}
// 发送投票消息
const onSendVoteEvent = ({ data, callBack }) => {
let response = ServeSendVote({
receiver_id: props.receiver_id,
mode: data.mode,
anonymous: data.anonymous,
title: data.title,
options: data.options
})
response.then(({ code, message }) => {
if (code == 200) {
callBack(true)
} else {
window['$message'].warning(message)
}
})
response.catch(() => callBack(false))
}
// 发送表情消息
const onSendEmoticonEvent = ({ data, callBack }) => {
onSendMessage({ type: 'emoticon', emoticon_id: data }, callBack)
}
const onSendMixedEvent = ({ data, callBack }) => {
let message = {
type: 'mixed',
quote_id: data.quoteId,
items: data.items
}
onSendMessage(message, callBack)
}
const onKeyboardPush = throttle(() => {
ws.emit('im.message.keyboard', {
sender_id: props.uid,
receiver_id: props.receiver_id
})
}, 3000)
// 编辑器输入事件
const onInputEvent = ({ data }) => {
talkStore.updateItem({
index_name: props.index_name,
draft_text: data
})
// 判断对方是否在线和是否需要推送
// 3秒时间内推送一次
if (settingsStore.isKeyboard && props.online) {
onKeyboardPush()
}
}
// 注册事件
const evnets = {
text_event: onSendTextEvent,
image_event: onSendImageEvent,
video_event: onSendVideoEvent,
code_event: onSendCodeEvent,
file_event: onSendFileEvent,
input_event: onInputEvent,
vote_event: onSendVoteEvent,
emoticon_event: onSendEmoticonEvent,
history_event: () => {
isShowHistory.value = true
},
mixed_event: onSendMixedEvent
}
// 编辑器事件
const onEditorEvent = (msg: any) => {
evnets[msg.event] && evnets[msg.event](msg)
}
onMounted(() => {
editorStore.loadUserEmoticon()
})
</script>
<template>
<footer class="el-footer">
<MultiSelectFooter v-if="dialogueStore.isOpenMultiSelect" />
<Editor v-else @editor-event="onEditorEvent" :vote="talk_type == 2" :members="members" />
</footer>
<HistoryRecord
v-if="isShowHistory"
:talk-type="talk_type"
:receiver-id="receiver_id"
@close="isShowHistory = false"
/>
</template>
<style lang="less">
.el-footer {
height: inherit;
}
</style>