You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-fly/static/html/chat_main.html

672 lines
29 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<html lang="cn">
<head>
<meta charset="utf-8">
<meta name="description" content="">
<meta name="author" content="陶士涵">
<title>聊天界面</title>
<link rel="stylesheet" href="/static/css/common.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.13.1/lib/theme-chalk/index.css">
<script src="/static/js/functions.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.13.1/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/reconnecting-websocket/1.0.0/reconnecting-websocket.min.js"></script>
<style>
html, body {height: 100%;padding: 0;margin: 0;background-color: #f5f5f5;}
.el-row{width:100%}#app{margin-top: 10px;}
.chatBg{min-height: 100%;background: #fff;border: solid 1px #e6e6e6;overflow: hidden;}
.chatLeft{ margin-left: 4px;}
.sw-bg{background: #fff;border: solid 1px #e6e6e6;boder-top:none;padding:5px 10px;}
.chatContext .el-row{margin-bottom: 5px;}
.chatContext{position: relative;}
.chatUser{
line-height: 24px;
font-size: 12px;
white-space: nowrap;
color: #999;
}
.chatContent{
text-align: left;
background-color: rgb(166,212,242);
color: #000;
border: 1px solid rgb(152, 199, 230);
padding: 8px 15px;
min-height: 26px;
word-break: break-all;
position: relative;
border-radius: 5px;
display: inline-block;
}
.chatContent:after {
content: '';
position: absolute;
left: -10px;
top: 13px;
width: 0;
height: 0;
border-style: dashed;
border-color: transparent;
overflow: hidden;
border-width: 10px;
border-top-style: solid;
border-top-color: rgb(166,212,242);
}
.chatBoxMe .chatContent{float: right;background-color: rgb(152,225,101);border: 1px solid rgb(145, 215, 96);}
.chatBoxMe .chatContent:after{border-top-color: rgb(152,225,101);}
.chatBoxMe .el-col-3{float: right;text-align: right;}
.chatBoxMe .chatUser{text-align: right}
.chatBoxMe .chatContent:after{left:auto;right: -10px;}
.chatArea{margin: 10px 0;}
.chatBox{max-height: 350px;overflow-y: auto;overflow-x: hidden;}
.onlineUsers{padding: 5px;height: 40px;line-height: 40px;font-size: 14px;border-bottom: solid 1px #e6e6e6;}
.onlineUsers:hover,.onlineUsers.cur{background-color: #f0f9eb;color: #67C23A;}
.imgGray {-webkit-filter: grayscale(100%);-ms-filter: grayscale(100%);filter: grayscale(100%);filter: gray;color:#888;}
.chatTime{text-align: center;color: #bbb;margin: 5px 0;font-size: 12px;}
</style>
</head>
<body>
<div id="app">
<template>
<el-row :gutter="2">
<el-col :span="6">
<div class="chatBg chatLeft">
<el-tabs v-model="leftTabActive" @tab-click="handleTabClick">
<el-tab-pane label="在线用户" name="first">
<el-row v-for="item in users" :key="item.uid" class="">
<div style="cursor:pointer" class="onlineUsers" v-bind:class="{'cur': item.uid==currentGuest }" v-on:click="talkTo(item.uid,item.username)">
<el-col :span="4">
<el-avatar :size="40" :src="item.avator"></el-avatar>
</el-col>
<el-col :span="16">
<{item.username}>
</el-col>
</div>
</el-row>
</el-tab-pane>
<el-tab-pane label="所有访客" name="second">
<el-row v-for="item in visitors" :key="item.uid" class="">
<div style="cursor:pointer" class="onlineUsers" v-bind:class="{'cur': item.visitor_id==currentGuest }" v-on:click="talkTo(item.visitor_id,item.name)">
<el-col :span="4">
<el-avatar v-bind:class="{'imgGray': item.status==0 }" :size="40" :src="item.avator"></el-avatar>
</el-col>
<el-col :span="16" v-bind:class="{'imgGray': item.status==0 }">
<{item.name}>
</el-col>
</div>
</el-row>
<el-pagination
background
@current-change="visitorPage"
:current-page="visitorCurrentPage"
layout="prev,pager, next"
:page-size="visitorPageSize"
:total="visitorCount">
</el-pagination>
</el-tab-pane>
</el-tabs>
</div>
</el-col>
<el-col :span="12">
<div class="sw-bg chatContext">
<el-alert
:title="chatTitle"
type="success">
</el-alert>
<div class="chatBox">
<el-row :gutter="2" v-for="v in msgList" v-bind:class="{'chatBoxMe': v.is_kefu==true}">
<div class="chatTime"><{v.time}></div>
<el-col :span="3"><el-avatar :size="60" :src="v.avator"></el-avatar></el-col>
<el-col :span="21">
<div class="chatUser"><{v.name}></div>
<div class="chatContent" v-html="v.content"></div>
</el-col>
</el-row>
</div>
<div class="faceBox kefuFaceBox">
<ul class="faceBoxList">
<li v-on:click="faceIconClick(i)" class="faceIcon" v-for="(v,i) in face" :title="v.name"><img :src=v.path></li>
</ul>
<div class="clear"></div>
</div>
<el-input type="textarea" class="chatArea" v-model="messageContent" v-on:keyup.enter.native="chatToUser"></el-input>
<div class="faceBtn"></div>
<div class="imageBtn" id="uploadImg" v-on:click="uploadImg('/uploadimg')"></div>
<el-button class="floatRight" type="primary" v-on:click="chatToUser">发送</el-button>
<div class="clear"></div>
</div>
</el-col>
<el-col :span="6">
<div class="chatBg chatRight">
<el-tabs v-model="rightTabActive" @tab-click="handleTabClick">
<el-tab-pane label="访客信息" name="visitorInfo">
<el-menu>
<el-tooltip content="点击加入黑名单" placement="left">
<el-menu-item v-on:click="addIpblack(visitor.source_ip)" title="点击加入黑名单" style="padding-left:2px;">
<i class="el-icon-s-tools"></i>
<span slot="title">ClientIP<{visitor.source_ip}></span>
</el-menu-item>
</el-tooltip>
<el-tooltip content="点击加入黑名单" placement="left">
<el-menu-item v-on:click="addIpblack(visitor.client_ip)" title="点击加入黑名单" style="padding-left:2px;">
<i class="el-icon-s-tools"></i>
<span slot="title">IP<{visitor.client_ip}></span>
</el-menu-item>
</el-tooltip>
<el-menu-item v-on:click="openUrl('https://www.baidu.com/s?wd='+visitor.client_ip)" style="padding-left:2px;">
<i class="el-icon-s-tools"></i>
<span slot="title">城市:<{visitor.city}></span>
</el-menu-item>
<el-popover
ref="popover"
placement="top"
title="来源"
width="300"
trigger="hover"
:content="visitor.refer">
</el-popover>
<el-menu-item v-popover:popover style="padding-left:2px;">
<i class="el-icon-s-tools"></i>
<span slot="title" >来源:<{visitor.refer}></span>
</el-menu-item>
<el-menu-item style="padding-left:2px;">
<i class="el-icon-s-tools"></i>
<span slot="title">时间:<{visitor.created_at}></span>
</el-menu-item>
<el-menu-item style="padding-left:2px;">
<i class="el-icon-s-tools"></i>
<span slot="title">状态:<{visitor.status}></span>
</el-menu-item>
</el-menu>
</el-tab-pane>
<el-tab-pane label="黑名单" name="blackList">
<el-row v-for="item in visitors" :key="item.uid" class="">
<div style="cursor:pointer" class="onlineUsers imgGray">
待开发
</div>
</el-row>
</el-tab-pane>
</el-tabs>
</div>
</el-col>
</el-row>
</template>
</div>
</body>
<script>
var app=new Vue({
el: '#app',
delimiters:["<{","}>"],
data: {
fullscreenLoading:true,
leftTabActive:"first",
rightTabActive:"visitorInfo",
users:[],
usersMap:[],
server:getWsBaseUrl()+"/chat_server",
socket:null,
messageContent:"",
currentGuest:"",
msgList:[],
chatTitle:"暂时未处理咨询",
kfConfig:{
id : "kf_1",
name : "客服丽丽",
avator : "",
to_id : "",
},
visitor:{
refer:"",
client_ip:"",
city:"",
status:"",
source_ip:"",
created_at:"",
},
visitors:[],
visitorCount:0,
visitorCurrentPage:1,
visitorPageSize:10,
face:[],
},
methods: {
//跳转
openUrl(url) {
window.location.href = url;
},
sendKefuOnline(){
let mes = {}
mes.type = "kfOnline";
mes.data = this.kfConfig;
this.socket.send(JSON.stringify(mes));
},
//心跳
ping(){
let _this=this;
let mes = {}
mes.type = "ping";
mes.data = "";
setInterval(function () {
if(_this.socket!=null){
_this.socket.send(JSON.stringify(mes));
}
},5000)
},
//初始化websocket
initConn() {
let socket = new ReconnectingWebSocket(this.server);//创建Socket实例
this.socket = socket
this.socket.onmessage = this.OnMessage;
this.socket.onopen = this.OnOpen;
},
OnOpen() {
this.sendKefuOnline();
},
OnMessage(e) {
const redata = JSON.parse(e.data);
switch (redata.type){
case "allUsers":
this.handleOnlineUsers(redata.data);
//this.sendKefuOnline();
break;
case "userOnline":
this.addOnlineUser(redata.data);
//发送通知
let _this=this;
notify(redata.data.username, {
body: "来了",
icon: redata.data.avator
}, function(notification) {
//可直接打开通知notification相关联的tab窗口
window.focus();
$('#tab-first').trigger('click');
notification.close();
_this.talkTo(redata.data.uid,redata.data.username);
});
break;
case "userOffline":
this.removeOfflineUser(redata.data);
//this.sendKefuOnline();
break;
case "notice":
// if(!this.usersMap[redata.data.uid]){
// this.$notify({
// title: "通知",
// message: "新客户访问",
// type: 'success',
// duration: 0,
// });
// }
this.sendKefuOnline();
break;
}
// if (redata.type == "notice") {
// this.$notify({
// title: "通知",
// message: "新客户访问",
// type: 'success',
// duration: 0,
// });
//发送给客户我在线
// let mes = {}
// mes.type = "kfConnect";
// kfConfig.guest_id=redata.data[0].uid;
// mes.data = kfConfig;
// this.socket.send(JSON.stringify(mes));
//}
if (redata.type == "message") {
let msg = redata.data
let content = {}
let _this=this;
content.avator = msg.avator;
content.name = msg.name;
content.content = replaceContent(msg.content);
content.is_kefu = false;
content.time = msg.time;
if (msg.id == this.currentGuest) {
this.msgList.push(content);
}
//发送通知
notify(msg.name, {
body: msg.content,
icon: msg.avator
}, function(notification) {
//可直接打开通知notification相关联的tab窗口
window.focus();
notification.close();
_this.talkTo(msg.id,msg.name);
});
this.scrollBottom();
}
},
//接手客户
talkTo(guestId,name) {
this.currentGuest = guestId;
this.chatTitle=name+"|"+guestId+",正在处理中...";
//发送给客户
let mes = {}
mes.type = "kfConnect";
this.kfConfig.to_id=guestId;
mes.data = this.kfConfig;
this.socket.send(JSON.stringify(mes));
//获取当前访客信息
this.getVistorInfo(guestId);
//获取当前客户消息
this.getMesssagesByVisitorId(guestId);
},
//发送给客户
chatToUser() {
this.messageContent=this.messageContent.trim("\r\n");
if(this.messageContent==""||this.messageContent=="\r\n"||this.currentGuest==""){
return;
}
let _this=this;
let mes = {};
mes.type = "kefu";
mes.content = this.messageContent;
mes.from_id = this.kfConfig.id;
mes.to_id = this.currentGuest;
mes.content = this.messageContent;
$.post("/message",mes,function(){
_this.messageContent = "";
});
let content = {}
content.avator = this.kfConfig.avator;
content.name = this.kfConfig.name;
content.content = replaceContent(this.messageContent);
content.is_kefu = true;
content.time = '';
this.msgList.push(content);
this.scrollBottom();
},
//处理当前在线用户列表
addOnlineUser:function (retData) {
let vid=retData.uid;
this.users.push(retData);
for(let i=0;i<this.visitors.length;i++){
if(this.visitors[i].visitor_id==vid){
this.visitors[i].status=1;
break;
}
}
},
//处理当前在线用户列表
removeOfflineUser:function (retData) {
for(let i=0;i<this.users.length;i++){
if(this.users[i].uid==retData.uid){
this.users.splice(i,1);
}
}
let vid=retData.uid;
for(let i=0;i<this.visitors.length;i++){
if(this.visitors[i].visitor_id==vid){
this.visitors[i].status=0;
break;
}
}
},
//处理当前在线用户列表
handleOnlineUsers:function (retData) {
this.users = retData;
if (this.currentGuest == "") {
this.chatTitle = "连接成功,等待处理中...";
}
this.usersMap=[];
for(let i=0;i<retData.length;i++){
this.usersMap[retData[i].uid]=retData[i].username;
}
for(let i=0;i<this.visitors.length;i++){
let vid=this.visitors[i].visitor_id;
if(typeof this.usersMap[vid]=="undefined"){
this.visitors[i].status=0;
}else{
this.visitors[i].status=1;
}
}
},
//获取客服信息
getKefuInfo(){
let _this=this;
$.ajax({
type:"get",
url:"/kefuinfo",
headers:{
"token":localStorage.getItem("token")
},
success: function(data) {
if(data.code==200 && data.result!=null){
_this.kfConfig.id=data.result.id;
_this.kfConfig.name=data.result.name;
_this.kfConfig.avator=data.result.avator;
_this.initConn();
}
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
}
}
});
},
//获取信息列表
getMesssagesByVisitorId(visitorId){
let _this=this;
$.ajax({
type:"get",
url:"/messages?visitorId="+visitorId,
headers:{
"token":localStorage.getItem("token")
},
success: function(data) {
if(data.code==200 && data.result!=null){
let msgList=data.result;
_this.msgList=[];
for(let i=0;i<msgList.length;i++){
let visitorMes=msgList[i];
let content = {}
if(visitorMes["mes_type"]=="kefu"){
content.is_kefu = true;
content.avator = visitorMes["kefu_avator"];
content.name = visitorMes["kefu_name"];
}else{
content.is_kefu = false;
content.avator = visitorMes["visitor_avator"];
content.name = visitorMes["visitor_name"];
}
content.content = replaceContent(visitorMes["content"]);
content.time = visitorMes["time"];
_this.msgList.push(content);
_this.scrollBottom();
}
}
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
}
}
});
},
//获取客服信息
getVistorInfo(vid){
let _this=this;
$.ajax({
type:"get",
url:"/visitor",
data:{visitorId:vid},
headers:{
"token":localStorage.getItem("token")
},
success: function(data) {
if(data.result!=null){
let r=data.result;
_this.visitor.created_at=r.created_at;
_this.visitor.refer=r.refer;
_this.visitor.city=r.city;
_this.visitor.client_ip=r.client_ip;
_this.visitor.source_ip=r.source_ip;
_this.visitor.status=r.status==1?"在线":"离线";
}
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
}
}
});
},
//处理tab切换
handleTabClick(tab, event){
let _this=this;
if(tab.name=="second"){
this.getVisitorPage(1);
}
if(tab.name=="blackList"){
}
},
//所有访客分页展示
visitorPage(page){
this.getVisitorPage(page);
},
//获取访客分页
getVisitorPage(page){
let _this=this;
$.ajax({
type:"get",
url:"/visitors",
data:{page:page},
headers:{
"token":localStorage.getItem("token")
},
success: function(data) {
if(data.result.list!=null){
_this.visitors=data.result.list;
_this.visitorCount=data.result.count;
_this.visitorPageSize=data.result.pagesize;
}
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
}
}
});
},
//滚到底部
scrollBottom(){
this.$nextTick(() => {
$('.chatBox').scrollTop($(".chatBox")[0].scrollHeight);
});
},
//jquery
initJquery(){
this.$nextTick(() => {
var _this=this;
$(function () {
//展示表情
var faces=placeFace();
$.each(faceTitles, function (index, item) {
_this.face.push({"name":item,"path":faces[item]});
});
$(".faceBtn").click(function(){
var status=$('.faceBox').css("display");
if(status=="block"){
$('.faceBox').hide();
}else{
$('.faceBox').show();
}
});
});
});
},
//表情点击事件
faceIconClick(index){
$('.faceBox').hide();
this.messageContent+="face"+this.face[index].name;
},
//上传图片
uploadImg (url){
let _this=this;
$('#uploadImg').after('<input type="file" accept="image/gif,image/jpeg,image/jpg,image/png" id="uploadImgFile" name="file" style="display:none" >');
$("#uploadImgFile").click();
$("#uploadImgFile").change(function (e) {
var formData = new FormData();
var file = $("#uploadImgFile")[0].files[0];
formData.append("imgfile",file); //传给后台的file的key值是可以自己定义的
filter(file) && $.ajax({
url: url || '',
type: "post",
data: formData,
contentType: false,
processData: false,
dataType: 'JSON',
mimeType: "multipart/form-data",
success: function (res) {
if(res.code!=200){
_this.$message({
message: res.msg,
type: 'error'
});
}else{
_this.messageContent+='img[/' + res.result.path + ']';
_this.chatToUser();
}
},
error: function (data) {
console.log(data);
}
});
});
},
addIpblack(ip){
let _this=this;
$.ajax({
type:"post",
url:"/ipblack",
data:{ip:ip},
headers:{
"token":localStorage.getItem("token")
},
success: function(data) {
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
}else{
_this.$message({
message: data.msg,
type: 'success'
});
}
}
});
},
openUrl(url){
window.open(url);
},
},
created: function () {
//jquery
this.initJquery();
this.getKefuInfo();
//心跳
this.ping();
}
})
</script>
</html>