201 lines
7.6 KiB
HTML
201 lines
7.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>柏悦物流 - 管理后台</title>
|
|
<style>
|
|
body{font-family:-apple-system,"Microsoft YaHei",sans-serif;background:#f0f4fa;margin:0;padding:20px;}
|
|
h1{color:#18479c;}
|
|
.card{background:#fff;border-radius:12px;padding:20px;margin-bottom:16px;box-shadow:0 2px 8px rgba(0,0,0,0.06);}
|
|
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;}
|
|
.stat{background:linear-gradient(135deg,#e8f0ff,#fff);border-radius:10px;padding:14px;text-align:center;}
|
|
.stat .num{font-size:24px;font-weight:bold;color:#2978e8;}
|
|
.stat .label{font-size:12px;color:#666;}
|
|
.layout{display:grid;grid-template-columns:220px 1fr;gap:16px;}
|
|
#sessionList{max-height:400px;overflow-y:auto;}
|
|
.session-item{padding:10px;border-radius:8px;cursor:pointer;margin-bottom:6px;background:#f5f8fc;font-size:13px;}
|
|
.session-item.active{background:#2978e8;color:white;}
|
|
.session-item .badge{background:#dc2626;color:white;border-radius:10px;padding:1px 6px;font-size:11px;margin-left:4px;}
|
|
#chatBox{height:350px;overflow-y:auto;background:#f8fafc;border-radius:8px;padding:10px;margin-bottom:10px;}
|
|
.msg{margin-bottom:8px;padding:6px 10px;border-radius:8px;max-width:70%;font-size:14px;}
|
|
.cust{background:#dbeafe;margin-right:auto;}
|
|
.agent{background:#dcfce7;margin-left:auto;}
|
|
.btn{background:#2978e8;color:#fff;border:none;padding:6px 14px;border-radius:6px;cursor:pointer;font-size:13px;}
|
|
.btn:hover{background:#18479c;}
|
|
.btn.red{background:#dc2626;}
|
|
input[type=text]{width:75%;padding:8px;border:1px solid #ddd;border-radius:6px;}
|
|
table{width:100%;border-collapse:collapse;font-size:13px;}
|
|
th,td{padding:6px 10px;border-bottom:1px solid #eee;text-align:left;}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>📊 柏悦物流客服后台</h1>
|
|
<div class="grid">
|
|
<div class="stat"><div class="num" id="s1">-</div><div class="label">总对话数</div></div>
|
|
<div class="stat"><div class="num" id="s2">-</div><div class="label">总消息数</div></div>
|
|
<div class="stat"><div class="num" id="s3">-</div><div class="label">今日新增</div></div>
|
|
<div class="stat"><div class="num" id="s4">-</div><div class="label">👍</div></div>
|
|
<div class="stat"><div class="num" id="s5">-</div><div class="label">👎</div></div>
|
|
</div>
|
|
|
|
<div class="layout">
|
|
<div class="card">
|
|
<h3>进行中对话</h3>
|
|
<div id="sessionList"></div>
|
|
<h3 style="margin-top:16px;">待接入</h3>
|
|
<div id="queue"></div>
|
|
</div>
|
|
<div class="card" id="chatCard" style="display:none;">
|
|
<h3 id="chatTitle">对话</h3>
|
|
<div id="chatBox"></div>
|
|
<input type="text" id="replyText" placeholder="输入回复,Enter发送...">
|
|
<button class="btn" onclick="sendReply()">发送</button>
|
|
<button class="btn red" onclick="closeSession()">结束会话</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>最近对话</h3>
|
|
<table><thead><tr><th>ID</th><th>标题</th><th>时间</th></tr></thead><tbody id="tbl"></tbody></table>
|
|
</div>
|
|
<a href="/">← 返回客服页</a>
|
|
|
|
<script>
|
|
var curSid = 0;
|
|
var sessions = {};
|
|
|
|
fetch("/api/admin/stats").then(function(r){return r.json()}).then(function(d){
|
|
s1.textContent=d.total_conv; s2.textContent=d.total_msg;
|
|
s3.textContent=d.today_conv; s4.textContent=d.good; s5.textContent=d.bad;
|
|
});
|
|
|
|
function loadQueue(){
|
|
fetch("/api/human/queue").then(function(r){return r.json()}).then(function(list){
|
|
document.getElementById("queue").innerHTML = list.length
|
|
? list.map(function(x){return '<button class="btn" onclick="accept('+x.id+')">#'+x.id+' '+x.ctime+'</button>'}).join(" ")
|
|
: "暂无";
|
|
if(list.length && !sessions[list[0].id]){
|
|
accept(list[0].id);
|
|
}
|
|
});
|
|
}
|
|
setInterval(loadQueue, 3000);
|
|
|
|
// 加载已有进行中的对话
|
|
fetch("/api/human/active").then(function(r){return r.json()}).then(function(list){
|
|
list.forEach(function(s){
|
|
sessions[String(s.id)] = {lastId:0, msgs:[], unread:0};
|
|
});
|
|
renderSessionList();
|
|
if(list.length && !curSid) switchSession(String(list[0].id));
|
|
});
|
|
|
|
function accept(sid){
|
|
sid = String(sid);
|
|
if(sessions[sid]) return;
|
|
fetch("/api/human/accept",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:parseInt(sid)})}).then(function(){
|
|
sessions[sid] = {lastId:0, msgs:[], unread:0};
|
|
switchSession(sid);
|
|
});
|
|
}
|
|
|
|
function switchSession(sid){
|
|
curSid = String(sid);
|
|
document.getElementById("chatCard").style.display="block";
|
|
document.getElementById("chatTitle").textContent="对话 #"+sid;
|
|
document.getElementById("chatBox").innerHTML = "";
|
|
sessions[sid].msgs.forEach(function(m){
|
|
appendMsg(m);
|
|
});
|
|
sessions[sid].unread = 0;
|
|
renderSessionList();
|
|
}
|
|
|
|
function appendMsg(m){
|
|
var div=document.createElement("div");
|
|
div.className="msg "+(m.side==="customer"?"cust":"agent");
|
|
div.textContent=(m.side==="customer"?"客户: ":"客服: ")+m.content;
|
|
document.getElementById("chatBox").appendChild(div);
|
|
document.getElementById("chatBox").scrollTop=999999;
|
|
}
|
|
|
|
function renderSessionList(){
|
|
var el=document.getElementById("sessionList");
|
|
el.innerHTML="";
|
|
Object.keys(sessions).forEach(function(sid){
|
|
var unread = sessions[sid].unread || 0;
|
|
var div=document.createElement("div");
|
|
div.className="session-item"+(sid==curSid?" active":"");
|
|
div.innerHTML="对话 #"+sid+(unread>0?'<span class="badge">'+unread+'</span>':"");
|
|
div.onclick=function(){ switchSession(sid); };
|
|
el.appendChild(div);
|
|
});
|
|
}
|
|
|
|
function poll(){
|
|
// 先拉所有进行中的会话
|
|
fetch("/api/human/active").then(function(r){return r.json()}).then(function(list){
|
|
list.forEach(function(s){
|
|
if(!sessions[String(s.id)]){
|
|
sessions[String(s.id)] = {lastId:0, msgs:[], unread:0};
|
|
}
|
|
});
|
|
renderSessionList();
|
|
}).then(function(){
|
|
Object.keys(sessions).forEach(function(sid){
|
|
fetch("/api/human/poll?sid="+parseInt(sid)+"&after="+sessions[sid].lastId)
|
|
.then(function(r){return r.json()})
|
|
.then(function(d){
|
|
if(d.status==="closed"){
|
|
delete sessions[sid];
|
|
if(sid==curSid){ curSid=0; document.getElementById("chatCard").style.display="none"; }
|
|
renderSessionList();
|
|
return;
|
|
}
|
|
d.messages.forEach(function(m){
|
|
sessions[sid].lastId=m.id;
|
|
sessions[sid].msgs.push(m);
|
|
if(m.side==="customer" && sid!=curSid){
|
|
sessions[sid].unread=(sessions[sid].unread||0)+1;
|
|
renderSessionList();
|
|
}
|
|
if(sid==curSid) appendMsg(m);
|
|
});
|
|
})
|
|
.catch(function(){});
|
|
});
|
|
});
|
|
}
|
|
setInterval(poll, 1000);
|
|
|
|
function sendReply(){
|
|
var t=document.getElementById("replyText").value.trim();
|
|
if(!t || !curSid) return;
|
|
fetch("/api/human/send",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:parseInt(curSid),side:"agent",content:t})});
|
|
document.getElementById("replyText").value="";
|
|
document.getElementById("replyText").focus();
|
|
}
|
|
|
|
function closeSession(){
|
|
fetch("/api/human/close",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:parseInt(curSid)})});
|
|
delete sessions[curSid];
|
|
curSid=0;
|
|
document.getElementById("chatCard").style.display="none";
|
|
renderSessionList();
|
|
}
|
|
|
|
document.getElementById("replyText").addEventListener("keydown", function(e){
|
|
if(e.key==="Enter"){ e.preventDefault(); sendReply(); }
|
|
});
|
|
|
|
fetch("/api/get_history").then(function(r){return r.json()}).then(function(list){
|
|
var tb=document.getElementById("tbl");
|
|
list.slice(0,20).forEach(function(c){
|
|
tb.innerHTML+="<tr><td>"+c.id+"</td><td>"+c.title+"</td><td>"+(c.utime||c.ctime)+"</td></tr>";
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|