define(['vue'], function (Vue) { Vue.directive('dialogDrag', { bind(el, binding, vnode, oldVnode) { const dialogHeaderEl = el.querySelector('.el-dialog__header'); const dragDom = el.querySelector('.el-dialog'); dialogHeaderEl.style.cssText += ';cursor:move;' dragDom.style.cssText += ';top:0px;' const sty = (() => { if (window.document.currentStyle) { return (dom, attr) => dom.currentStyle[attr]; } else { return (dom, attr) => getComputedStyle(dom, false)[attr]; } })() dialogHeaderEl.onmousedown = (e) => { document.onselectstart = function () { return false; }; document.ondragstart = function () { return false; }; $(document).find("iframe").css("pointer-events", "none"); const disX = e.clientX - dialogHeaderEl.offsetLeft; const disY = e.clientY - dialogHeaderEl.offsetTop; const screenWidth = document.body.clientWidth; const screenHeight = document.documentElement.clientHeight; const dragDomWidth = dragDom.offsetWidth; const dragDomheight = dragDom.offsetHeight; const minDragDomLeft = dragDom.offsetLeft; const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth; const minDragDomTop = dragDom.offsetTop; const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight; let styL = sty(dragDom, 'left'); let styT = sty(dragDom, 'top'); if (styL.includes('%')) { styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100); styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100); } else { styL = +styL.replace(/\px/g, ''); styT = +styT.replace(/\px/g, ''); }; document.onmousemove = function (e) { let left = e.clientX - disX; let top = e.clientY - disY; if (-(left) > minDragDomLeft) { left = -(minDragDomLeft); } else if (left > maxDragDomLeft) { left = maxDragDomLeft; } if (-(top) > minDragDomTop) { top = -(minDragDomTop); } else if (top > maxDragDomTop) { top = maxDragDomTop; } dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`; }; document.onmouseup = function (e) { document.onselectstart = function () { return true; }; $(document).find("iframe").css("pointer-events", "auto"); document.onmousemove = null; document.onmouseup = null; }; } } }) Vue.component('Chat', { template: '#chatTemp', props: ['passvalue'], data() { return { isButtonShow: false, isClick: false, dialogVisible: false, lineOptions: [{ value: 'online', label: '在线', color: '#7438D5', }, { value: 'offline', label: '离开', color: '#ED655F', }, { value: 'busy', label: '忙碌', color: '#666666', }], lineStatus: '', liaoData: [{ title: '会话中', type: 'buyerOnlines', }, { title: '排队中', type: 'buyerWaitings', }, { title: '历史记录', type: 'buyerHistories', }], openGroupList: [], groupIndex: 0, commonWordsvisible: false, sendMessage: '', specificChatMessage: [], last_id: '', page: 1, per_page: 10, emoji_list: [], emojivisible: false, websocket: null, relink: 0, max_relink: 0, buylist: { buyerHistories: [], buyerOnlines: [], buyerWaitings: [], }, isChatingId: '', temporaryObj: {}, //临时消息的id user_offline: [], //用户下线 shoproKefuShake: false, shoproChatContainerRefScrollTop: 0, scrollHeight: 0, message_num: 10, adminData: [], commonWordsList: [], timeInterval: null, kefuButtonvisible: false, rightShowId: null, rightShowPosition: { top: 0, left: 0 }, changeKefu: null, changeKefuList: [], value: '', transferAdminList: '', openPing: null, errorTipVisible: false, // 客服是否介入 kefuAccessStatus: false, kefuAccessStatusSend: false, kefuAccessTip: '', kefuLinkNum: 0 } }, created() { this.adminData = this.passvalue.adminData; this.emoji_list = this.passvalue.emoji_list; this.commonWordsList = this.passvalue.commonWordsList; if ("WebSocket" in window) { this.linkWebSocket(this.passvalue.wsUri, this.passvalue.token, this.passvalue.expire_time, this.passvalue.customer_service_id); } else { this.$notify({ title: '警告', message: `浏览器不支持 WebSocket!`, type: 'warning' }); } }, mounted() { this.$nextTick(() => { if (localStorage.getItem("shopro_kefu_button_position")) { let left = localStorage.getItem("shopro_kefu_button_position").split(',')[0]; let top = localStorage.getItem("shopro_kefu_button_position").split(',')[1]; this.$refs.shoproKefuButtonRef.style.left = `${left}px`; this.$refs.shoproKefuButtonRef.style.top = `${top}px` this.isButtonShow = true; } else { this.isButtonShow = true; } window.onresize = () => { return (() => { // this.dialogVisible = false; this.$refs.shoproKefuButtonRef.style.left = `unset`; this.$refs.shoproKefuButtonRef.style.top = `80%`; this.rightShowId = null; })(); }; }) }, methods: { closerightShowId() { this.rightShowId = null; this.$forceUpdate(); }, show(e, service_id) { if (this.groupIndex == 0) { this.rightShowId = service_id; this.rightShowPosition.top = e.clientY + 'px'; this.rightShowPosition.left = e.clientX + 'px'; } }, transferKfu(value) { let data = { session_id: this.rightShowId, identify: 'customer_service', type: 'transfer', data: { customer_service_id: value } } this.websocket.send(JSON.stringify(data)); this.rightShowId = null; this.isChatingId = false }, getimageload(index) { this.$set(this.specificChatMessage[index], 'hidden', true) }, imagerror(index) { this.$set(this.specificChatMessage[index], 'hidden', true) }, linkWebSocket(wsUri, token, expire_time, customer_service_id) { this.errorTipVisible = false wsUri = wsUri + '?identify=customer_service&token=' + token + '&expire_time=' + expire_time + '&customer_service_id=' + customer_service_id this.websocket = new WebSocket(wsUri); this.websocket.onopen = (evt) => { this.relink = 1; this.openPing = setInterval(() => { let data = { type: 'ping', } this.websocket.send(JSON.stringify(data)); }, 20000) }; this.websocket.onclose = (evt) => { this.errorTipVisible = true this.lineStatus = 'offline' this.kefuLinkNum++ if(this.kefuLinkNum<=3){ this.linkWebSocket(this.passvalue.wsUri, this.passvalue.token, this.passvalue.expire_time, this.passvalue.customer_service_id); } }; this.websocket.onmessage = (evt) => { let result = JSON.parse(evt.data); let data = result.data; if (result.code) { switch (result.type) { case 'nologin': //请先登录管理后台 this.$notify({ title: '警告', message: '请先登录管理后台', type: 'warning' }); break; case 'no_customer_service': //您还不是客服 this.$notify({ title: '警告', message: '您还不是客服', type: 'warning' }); break; case 'init': //链接成功 this.lineStatus = data.customer_service.status; this.buylist.buyerHistories = data.histories; this.buylist.buyerOnlines = data.onlines; this.buylist.buyerWaitings = data.waitings; this.openGroupList = this.buylist[this.liaoData[this.groupIndex].type]; this.buylist.buyerHistories.forEach(h => { if (h.status == 0) { this.user_offline.push(h.session_id) } }) this.lineStatus = 'online' break; case 'customer_service_update': if (data.customer_services.length > 0) { data.customer_services.forEach((i, index) => { if (i.id == this.adminData.customer_service.id) { data.customer_services.splice(index, 1) } }) } this.changeKefuList = data.customer_services; break; case 'transfer_success': this.transferAdminList = '' this.buylist.buyerOnlines.forEach((i, index) => { if (i.session_id == data.session_id) { this.buylist.buyerOnlines.splice(index, 1) } }) break; case 'user_online': //用户上线 if (this.user_offline.includes(data.session_id)) { this.user_offline.splice(this.user_offline.indexOf(data.session_id), 1) } // 更改历史接待人状态上线 this.buylist.buyerHistories.forEach(history => { if (history.session_id == data.session_id) { history.customer_service = data.customer_service history.status = 1 } }) break; case 'user_offline': //用户下线 this.user_offline.push(data.session_id); // 更改历史接待人状态下线 this.buylist.buyerHistories.forEach(history => { if (history.session_id == data.session_id) { history.customer_service = null history.status = 0 } }) break; case 'user_accessed': //用户被接入 从排队中删除 if (this.buylist.buyerWaitings.length > 0) { this.buylist.buyerWaitings.forEach((i, index) => { if (i.session_id == data.session_id) { this.buylist.buyerWaitings.splice(index, 1) } }) } //切换列表 // this.changeGroup(0) // 更改历史接待人状态上线 this.buylist.buyerHistories.forEach(history => { if (history.session_id == data.session_id) { history.customer_service = data.customer_service history.status = 1 } }) // 前台刷新 if (this.adminData.customer_service.id != data.customer_service.id) { let deleteIndex = -1 this.buylist.buyerOnlines.forEach((online, oindex) => { if (online.session_id == data.session_id) { deleteIndex = oindex } }) if (deleteIndex != -1) { this.buylist.buyerOnlines.splice(deleteIndex, 1) } } break; case 'user_access': //用户接入 从排队中删除,放到会话中 this.voicePlay(); this.rightShowId = null; if (this.buylist.buyerWaitings.length > 0) { this.buylist.buyerWaitings.forEach((i, index) => { if (i.session_id == data.session_id) { this.buylist.buyerWaitings.splice(index, 1) } }) } let flag = true if (this.buylist.buyerOnlines.length > 0) { this.buylist.buyerOnlines.forEach(i => { if (i.session_id == data.session_id) { flag = false } }) } if (flag) { this.buylist.buyerOnlines.push(data.chat_user) } break; case 'receipt': //发送成功 this.$refs.shoproChatContainerRef.scrollTop = this.$refs.shoproChatContainerRef.scrollHeight; $(".shopro-send-pre").empty(); break; case 'message': //接收到消息 this.voicePlay(); this.rightShowId = null; this.kefuButtonvisible = true; if (data.message.message_type == 'text') { data.message.message = this.replaceEmjio(data.message.message); } if (data.message.message_type == 'goods' || data.message.message_type == 'order') { data.message.message = JSON.parse(data.message.message); } if (this.isChatingId == data.session_id) { this.specificChatMessage.push(data.message) } else { this.buylist.buyerOnlines.forEach(k => { if (k.session_id == data.session_id) { k.message_unread_num++ } }) } let addIndex = true this.buylist.buyerOnlines.forEach((j, jindex) => { if (j.session_id == data.session_id) { j.last_message = data.message addIndex = false } }) if (addIndex) { this.buylist.buyerOnlines.unshift({ avatar: data.message.identify.avatar, createtime: data.message.identify.createtime, customer_service_id: data.message.identify.customer_service_id, id: data.message.identify.id, last_message: data.message, lasttime: data.message.identify.lasttime, message_unread_num: 1, nickname: data.message.identify.nickname, session_id: data.message.identify.session_id, status: 1, updatetime: data.message.identify.updatetime, user_id: data.message.identify.user_id, }) } this.$forceUpdate(); //调整滚动条 this.$nextTick(() => { if (this.$refs.shoproChatContainerRef) { this.$refs.shoproChatContainerRef.scrollTop = this.$refs.shoproChatContainerRef.scrollHeight; } }) this.shoproKefuShake = true break; case 'message_list': //接收到的消息列表 data.message_list.data.forEach(i => { if (i.message_type == 'goods' || i.message_type == 'order') { i.message = JSON.parse(i.message) } if (i.message_type == 'text') { i.message = this.replaceEmjio(i.message); } }); //拿到this.last_id if (this.page == 1 && data.message_list.data.length > 0) { this.last_id = data.message_list.data[0].id } this.message_num = JSON.parse(JSON.stringify(data.message_list.data)).length this.specificChatMessage = data.message_list.data.reverse().concat(this.specificChatMessage) //滚动条 this.$nextTick(() => { if (this.page == 1) { this.$refs.shoproChatContainerRef.scrollTop = this.$refs.shoproChatContainerRef.scrollHeight; } else { this.$refs.shoproChatContainerRef.scrollTop = this.$refs.shoproChatContainerRef.scrollHeight - this.scrollHeight; } //保存原来的滚动条 this.scrollHeight = this.$refs.shoproChatContainerRef.scrollHeight; this.$refs.shoproChatContainerRef.removeEventListener("scroll", this.listmore, false); this.$refs.shoproChatContainerRef.addEventListener('scroll', this.listmore, false) }) this.shoproKefuShake = false; break; case 'del_success': //接收到的消息列表 this.buylist[this.liaoData[this.groupIndex].type].forEach((i, index) => { if (i.session_id == data.session_id) { this.buylist[this.liaoData[this.groupIndex].type].splice(index, 1) } }) this.isChatingId = null; break; case 'switch_ok': this.lineStatus = data.status break; } } else { this.$notify({ title: '警告', message: `错误响应:${result.msg}`, type: 'warning' }); } }; this.websocket.onerror = (evt) => { }; }, closeErrorTipVisible() { this.errorTipVisible = false }, listmore() { let that = this; if (that.$refs.shoproChatContainerRef.scrollTop <= 0 && that.message_num == 10) { this.page++; let ChatingData = { identify: 'customer_service', type: 'message_list', session_id: that.isChatingId, data: { page: that.page, per_page: that.per_page, last_id: that.last_id } } that.websocket.send(JSON.stringify(ChatingData)); } }, //切换在线状态 changeLineStatus(value) { this.lineStatus = value; let lineStatusData = { identify: 'customer_service', type: 'switch_status', data: { status: value } } this.websocket.send(JSON.stringify(lineStatusData)); }, //更换左侧列表 changeGroup(index) { this.kefuAccessStatus = false; this.kefuAccessStatusSend = false; this.kefuAccessTip = '' this.isChatingId = ''; this.groupIndex = index; this.openGroupList = this.buylist[this.liaoData[this.groupIndex].type]; }, //选择聊天 selectChating(chatid, index, opt, row) { this.rightShowId = null; this.kefuAccessStatus = false; this.kefuAccessStatusSend = false; this.kefuAccessTip = '' //避免重复请求 if (chatid == this.isChatingId && opt) { return false; } this.isChatingId = chatid; this.page = 1; this.specificChatMessage = []; this.message_num = 10; this.scrollHeight = 0; //清除未读 this.buylist[this.liaoData[this.groupIndex].type][index].message_unread_num = 0; //将排队中的拿过来 if (this.groupIndex == 1) { this.buylist.buyerWaitings.splice(index, 1); let data = { session_id: chatid, identify: 'customer_service', type: 'access', } this.websocket.send(JSON.stringify(data)); // } else { let ChatingData = { identify: 'customer_service', type: 'message_list', session_id: this.isChatingId, data: { page: this.page, per_page: this.per_page, // last_id: 第一页不用传,以后一直传 第一页的第一条的消息的 id } } this.websocket.send(JSON.stringify(ChatingData)); // 监听这个dom的scroll事件 if (this.$refs.shoproChatContainerRef) { this.$refs.shoproChatContainerRef.removeEventListener("scroll", this.listmore, false); } } if (this.groupIndex == 2) { if (row.status == 1) { if (!row.customer_service) { this.kefuAccessStatus = true; this.kefuAccessStatusSend = true; this.kefuAccessTip = '等待客服接入'; return false } if (row.customer_service && row.customer_service.id != this.adminData.customer_service.id) { this.kefuAccessStatus = true; this.kefuAccessStatusSend = true; this.kefuAccessTip = '客服 ' + row.customer_service.name + ' 正在服务,暂时无法与之会话!'; return false } } } }, closeKefuAccessStatus() { this.kefuAccessStatus = false; this.kefuAccessTip = '' }, deleteChating(chatid, index, opt) { let data = { identify: 'customer_service', type: 'del_user', data: { session_id: chatid } } this.websocket.send(JSON.stringify(data)); }, selectCommonWords(index) { this.commonWordsvisible = false; this.doSend('text', this.commonWordsList[index].content) this.specificChatMessage.push(this.initSendData('text', this.commonWordsList[index].content)); }, selectEmoji(emoji) { this.emojivisible = false $(".shopro-send-pre").append(``) }, selectGoods() { let that = this; parent.Fast.api.open("shopro/goods/goods/select?multiple=false", "选择商品", { callback: function (data) { that.doSend('goods', { id: data.data.id, title: data.data.title, image: data.data.image, price: data.data.price, stock: data.data.stock }) that.specificChatMessage.push(that.initSendData('goods', data.data)); } }); }, openGoods(id) { Fast.api.open("shopro/goods/goods/edit/ids/" + id + "?id=" + id + "&type=edit", '查看商品'); }, openOrder(id) { Fast.api.open('shopro/order/order/detail?id=' + id, '查看订单'); }, successMessage(event) { if (this.lineStatus == 'offline') { this.$notify({ title: '警告', message: '离开不能发消息,请切换为在线', type: 'warning' }); return false } if (event) { //enter if (event.keyCode == "13" && !event.ctrlKey) { event.preventDefault(); event.cancelBubble = true; let html = $(".shopro-send-pre").html(); var reg = new RegExp('

', "g") html = html.replace(reg, '') if (html) { this.doSend('text', html) this.specificChatMessage.push(this.initSendData('text', html)); } else { this.$notify({ title: '警告', message: '消息不能为空', type: 'warning' }); $(".shopro-send-pre").html('') return false; } } else if (event.keyCode == "13" && event.ctrlKey) { $('.shopro-send-pre').html($('.shopro-send-pre').html() + "

"); var send = $('.shopro-send-pre')[0]; //兼容问题 if (window.getSelection) { send.focus(); var move = window.getSelection(); move.selectAllChildren(send); move.collapseToEnd(); } else if (document.selection) { var move = document.selection.createRange(); move.moveToElementText(send); move.collapse(false); move.select(); } } } else { //发送按钮 if ($(".shopro-send-pre").html()) { this.doSend('text', $(".shopro-send-pre").html()) this.specificChatMessage.push(this.initSendData('text', $(".shopro-send-pre").html())); } else { this.$notify({ title: '警告', message: '消息不能为空', type: 'warning' }); return false; } } }, handleClose() { this.dialogVisible = false; this.shoproKefuShake = false; this.kefuButtonvisible = false; this.rightShowId = null; }, moveKefu(e) { this.isClick = false let odiv = e.target; document.onselectstart = function () { return false; }; document.ondragstart = function () { return false; }; $(document).find("iframe").css("pointer-events", "none"); let disX = e.clientX - odiv.offsetLeft; let disY = e.clientY - odiv.offsetTop; document.onmousemove = (e) => { let left = e.clientX - disX; let top = e.clientY - disY; if (Math.abs(left - this.positionY + top - this.positionX) > 6) { this.isClick = true; } this.positionX = top; this.positionY = left; odiv.style.left = left + 'px'; odiv.style.top = top + 'px'; var shopro_kefu_button_position = [left, top]; localStorage.setItem("shopro_kefu_button_position", shopro_kefu_button_position); document.querySelector(".error-tip-popover").style.left = (left - 275) + 'px' document.querySelector(".error-tip-popover").style.top = (top + 3) + 'px'; }; document.onmouseup = (e) => { document.onselectstart = function () { return true; }; document.onmousemove = null; document.onmouseup = null; $(document).find("iframe").css("pointer-events", "auto"); }; }, openDialog() { if (!this.isClick) { this.dialogVisible = true; this.kefuButtonvisible = false; } }, addfile(event) { let that = this; let formData = new FormData(); formData.append("file", that.$refs.inputFileRef.files[0]); $.ajax({ url: window.location.origin + "/addons/shopro/index/upload", type: 'post', data: formData, contentType: false, processData: false, success: function (res) { that.doSend('image', res.data.url) that.specificChatMessage.push(that.initSendData('image', res.data.url)) }, error: function (e) { layer.msg('文件上传失败'); }, complete: function () { that.$refs.inputFileRef.value = "" } }) }, voicePlay() { new Audio('/assets/addons/shopro/audio/notice.wav').play(); }, //发送消息的数据组装 doSend(type, message, index) { var submessage = message if (type == 'text') { let reg = /|\/>)/gi; // [] img let zhEmojiName = submessage.match(reg); if (zhEmojiName) { zhEmojiName.forEach(item => { let srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i let path = item.match(srcReg)[1] let pathArr = path.split('/'); let file = pathArr[pathArr.length - 1]; let noimage = { name: '[常用语]' } let emojiItem = this.selEmjioFile('file', file) ? this.selEmjioFile('file', file) : noimage; submessage = submessage.replace( item, `${emojiItem.name}` ); }); } } let data = { session_id: this.isChatingId, identify: 'customer_service', type: 'message', message: { message_type: type, message: submessage }, } this.websocket.send(JSON.stringify(data)); }, //发送之前处理数据 initSendData(type, message) { return { message: message, message_type: type, sender_identify: "customer_service", identify: this.adminData.customer_service } }, timeFilter(datetime) { let timeday = '' // let timestamp = Date.parse(new Date()) let datestamp = new Date(datetime) // if (timestamp - datetime < 86400) { // var hour = ("0" + datestamp.getHours()).slice(-2), // minute = ("0" + datestamp.getMinutes()).slice(-2); // timeday = hour + ":" + minute // }else if (timestamp - datetime < 172800 && timestamp - datetime > 86400) { // timeday = '昨天' // } else { timeday = datestamp.getFullYear() + '/' + (Number(datestamp.getMonth()) + 1) + '/' + datestamp.getDate() + ' ' + ("0" + datestamp.getHours()).slice(-2) + ':' + ("0" + datestamp.getMinutes()).slice(-2) // } return timeday; }, // 表情地址 selEmjioFile(type, value) { for (let index in this.emoji_list) { if (this.emoji_list[index][type] === value) { return this.emoji_list[index]; } } return false; }, // 替换表情 replaceEmjio(data) { let newData = data; let reg = /\[(.+?)\]/g; // [] 中括号 let zhEmojiName = newData.match(reg); if (zhEmojiName) { zhEmojiName.forEach(item => { let emojiItem = this.selEmjioFile('name', item); newData = newData.replace( item, `` ); }); } return newData; }, }, }) })