pull/30/head
陶士涵 4 years ago
commit e2346f11c0

@ -16,6 +16,7 @@ const AccountConf = Dir + "account.json"
const MysqlConf = Dir + "mysql.json"
const MailConf = Dir + "mail.json"
const LangConf=Dir+"language.json"
const MainConf = Dir + "config.json"
type Mysql struct{
Server string
Port string
@ -27,14 +28,23 @@ type MailServer struct {
Server, Email, Password string
}
type Config struct {
Mysql *Mysql
Upload string
}
func CreateConfig()*Config{
mysql :=CreateMysql()
var configObj Config
c:=&Config{
Mysql: mysql,
Upload: "static/upload/",
}
return c
isExist, _ := tools.IsFileExist(MainConf)
if !isExist {
return c
}
info, err := ioutil.ReadFile(MainConf)
if err != nil {
return c
}
err = json.Unmarshal(info, &configObj)
return &configObj
}
func CreateMailServer() *MailServer {
var imap MailServer

@ -64,3 +64,13 @@ CREATE TABLE `role` (
`name` varchar(100) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
DROP TABLE IF EXISTS `welcome`;
CREATE TABLE `welcome` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` varchar(100) NOT NULL DEFAULT '',
`content` varchar(500) NOT NULL DEFAULT '',
`is_default` tinyint(3) unsigned NOT NULL DEFAULT '0',
`ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

@ -2,9 +2,15 @@ package controller
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/taoshihan1991/imaptool/config"
"github.com/taoshihan1991/imaptool/models"
"github.com/taoshihan1991/imaptool/tools"
"os"
"path"
"strings"
"time"
)
// @Summary 发送消息接口
@ -106,3 +112,39 @@ func SendMessage(c *gin.Context) {
"msg": "ok",
})
}
func UploadImg(c *gin.Context){
config:=config.CreateConfig()
f, err := c.FormFile("imgfile")
if err != nil {
c.JSON(200, gin.H{
"code": 400,
"msg": "上传失败!",
})
return
} else {
fileExt:=strings.ToLower(path.Ext(f.Filename))
if fileExt!=".png"&&fileExt!=".jpg"&&fileExt!=".gif"&&fileExt!=".jpeg"{
c.JSON(200, gin.H{
"code": 400,
"msg": "上传失败!只允许png,jpg,gif,jpeg文件",
})
return
}
fileName:=tools.Md5(fmt.Sprintf("%s%s",f.Filename,time.Now().String()))
fildDir:=fmt.Sprintf("%s%d%s/",config.Upload,time.Now().Year(),time.Now().Month().String())
isExist,_:=tools.IsFileExist(fildDir)
if !isExist{
os.Mkdir(fildDir,os.ModePerm)
}
filepath:=fmt.Sprintf("%s%s%s",fildDir,fileName,fileExt)
c.SaveUploadedFile(f, filepath)
c.JSON(200, gin.H{
"code": 200,
"msg": "上传成功!",
"result":gin.H{
"path":filepath,
},
})
}
}

@ -15,18 +15,25 @@ func GetNotice(c *gin.Context) {
kefuId:=c.Query("kefu_id")
lang,_:=c.Get("lang")
language:=config.CreateLanguage(lang.(string))
welcome:=models.FindWelcomeByUserId(kefuId)
user:=models.FindUser(kefuId)
info:=make(map[string]interface{})
info["nickname"]=user.Nickname
info["avator"]=user.Avator
info["name"]=user.Name
info["content"]=language.Notice
info["time"]=time.Now().Format("2006-01-02 15:04:05")
var content string
log.Println(welcome)
if welcome.Content!=""{
content=welcome.Content
}else {
content=language.Notice
}
c.JSON(200, gin.H{
"code": 200,
"msg": "ok",
"result":info,
"result":gin.H{
"nickname":user.Nickname,
"avator":user.Avator,
"name":user.Name,
"content":content,
"time":time.Now().Format("2006-01-02 15:04:05"),
},
})
}
var upgrader = websocket.Upgrader{}

@ -27,7 +27,8 @@ func NewTcpServer(tcpBaseServer string){
}
func PushServerTcp(str []byte){
for ip,conn:=range clientTcpList{
_,err:=conn.Write(str)
line:=append(str,[]byte("\r\n")...)
_,err:=conn.Write(line)
log.Println(ip,err)
if err!=nil{
conn.Close()
@ -43,6 +44,10 @@ func DeleteOnlineTcp(c *gin.Context) {
conn.Close()
delete(clientTcpList,ip)
}
if ip=="all"{
conn.Close()
delete(clientTcpList,ipkey)
}
}
c.JSON(200, gin.H{
"code": 200,

@ -53,6 +53,7 @@ func main() {
engine := gin.Default()
engine.LoadHTMLGlob("static/html/*")
engine.Static("/static", "./static")
//首页
engine.GET("/", controller.Index)
engine.GET("/index", tmpl.PageIndex)
@ -60,6 +61,7 @@ func main() {
engine.GET("/login", tmpl.PageLogin)
//咨询界面
engine.GET("/chat_page",middleware.SetLanguage, tmpl.PageChat)
engine.GET("/chatIndex",middleware.SetLanguage, tmpl.PageChat)
//登陆验证
engine.POST("/check", controller.LoginCheckPass)
//框架界面
@ -74,6 +76,8 @@ func main() {
engine.GET("/messages", controller.GetVisitorMessage)
//发送单条消息
engine.POST("/message",controller.SendMessage)
//上传文件
engine.POST("/uploadimg",controller.UploadImg)
//获取未读消息数
engine.GET("/message_status",controller.GetVisitorMessage)
//设置消息已读

@ -0,0 +1,16 @@
package models
import "time"
type Welcome struct {
ID uint `gorm:"primary_key" json:"id"`
UserId string `json:"user_id"`
Content string `json:"content"`
IsDefault uint `json:"is_default"`
Ctime time.Time `json:"ctime"`
}
func FindWelcomeByUserId(userId interface{})Welcome{
var w Welcome
DB.Where("user_id = ? and is_default=?", userId,1).First(&w)
return w
}

@ -1,15 +1,21 @@
*{padding:0;margin:0}
.floatRight{float: right;}
.clear{clear: both;}
.faceBtn, .faceBtn:after, .faceBtn {
border: 1px solid;
}
.iconBtns{
border-top:1px solid #e4e4e4;
border-bottom:1px solid #e4e4e4;
padding: 2px 0;
}
.visitorFaceBtn{
float: right;
margin: 16px 4px 0 0;
float: left;
margin-left: 5px;
}
.visitorFaceBox{
position: absolute;
bottom: 70px;
bottom: 86px;
}
.kefuFaceBox{
position: absolute;
@ -68,6 +74,52 @@
top: 10%;
width: 15px;
}
.imageBtn {
width: 32px;
height: 23px;
overflow: hidden;
display: inline-block;
vertical-align: middle;
position: relative;
font-style: normal;
color: #9da0a0;
text-align: left;
text-indent: -9999px;
direction: ltr;
border: 1px solid;
}
.imageBtn:before {
content: '';
position: absolute;
width: 17px;
height: 16px;
left: -2px;
top: 10px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
box-shadow: inset 0 0 0 32px, 10px -6px 0 0;
}
.imageBtn:after {
content: '';
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-o-border-radius: 50%;
border-radius: 50%;
position: absolute;
width: 3px;
height: 3px;
box-shadow: inset 0 0 0 32px;
top: 5px;
right: 5px
}
.visitorImageBtn{
float: left;
margin-left: 20px;
margin-top: 2px;
}
.faceBox{
width: 100%;
background: #fff;

@ -132,6 +132,7 @@
</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>
@ -575,6 +576,40 @@
$('.faceBox').hide();
this.messageContent+="face"+this.face[index].name;
},
//上传图片
uploadImg (url){
let _this=this;
$('#uploadImg').after('<input type="file" 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);
}
});
});
},
},
created: function () {
//jquery

@ -6,18 +6,18 @@
<meta name="author" content="陶士涵">
<title>GO-FLY咨询页</title>
<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="/static/js/functions.js?v=0.1.1"></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>
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="/static/css/common.css" />
<link rel="stylesheet" href="/static/css/common.css?v=0.1.1" />
<style>
html,
body {
height: 100%;
background: rgb(245,245,245);
background: #fff;
}
.chatCenter{background: #fff;max-width: 800px;margin: 0 auto;}
.chatContext{
@ -25,7 +25,7 @@
width: 100%;
text-align: left;
position: relative;
margin-bottom: 69px;
margin-bottom: 86px;
}
.chatBox{
/*max-height: 600px;*/
@ -72,15 +72,16 @@
.chatBoxMe .el-col-3{float: right;text-align: right;}
.chatBoxMe .chatUser{text-align: right}
.chatBoxMe .chatContent:after{left:auto;right: -10px;}
.chatArea{float: left;width: 76%;margin: 4px 0 0 4px;}
.btnArea{width: 20%;float: right;}
.chatArea{float: left;width: 85%;margin: 4px 0 0 4px;}
.btnArea{width: 10%;float: right;}
@media screen and (max-width: 500px) {
.chatArea {width: 65%;}
.btnArea{width: 30%;}
body{background: #fff}
.chatArea {width: 70%;}
.btnArea{width: 20%;}
}
.chatTitle{height: 30px;line-height: 30px;color: #1989fa}
.chatBoxSend{background: #fff;position: fixed;bottom: 0;width: 100%;height: 67px;max-width: 800px;}
.chatBoxSend{background: #f5f5f5;position: fixed;bottom: 0;width: 100%;height: 86px;max-width: 800px;}
.chatBoxSendBtn{float: right;margin: 12px 4px 0 0;}
.chatTime{text-align: center;color: #bbb;margin: 5px 0;font-size: 12px;}
.chatTimeHide{display: none;}
@ -117,6 +118,11 @@
</div>
</div>
<div class="chatBoxSend">
<div class="iconBtns">
<div class="faceBtn visitorFaceBtn"></div>
<div class="imageBtn visitorImageBtn" id="uploadImg" v-on:click="uploadImg('/uploadimg')"></div>
<div class="clear"></div>
</div>
<el-input type="textarea" class="chatArea" v-model="messageContent" v-on:keyup.enter.native="chatToUser"></el-input>
<div class="faceBox visitorFaceBox">
<ul class="faceBoxList">
@ -126,8 +132,6 @@
</div>
<div class="btnArea">
<el-button type="primary" class="chatBoxSendBtn" size="small" v-on:click="chatToUser">{{.SendBtn}}</el-button>
<div class="faceBtn visitorFaceBtn"></div>
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
@ -186,7 +190,7 @@
if (redata.type == "kfOnline") {
let msg = redata.data
guest.to_id=msg.id;
this.chatTitle=msg.name+",为您服务!"
this.chatTitle=msg.name+",正在与您沟通!"
$(".chatBox").append("<div class=\"chatTime\">"+this.chatTitle+"</div>");
this.scrollBottom();
}
@ -374,7 +378,7 @@
setTimeout(function () {
_this.msgList.push(content);
_this.scrollBottom();
}, 2000);
}, 4000);
;
}
});
@ -435,6 +439,40 @@
$('.faceBox').hide();
this.messageContent+="face"+this.face[index].name;
},
//上传图片
uploadImg (url){
let _this=this;
$('#uploadImg').after('<input type="file" 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);
}
});
});
},
},
created: function () {
this.init();

@ -1,97 +1,77 @@
<html lang="cn">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="description" content="">
<meta name="author" content="陶士涵">
<title>GO-FLY登录页</title>
<title>GO-FLY客服系统登录页</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.13.1/lib/theme-chalk/index.css">
<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.bootcdn.net/ajax/libs/layer/3.1.1/layer.min.js"></script>
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<style>
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
html,
body {
height: 100%;
}
body {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
margin: 0;
padding: 0;
}
.signin {
width: 100%;
max-width: 400px;
width: 350px;
padding: 20px;
margin:0 auto;
margin:100px auto;
background: #fff;
-webkit-box-shadow: 0 1px 2px 0 rgba(101,129,156,.08);
box-shadow: 0 1px 2px 0 rgba(101,129,156,.08);
}
.chatBtn{
position: fixed;
right: 10px;
bottom: 10px;
.signin h1,.signin h2,.signin .copyright{
font-weight: normal;
color: #4d627b;
text-align: center;
}
.signin .loginTitle{
font-size: 24px;
}
.signin .loginDesc{
font-size: 14px;
margin-bottom: 15px;
}
.signin .loginDesc a{
color: #409EFF;
text-decoration: none;
}
.signin .copyright{
font-size: 12px;
}
@media (max-width: 768px) {
.signin{
width: 90%;
margin:40px auto;
background-color: #f5f5f5;
box-shadow:none;
}
}
</style>
</head>
<body class="text-center">
<body>
<div id="app" class="signin">
<template>
<h1 class="h3 mb-3 font-weight-normal">登录页</h1>
<el-tabs v-model="activeName">
<el-tab-pane label="管理员登陆" name="first">
<el-form :model="localAuth" :rules="rules" ref="localAuth">
<el-form-item prop="username">
<el-input v-model="localAuth.username" placeholder="用户名"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="localAuth.password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button :loading="loading" type="primary" @click="checkLocal('localAuth')">本地验证</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="客服登陆" name="second">
<el-form :model="kefuForm" :rules="rules" ref="kefuForm">
<el-form-item prop="username">
<el-input v-model="kefuForm.username" placeholder="用户名"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="kefuForm.password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button :loading="loading" type="primary" @click="kefuLogin('kefuForm')">客服登陆</el-button>
<el-button type="primary" @click="showKefu">访客咨询</el-button>
<el-button type="primary" @click="window.location.href='/docs/index.html'">接口文档</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<p class="mt-5 mb-3 text-muted">&copy; 2020</p>
<!--客服代码-->
<div class="chatBtn">
<el-button type="primary" @click="showKefu">在线咨询</el-button>
</div>
<!--//客服代码-->
</template>
<h1 class="loginTitle">账户登录</h1>
<h2 class="loginDesc">请联系<a href="/chatIndex?kefu_id=kefu2">开发者</a>获取登录账号</h2>
<el-form :model="kefuForm" :rules="rules" ref="kefuForm">
<el-form-item prop="username">
<el-input v-model="kefuForm.username" placeholder="用户名"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="kefuForm.password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button style="width: 100%" :loading="loading" type="primary" @click="kefuLogin('kefuForm')">登录</el-button>
</el-form-item>
</el-form>
<p class="copyright">陶士涵版权所有 &copy; 2020 </p>
</template>
</div>
</body>
<script>
@ -168,55 +148,6 @@
resetForm(formName) {
this.loading=false;
this.$refs[formName].resetFields();
},
//本地验证
checkLocal(formName){
let _this=this;
this.$refs[formName].validate((valid) => {
if (valid) {
var data={}
data.type="local";
data.username=_this.localAuth.username;
data.password=_this.localAuth.password;
_this.loading=true;
$.post("/check",data,function(data){
if(data.code==200){
_this.$message({
message: data.msg,
type: 'success'
});
localStorage.setItem("token",data.result.token);
localStorage.setItem("ref_token",data.result.ref_token);
localStorage.setItem("create_time",data.result.create_time);
window.location.href="/main";
}else{
_this.$message({
message: data.msg,
type: 'error'
});
}
_this.loading=false;
});
} else {
return false;
}
});
},
//展示客服弹窗
showKefu:function () {
$(".chatBtn").hide();
layer.open({
type: 2,
title: '在线咨询',
shadeClose: true,
shade: false,
maxmin: true, //开启最大化最小化按钮
area: ['550px', '520px'],
content: ['/chat_page','no'],
end: function(){
$(".chatBtn").show();
}
});
},
},
created: function () {

@ -2,7 +2,7 @@
<el-menu
default-active="3"
mode="horizontal">
<el-menu-item class="mainLogo" v-on:click="openUrl('/login')">GO-FLY<span class="version">V0.0.8</span></el-menu-item>
<el-menu-item class="mainLogo" v-on:click="openUrl('/login')">GO-FLY<span class="version">V0.1.1</span></el-menu-item>
<el-menu-item style="display:none" index="2" v-on:click="openIframeUrl('/list')">邮箱<el-badge class="mark" :value="mailTotal" style="margin-bottom: 20px;"/>
</el-menu-item>
<el-menu-item index="3" v-on:click="openIframeUrl('/chat_main')">聊天</el-menu-item>

@ -73,8 +73,24 @@ function replaceContent (content) {// 转义聊天内容中的特殊字符
var alt = face.replace(/^face/g, '');
return '<img alt="' + alt + '" title="' + alt + '" src="' + faces[alt] + '">';
})
.replace(/img\[([^\s\[\]]+?)\]/g, function (face) { // 转义图片
var src = face.replace(/^img\[/g, '').replace(/\]/g, '');;
return '<img src="' + src + '" style="max-width: 100%"/>';
})
.replace(html(), '\<$1 $2\>').replace(html('/'), '\</$1\>') // 转移HTML代码
.replace(/\n/g, '<br>') // 转义换行
return content;
}
function filter (obj){
var imgType = ["image/jpeg","image/png","image/jpg"];
var filetypes = imgType;
var isnext = false;
for (var i = 0; i < filetypes.length; i++) {
if (filetypes[i] == obj.type) {
return true;
}
}
return false;
}

@ -1,6 +1,11 @@
var launchButtonFlag=false;
$("#launchButton").click(function() {
if (launchButtonFlag) return;
var width=$(window).width();
if(width<768){
window.open(GOFLY_URL+'/chatIndex?refer='+window.location.host);
return;
}
layer.open({
type: 2,
title: "Chat with us",

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Loading…
Cancel
Save