164 lines
6.4 KiB
JavaScript
164 lines
6.4 KiB
JavaScript
const apiFetch = async (url, options = {}) => {
|
|
const opts = {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
...options,
|
|
};
|
|
if (opts.body && typeof opts.body !== 'string') {
|
|
opts.body = JSON.stringify(opts.body);
|
|
}
|
|
const res = await fetch(url, opts);
|
|
if (!res.ok) {
|
|
throw new Error(`API error: ${res.status}`);
|
|
}
|
|
return res.json();
|
|
};
|
|
|
|
const showToast = (message, type = 'success') => {
|
|
const container = document.querySelector('.toast-container');
|
|
if (!container) return;
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast align-items-center text-bg-${type} border-0 show`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.innerHTML = `
|
|
<div class="d-flex">
|
|
<div class="toast-body">${message}</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
|
</div>`;
|
|
container.appendChild(toast);
|
|
setTimeout(() => toast.remove(), 3200);
|
|
};
|
|
|
|
const initAdmin = () => {
|
|
const navLinks = document.querySelectorAll('[data-section-link]');
|
|
const sections = document.querySelectorAll('[data-section]');
|
|
|
|
navLinks.forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const target = link.dataset.sectionLink;
|
|
navLinks.forEach(l => l.classList.remove('active'));
|
|
link.classList.add('active');
|
|
sections.forEach(s => s.classList.add('d-none'));
|
|
document.querySelector(`[data-section="${target}"]`).classList.remove('d-none');
|
|
|
|
if (target === 'dashboard') loadDashboard();
|
|
if (target === 'contacts') loadAdminContacts();
|
|
if (target === 'messages') loadAdminMessages();
|
|
if (target === 'auto') loadAutoReplies();
|
|
if (target === 'settings') loadSettings();
|
|
});
|
|
});
|
|
|
|
// Forms
|
|
document.querySelector('[data-send-form]')?.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const data = Object.fromEntries(new FormData(e.target));
|
|
try {
|
|
await apiFetch('/api/messages.php', { method: 'POST', body: { contact_phone: data.phone, body: data.body, direction: 'out' } });
|
|
showToast('发送成功');
|
|
e.target.reset();
|
|
} catch (e) { showToast('发送失败', 'danger'); }
|
|
});
|
|
|
|
document.querySelector('[data-reply-form]')?.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const data = Object.fromEntries(new FormData(e.target));
|
|
try {
|
|
await apiFetch('/api/auto_reply.php', { method: 'POST', body: data });
|
|
showToast('已添加规则');
|
|
e.target.reset();
|
|
loadAutoReplies();
|
|
} catch (e) { showToast('添加失败', 'danger'); }
|
|
});
|
|
|
|
document.querySelector('[data-settings-form]')?.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const data = Object.fromEntries(new FormData(e.target));
|
|
try {
|
|
await apiFetch('/api/settings.php', { method: 'POST', body: data });
|
|
showToast('配置已保存');
|
|
} catch (e) { showToast('保存失败', 'danger'); }
|
|
});
|
|
|
|
const loadDashboard = async () => {
|
|
try {
|
|
const data = await apiFetch('/api/stats.php');
|
|
document.querySelector('[data-stat="sent"]').textContent = data.sent || 0;
|
|
document.querySelector('[data-stat="received"]').textContent = data.received || 0;
|
|
document.querySelector('[data-stat="active"]').textContent = data.active || 0;
|
|
} catch (e) { console.error(e); }
|
|
};
|
|
|
|
const loadAdminContacts = async () => {
|
|
const data = await apiFetch('/api/contacts.php');
|
|
const list = document.querySelector('[data-admin-contacts]');
|
|
list.innerHTML = (data.contacts || []).map(c => `
|
|
<tr>
|
|
<td>${c.phone}</td>
|
|
<td>${c.tags || '-'}</td>
|
|
<td><span class="badge ${c.status === 'blocked' ? 'bg-danger' : 'bg-success'}">${c.status}</span></td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="toggleBlock('${c.id}', '${c.status}')">切换拉黑</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteContact('${c.id}')">删除</button>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
};
|
|
|
|
window.toggleBlock = async (id, currentStatus) => {
|
|
const newStatus = currentStatus === 'blocked' ? 'normal' : 'blocked';
|
|
await apiFetch(`/api/contacts.php?action=update&id=${id}`, { method: 'POST', body: { status: newStatus } });
|
|
loadAdminContacts();
|
|
};
|
|
|
|
window.deleteContact = async (id) => {
|
|
if (!confirm('确定删除?')) return;
|
|
await apiFetch(`/api/contacts.php?action=delete&id=${id}`, { method: 'POST' });
|
|
loadAdminContacts();
|
|
};
|
|
|
|
const loadAdminMessages = async () => {
|
|
const data = await apiFetch('/api/messages.php');
|
|
const list = document.querySelector('[data-admin-messages]');
|
|
list.innerHTML = (data.messages || []).map(m => `
|
|
<tr>
|
|
<td>${m.phone}</td>
|
|
<td>${m.direction}</td>
|
|
<td>${m.body}</td>
|
|
<td>${m.created_at}</td>
|
|
</tr>
|
|
`).join('');
|
|
};
|
|
|
|
const loadAutoReplies = async () => {
|
|
const data = await apiFetch('/api/auto_reply.php');
|
|
const list = document.querySelector('[data-reply-list]');
|
|
list.innerHTML = (data.rules || []).map(r => `
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<div><b>${r.keyword}</b>: ${r.reply}</div>
|
|
<button class="btn btn-sm btn-danger" onclick="deleteRule('${r.id}')">删除</button>
|
|
</li>
|
|
`).join('');
|
|
};
|
|
|
|
window.deleteRule = async (id) => {
|
|
await apiFetch(`/api/auto_reply.php?action=delete&id=${id}`, { method: 'POST' });
|
|
loadAutoReplies();
|
|
};
|
|
|
|
const loadSettings = async () => {
|
|
const data = await apiFetch('/api/settings.php');
|
|
if (!data) return;
|
|
const form = document.querySelector('[data-settings-form]');
|
|
form.sid.value = data.sid || '';
|
|
form.token.value = data.token || '';
|
|
form.from.value = data.from || '';
|
|
form.webhook.value = data.webhook || '';
|
|
};
|
|
|
|
loadDashboard();
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (document.body.dataset.page === 'admin') initAdmin();
|
|
}); |