469 lines
14 KiB
Vue
469 lines
14 KiB
Vue
<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>
|