import { mapActions, mapState, mapMutations } from 'vuex' import { scrollMsgIntoView, lazyloadImage, getMeechatType, getUserOpt, setUserOpt } from '@/util/util.js' import { emojiList } from '@/util/emoji' import { Message } from 'element-ui' import ImageMin from '@/util/imageMin.js' // 三端公共chat export const chatCommonMixin = { watch: { isRefreshImg (val) { if (!val) return this.$nextTick(() => { lazyloadImage({ wrap: this.$refs.scrollWrap, imageArr: this.chatImageArrSel, derection: 'up' }) }) this.setIsRefreshImg(false) } }, computed: { ...mapState({ isRefreshImg: state => state.group.isRefreshImg }) }, methods: { ...mapMutations([ 'addPinChatItem', 'setIsRefreshImg', 'deleteChatItem', 'removeAtListLast' ]), pinMsgClose () { this.pinMsg.visible = false }, /** * @des 某条消息被删除 */ deleteMsg (hash) { this.deleteChatItem(hash) }, scrollToMsg (index) { let hash = this.atList[index].hash let eleIndex = this.group.chatList.findIndex(item => item.hash === hash) if (eleIndex >= 0) { let node = this.$refs.msgWrap.getElementsByClassName('msg-item-common').item(eleIndex) scrollMsgIntoView( this.$refs.scrollWrap, node.offsetTop - (this.pinMsg ? 40 : 10), node ) } this.removeAtListLast() }, scrollToView () { if (!this.pinMsg) return let hash = this.pinMsg.hash let index = this.group.chatList.findIndex(item => item.hash === hash && item.msg_type == 0) if (index < 0) { this.addPinChatItem(this.pinMsg) index = 0 } this.$nextTick(() => { let node = this.$refs.msgWrap.getElementsByClassName('msg-item-common')[index] let toOffsetTop = index >= 0 ? node.offsetTop : 0 let lazy = function () { lazyloadImage({ wrap: this.$refs.scrollWrap, imageArr: this.chatImageArrSel, derection: 'up' }) } scrollMsgIntoView( this.$refs.scrollWrap, toOffsetTop, node, lazy.bind(this) ) // 防止加载更多 this.isScrollToView = true setTimeout(() => { this.isScrollToView = false }, 2000) }) }, async joinGroup () { this.isLoadingRoom = true await this.$store.dispatch('joinGroup') this.isLoadingRoom = false } } } // 聊天mixin 用于chatRoom组件 export const chatMixin = { mixins: [chatCommonMixin], watch: { '$route' (val) { this.bdHiden = true // 切换房间 this.groupSet = false this.lockMore = false this.lockEnd = false this.enableScroll = false this.initRoom() }, chatList (val) { let lastVal = val[val.length - 1] if ((lastVal && lastVal.msg_type == 4) || this.isBottom) { // 自己发的红包自动滚动到底部 this.$nextTick(this.resizeToBottom) } }, isJoinGroup (val) { if (val == 1) setTimeout(this.resizeToBottom.bind(this), 100) } }, data () { return { isLoadingRoom: true, groupSet: false, lockMore: false, lockEnd: false, enableScroll: false, // 记录滚动条是否激活的状态 isBottom: true, scrollHeight: 100, // 滚动条高度 isScrollToView: false, isShowGroudMgr: false, // 是否显示群管理 chatImageArrSel: null, // 图片组 meechatType: getMeechatType() } }, computed: { ...mapState(['curSession', 'group', 'chat', 'userId', 'userInfo']), ...mapState({ creator: state => state.group.creator, isJoin: state => state.group.isJoin, pinMsg: state => state.group.pinMsg, atList: state => state.group.atList, unreadNums: state => state.group.unreadNums, chatList: state => state.group.chatList, members: state => state.group.members, sessionId: state => state.curSession, sessionInfo: state => state.group.sessionInfo }), isExist () { // 是否存在在会话列表中 let sessionList = this.chat.sessionList if (sessionList && sessionList.length) { return sessionList.some(e => { return e.session_id == this.sessionId }) } else { return true } }, isAdmin () { return (this.group.adminList && this.group.adminList.some(id => id == this.userId)) || this.group.creator == this.userId }, isPrivate () { return this.$store.getters.isPrivate }, isCreator () { return this.userId == this.creator }, isJoinGroup () { if (this.group && this.group.groupId) { return this.isJoin ? 1 : 0 } else { return 1 } }, linkToCreator () { let { creator, userId } = this.group let sessionId = creator > userId ? `${userId}-${creator}` : `${creator}-${userId}` return `${location.origin}/#/pm/${sessionId}` } }, mounted () { this.initRoom() document.getElementById('app').addEventListener('contextmenu', e => e.preventDefault()) this.chatImageArrSel = this.$refs.scrollWrap.getElementsByTagName('img') // this.$nextTick(() => { // this.$refs.msgWrap.style.height = 'auto' // }) }, methods: { ...mapMutations([ 'initGroup', 'setUserId', 'setToken', 'resetUnreadNums', 'addChatItem', 'initState', 'clearAtList', 'clearHash', 'setSessionItemUnread', 'clearChatList', 'changeSessionId', 'updateMembers' ]), ...mapActions([ 'setAccount', 'getGroupInfo', 'getUserInfo', 'getNewMsgFromDb', 'getNewMsg', 'getHistoryMsg', 'doSendMsg', 'getPrivateNewMsgFromDb', 'getPrivateNewMsg', 'getPrivateHistoryMsg', 'doSendPrivateMsg' ]), async initRoom () { if (!this.userInfo) { await this.getUserInfo() } if (!this.userInfo) return this.changeSessionId(this.$route.params.id) this.clearHash() this.clearChatList() this.initState() if (this.isExist) { // 把会话列表的消息数设置为0 this.$store.commit('setSessionItemUnread', { session_id: this.curSession, unread: 0, curSession: this.curSession }) } if (this.isPrivate) await this.initPersonChat() else await this.initGroupChat() }, /** * @des 私聊初始化处理 */ async initPersonChat () { let flag = await this.getPrivateNewMsgFromDb() if (!flag) { // 如果indexDB没数据时,才需要loading this.showLoadingRoom(true) } let data = await this.getPrivateNewMsg() this.showLoadingRoom(false) this.$nextTick(() => { this.resizeToBottom() this.bdHiden = false }) // 没消息时member添加成员 if (!this.members[this.userId]) { this.updateMembers({ [this.userId]: this.userInfo }) } // 房间名 let userIds = this.curSession.split('-') let otherId = userIds[0] != this.userId ? userIds[0] : userIds[1] let otherInfo = data.data.userMap[otherId] if (otherInfo) { this.$store.commit('updateGroup', { key: 'privateName', data: otherInfo.nick_name }) // 不存在会话,则添加会话 if (!this.isExist) { let obj = { cover_photo: otherInfo.cover_photo, is_group: '0', name: otherInfo.nick_name, session_id: this.sessionId } this.$store.commit('addSessionItem', obj) } } return data }, /** * @des 聊天群初始化处理 */ async initGroupChat () { this.initGroup({ userId: this.userId, groupId: this.sessionId, useCache: false }) this.isShowGroudMgr = false this.getGroupInfo() let flag = await this.getNewMsgFromDb() // 如果indexDB没数据时,才需要loading if (!flag) this.showLoadingRoom(true) await this.getNewMsg() this.showLoadingRoom(false) this.$nextTick(() => { this.resizeToBottom() this.bdHiden = false lazyloadImage({ wrap: this.$refs.scrollWrap, imageArr: this.chatImageArrSel, derection: 'up' }) }) setTimeout(() => { // 不存在会话,则添加会话 if (!this.isExist) { // 获取对方信息 if (this.group) { let obj = { cover_photo: this.group.coverPhoto, is_group: '1', name: this.group.groupName, session_id: this.sessionId } this.$store.commit('addSessionItem', obj) } } }, 1000) }, /** * @des 滚动事件监听 */ initScrollEvent () {}, /** * @des 聊天窗体滚动事件处理集 */ async handleScroll (e) { // 防止切换房间时触发滚动处理 if (!this.group.chatList.length) { return } // 防止滚动到置顶消息触发滚动 // if (this.isScrollToView) { // return // } // 激活滚动条 this.enableScroll = true let totalHeight = this.$refs.msgWrap.offsetHeight - 16 let scrollTop = e.target.scrollTop // 差不多滚动到顶部 if (scrollTop == 0 && !this.lockMore) { if (this.group.endHash !== null) { this.lockMore = true let res if (this.isPrivate) { res = await this.getPrivateHistoryMsg() } else { res = await this.getHistoryMsg() } if (res === 'end') { this.lockEnd = true } else { let scrollBottom = totalHeight - scrollTop this.$nextTick(() => { e.target.scrollTop = this.$refs.msgWrap.offsetHeight - scrollBottom setTimeout(() => { this.lockMore = false }, 800) }) } } } // 滚动到底部清空未读消息状态 if (scrollTop + e.target.offsetHeight > totalHeight) { this.isBottom = true if (this.group.unreadNums) { this.resetUnreadNums() } } else { this.isBottom = false } lazyloadImage({ wrap: this.$refs.scrollWrap, imageArr: this.chatImageArrSel, derection: 'up' }) }, /** * @des 聊天窗体滚动到底部 */ resizeToBottom () { if (!this.$refs.msgWrap) return this.$refs.msgWrap.style.height = 'auto' this.$refs.scrollWrap.scrollTop = this.$refs.msgWrap.offsetHeight this.resetUnreadNums() lazyloadImage({ wrap: this.$refs.scrollWrap, imageArr: this.chatImageArrSel, derection: 'up' }, 200) }, /** * @des 点击,查看未读消息 * 直接滚动到聊天列表底部 */ doSetRead () { this.resizeToBottom() }, /** * @des 引用某条消息 */ quoteMsg (msg) { this.$refs.inputArea.inputMsg = msg }, // 群管理 showGroudMgr (flag) { this.isShowGroudMgr = flag == 1 }, // 关闭表情,文件栏 initEmojiAndTool () { this.emojiShow = false this.toolShow = false }, showLoadingRoom (flag) { this.isLoadingRoom = flag } }, beforeDestroy () { document.body.removeEventListener('click', this.initEmojiAndTool) } } // 聊天输入框mixin export const inputMixin = { computed: { ...mapState(['group', 'userId']), ...mapState({ chatInputFocus: state => state.group.chatInputFocus, blockList: state => state.group.blockList }), isPrivate () { return this.$store.getters.isPrivate }, emojiMap () { var emojiMap = {} for (let i in emojiList) { let arr = emojiList[i] arr.forEach(v => { let names = JSON.stringify(v.names) let emoji = v.surrogates emojiMap[names] = emoji }) } return emojiMap } }, data () { return { filePreviewShow: false, // 是否显示文件预览 emojiShow: false, // 是否显示emoji fileInfo: null, // 当前上传文件 inputMsg: '', atInd: 0, meechatType: getMeechatType() } }, mounted () { }, methods: { ...mapMutations(['updateChatInputFocus', 'addChatItem']), ...mapActions(['doSendMsg', 'doSendFile', 'doSendPrivateMsg']), addEmoji (val) { this.inputMsg += val if (this.meechatType == 'pc') { this.emojiShow = false this.$refs.chatInput.focus() } }, closeFilePreview () { this.fileInfo = null this.filePreviewShow = false this.emptyFileForm() }, showFilePreview (fileInfo) { if (!fileInfo) return this.fileInfo = fileInfo this.filePreviewShow = true }, /** * @des 处理消息发送 */ handleInput () { this.inputMsg = this.inputMsg.substring(0, 5000) }, /** * @des 处理消息发送 */ async handleSend (e) { // 判断是否被禁言 if (this.blockList.some(id => id == this.userId)) { Message({ message: '您已被禁言', type: 'error' }) return } // 替换emoji字符串 let _inputMsg = this.inputMsg let parts = _inputMsg.match(/\["[a-z0-9A-Z_]+"\]/g) for (let k in parts) { let emoji = this.emojiMap[parts[k]] if (emoji) { _inputMsg = _inputMsg.replace(parts[k], emoji) } } let text = _inputMsg.trim().substring(0, 5000) if (text.length === 0) { Message({ message: '聊天内容不能为空', type: 'warning' }) return } let opt = { type: 0, msg: text } // 用户不是第一次发言 if (this.group.members[this.userId]) { let createTime = Date.now() let lastShowMsgUid = getUserOpt('lastShowMsgUid') || 0 this.addChatItem({ from: this.userId, content: text, hash: `${createTime}`, timestamp: createTime, createTime, msg_type: '0', loading: true, isShowFullInfo: this.userId != lastShowMsgUid }) if (this.userId != lastShowMsgUid)lastShowMsgUid = this.userId setUserOpt('lastShowMsgUid', lastShowMsgUid) opt.createTime = createTime } this.inputMsg = '' let data = this.isPrivate ? await this.doSendPrivateMsg(opt) : await this.doSendMsg(opt) // // 发送成功后,才加 this.$store.commit('setSessionItemUnread', { session_id: this.curSession, unread: 0, curSession: this.curSession, cont: text, timestamp: data.timestamp }) // 滚到底部 this.$nextTick(function () { this.resizeToBottom ? this.resizeToBottom() : this.$emit('toBottom') lazyloadImage({ wrap: this.$refs.scrollWrap, imageArr: this.chatImageArrSel, derection: 'up' }) }) e.preventDefault() return false }, /** * 文件预处理 * @return {Object} data 预处理文件信息 * @param {Number} data.type * @param {File} data.res */ async preHandleFile (file) { let type = file.type let size = file.size if (type.match('video')) { return size > 3 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({ type: 2, res: file, preview: window.webkitURL.createObjectURL(file) }) } else if (type.match('audio')) { return size > 2 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({ type: 3, res: file, preview: window.webkitURL.createObjectURL(file) }) } else if (type.match('image')) { let image = await new ImageMin({ file: file, maxSize: 1024 * 1024 }) return { type: 1, preview: image.base64, res: image.res } } }, /** * @des 处理文件发送 */ async handleFile (e) { let inputfile if (e.constructor === File) { inputfile = e } else { inputfile = e.target.files[0] } try { let fileInfo = await this.preHandleFile(inputfile) if (this.meechatType == 'pc') this.showFilePreview(fileInfo) else this.handleFileSend(fileInfo) } catch (error) { Message({ message: '上传文件大小限制:音频2M以内,视频3M以内', type: 'warning' }) } }, // 发送文件消息 async handleFileSend (fileInfo) { this.filePreviewShow = false let opt = { res: fileInfo.res } let createTime = Date.now() this.addChatItem({ content: fileInfo.preview || '', from: this.userId, hash: `${createTime}`, msg_type: fileInfo.type, timestamp: createTime, res: fileInfo.res, loading: true, createTime }) opt.createTime = createTime await this.doSendFile(opt) setTimeout(() => { this.emptyFileForm() this.$refs.toolbar && this.$refs.toolbar.resetInput() this.resizeToBottom ? this.resizeToBottom() : this.$emit('toBottom') }, 100) }, // 初始文件表单 emptyFileForm () { if (this.$refs.inputFile) { this.$refs.inputFile.value = null } if (this.$refs.inputFile1) { this.$refs.inputFile1.value = null } if (this.$refs.inputFile2) { this.$refs.inputFile2.value = null } if (this.$refs.inputFile3) { this.$refs.inputFile3.value = null } } } }