From 678400ba663a3f64bc7461eaba47b872635f30e1 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 18 Mar 2026 13:31:51 +0000 Subject: [PATCH] Autosave: 20260318-133151 --- admin.php | 33 +- assets/css/custom.css | 374 +-------------------- assets/js/main.js | 329 +++++++----------- assets/pasted-20260318-130451-33711195.png | Bin 0 -> 8457 bytes assets/pasted-20260318-132245-b6b02e61.png | Bin 0 -> 7122 bytes db/migrations/001_create_tables.sql | 31 ++ db/migrations/run.php | 6 + login.php | 47 +++ logout.php | 5 + output.html | 4 +- 10 files changed, 232 insertions(+), 597 deletions(-) create mode 100644 assets/pasted-20260318-130451-33711195.png create mode 100644 assets/pasted-20260318-132245-b6b02e61.png create mode 100644 db/migrations/001_create_tables.sql create mode 100644 db/migrations/run.php create mode 100644 login.php create mode 100644 logout.php diff --git a/admin.php b/admin.php index f9c4a17..d09023f 100644 --- a/admin.php +++ b/admin.php @@ -1,8 +1,9 @@ @@ -10,25 +11,6 @@ declare(strict_types=1); SMS Chat — 管理后台 - - - - - - - - - - - - - - - @@ -44,7 +26,8 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
● 系统正常 管理员:Admin - 返回工作台 + 退出登录 + 返回工作台
@@ -210,4 +193,4 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; - + \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css index 47bfc91..df83b66 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,364 +1,16 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); - -:root { - --bg: #f5f7f9; - --surface: #ffffff; - --text: #111827; - --muted: #6b7280; - --border: #e5e7eb; - --primary: #25d366; - --primary-dark: #1ea856; - --sidebar-dark: #1f2937; - --sidebar-dark-muted: #9ca3af; - --shadow: 0 8px 24px rgba(15, 23, 42, 0.08); - --radius-sm: 8px; - --radius-md: 12px; -} - -* { - box-sizing: border-box; -} - -body { - font-family: 'Inter', system-ui, -apple-system, Segoe UI, sans-serif; - background: var(--bg); - color: var(--text); -} - .topbar { - height: 64px; - background: var(--surface); - border-bottom: 1px solid var(--border); - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 1.5rem; - position: sticky; - top: 0; - z-index: 10; -} - -.brand { - display: flex; - align-items: center; - gap: 0.75rem; - font-weight: 700; - font-size: 1.1rem; -} - -.brand-badge { - width: 36px; - height: 36px; - border-radius: 10px; - background: var(--primary); - color: #fff; - display: grid; - place-items: center; - font-weight: 700; -} - -.topbar .meta { - display: flex; - align-items: center; - gap: 1rem; - font-size: 0.9rem; - color: var(--muted); -} - -.status-pill { - display: inline-flex; - align-items: center; - gap: 0.4rem; - padding: 0.25rem 0.6rem; - border-radius: 999px; - background: rgba(37, 211, 102, 0.12); - color: var(--primary-dark); - font-weight: 600; - font-size: 0.75rem; -} - -.app-shell { - height: calc(100vh - 64px); - display: flex; - gap: 1rem; - padding: 1rem 1.5rem 1.5rem; -} - -.panel { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-md); - box-shadow: var(--shadow); -} - -.contacts-panel { - width: 320px; - display: flex; - flex-direction: column; -} - -.contacts-panel .search { - padding: 1rem; - border-bottom: 1px solid var(--border); -} - -.contacts-list { - overflow-y: auto; -} - -.contact-item { - padding: 0.85rem 1rem; - border-bottom: 1px solid var(--border); - cursor: pointer; - display: flex; - justify-content: space-between; - gap: 0.75rem; - transition: background 0.15s ease; -} - -.contact-item:hover, -.contact-item.active { - background: rgba(37, 211, 102, 0.08); -} - -.contact-item .meta { - font-size: 0.75rem; - color: var(--muted); -} - -.unread { - background: var(--primary); - color: #fff; - border-radius: 999px; - font-size: 0.7rem; - padding: 0.1rem 0.45rem; - font-weight: 700; -} - -.chat-panel { - flex: 1; - display: flex; - flex-direction: column; -} - -.chat-header { - padding: 1rem 1.5rem; - border-bottom: 1px solid var(--border); - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; -} - -.chat-actions .btn { - border-radius: 999px; - font-size: 0.8rem; -} - -.chat-body { - flex: 1; - padding: 1.25rem; - overflow-y: auto; - background: #fafafa; -} - -.message { - max-width: 70%; - padding: 0.65rem 0.9rem; - border-radius: 14px; - margin-bottom: 0.6rem; - font-size: 0.9rem; - line-height: 1.4; -} - -.message.in { - background: #ffffff; - border: 1px solid var(--border); -} - -.message.out { - background: rgba(37, 211, 102, 0.18); - margin-left: auto; - border: 1px solid rgba(37, 211, 102, 0.3); -} - -.message .time { - font-size: 0.7rem; - color: var(--muted); - margin-top: 0.35rem; -} - -.chat-input { - border-top: 1px solid var(--border); - padding: 1rem 1.25rem; - background: var(--surface); -} - -.chat-input .form-control { - border-radius: 999px; -} - -.shortcut-list { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; -} - -.shortcut-list .btn { - border-radius: 999px; - font-size: 0.75rem; -} - -.admin-shell { - display: grid; - grid-template-columns: 240px 1fr; - height: calc(100vh - 64px); -} - -.admin-sidebar { - background: var(--sidebar-dark); - color: #fff; - padding: 1.5rem 1rem; -} - -.admin-sidebar .nav-link { - color: var(--sidebar-dark-muted); - border-radius: 8px; - margin-bottom: 0.3rem; - padding: 0.6rem 0.85rem; - font-weight: 600; -} - -.admin-sidebar .nav-link.active, -.admin-sidebar .nav-link:hover { - background: rgba(255, 255, 255, 0.08); - color: #fff; -} - -.admin-content { - padding: 1.5rem; - overflow-y: auto; -} - -.stat-card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-md); - padding: 1.25rem; - box-shadow: var(--shadow); -} - -.table thead th { - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.04em; - color: var(--muted); -} - -.badge-status { - padding: 0.35rem 0.6rem; - border-radius: 999px; - font-size: 0.7rem; -} - -.badge-status.normal { - background: rgba(37, 211, 102, 0.15); - color: var(--primary-dark); -} - -.badge-status.blocked { - background: rgba(239, 68, 68, 0.12); - color: #b91c1c; -} - -.section-card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-md); - padding: 1.5rem; - box-shadow: var(--shadow); -} - -.toast-container { - position: fixed; - bottom: 1.5rem; - right: 1.5rem; - z-index: 1055; -} - -.form-control, -.form-select { - border-radius: 8px; -} - -.btn-primary { - background: var(--primary); - border-color: var(--primary); -} - -.btn-primary:hover { - background: var(--primary-dark); - border-color: var(--primary-dark); -} - -.muted { - color: var(--muted); -} - -.empty { - text-align: center; - color: var(--muted); - padding: 2rem 1rem; -} - -@media (max-width: 992px) { - .app-shell { - flex-direction: column; - height: auto; - } - .contacts-panel { - width: 100%; - } - .admin-shell { - grid-template-columns: 1fr; - } - .admin-sidebar { display: flex; - gap: 0.5rem; - overflow-x: auto; - } -} - -/* Emoji Button Alignment */ -.chat-input .position-relative { - display: flex; - align-items: center; -} - -[data-emoji-trigger] { - padding: 0.5rem 0.7rem; - font-size: 1.25rem; - line-height: 1; - background-color: var(--surface) !important; - border-color: var(--border) !important; - border-radius: 999px !important; -} - -emoji-picker { - --emoji-size: 1.5rem; - --indicator-color: var(--primary); - --border-color: var(--border); - width: 320px; - height: 400px; -} - -/* Force hide any potential branding that might be injected */ -[class*="Flatlogic"], -[id*="Flatlogic"], -.flatlogic-watermark, -.built-with-flatlogic { - display: none !important; - visibility: hidden !important; - opacity: 0 !important; - pointer-events: none !important; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + background: #1f2937; + color: #fff; } +.brand { display: flex; align-items: center; gap: 10px; } +.brand-badge { background: #25D366; color: white; padding: 5px 12px; border-radius: 5px; font-weight: bold; } +.admin-shell { display: flex; min-height: 90vh; background: #f3f4f6; } +.admin-sidebar { width: 240px; background: white; padding: 20px; border-right: 1px solid #e5e7eb; } +.admin-content { flex: 1; padding: 20px; } +.section-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } +.stat-card { padding: 15px; border-radius: 8px; background: #f9fafb; border: 1px solid #e5e7eb; } +.toast-container { position: fixed; bottom: 20px; right: 20px; } diff --git a/assets/js/main.js b/assets/js/main.js index ebd13e4..7690a0e 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -28,226 +28,137 @@ const showToast = (message, type = 'success') => { setTimeout(() => toast.remove(), 3200); }; -const formatTime = (value) => { - if (!value) return ''; - const d = new Date(value); - if (Number.isNaN(d.getTime())) return value; - return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); -}; +const initAdmin = () => { + const navLinks = document.querySelectorAll('[data-section-link]'); + const sections = document.querySelectorAll('[data-section]'); -const initAgent = () => { - const listEl = document.querySelector('[data-contacts]'); - const chatTitle = document.querySelector('[data-chat-title]'); - const chatMeta = document.querySelector('[data-chat-meta]'); - const chatBody = document.querySelector('[data-chat-body]'); - const form = document.querySelector('[data-chat-form]'); - const input = document.querySelector('[data-chat-input]'); - const emojiTrigger = document.querySelector('[data-emoji-trigger]'); - const emojiPicker = document.querySelector('[data-emoji-picker]'); - const shortcutContainer = document.querySelector('[data-shortcuts]'); - const shortcutList = document.getElementById('shortcutList'); - const newShortcut = document.getElementById('newShortcut'); - const addShortcutBtn = document.getElementById('addShortcut'); - - let contacts = []; - let activeId = null; - let shortcuts = JSON.parse(localStorage.getItem('sms_shortcuts') || '[]'); - - // Quick reply logic - const renderShortcuts = () => { - shortcutList.innerHTML = ''; - shortcutContainer.innerHTML = ''; - shortcuts.forEach((s, i) => { - const li = document.createElement('li'); - li.className = 'list-group-item d-flex justify-content-between'; - li.innerHTML = `${s}`; - shortcutList.appendChild(li); - - const btn = document.createElement('button'); - btn.className = 'btn btn-sm btn-outline-primary me-1 mb-1'; - btn.textContent = s; - btn.onclick = () => { input.value = s; }; - shortcutContainer.appendChild(btn); + 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(); + }); }); - }; - window.removeShortcut = (index) => { - shortcuts.splice(index, 1); - localStorage.setItem('sms_shortcuts', JSON.stringify(shortcuts)); - renderShortcuts(); - }; - - addShortcutBtn.onclick = () => { - const val = newShortcut.value.trim(); - if (!val) return; - shortcuts.push(val); - localStorage.setItem('sms_shortcuts', JSON.stringify(shortcuts)); - newShortcut.value = ''; - renderShortcuts(); - }; - renderShortcuts(); - - // Emoji picker - emojiTrigger.addEventListener("click", (e) => { - e.stopPropagation(); - emojiPicker.classList.toggle("d-none"); - }); - - emojiPicker.addEventListener("emoji-click", e => { - input.value += e.detail.unicode; - emojiPicker.classList.add('d-none'); - }); - - document.addEventListener('click', (e) => { - if (!emojiTrigger.contains(e.target) && !emojiPicker.contains(e.target)) { - emojiPicker.classList.add('d-none'); - } - }); - - // Country selector - const countryListEl = document.getElementById('countryList'); - const countrySearch = document.getElementById('countrySearch'); - const selectedCode = document.getElementById('selectedCode'); - const phoneInput = document.getElementById('phoneNumber'); - const startChatBtn = document.getElementById('startChat'); - - const renderCountries = (filter = "") => { - countryListEl.innerHTML = ''; - countries.filter(c => c.name.includes(filter)).forEach(c => { - const item = document.createElement('div'); - item.className = 'country-item'; - item.innerHTML = `${c.name} (${c.code})`; - item.onclick = () => { - selectedCode.textContent = c.code; - countrySearch.value = ""; - renderCountries(); - }; - countryListEl.appendChild(item); - }); - }; - countrySearch.addEventListener('input', (e) => renderCountries(e.target.value)); - renderCountries(); - - startChatBtn.addEventListener('click', async () => { - const fullPhone = selectedCode.textContent + phoneInput.value; - if (!phoneInput.value) return showToast('请输入手机号', 'danger'); - try { - const res = await apiFetch('/api/contacts.php', { method: 'POST', body: { phone: fullPhone } }); - bootstrap.Modal.getInstance(document.getElementById('countryModal')).hide(); - phoneInput.value = ''; - await loadContacts(); - await setActive(res.id); - } catch (e) { showToast('无法启动聊天', 'danger'); } - }); - - // ESC to exit - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - activeId = null; - chatTitle.textContent = '请选择联系人'; - chatMeta.textContent = '状态'; - chatBody.innerHTML = ''; - renderContacts(); - } - }); - - const renderContacts = () => { - listEl.innerHTML = ''; - contacts.forEach((contact) => { - const item = document.createElement('div'); - item.className = `contact-item ${contact.id === activeId ? 'active' : ''}`; - - item.innerHTML = ` -
-
-
${contact.phone}
-
${contact.last_message || '暂无消息'}
-
- -
- `; - listEl.appendChild(item); + // 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'); } }); - }; - window.setActive = async (id) => { - activeId = id; - const contact = contacts.find(c => c.id === id); - chatTitle.textContent = contact ? contact.phone : '请选择联系人'; - chatMeta.textContent = contact ? contact.phone : "状态"; - renderContacts(); - await loadMessages(); - }; + 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'); } + }); - window.deleteContact = async (id) => { - if (!confirm('确定删除此联系人?')) return; - try { + 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 => ` + + ${c.phone} + ${c.tags || '-'} + ${c.status} + + + + + + `).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' }); - await loadContacts(); - showToast('已删除联系人'); - if (activeId === id) { - activeId = null; - chatTitle.textContent = '请选择联系人'; - chatMeta.textContent = '状态'; - chatBody.innerHTML = ''; - } - } catch (e) { showToast('删除失败', 'danger'); } - }; + loadAdminContacts(); + }; - window.blockContact = async (id) => { - if (!confirm('确定拉黑此联系人?')) return; - try { - await apiFetch(`/api/contacts.php?action=block&id=${id}`, { method: 'POST' }); - await loadContacts(); - showToast('已拉黑联系人'); - if (activeId === id) { - activeId = null; - chatTitle.textContent = '请选择联系人'; - chatMeta.textContent = '状态'; - chatBody.innerHTML = ''; - } - } catch (e) { showToast('操作失败', 'danger'); } - }; + const loadAdminMessages = async () => { + const data = await apiFetch('/api/messages.php'); + const list = document.querySelector('[data-admin-messages]'); + list.innerHTML = (data.messages || []).map(m => ` + + ${m.phone} + ${m.direction} + ${m.body} + ${m.created_at} + + `).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 => ` +
  • +
    ${r.keyword}: ${r.reply}
    + +
  • + `).join(''); + }; - const loadContacts = async () => { - const data = await apiFetch('/api/contacts.php'); - contacts = data.contacts || []; - renderContacts(); - }; + window.deleteRule = async (id) => { + await apiFetch(`/api/auto_reply.php?action=delete&id=${id}`, { method: 'POST' }); + loadAutoReplies(); + }; - const loadMessages = async () => { - if (!activeId) return; - const data = await apiFetch(`/api/messages.php?contact_id=${activeId}`); - chatBody.innerHTML = (data.messages || []).map(msg => ` -
    -
    ${msg.body}
    -
    ${formatTime(msg.created_at)}
    -
    - `).join(''); - chatBody.scrollTop = chatBody.scrollHeight; - }; + 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 || ''; + }; - form?.addEventListener('submit', async (e) => { - e.preventDefault(); - if (!activeId) return; - const body = input.value.trim(); - if (!body) return; - await apiFetch('/api/messages.php', { method: 'POST', body: { contact_id: activeId, body } }); - input.value = ''; - await loadMessages(); - }); - - loadContacts(); - setInterval(loadContacts, 3000); + loadDashboard(); }; document.addEventListener('DOMContentLoaded', () => { - if (document.body.dataset.page === 'agent') initAgent(); -}); -window.FL_SHOW_BUDGE = false; + if (document.body.dataset.page === 'admin') initAdmin(); +}); \ No newline at end of file diff --git a/assets/pasted-20260318-130451-33711195.png b/assets/pasted-20260318-130451-33711195.png new file mode 100644 index 0000000000000000000000000000000000000000..9271b8b8815164dfc104ab3e488610309b988721 GIT binary patch literal 8457 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 literal 0 HcmV?d00001 diff --git a/assets/pasted-20260318-132245-b6b02e61.png b/assets/pasted-20260318-132245-b6b02e61.png new file mode 100644 index 0000000000000000000000000000000000000000..4a40e8a7b5153a24e2ca0c48b19006e0340ea997 GIT binary patch literal 7122 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 U(IA&*ume$1x~o_!e?Rzt03!ANr2qf` literal 0 HcmV?d00001 diff --git a/db/migrations/001_create_tables.sql b/db/migrations/001_create_tables.sql new file mode 100644 index 0000000..aa8593a --- /dev/null +++ b/db/migrations/001_create_tables.sql @@ -0,0 +1,31 @@ +-- 客户信息表 +CREATE TABLE IF NOT EXISTS customers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 消息记录表 +CREATE TABLE IF NOT EXISTS messages ( + id INT AUTO_INCREMENT PRIMARY KEY, + customer_id INT, + direction ENUM('inbound', 'outbound') NOT NULL, + content TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (customer_id) REFERENCES customers(id) +); + +-- Twilio 和系统配置表 +CREATE TABLE IF NOT EXISTS settings ( + key_name VARCHAR(50) PRIMARY KEY, + key_value TEXT +); + +-- 自动回复设置 +CREATE TABLE IF NOT EXISTS auto_replies ( + id INT AUTO_INCREMENT PRIMARY KEY, + keyword VARCHAR(255), + response_text TEXT, + is_active BOOLEAN DEFAULT TRUE +); diff --git a/db/migrations/run.php b/db/migrations/run.php new file mode 100644 index 0000000..fd14e73 --- /dev/null +++ b/db/migrations/run.php @@ -0,0 +1,6 @@ +exec($sql); +echo "Migration completed.\n"; \ No newline at end of file diff --git a/login.php b/login.php new file mode 100644 index 0000000..feab053 --- /dev/null +++ b/login.php @@ -0,0 +1,47 @@ + + + + + + 登录 - 管理后台 + + + + + + + \ No newline at end of file diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..37bc5ab --- /dev/null +++ b/logout.php @@ -0,0 +1,5 @@ + - - \ No newline at end of file + +