新增:对话页面

This commit is contained in:
安浩浩
2024-04-27 23:10:03 +08:00
parent de27cfa8f6
commit a7e8433b27
33 changed files with 4008 additions and 91 deletions

View File

@ -0,0 +1,453 @@
<script setup>
import { ElLoading, ElMessageBox } from 'element-plus'
import { emojis } from '@/constant'
import { messageType } from '@/constant'
import _ from 'lodash'
/* 组件 */
import PreviewSendImg from '../suit/previewSendImg.vue'
import VueAt from 'vue-at/dist/vue-at-textarea' // for textarea
const { ALL_MESSAGE_TYPE, CHAT_TYPE, MENTION_ALL } = messageType
const nowPickInfo = ref({
id: '',
chatType: ''
})
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)
const showEmojisBox = () => {
console.log('>>>>>展开模态框')
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)
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>