新增:对话页面
This commit is contained in:
453
src/views/im/Message/components/inputBox/index.vue
Normal file
453
src/views/im/Message/components/inputBox/index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user