diff --git a/am.py b/am.py index 186613d..7d6be69 100644 --- a/am.py +++ b/am.py @@ -1456,13 +1456,61 @@ def uploaded_file(name): def index(): return render_template("index.html") +@app.route("/api/my_orders") +def my_orders(): + phone = request.args.get("phone", "").strip() + if not phone: + return jsonify({"orders": []}) + conn = sqlite3.connect(DB) + rows = conn.execute("SELECT track_no, status, created FROM orders WHERE phone=? ORDER BY id DESC", (phone,)).fetchall() + conn.close() + return jsonify({"orders": [{"track_no": r[0], "status": r[1], "created": r[2]} for r in rows]}) + +@app.route("/api/ticket", methods=["POST"]) +def add_ticket(): + data = request.get_json() or {} + conn = sqlite3.connect(DB) + conn.execute("INSERT INTO tickets(content, phone, ctime, status) VALUES(?,?,?,?)", + (data.get("content",""), data.get("phone",""), datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "pending")) + conn.commit(); conn.close() + return jsonify({"ok": True}) + +@app.route("/api/tickets") +def list_tickets(): + conn = sqlite3.connect(DB) + rows = conn.execute("SELECT id, content, phone, ctime FROM tickets WHERE status='pending' ORDER BY id DESC").fetchall() + conn.close() + return jsonify([{"id":r[0],"content":r[1],"phone":r[2],"ctime":r[3]} for r in rows]) + @app.route("/admin/login", methods=["POST"]) def admin_login(): - pwd = request.json.get("password", "") - if pwd == "123": - return jsonify({"ok": True}) + data = request.json or {} + u = data.get("username", "") + p = data.get("password", "") + conn = sqlite3.connect(DB) + row = conn.execute("SELECT name FROM agents WHERE username=? AND password=?", (u, p)).fetchone() + conn.close() + if row: + return jsonify({"ok": True, "name": row[0]}) return jsonify({"ok": False}), 401 +@app.route("/api/export_chat") +def export_chat(): + conn = sqlite3.connect(DB) + rows = conn.execute("SELECT h.title, m.role, m.content, m.ctime FROM messages m JOIN chat_history h ON m.conv_id=h.id ORDER BY m.id").fetchall() + conn.close() + lines = ["标题,角色,内容,时间"] + for r in rows: + safe = (r[2] or "").replace(",", ",").replace("\n", " ") + lines.append(f"{r[0]},{r[1]},{safe},{r[3]}") + import csv, io + buf = io.StringIO() + w = csv.writer(buf) + w.writerow(["标题","角色","内容","时间"]) + for r in rows: + w.writerow([r[0], r[1], (r[2] or ""), r[3]]) + return Response(buf.getvalue(), mimetype="text/csv", headers={"Content-Disposition":"attachment;filename=chat_export.csv"}) + @app.route("/admin") def admin(): return render_template("admin_login.html") diff --git a/templates/admin.html b/templates/admin.html index 80567ca..69245b4 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -65,7 +65,7 @@ body{font-family:-apple-system,"Microsoft YaHei",sans-serif;background:#f0f2f5;h 👍 - 👎 - -
在线
+
导出聊天记录 在线
@@ -103,6 +103,10 @@ body{font-family:-apple-system,"Microsoft YaHei",sans-serif;background:#f0f2f5;h
已催促,请稍候
感谢咨询
+
+

离线留言

+
+
@@ -125,6 +129,18 @@ function loadQueue(){ } 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 = '
暂无留言
'; return; } + el.innerHTML = list.map(function(t){ + return '
'+t.content+'
'+t.ctime+'
'; + }).join(""); + }); +} +setInterval(loadTickets, 5000); +loadTickets(); + function accept(sid){ sid = String(sid); if(sessions[sid]) return; @@ -144,6 +160,7 @@ function switchSession(sid){ 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){ @@ -190,7 +207,13 @@ function poll(){ 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(); } @@ -227,6 +250,17 @@ function closeSession(){ 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); +}); diff --git a/templates/admin_login.html b/templates/admin_login.html index ec4ba9a..b856d9e 100644 --- a/templates/admin_login.html +++ b/templates/admin_login.html @@ -14,15 +14,17 @@ button{width:100%;padding:10px;background:#2978e8;color:#fff;border:none;border-
-

🔒 后台登录

- -

密码错误

+

🔒 客服后台登录

+ + +

账号或密码错误