import { mapActions, mapState, mapMutations } from 'vuex' import { scrollMsgIntoView } from '@/util/util.js' import { emojiList } from '@/util/emoji' import { Message } from 'element-ui' import ImageMin from '@/util/imageMin.js' // 聊天mixin 用于chatRoom组件 export const chatMixin = { watch: { '$route' () { this.bdHiden = true // 切换房间 this.groupSet = false this.lockMore = false this.lockEnd = false this.enableScroll = false this.initRoom() }, unreadNums (val, newval) { if (val > 0 && this.isBottom) { setTimeout(this.resizeToBottom.bind(this), 150) } }, chatList (val) { let lastVal = val[val.length - 1] if (lastVal && lastVal.msg_type == 4) { // 自己发的红包自动滚动到底部 this.$nextTick(this.resizeToBottom) } }, isJoinGroup (val) { if (val == 1) setTimeout(this.resizeToBottom.bind(this), 100) } }, data () { return { groupSet: false, lockMore: false, lockEnd: false, enableScroll: false, // 记录滚动条是否激活的状态 isBottom: true, scrollHeight: 100, // 滚动条高度 isScrollToView: false, isShowGroudMgr: false // 是否显示群管理 } }, computed: { ...mapState(['group', 'userId', 'userInfo', '']), ...mapState({ creator: state => state.group.creator, isJoin: state => state.group.isJoin, pinMsg: state => state.group.pinMsg, pinList: state => state.group.pinList, 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 }), 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 } } }, mounted () { this.initRoom() document.addEventListener('contextmenu', e => e.preventDefault()) }, methods: { ...mapMutations([ 'initGroup', 'resetUnreadNums', 'addChatItem', 'deleteChatItem', 'initState', 'clearAtList', 'curSession', 'setSessionItemUnread' ]), ...mapActions([ 'getGroupInfo', 'getUserInfo', 'getNewMsg', 'getHistoryMsg', 'doSendMsg', 'getPrivateNewMsg', 'getPrivateHistoryMsg', 'doSendPrivateMsg' ]), async initRoom () { if (!this.userInfo) { await this.getUserInfo() } this.$store.commit('changeSessionId', this.$route.params.id) this.initState(this.userInfo) if (this.isPrivate) { this.initPersonChat() } else { this.initGroupChat() } }, /** * @des 私聊初始化处理 */ async initPersonChat () { await this.getPrivateNewMsg() this.$nextTick(() => { this.resizeToBottom() this.bdHiden = false }) }, /** * @des 聊天群初始化处理 */ async initGroupChat () { this.initGroup({ userId: this.userId, groupId: this.sessionId, useCache: false }) this.isShowGroudMgr = false await this.getGroupInfo() await this.getNewMsg() this.$nextTick(() => { this.resizeToBottom() this.bdHiden = false }) }, /** * @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 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 } }, /** * @des 聊天窗体滚动到底部 */ resizeToBottom () { this.$refs.scrollWrap.scrollTop = this.$refs.msgWrap.offsetHeight this.resetUnreadNums() this.isBottom = true }, /** * @des 点击,查看未读消息 * 直接滚动到聊天列表底部 */ doSetRead () { this.resizeToBottom() }, /** * @des 引用某条消息 */ quoteMsg (msg) { this.$refs.inputArea.inputMsg = msg }, /** * @des 某条消息被删除 */ deleteMsg (hash) { this.deleteChatItem(hash) }, pinMsgClose () { this.pinMsg.visible = false }, scrollToView () { if (this.pinList.length) { let node = this.$refs.msgWrap.querySelector('.msg-item') scrollMsgIntoView( this.$refs.scrollWrap, node.offsetTop - (this.pinMsg ? 40 : 10), node ) } else { let hash = this.pinMsg.hash let index = this.group.chatList.findIndex(item => item.hash === hash) if (index >= 0) { let node = this.$refs.msgWrap .querySelectorAll('.msg-item') .item(index) scrollMsgIntoView( this.$refs.scrollWrap, node.offsetTop - (this.pinMsg ? 40 : 10), node ) } } // 防止加载更多 this.isScrollToView = true setTimeout(() => { this.isScrollToView = false }, 2000) }, scrollToMsg (index) { let hash = this.atList[index].hash let eleIndex = this.group.chatList.findIndex(item => item.hash === hash) if (eleIndex >= 0) { let pinLen = this.group.pinList.length let node = this.$refs.msgWrap .querySelectorAll('.msg-item') .item(eleIndex + pinLen) scrollMsgIntoView( this.$refs.scrollWrap, node.offsetTop - (this.pinMsg ? 40 : 10), node ) } this.clearAtList() }, joinGroup () { this.$store.dispatch('joinGroup') }, // 群管理 showGroudMgr (flag) { this.isShowGroudMgr = flag == 1 } } } // 聊天输入框mixin export const inputMixin = { computed: { ...mapState(['group', 'userId', 'curSession']), ...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 { emojiShow: false, inputMsg: '', atInd: 0 } }, mounted () { document.body.addEventListener('click', () => { this.emojiShow = false }) }, methods: { ...mapMutations(['updateChatInputFocus', 'addChatItem']), ...mapActions(['doSendMsg', 'doSendFile', 'doSendPrivateMsg']), addEmoji (val) { this.inputMsg += val this.emojiShow = false this.$refs.chatInput.focus() }, /** * @des 处理消息发送 */ handleSend (e) { // 判断是否被禁言 if (this.blockList.some(id => id == this.userId)) { Message({ message: '您已被禁言', type: 'error' }) return } // 替换emoji字符串 let _inputMsg = this.inputMsg for (let k in this.emojiMap) { if (_inputMsg.indexOf(k) > -1) { let reg = new RegExp(k, 'g') _inputMsg = _inputMsg.replace(reg, this.emojiMap[k]) } } let text = _inputMsg.trim() 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() this.addChatItem({ from: this.userId, content: text, hash: `${createTime}`, timestamp: createTime, createTime, msg_type: '0', loading: true }) opt.createTime = createTime } this.$store.commit('setSessionItemUnread', { session_id: this.curSession, unread: 0, curSession: this.curSession, cont: this.inputMsg }) this.isPrivate ? this.doSendPrivateMsg(opt) : this.doSendMsg(opt) // 滚到底部 this.$nextTick(function () { this.inputMsg = '' this.resizeToBottom ? this.resizeToBottom() : this.$emit('toBottom') }) 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 }) } else if (type.match('audio')) { return size > 2 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({ type: 3, res: 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) let opt = { res: fileInfo.res } if (this.group.members[this.userId]) { 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 } this.doSendFile(opt) setTimeout(() => { 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 } this.resizeToBottom ? this.resizeToBottom() : this.$emit('toBottom') }, 100) } catch (error) { Message({ message: '上传文件大小限制:音频2M以内,视频3M以内', type: 'warning' }) } } } }