2024-04-28 22:37:48 +08:00

469 lines
14 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 setup>
import { ElLoading, ElMessageBox } from 'element-plus'
import { emojis } from '@/constant/im'
import { messageType } from '@/constant/im'
import _ from 'lodash'
import { onClickOutside } from '@vueuse/core'
/* 组件 */
import PreviewSendImg from '../suit/previewSendImg.vue'
import VueAt from 'vue-at/dist/vue-at-textarea' // for textarea
const props = defineProps({
nowPickInfo: {
type: Object,
required: true,
default: () => ({})
}
})
const { ALL_MESSAGE_TYPE, CHAT_TYPE, MENTION_ALL } = messageType
const { nowPickInfo } = toRefs(props)
const atMembersList = ref([])
//附件类上传加载状态
const loadingBox = ref(null)
const isAtAll = ref(false)
const atMembers = ref([])
//输入框插入@事件
const onInsert = (target) => {
// if (!) return false
console.log('onInset', target)
if (_.map(atMembers.value, 'value').includes(target.value)) return false
if (target.value === MENTION_ALL.VALUE) {
return (isAtAll.value = true)
} else {
atMembers.value.push({ ...target })
}
}
//校验消息内容中是否包含要@的成员
const checkAtMembers = (text) => {
if (!text) {
return false
}
//判断是否文本中是否有@ALL没有则直接设置为false
const patternAtAll = new RegExp(`@${MENTION_ALL.TEXT}`)
console.log('patternAtAll', patternAtAll)
if (isAtAll.value && !patternAtAll.test(text)) {
isAtAll.value = false
}
if (atMembers.value.length !== 0) {
//循环AT成员数组通过匹配文本内容判断是否存在已经移除@成员
_.map(atMembers.value, 'text').forEach((item, index) => {
console.log('atMembers item', item, index)
const pattern = new RegExp(`@${item}`)
const result = pattern.test(text)
if (!result) {
console.log('文本中不满足条件')
//不包含则从@列表中移除该成员
atMembers.value.splice(index, 1)
console.log('>>>>>已删除', atMembers.value)
}
})
}
}
//emojis框展开
const isShowEmojisBox = ref(false)
const emojisBox = ref(null)
onClickOutside(emojisBox, (event) => {
console.log('>>>>>关闭模态框')
isShowEmojisBox.value = false
event.stopPropagation()
})
const showEmojisBox = () => {
console.log('>>>>>展开模态框')
if (!isShowEmojisBox.value) {
isShowEmojisBox.value = true
}
}
//新增一个emoji
const addOneEmoji = (emoji) => {
console.log('>>>>>>emoji', emoji)
textContent.value = textContent.value + emoji
}
//消息引用
const messageQuoteRef = ref(null)
const handleQuoteMessage = (msgBody) => {
messageQuoteRef.value && messageQuoteRef.value.setQuoteContent(msgBody)
}
//换行操作
const insertNewLine = () => (textContent.value += '\n')
//发送文本内容
const textContent = ref('')
const sendTextMessage = _.debounce(async () => {
//如果输入框全部为空格同样拒绝发送
if (textContent.value.match(/^\s*$/)) return
console.log('atMembers.value', atMembers.value)
checkAtMembers(textContent.value)
const msgOptions = {
id: nowPickInfo.value.id,
chatType: nowPickInfo.value.chatType,
msg: textContent.value,
ext: {
em_at_list: isAtAll.value ? MENTION_ALL.VALUE : _.map(atMembers.value, 'value')
}
}
//关闭引用框
if (messageQuoteRef.value?.isShowQuoteMsgBox) {
}
textContent.value = ''
messageQuoteRef.value?.clearQuoteContent()
try {
console.log('msgOptions', msgOptions)
// await store.dispatch('sendShowTypeMessage', {
// msgType: ALL_MESSAGE_TYPE.TEXT,
// msgOptions
// })
} catch (error) {
//handleSDKErrorNotifi(error.type, error.message)
console.log('>>>>>>>发送失败+++++++', error)
} finally {
isAtAll.value = false
atMembers.value = []
}
}, 50)
//监听键盘按下事件如果为enter键则发送文本内容,shift+enter则换行。
const onTextInputKeyDown = (event) => {
if (event.keyCode === 13 && !event.shiftKey) {
event.preventDefault()
// 执行发送操作
sendTextMessage()
} else if (event.keyCode === 13 && event.shiftKey) {
// 换行操作
insertNewLine()
}
}
/* 图片消息相关 */
//选择图片
const uploadImgs = ref(null)
const chooseImages = () => {
uploadImgs.value.click()
console.log('uploadImgs')
}
//发送图片
const sendImagesMessage = async (type) => {
const file = {
data: null, // file 对象。
filename: '', //文件名称。
filetype: '' //文件类型。
}
const url = window.URL || window.webkitURL
const img = new Image() //手动创建一个Image对象
const msgOptions = {
id: nowPickInfo.value.id,
chatType: nowPickInfo.value.chatType,
file: file,
width: 0,
height: 0
}
if (type === 'common') {
//读取图片的宽高
const imgFile = uploadImgs.value.files[0]
file.data = imgFile
file.filename = imgFile.name
file.filetype = imgFile.type
console.log('imgFile', file)
img.src = url.createObjectURL(imgFile) //创建Image的对象的url
img.onload = async () => {
const loadingInstance = ElLoading.service({
target: loadingBox.value,
background: '#f7f7f7'
})
msgOptions.width = img.width
msgOptions.height = img.height
console.log('height:' + img.height + '----' + img.width)
try {
// await store.dispatch('sendShowTypeMessage', {
// msgType: ALL_MESSAGE_TYPE.IMAGE,
// msgOptions: _.cloneDeep(msgOptions)
// })
loadingInstance.close()
uploadImgs.value.value = null
} catch (error) {
console.log('>>>>>发送失败', error)
if (error.type && error?.data) {
handleSDKErrorNotifi(error.type, error.data.error || 'none')
} else {
handleSDKErrorNotifi(0, 'none')
}
loadingInstance.close()
uploadImgs.value.value = null
}
}
} else if (type === 'other') {
console.log('fileObjfileObjfileObj', fileObj)
const imgFile = fileObj
file.data = imgFile
file.filename = imgFile.name
file.filetype = imgFile.type
console.log('imgFile', file)
img.src = url.createObjectURL(imgFile) //创建Image的对象的url
img.onload = async () => {
const loadingInstance = ElLoading.service({
target: loadingBox.value,
background: '#f7f7f7'
})
msgOptions.width = img.width
msgOptions.height = img.height
console.log('height:' + img.height + '----' + img.width)
try {
await store.dispatch('sendShowTypeMessage', {
msgType: ALL_MESSAGE_TYPE.IMAGE,
msgOptions: _.cloneDeep(msgOptions)
})
loadingInstance.close()
uploadImgs.value.value = null
} catch (error) {
console.log('>>>>>发送失败', error)
if (error.type && error?.data) {
handleSDKErrorNotifi(error.type, error.data.error || 'none')
} else {
handleSDKErrorNotifi(0, 'none')
}
loadingInstance.close()
uploadImgs.value.value = null
}
}
}
}
//贴图发送
const previewSendImg = ref(null)
const onPasteImage = (event) => {
console.log('>>>>>>监听粘贴事件', event)
const data = event.clipboardData || window.clipboardData
//获取图片内容
const imgContent = data.items[0].getAsFile()
//判断是不是图片,最好通过文件类型判断
const isImg = (imgContent && 1) || -1
const reader = new FileReader()
if (isImg >= 0) {
//将文件读取为 DataURL
reader.readAsDataURL(imgContent)
}
//文件读取完成时触发
reader.onload = (event) => {
//获取base64流
const base64_str = event.target.result
const imgInfo = {
imgFile: imgContent,
tempFilePath: base64_str
}
previewSendImg.value.showPreviewImgModal({ ...imgInfo })
console.log('>>>>>获取到粘贴到的文本', imgInfo)
}
}
/* 文件消息相关 */
//选择文件
const uploadFiles = ref(null)
const chooseFiles = () => {
uploadFiles.value.click()
}
//发送文件
const sendFilesMessages = async () => {
const commonFile = uploadFiles.value.files[0]
const file = {
data: commonFile, // file 对象。
filename: commonFile.name, //文件名称。
filetype: commonFile.type, //文件类型。
size: commonFile.size
}
console.log('>>>>>调用发送文件', file)
const msgOptions = {
id: nowPickInfo.value.id,
chatType: nowPickInfo.value.chatType,
file: file
}
const loadingInstance = ElLoading.service({
target: loadingBox.value,
background: '#f7f7f7'
})
try {
// await store.dispatch('sendShowTypeMessage', {
// msgType: ALL_MESSAGE_TYPE.FILE,
// msgOptions: _.cloneDeep(msgOptions)
// })
loadingInstance.close()
uploadFiles.value.value = null
} catch (error) {
console.log('>>>>file error', error)
if (error.type && error?.data) {
handleSDKErrorNotifi(error.type, error.data.error || 'none')
} else {
handleSDKErrorNotifi(0, 'none')
}
uploadFiles.value.value = null
loadingInstance.close()
}
}
/* 语音消息相关 */
//展示录音对话框
const isHttps = window.location.protocol === 'https:' || window.location.hostname === 'localhost'
const isShowRecordBox = ref(false)
const recordBox = ref(null)
onClickOutside(recordBox, () => {
isShowRecordBox.value = false
})
const showRecordBox = () => {
isShowRecordBox.value = true
}
const sendAudioMessages = async (audioData) => {
const file = {
// url: EaseChatSDK.utils.parseDownloadResponse(audioData.src),
filename: '录音',
filetype: '.amr',
data: audioData.src
}
console.log('>>>>>audioData', audioData, file)
const msgOptions = {
id: nowPickInfo.value.id,
chatType: nowPickInfo.value.chatType,
file: file,
length: audioData.length
}
try {
// await store.dispatch('sendShowTypeMessage', {
// msgType: ALL_MESSAGE_TYPE.AUDIO,
// msgOptions: _.cloneDeep(msgOptions)
// })
isShowRecordBox.value = false
} catch (error) {
// if (error.type && error?.data) {
// handleSDKErrorNotifi(error.type, error.data.error || 'none')
// } else {
// handleSDKErrorNotifi(0, 'none')
// }
isShowRecordBox.value = false
}
}
/*清除屏幕*/
const clearScreen = () => {
ElMessageBox.confirm('确认清空当前消息内容?', '消息清屏', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
const key = nowPickInfo.value.id
// store.commit('CLEAR_SOMEONE_MESSAGE', key)
})
.catch(() => {
return false
})
}
//func 对应事件 icon class样式等
const all_func = [
{
className: 'icon-emoji',
style: 'font-size:20px;margin-left: 20px;',
title: '选择表情',
methodName: showEmojisBox
},
{
className: 'icon-tuku',
style: 'font-size: 26px;',
title: '发送图片',
methodName: chooseImages
},
{
className: 'icon-wenjian',
style: 'font-size: 20px;',
title: '发送文件',
methodName: chooseFiles
},
{
className: 'icon-01',
style: 'font-size: 20px;',
title: '发送语音',
methodName: showRecordBox
},
{
className: 'icon-lajitong',
style: 'font-size: 23px;',
title: '清屏',
methodName: clearScreen
}
]
defineExpose({
textContent,
handleQuoteMessage
})
</script>
<template>
<div class="chat_func_box">
<span
v-for="iconItem in all_func"
:class="['iconfont', iconItem.className]"
:key="iconItem.className"
:style="iconItem.style"
:title="iconItem.title"
@click.stop="iconItem.methodName"
></span>
<!-- 表情框 -->
<el-scrollbar ref="emojisBox" v-if="isShowEmojisBox" class="emojis_box" tag="div">
<span
class="emoji"
v-for="(emoji, index) in emojis"
:key="index"
@click="addOneEmoji(emoji)"
>{{ emoji }}</span
>
</el-scrollbar>
<!-- 图片附件choose -->
<input
ref="uploadImgs"
type="file"
style="display: none"
@change="sendImagesMessage('common')"
accept="image/*"
/>
<!-- 文件附件choose -->
<input ref="uploadFiles" type="file" style="display: none" @change="sendFilesMessages" />
<!-- 录音采集框 -->
<el-card ref="recordBox" v-if="isShowRecordBox" class="record_box" shadow="always">
<p v-if="!isHttps"> 由于浏览器限制,录音功能必须为https环境或者为localhost环境下使用 </p>
<!-- <CollectAudio v-else @sendAudioMessages="sendAudioMessages" />-->
</el-card>
<!-- 附件上传加载容器 -->
<div ref="loadingBox" class="loading_box"></div>
</div>
<template v-if="nowPickInfo.chatType === CHAT_TYPE.SINGLE">
<textarea
ref="editable"
v-model="textContent"
class="chat_content_editable"
spellcheck="false"
contenteditable="true"
placeholder="请输入消息内容..."
@keydown="onTextInputKeyDown"
@paste="onPasteImage"
>
</textarea>
</template>
<template v-else-if="nowPickInfo.chatType === CHAT_TYPE.GROUP">
<vue-at :members="atMembersList" name-key="text" @insert="onInsert">
<textarea
ref="editable"
v-model="textContent"
class="chat_content_editable"
spellcheck="false"
contenteditable="true"
placeholder="请输入消息内容..."
@keydown="onTextInputKeyDown"
@paste="onPasteImage"
>
</textarea>
</vue-at>
</template>
<el-button
:class="[textContent === '' ? 'no_content_send_btn' : 'chat_send_btn']"
type="primary"
@click="sendTextMessage"
>发送</el-button
>
<PreviewSendImg ref="previewSendImg" @send-images-message="sendImagesMessage" />
</template>
<style lang="scss" scoped>
@import './index.scss';
@import '@/styles/iconfont/iconfont.css';
</style>