2024-12-24 08:14:21 +00:00
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
import { ref, onMounted } from 'vue'
|
|
|
|
|
import {
|
|
|
|
|
useTalkStore,
|
|
|
|
|
useDialogueStore,
|
|
|
|
|
useSettingsStore,
|
|
|
|
|
useUploadsStore,
|
2025-05-14 03:50:52 +00:00
|
|
|
|
useEditorStore,
|
|
|
|
|
useUserStore
|
2024-12-24 08:14:21 +00:00
|
|
|
|
} from '@/store'
|
|
|
|
|
import ws from '@/connect'
|
|
|
|
|
import { ServePublishMessage, ServeSendVote } from '@/api/chat'
|
|
|
|
|
import { throttle, getVideoImage } from '@/utils/common'
|
2025-05-14 03:50:52 +00:00
|
|
|
|
import { parseTime } from '@/utils/datetime'
|
2024-12-24 08:14:21 +00:00
|
|
|
|
import Editor from '@/components/editor/Editor.vue'
|
|
|
|
|
import MultiSelectFooter from './MultiSelectFooter.vue'
|
|
|
|
|
import HistoryRecord from '@/components/talk/HistoryRecord.vue'
|
|
|
|
|
import { uploadImg } from '@/api/upload'
|
2025-05-14 03:50:52 +00:00
|
|
|
|
const userStore = useUserStore()
|
2024-12-24 08:14:21 +00:00
|
|
|
|
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 }) => {
|
2025-05-14 03:50:52 +00:00
|
|
|
|
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: {
|
2025-05-26 08:43:11 +00:00
|
|
|
|
url: '',
|
2025-05-14 03:50:52 +00:00
|
|
|
|
size: data.size,
|
|
|
|
|
is_uploading: true,
|
|
|
|
|
upload_id: uploadId,
|
|
|
|
|
percentage: 0
|
|
|
|
|
},
|
|
|
|
|
isCheck: false,
|
|
|
|
|
send_status: 1,
|
|
|
|
|
float: 'right' // 我发送的消息显示在右侧
|
2024-12-24 08:14:21 +00:00
|
|
|
|
}
|
2025-05-14 03:50:52 +00:00
|
|
|
|
|
|
|
|
|
// 直接添加到对话记录中
|
|
|
|
|
dialogueStore.addDialogueRecord(tempMessage)
|
|
|
|
|
uploadsStore.initUploadFile(
|
|
|
|
|
data,
|
|
|
|
|
props.talk_type,
|
|
|
|
|
props.receiver_id,
|
|
|
|
|
uploadId,
|
|
|
|
|
async (percentage) => {
|
|
|
|
|
dialogueStore.updateUploadProgress(uploadId, percentage)
|
|
|
|
|
},
|
2025-05-15 08:07:56 +00:00
|
|
|
|
async () => {
|
|
|
|
|
dialogueStore.batchDelDialogueRecord([uploadId])
|
2025-05-14 03:50:52 +00:00
|
|
|
|
}
|
|
|
|
|
)
|
2024-12-24 08:14:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发送代码消息
|
|
|
|
|
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!')
|
|
|
|
|
}
|
2025-05-26 08:43:11 +00:00
|
|
|
|
const clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}`
|
2025-05-14 03:50:52 +00:00
|
|
|
|
|
|
|
|
|
const tempMessage = {
|
2025-05-26 08:43:11 +00:00
|
|
|
|
msg_id: clientUploadId,
|
2025-05-14 03:50:52 +00:00
|
|
|
|
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,
|
2025-05-26 08:43:11 +00:00
|
|
|
|
upload_id: clientUploadId,
|
2025-05-14 03:50:52 +00:00
|
|
|
|
percentage: 0
|
|
|
|
|
},
|
|
|
|
|
float: 'right'
|
|
|
|
|
}
|
|
|
|
|
dialogueStore.addDialogueRecord(tempMessage)
|
|
|
|
|
|
2025-05-26 08:43:11 +00:00
|
|
|
|
uploadsStore.initUploadFile(data, props.talk_type, props.receiver_id,clientUploadId,
|
2025-05-14 03:50:52 +00:00
|
|
|
|
async (percentage) => {
|
2025-05-26 08:43:11 +00:00
|
|
|
|
dialogueStore.updateUploadProgress(clientUploadId, percentage)
|
2025-05-14 03:50:52 +00:00
|
|
|
|
},
|
2025-05-15 08:07:56 +00:00
|
|
|
|
async () => {
|
2025-05-26 08:43:11 +00:00
|
|
|
|
dialogueStore.batchDelDialogueRecord([clientUploadId])
|
2025-05-14 03:50:52 +00:00
|
|
|
|
}
|
|
|
|
|
)
|
2024-12-24 08:14:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发送投票消息
|
|
|
|
|
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>
|