重构物流客服模块并完善会话接待工作台
- 拆分应用工厂、业务服务与路由,保留原有 HTTP/SSE 和存储入口 - 分离页面资源,重排客服工作台并完善响应式导航、草稿和附件交互 - 整理维护脚本、请求示例和贡献指南,移除启动脚本中的密钥配置 - 上传文件改为仅本地保留,补充隔离的后端与前端回归验证 验证:46 项 Python 测试通过,含 14 个已有子测试;12 个 Node 前端场景通过。
This commit is contained in:
+84
-258
@@ -1,266 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>柏悦物流 - 客服工作台</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:-apple-system,"Microsoft YaHei",sans-serif;background:#f0f2f5;height:100vh;display:flex;flex-direction:column;}
|
||||
.topbar{background:#fff;padding:10px 20px;display:flex;align-items:center;gap:16px;box-shadow:0 1px 4px rgba(0,0,0,0.08);z-index:10;}
|
||||
.topbar .logo{font-size:18px;font-weight:bold;color:#18479c;}
|
||||
.topbar .stats{display:flex;gap:20px;font-size:13px;color:#666;margin-left:20px;}
|
||||
.topbar .stats b{color:#2978e8;font-size:16px;}
|
||||
.topbar .status{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:13px;color:#666;}
|
||||
.topbar .dot{width:8px;height:8px;border-radius:50%;background:#22c55e;}
|
||||
.layout{flex:1;display:grid;grid-template-columns:260px 1fr 240px;overflow:hidden;}
|
||||
/* 左栏:对话列表 */
|
||||
.conv-list{background:#fff;border-right:1px solid #e8e8e8;overflow-y:auto;}
|
||||
.conv-list h3{padding:12px 14px;font-size:13px;color:#999;border-bottom:1px solid #f0f0f0;}
|
||||
.conv-item{padding:12px 14px;cursor:pointer;border-bottom:1px solid #f5f5f5;display:flex;align-items:center;gap:10px;}
|
||||
.conv-item:hover{background:#f5f8fc;}
|
||||
.conv-item.active{background:#e8f0ff;border-left:3px solid #2978e8;}
|
||||
.conv-item .avatar{width:36px;height:36px;border-radius:50%;background:#dbeafe;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;}
|
||||
.conv-item .info{flex:1;min-width:0;}
|
||||
.conv-item .name{font-size:14px;font-weight:500;}
|
||||
.conv-item .preview{font-size:12px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.conv-item .badge{background:#dc2626;color:#fff;border-radius:10px;padding:1px 7px;font-size:11px;}
|
||||
.queue-item{padding:10px 14px;background:#fff7ed;border-bottom:1px solid #ffedd5;}
|
||||
.queue-item .qt{font-size:13px;color:#ea580c;font-weight:500;}
|
||||
.queue-item button{margin-top:6px;width:100%;background:#ea580c;color:#fff;border:none;padding:5px;border-radius:4px;cursor:pointer;font-size:12px;}
|
||||
/* 中间:聊天 */
|
||||
.chat-area{display:flex;flex-direction:column;background:#f8fafc;}
|
||||
.chat-header{background:#fff;padding:12px 20px;border-bottom:1px solid #e8e8e8;display:flex;align-items:center;justify-content:space-between;}
|
||||
.chat-header .ct{font-size:15px;font-weight:500;}
|
||||
.chat-header .close{background:#dc2626;color:#fff;border:none;padding:6px 14px;border-radius:4px;cursor:pointer;font-size:13px;}
|
||||
#chatBox{flex:1;overflow-y:auto;padding:20px;}
|
||||
.msg{margin-bottom:12px;display:flex;}
|
||||
.msg .bubble{max-width:60%;padding:10px 14px;border-radius:12px;font-size:14px;line-height:1.5;overflow-wrap:break-word;}
|
||||
.msg.cust{justify-content:flex-start;}
|
||||
.msg.cust .bubble{background:#fff;border:1px solid #e8e8e8;border-top-left-radius:4px;}
|
||||
.msg.agent{justify-content:flex-end;}
|
||||
.msg.agent .bubble{background:#2978e8;color:#fff;border-top-right-radius:4px;}
|
||||
.msg .meta{font-size:11px;color:#aaa;margin-top:3px;}
|
||||
.input-bar{background:#fff;border-top:1px solid #e8e8e8;padding:12px 20px;display:flex;gap:10px;}
|
||||
.input-bar input{flex:1;border:1px solid #ddd;border-radius:6px;padding:10px 12px;font-size:14px;outline:none;}
|
||||
.input-bar input:focus{border-color:#2978e8;}
|
||||
.input-bar button{background:#2978e8;color:#fff;border:none;padding:0 20px;border-radius:6px;cursor:pointer;font-size:14px;}
|
||||
/* 右栏:客户信息+快捷回复 */
|
||||
.side{background:#fff;border-left:1px solid #e8e8e8;overflow-y:auto;}
|
||||
.side .card{padding:16px;border-bottom:1px solid #f0f0f0;}
|
||||
.side h4{font-size:13px;color:#999;margin-bottom:10px;}
|
||||
.side .info-row{font-size:13px;padding:4px 0;color:#333;}
|
||||
.side .quick{padding:8px 12px;background:#f5f8fc;border-radius:6px;margin-bottom:6px;font-size:13px;cursor:pointer;}
|
||||
.side .quick:hover{background:#e8f0ff;}
|
||||
.empty{display:flex;align-items:center;justify-content:center;height:100%;color:#bbb;font-size:14px;}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>客服工作台 · 柏悦物流</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='fa.jpg') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/theme.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<div class="logo">🎧 柏悦客服工作台</div>
|
||||
<div class="stats">
|
||||
<span>对话 <b id="s1">-</b></span>
|
||||
<span>消息 <b id="s2">-</b></span>
|
||||
<span>今日 <b id="s3">-</b></span>
|
||||
<span>👍 <b id="s4">-</b></span>
|
||||
<span>👎 <b id="s5">-</b></span>
|
||||
</div>
|
||||
<div class="status"><a href="/api/export_chat" style="font-size:13px;color:#2978e8;text-decoration:none;margin-right:16px;">导出聊天记录</a><span class="dot"></span> 在线</div>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
<div class="conv-list">
|
||||
<h3>待接入</h3>
|
||||
<div id="queue"></div>
|
||||
<h3>进行中</h3>
|
||||
<div id="sessionList"></div>
|
||||
</div>
|
||||
|
||||
<div class="chat-area" id="chatArea">
|
||||
<div class="empty" id="emptyTip">← 选择左侧对话开始服务</div>
|
||||
<div id="chatContent" style="display:none;flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
||||
<div class="chat-header">
|
||||
<span class="ct" id="chatTitle">对话</span>
|
||||
<button class="close" onclick="closeSession()">结束会话</button>
|
||||
</div>
|
||||
<div id="chatBox"></div>
|
||||
<div class="input-bar">
|
||||
<input type="text" id="replyText" placeholder="输入回复,Enter发送">
|
||||
<button onclick="sendReply()">发送</button>
|
||||
</div>
|
||||
<header class="topbar">
|
||||
<div class="app-identity">
|
||||
<a class="brand" href="/admin/dashboard"><span class="brand-mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4 7 8-4 8 4v10l-8 4-8-4Z"/><path d="m4 7 8 4 8-4M12 11v10M8 5l8 4"/></svg></span><span class="brand-name">柏悦物流</span></a>
|
||||
<span class="nav-divider" aria-hidden="true"></span><h1>客服工作台</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="side">
|
||||
<div class="card">
|
||||
<h4>客户信息</h4>
|
||||
<div class="info-row" id="cInfo">选择对话后显示</div>
|
||||
<div class="topbar-actions">
|
||||
<a class="workspace-link" href="/">客户入口 ↗</a>
|
||||
<details class="data-overview" id="data-overview">
|
||||
<summary><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M4 4v16h17M8 16v-5M13 16V7M18 16V3"/></svg>数据概览</summary>
|
||||
<div class="stats" aria-label="历史对话统计">
|
||||
<h2>历史对话与反馈</h2>
|
||||
<div class="stat"><span>历史对话</span><b id="s1">—</b></div>
|
||||
<div class="stat"><span>历史消息</span><b id="s2">—</b></div>
|
||||
<div class="stat"><span>今日新对话</span><b id="s3">—</b></div>
|
||||
<div class="stat"><span>有帮助</span><b id="s4">—</b></div>
|
||||
<div class="stat"><span>需改进</span><b id="s5">—</b></div>
|
||||
<a class="export-link" href="/api/export_chat">导出记录 ↗</a>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h4>快捷回复</h4>
|
||||
<div class="quick" onclick="quickReply('您好,请问有什么可以帮您?')">您好,请问有什么可以帮您?</div>
|
||||
<div class="quick" onclick="quickReply('请提供运单号,我帮您查询。')">请提供运单号</div>
|
||||
<div class="quick" onclick="quickReply('已为您催促,请稍候。')">已催促,请稍候</div>
|
||||
<div class="quick" onclick="quickReply('感谢您的咨询,祝您生活愉快!')">感谢咨询</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h4>离线留言</h4>
|
||||
<div id="ticketList"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 '<div class="queue-item"><div class="qt">#'+x.id+' '+x.ctime+'</div><button onclick="accept('+x.id+')">接入</button></div>'}).join("")
|
||||
: '<div style="padding:10px 14px;font-size:12px;color:#ccc;">暂无排队</div>';
|
||||
if(list.length && !sessions[String(list[0].id)]) accept(list[0].id);
|
||||
});
|
||||
}
|
||||
setInterval(loadQueue, 3000);
|
||||
|
||||
function loadTickets(){
|
||||
fetch("/api/tickets").then(function(r){return r.json()}).then(function(list){
|
||||
var el = document.getElementById("ticketList");
|
||||
if(!list.length){ el.innerHTML = '<div style="font-size:12px;color:#ccc;">暂无留言</div>'; return; }
|
||||
el.innerHTML = list.map(function(t){
|
||||
return '<div style="padding:6px;background:#fff7ed;border-radius:6px;margin-bottom:6px;font-size:12px;">'+t.content+'<br><span style="color:#999;">'+t.ctime+'</span></div>';
|
||||
}).join("");
|
||||
});
|
||||
}
|
||||
setInterval(loadTickets, 5000);
|
||||
loadTickets();
|
||||
|
||||
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("emptyTip").style.display="none";
|
||||
document.getElementById("chatContent").style.display="flex";
|
||||
document.getElementById("chatTitle").textContent="对话 #"+sid;
|
||||
document.getElementById("cInfo").innerHTML="会话编号:#"+sid;
|
||||
document.getElementById("chatBox").innerHTML="";
|
||||
sessions[sid].msgs.forEach(function(m){ appendMsg(m); });
|
||||
sessions[sid].unread = 0;
|
||||
renderList();
|
||||
fetch("/api/human/read",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:parseInt(sid)})});
|
||||
}
|
||||
|
||||
function appendMsg(m){
|
||||
var div=document.createElement("div");
|
||||
div.className="msg "+(m.side==="customer"?"cust":"agent");
|
||||
var c = m.content.replace(/</g,"<");
|
||||
var imgMatch = c.match(/\[图片\](\/uploads\/\S+)/);
|
||||
if(imgMatch){
|
||||
c = c.replace(imgMatch[0], '<img src="'+imgMatch[1]+'" style="max-width:200px;border-radius:6px;display:block;">');
|
||||
}
|
||||
div.innerHTML='<div><div class="bubble">'+c+'</div><div class="meta">'+(m.side==="customer"?"客户":"客服")+'</div></div>';
|
||||
document.getElementById("chatBox").appendChild(div);
|
||||
document.getElementById("chatBox").scrollTop=999999;
|
||||
}
|
||||
|
||||
function renderList(){
|
||||
var el=document.getElementById("sessionList");
|
||||
el.innerHTML="";
|
||||
Object.keys(sessions).forEach(function(sid){
|
||||
var last = sessions[sid].msgs[sessions[sid].msgs.length-1];
|
||||
var preview = last ? last.content.substring(0,20) : "等待中...";
|
||||
var unread = sessions[sid].unread || 0;
|
||||
var div=document.createElement("div");
|
||||
div.className="conv-item"+(sid==curSid?" active":"");
|
||||
div.innerHTML='<div class="avatar">'+(sid==curSid?"🟢":"👤")+'</div><div class="info"><div class="name">对话 #'+sid+'</div><div class="preview">'+preview+'</div></div>'+(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};
|
||||
});
|
||||
renderList();
|
||||
}).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("chatContent").style.display="none"; document.getElementById("emptyTip").style.display="flex"; }
|
||||
renderList(); return;
|
||||
}
|
||||
// 客户正在输入
|
||||
var t = document.getElementById("typing-ind");
|
||||
if(d.typing && sid==curSid){ if(!t){ t=document.createElement("div"); t.id="typing-ind"; t.style.cssText="font-size:12px;color:#999;padding:4px 0;"; t.textContent="客户正在输入..."; document.getElementById("chatBox").appendChild(t); } }
|
||||
else if(t){ t.remove(); }
|
||||
d.messages.forEach(function(m){
|
||||
var exists = sessions[sid].msgs.some(function(x){return x.id===m.id});
|
||||
if(exists) return;
|
||||
sessions[sid].lastId=m.id;
|
||||
sessions[sid].msgs.push(m);
|
||||
if(m.side==="customer" && sid!=curSid){ sessions[sid].unread=(sessions[sid].unread||0)+1; renderList(); }
|
||||
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 quickReply(t){
|
||||
document.getElementById("replyText").value=t;
|
||||
sendReply();
|
||||
}
|
||||
|
||||
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("chatContent").style.display="none";
|
||||
document.getElementById("emptyTip").style.display="flex";
|
||||
renderList();
|
||||
}
|
||||
|
||||
document.getElementById("replyText").addEventListener("keydown", function(e){
|
||||
if(e.key==="Enter"){ e.preventDefault(); sendReply(); }
|
||||
});
|
||||
|
||||
// 客服正在输入通知客户
|
||||
var typeTimer = null;
|
||||
document.getElementById("replyText").addEventListener("input", function(){
|
||||
if(!curSid) return;
|
||||
fetch("/api/human/typing",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:parseInt(curSid)})});
|
||||
clearTimeout(typeTimer);
|
||||
typeTimer = setTimeout(function(){
|
||||
fetch("/api/human/stop_typing",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:parseInt(curSid)})});
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
</header>
|
||||
<div id="sync-status" class="sync-status" role="status" hidden>会话连接中断,正在重试…</div>
|
||||
<main class="layout">
|
||||
<aside class="conv-list" id="session-browser" aria-label="会话列表">
|
||||
<div class="list-heading">
|
||||
<h2>客户会话</h2><p>查看咨询,及时接待与回复</p>
|
||||
<div class="session-search-wrap"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 4 4"/></svg><input id="session-search" type="search" aria-label="搜索客户会话" placeholder="搜索会话或消息" autocomplete="off"></div>
|
||||
</div>
|
||||
<div class="session-tabs" role="tablist" aria-label="会话状态">
|
||||
<button id="tab-active" type="button" role="tab" aria-selected="true" aria-controls="sessionList">接待中 <span id="active-count" class="section-count">0</span></button>
|
||||
<button id="tab-queue" type="button" role="tab" aria-selected="false" aria-controls="queue" tabindex="-1">排队中 <span id="queue-count" class="section-count">0</span></button>
|
||||
</div>
|
||||
<div class="session-scroll">
|
||||
<div id="sessionList" role="tabpanel" aria-labelledby="tab-active"><div class="list-empty">暂无进行中的会话</div></div>
|
||||
<div id="queue" role="tabpanel" aria-labelledby="tab-queue" hidden><div class="list-empty">正在加载会话…</div></div>
|
||||
</div>
|
||||
<div class="list-footer"><span><span class="list-footer-mark" aria-hidden="true"></span>柏悦物流 · 客户服务</span><button id="tools-from-list" type="button" aria-controls="service-tools" aria-expanded="false">服务资料</button></div>
|
||||
</aside>
|
||||
<section class="chat-area" id="chatArea" aria-label="会话内容">
|
||||
<header class="chat-header">
|
||||
<button id="sessions-back" class="sessions-back" type="button" aria-label="返回会话列表">‹</button>
|
||||
<div class="participant-avatar" aria-hidden="true">客</div>
|
||||
<div class="participant-copy"><h2 id="chatTitle">会话接待</h2><p id="chat-subtitle">选择一段会话,开始回复客户</p></div>
|
||||
<div class="chat-header-actions">
|
||||
<button id="tools-toggle" class="tools-toggle" type="button" aria-label="资料与快捷回复" aria-controls="service-tools" aria-expanded="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><rect x="4" y="3" width="16" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="M6 16a3 3 0 0 1 6 0M14 8h3M14 12h3M14 16h3"/></svg><span>资料与回复</span></button>
|
||||
<button id="close-session" class="close" type="button" onclick="closeSession()">结束会话</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="empty" id="emptyTip"><div>
|
||||
<div class="empty-illustration"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4 7 8-4 8 4v10l-8 4-8-4Z"/><path d="m4 7 8 4 8-4M12 11v10M8 5l8 4"/></svg></div>
|
||||
<h2>选择会话,开始接待</h2><p>在会话列表查看客户咨询,<br>新咨询可从「排队中」接入。</p>
|
||||
</div></div>
|
||||
<div id="chatContent" class="session-panel" style="display:none">
|
||||
<div id="chatBox" role="log" aria-live="polite" aria-label="会话消息"></div>
|
||||
<footer class="agent-composer">
|
||||
<div class="composer-heading"><label for="replyText">回复客户</label><span>Shift + Enter 换行</span></div>
|
||||
<div class="input-bar">
|
||||
<textarea id="replyText" rows="2" aria-label="回复客户" placeholder="输入回复内容,按 Enter 发送…"></textarea>
|
||||
<div class="input-actions"><span>常用回复可在「资料与回复」中选择</span><button id="reply-send" type="button" onclick="sendReply()">发送</button></div>
|
||||
</div>
|
||||
<p id="reply-status" class="reply-status" role="status" hidden></p>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
<aside class="side" id="service-tools" aria-label="客户信息与回复" aria-hidden="true" inert>
|
||||
<header class="side-heading"><h2>服务资料</h2><button id="tools-close" type="button" class="icon-button" aria-label="关闭服务资料">×</button></header>
|
||||
<section class="card"><h3>客户信息</h3><div class="info-row" id="cInfo">选择会话后查看客户信息</div><p class="info-hint">咨询运费时,可先确认运单号、货物重量、尺寸与目的地。</p></section>
|
||||
<section class="card"><h3>常用回复</h3>
|
||||
<p class="card-caption">点击填入草稿,编辑后再发送</p>
|
||||
<button type="button" class="quick" onclick="quickReply('您好,请问有什么可以帮您?')">您好,有什么可以帮您?</button>
|
||||
<button type="button" class="quick" onclick="quickReply('请提供运单号,我帮您查询。')">请提供运单号,我帮您查询</button>
|
||||
<button type="button" class="quick" onclick="quickReply('已为您催促,请稍候。')">已为您催促,请稍候</button>
|
||||
<button type="button" class="quick" onclick="quickReply('感谢您的咨询,祝您生活愉快!')">感谢咨询,祝您生活愉快</button>
|
||||
</section>
|
||||
<section class="card"><h3>离线留言</h3><div id="ticketList"><div class="list-empty">正在加载留言…</div></div></section>
|
||||
</aside>
|
||||
</main>
|
||||
<button class="tools-backdrop" id="tools-backdrop" type="button" aria-label="关闭服务资料"></button>
|
||||
<script src="{{ url_for('static', filename='js/admin.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user