python-wechat-kf/app/static/app.js

202 lines
7.0 KiB
JavaScript

// 状态
let currentUser = "";
let conversations = [];
let currentKfid = "";
// 初始化
document.addEventListener("DOMContentLoaded", () => {
fetchAccounts();
});
// Toast
function showToast(msg, type = "success") {
const el = document.getElementById("toast");
el.textContent = msg;
el.className = "toast toast-" + type;
el.style.display = "block";
el.style.opacity = "1";
setTimeout(() => { el.style.opacity = "0"; setTimeout(() => el.style.display = "none", 300); }, 2000);
}
// 获取客服账号列表,填充下拉框
async function fetchAccounts() {
const select = document.getElementById("accountSelect");
select.innerHTML = '<option value="">加载中...</option>';
try {
const resp = await fetch("/api/accounts");
const data = await resp.json();
if (data.errcode === 0 && data.account_list && data.account_list.length > 0) {
const accounts = data.account_list;
select.innerHTML = accounts.map((a, i) =>
`<option value="${a.open_kfid}" ${i === 0 ? 'selected' : ''}>${a.name || a.open_kfid}</option>`
).join("");
currentKfid = accounts[0].open_kfid;
showToast(`已加载 ${accounts.length} 个客服账号`);
// 自动加载会话
loadConversations();
} else {
select.innerHTML = '<option value="">获取失败</option>';
showToast("获取账号列表失败: " + (data.errmsg || "未知错误"), "error");
}
} catch (e) {
select.innerHTML = '<option value="">请求失败</option>';
showToast("获取账号列表失败: " + e.message, "error");
}
}
function onAccountChange() {
const select = document.getElementById("accountSelect");
currentKfid = select.value;
if (currentKfid) {
currentUser = "";
document.getElementById("msgHeader").textContent = "请选择会话";
document.getElementById("messageList").innerHTML = '<div class="empty-state">选择左侧会话查看消息</div>';
document.getElementById("sendBtn").disabled = true;
loadConversations();
}
}
// 加载会话列表
async function loadConversations() {
try {
const resp = await fetch("/api/conversations?open_kfid=" + encodeURIComponent(currentKfid));
const data = await resp.json();
conversations = data.conversations || [];
renderConversations();
if (conversations.length === 0) {
showToast('暂无会话,请先点击 同步拉取 获取消息', "error");
}
} catch (e) {
showToast("加载会话失败: " + e.message, "error");
}
}
// 渲染会话列表
function renderConversations() {
const list = document.getElementById("conversationList");
if (conversations.length === 0) {
list.innerHTML = '<div style="padding:20px;color:#999;text-align:center;">暂无会话</div>';
return;
}
list.innerHTML = conversations.map(c => `
<div class="conv-item ${c.external_userid === currentUser ? 'active' : ''}"
onclick="selectConversation('${c.external_userid}')">
<div class="userid">${c.external_userid}</div>
<div class="preview">${escapeHtml(c.latest_content || '[非文本消息]')}</div>
<div class="time">${formatTime(c.latest_time)}</div>
</div>
`).join("");
}
// 选择会话
async function selectConversation(userid) {
currentUser = userid;
document.getElementById("msgHeader").textContent = "客户: " + userid;
document.getElementById("sendBtn").disabled = false;
document.getElementById("msgInput").disabled = false;
renderConversations();
await loadMessages(userid);
}
// 加载消息
async function loadMessages(userid) {
try {
const resp = await fetch(`/api/messages?external_userid=${userid}&open_kfid=${encodeURIComponent(currentKfid)}`);
const data = await resp.json();
const msgs = data.messages || [];
renderMessages(msgs);
} catch (e) {
showToast("加载消息失败: " + e.message, "error");
}
}
// 渲染消息
function renderMessages(msgs) {
const container = document.getElementById("messageList");
if (msgs.length === 0) {
container.innerHTML = '<div class="empty-state">暂无消息</div>';
return;
}
container.innerHTML = msgs.map(m => `
<div class="msg-bubble ${m.direction === 'outbound' ? 'msg-outbound' : 'msg-inbound'}">
<div>${escapeHtml(m.content || `[${m.msgtype}]`)}</div>
<div class="msg-time">${formatTime(m.send_time)}</div>
</div>
`).join("");
container.scrollTop = container.scrollHeight;
}
// 发送消息
async function sendMessage() {
if (!currentUser) return;
const input = document.getElementById("msgInput");
const content = input.value.trim();
if (!content) return;
const btn = document.getElementById("sendBtn");
btn.disabled = true;
btn.textContent = "发送中...";
try {
const resp = await fetch("/api/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ external_userid: currentUser, content, open_kfid: currentKfid }),
});
const result = await resp.json();
if (result.errcode === 0) {
input.value = "";
showToast("发送成功");
await loadMessages(currentUser);
} else {
showToast("发送失败: " + (result.errmsg || JSON.stringify(result)), "error");
}
} catch (e) {
showToast("发送失败: " + e.message, "error");
} finally {
btn.disabled = false;
btn.textContent = "发送";
}
}
// 同步拉取
async function syncMessages() {
const statusEl = document.getElementById("syncStatus");
statusEl.textContent = "同步中...";
try {
const resp = await fetch("/api/sync", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ open_kfid: currentKfid }),
});
const data = await resp.json();
if (data.errcode === 0) {
showToast(`同步成功,获取 ${data.total} 条消息,新增入库 ${data.saved}`);
await loadConversations();
if (currentUser) await loadMessages(currentUser);
} else {
showToast("同步失败: " + (data.errmsg || JSON.stringify(data)), "error");
}
} catch (e) {
showToast("同步失败: " + e.message, "error");
} finally {
statusEl.textContent = "";
}
}
// 格式化时间
function formatTime(iso) {
if (!iso) return "";
const d = new Date(iso);
const pad = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
// HTML 转义
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}