Autosave: 20260318-152931

This commit is contained in:
Flatlogic Bot 2026-03-18 15:29:32 +00:00
parent 678400ba66
commit ba0aeb359f
10 changed files with 333 additions and 214 deletions

309
admin.php
View File

@ -10,184 +10,177 @@ if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SMS Chat 管理后台</title>
<title>SMS后台管理</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="assets/css/custom.css?v=<?= time() ?>" rel="stylesheet">
</head>
<body data-page="admin">
<header class="topbar">
<div class="brand">
<div class="brand-badge">S</div>
<div>
SMS Chat
<div class="small text-muted">管理后台</div>
<div class="app-admin">
<header class="topbar">
<div class="brand">
<div class="brand-logo" style="width: 40px; height: 40px; background: #25D366; color: white; display: flex; align-items: center; justify-content: center; border-radius: 8px; font-weight: bold; font-size: 1.2rem;">S</div>
<div>
<div class="fs-5 fw-bold text-white">SMS后台管理</div>
</div>
</div>
</div>
<div class="meta">
<span class="status-pill"> 系统正常</span>
<span>管理员Admin</span>
<a class="btn btn-sm btn-outline-secondary" href="logout.php">退出登录</a>
<a class="btn btn-sm btn-outline-secondary ms-2" href="index.php">返回工作台</a>
</div>
</header>
<div class="meta d-flex align-items-center gap-3">
<span class="status-pill text-white"><small> 系统正常运行</small></span>
<span class="text-white">管理员Admin</span>
<a class="btn btn-sm btn-outline-light" href="logout.php">退出登录</a>
</div>
</header>
<main class="admin-shell">
<aside class="admin-sidebar">
<nav class="nav flex-column">
<a class="nav-link active" href="#" data-section-link="dashboard">仪表盘</a>
<a class="nav-link" href="#" data-section-link="contacts">客户管理</a>
<a class="nav-link" href="#" data-section-link="messages">消息记录</a>
<a class="nav-link" href="#" data-section-link="send">发送短信</a>
<a class="nav-link" href="#" data-section-link="auto">自动回复</a>
<a class="nav-link" href="#" data-section-link="settings">Twilio 配置</a>
<a class="nav-link" href="#" data-section-link="system">系统设置</a>
</nav>
</aside>
<main class="admin-shell">
<aside class="admin-sidebar">
<nav class="nav flex-column gap-1">
<a class="nav-link active" href="#" data-section-link="dashboard">📊 仪表盘</a>
<a class="nav-link" href="#" data-section-link="contacts">👥 客户管理</a>
<a class="nav-link" href="#" data-section-link="messages">💬 消息记录</a>
<a class="nav-link" href="#" data-section-link="send">📤 发送短信</a>
<a class="nav-link" href="#" data-section-link="auto">🤖 自动回复</a>
<a class="nav-link" href="#" data-section-link="settings">⚙️ Twilio 配置</a>
<a class="nav-link" href="#" data-section-link="system">🖥 系统设置</a>
</nav>
</aside>
<section class="admin-content">
<div class="section-card mb-4" data-section="dashboard">
<h5 class="mb-3">今日概览</h5>
<div class="row g-3">
<div class="col-md-4">
<div class="stat-card">
<div class="muted">今日发送</div>
<div class="fs-3 fw-semibold" data-stat="sent">0</div>
<section class="admin-content">
<div class="section-card mb-4" data-section="dashboard">
<h5 class="mb-3">📊 今日概览</h5>
<div class="row g-3">
<div class="col-md-4">
<div class="stat-card">
<div class="text-muted small">今日发送数量</div>
<div class="fs-3 fw-bold mt-1" data-stat="sent">0</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card">
<div class="muted">今日接收</div>
<div class="fs-3 fw-semibold" data-stat="received">0</div>
<div class="col-md-4">
<div class="stat-card">
<div class="text-muted small">今日接收数量</div>
<div class="fs-3 fw-bold mt-1" data-stat="received">0</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card">
<div class="muted">活跃客户</div>
<div class="fs-3 fw-semibold" data-stat="active">0</div>
<div class="col-md-4">
<div class="stat-card">
<div class="text-muted small">活跃客户数</div>
<div class="fs-3 fw-bold mt-1" data-stat="active">0</div>
</div>
</div>
</div>
</div>
</div>
<div class="section-card mb-4 d-none" data-section="contacts">
<h5 class="mb-3">客户管理</h5>
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th>手机号</th>
<th>标签</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody data-admin-contacts></tbody>
</table>
<div class="section-card mb-4 d-none" data-section="contacts">
<h5 class="mb-3">👥 客户管理</h5>
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th>手机号</th>
<th>标签</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody data-admin-contacts></tbody>
</table>
</div>
</div>
</div>
<div class="section-card mb-4 d-none" data-section="messages">
<h5 class="mb-3">消息记录</h5>
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th>手机号</th>
<th>方向</th>
<th>内容</th>
<th>时间</th>
</tr>
</thead>
<tbody data-admin-messages></tbody>
</table>
<div class="section-card mb-4 d-none" data-section="messages">
<h5 class="mb-3">💬 消息记录</h5>
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th>手机号</th>
<th>方向</th>
<th>内容</th>
<th>时间</th>
</tr>
</thead>
<tbody data-admin-messages></tbody>
</table>
</div>
</div>
</div>
<div class="section-card mb-4 d-none" data-section="send">
<h5 class="mb-3">发送短信</h5>
<form class="row g-3" data-send-form>
<div class="col-md-4">
<label class="form-label">手机号</label>
<input class="form-control" name="phone" placeholder="+86 138 0013 8000" required>
</div>
<div class="col-md-8">
<label class="form-label">短信内容</label>
<input class="form-control" name="body" placeholder="输入要发送的短信内容" required>
</div>
<div class="col-12">
<button class="btn btn-primary px-4" type="submit">发送</button>
</div>
</form>
</div>
<div class="section-card mb-4 d-none" data-section="send">
<h5 class="mb-3">📤 发送短信</h5>
<form class="row g-3" data-send-form>
<div class="col-md-4">
<label class="form-label">手机号</label>
<input class="form-control" name="phone" placeholder="+86 138 0013 8000" required>
</div>
<div class="col-md-8">
<label class="form-label">短信内容</label>
<input class="form-control" name="body" placeholder="输入要发送的内容" required>
</div>
<div class="col-12">
<button class="btn btn-success px-4" type="submit">发送短信</button>
</div>
</form>
</div>
<div class="section-card mb-4 d-none" data-section="auto">
<h5 class="mb-3">自动回复</h5>
<form class="row g-3 mb-4" data-reply-form>
<div class="col-md-4">
<label class="form-label">关键词</label>
<input class="form-control" name="keyword" required>
</div>
<div class="col-md-8">
<label class="form-label">回复内容</label>
<input class="form-control" name="reply" required>
</div>
<div class="col-12">
<button class="btn btn-primary px-4" type="submit">添加规则</button>
</div>
</form>
<ul class="list-group" data-reply-list></ul>
</div>
<div class="section-card mb-4 d-none" data-section="auto">
<h5 class="mb-3">🤖 自动回复规则</h5>
<form class="row g-3 mb-4" data-reply-form>
<div class="col-md-4">
<label class="form-label">关键词</label>
<input class="form-control" name="keyword" required>
</div>
<div class="col-md-8">
<label class="form-label">回复内容</label>
<input class="form-control" name="reply" required>
</div>
<div class="col-12">
<button class="btn btn-success px-4" type="submit">添加规则</button>
</div>
</form>
<ul class="list-group" data-reply-list></ul>
</div>
<div class="section-card mb-4 d-none" data-section="settings">
<h5 class="mb-3">Twilio 配置</h5>
<form class="row g-3" data-settings-form>
<div class="col-md-4">
<label class="form-label">Account SID</label>
<input class="form-control" name="sid" placeholder="ACxxxxxxxx">
</div>
<div class="col-md-4">
<label class="form-label">Auth Token</label>
<input class="form-control" name="token" placeholder="••••••••">
</div>
<div class="col-md-4">
<label class="form-label">From 号码</label>
<input class="form-control" name="from" placeholder="+14155550199">
</div>
<div class="col-12">
<label class="form-label">Webhook 地址</label>
<input class="form-control" name="webhook" placeholder="https://your-domain.com/twilio/webhook">
</div>
<div class="col-12">
<button class="btn btn-primary px-4" type="submit">保存配置</button>
</div>
</form>
</div>
<div class="section-card mb-4 d-none" data-section="settings">
<h5 class="mb-3">⚙️ Twilio 配置</h5>
<form class="row g-3" data-settings-form>
<div class="col-md-4">
<label class="form-label">Account SID</label>
<input class="form-control" name="sid" placeholder="ACxxxxxxxx">
</div>
<div class="col-md-4">
<label class="form-label">Auth Token</label>
<input class="form-control" name="token" placeholder="••••••••">
</div>
<div class="col-md-4">
<label class="form-label">From 号码</label>
<input class="form-control" name="from" placeholder="+14155550199">
</div>
<div class="col-12">
<label class="form-label">Webhook 地址</label>
<input class="form-control" name="webhook" placeholder="https://your-domain.com/twilio/webhook">
</div>
<div class="col-12">
<button class="btn btn-success px-4" type="submit">保存配置</button>
</div>
</form>
</div>
<div class="section-card d-none" data-section="system">
<h5 class="mb-3">系统设置</h5>
<form class="row g-3" data-system-form>
<div class="col-md-4">
<label class="form-label">刷新频率(秒)</label>
<input class="form-control" name="refresh_interval" value="3">
</div>
<div class="col-md-4">
<label class="form-label">主题</label>
<select class="form-select" name="theme">
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">管理员名称</label>
<input class="form-control" name="admin_name" value="Admin">
</div>
<div class="col-12">
<button class="btn btn-primary px-4" type="submit">保存设置</button>
</div>
</form>
</div>
</section>
</main>
<div class="section-card d-none" data-section="system">
<h5 class="mb-3">🖥 系统设置</h5>
<form class="row g-3" data-system-form>
<div class="col-md-4">
<label class="form-label">刷新频率(秒)</label>
<input class="form-control" name="refresh_interval" value="3">
</div>
<div class="col-md-4">
<label class="form-label">管理员名称</label>
<input class="form-control" name="admin_name" value="Admin">
</div>
<div class="col-12">
<button class="btn btn-success px-4" type="submit">保存设置</button>
</div>
</form>
</div>
</section>
</main>
</div>
<div class="toast-container"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" defer></script>

View File

@ -1,16 +1,62 @@
.topbar {
/* Wrapper for Frontend */
.app-frontend {
height: 100vh;
display: flex;
flex-direction: column;
}
.app-frontend .frontend-topbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background: #1f2937;
color: #fff;
padding: 0.8rem 2rem;
background: #fff;
border-bottom: 1px solid #e5e7eb;
color: #333;
height: 60px;
}
.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; }
.app-frontend .brand { display: flex; align-items: center; gap: 15px; font-weight: bold; }
.app-frontend .frontend-app-shell {
display: flex;
flex: 1;
overflow: hidden;
}
.app-frontend .contacts-panel { width: 300px; border-right: 1px solid #e5e7eb; padding: 1rem; background: #fff;}
.app-frontend .chat-panel { flex: 1; background: #f8f9fa; display: flex; flex-direction: column;}
.app-frontend .frontend-chat-header { padding: 1rem; border-bottom: 1px solid #e5e7eb; background: #fff; }
.app-frontend .frontend-chat-body { flex: 1; padding: 1rem; overflow-y: auto; }
.app-frontend .frontend-chat-input-wrapper { padding: 1rem; border-top: 1px solid #e5e7eb; background: #fff; }
/* Wrapper for Admin */
.app-admin {
height: 100vh;
display: flex;
flex-direction: column;
}
.app-admin .topbar {
height: 60px;
background: #25D366;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.app-admin .brand { display: flex; align-items: center; gap: 15px; }
.app-admin .admin-shell { display: flex; flex: 1; background: #f3f4f6; }
.app-admin .admin-sidebar { width: 260px; background: white; padding: 20px; border-right: 1px solid #e5e7eb; }
.app-admin .admin-sidebar .nav-link {
color: #374151;
padding: 12px 16px;
border-radius: 8px;
transition: all 0.2s;
}
.app-admin .admin-sidebar .nav-link:hover, .app-admin .admin-sidebar .nav-link.active {
background: #ecfdf5;
color: #059669;
font-weight: 600;
}
.app-admin .admin-content { flex: 1; padding: 20px; overflow-y: auto; }
.app-admin .section-card { background: white; padding: 24px; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); }
.app-admin .stat-card { padding: 20px; border-radius: 12px; background: #f9fafb; border: 1px solid #e5e7eb; }
.app-admin .btn-success { background-color: #25D366; border-color: #25D366; color: white; }
.app-admin .btn-success:hover { background-color: #128C7E; border-color: #128C7E; }
.app-admin .toast-container { position: fixed; bottom: 20px; right: 20px; }

View File

@ -28,6 +28,90 @@ const showToast = (message, type = 'success') => {
setTimeout(() => toast.remove(), 3200);
};
const initFrontend = () => {
const countrySearch = document.getElementById('countrySearch');
const countryList = document.getElementById('countryList');
const selectedCode = document.getElementById('selectedCode');
const phoneNumber = document.getElementById('phoneNumber');
const startChat = document.getElementById('startChat');
const shortcutListUI = document.getElementById('shortcutList');
const newShortcut = document.getElementById('newShortcut');
const addShortcut = document.getElementById('addShortcut');
const chatInput = document.querySelector('[data-chat-input]');
const shortcutsDisplay = document.querySelector('[data-shortcuts]');
const emojiTrigger = document.querySelector('[data-emoji-trigger]');
const emojiPicker = document.querySelector('[data-emoji-picker]');
// Country Search & Selection
const renderCountries = (filter = '') => {
countryList.innerHTML = countries
.filter(c => c.name.includes(filter) || c.code.includes(filter))
.map(c => `
<div class="p-2 border-bottom" role="button" onclick="selectCountry('${c.name}', '${c.code}')">
${c.name} <span class="text-muted">${c.code}</span>
</div>
`).join('');
};
countrySearch.addEventListener('input', (e) => renderCountries(e.target.value));
window.selectCountry = (name, code) => {
selectedCode.textContent = code;
document.getElementById('searchTrigger').textContent = name + ' (' + code + ')';
bootstrap.Modal.getInstance(document.getElementById('countryModal')).hide();
};
renderCountries();
// Shortcuts Management
let shortcuts = JSON.parse(localStorage.getItem('shortcuts') || '[]');
const renderShortcuts = () => {
shortcutListUI.innerHTML = shortcuts.map((s, i) => `
<li class="list-group-item d-flex justify-content-between">${s}
<button class="btn btn-sm btn-danger" onclick="deleteShortcut(${i})">删除</button>
</li>
`).join('');
shortcutsDisplay.innerHTML = shortcuts.map(s => `
<button class="btn btn-sm btn-outline-info me-1" onclick="insertShortcut('${s}')">${s}</button>
`).join('');
};
window.insertShortcut = (text) => chatInput.value += text;
window.deleteShortcut = (index) => {
shortcuts.splice(index, 1);
localStorage.setItem('shortcuts', JSON.stringify(shortcuts));
renderShortcuts();
};
addShortcut.addEventListener('click', () => {
if (!newShortcut.value) return;
shortcuts.push(newShortcut.value);
localStorage.setItem('shortcuts', JSON.stringify(shortcuts));
newShortcut.value = '';
renderShortcuts();
});
renderShortcuts();
// Emoji Picker
emojiTrigger.addEventListener('click', () => emojiPicker.toggleAttribute('hidden'));
emojiPicker.addEventListener('emoji-click', (e) => {
chatInput.value += e.detail.unicode;
emojiPicker.setAttribute('hidden', '');
});
// Start Chat
startChat.addEventListener('click', () => {
if (!phoneNumber.value) return;
const phone = selectedCode.textContent + phoneNumber.value;
document.querySelector('[data-chat-title]').textContent = phone;
bootstrap.Modal.getInstance(document.getElementById('countryModal')).hide();
showToast('已开始与 ' + phone + ' 的聊天');
});
};
const initAdmin = () => {
const navLinks = document.querySelectorAll('[data-section-link]');
const sections = document.querySelectorAll('[data-section]');
@ -160,5 +244,6 @@ const initAdmin = () => {
};
document.addEventListener('DOMContentLoaded', () => {
if (document.body.dataset.page === 'agent') initFrontend();
if (document.body.dataset.page === 'admin') initAdmin();
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -14,58 +14,53 @@ declare(strict_types=1);
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/emoji-picker-element@1.18.10/dist/index.css" rel="stylesheet">
<link href="assets/css/custom.css?v=<?= time() ?>" rel="stylesheet">
<style>
.country-item { cursor: pointer; padding: 10px; border-bottom: 1px solid #eee; }
.country-item:hover { background-color: #f8f9fa; }
</style>
</head>
<body data-page="agent">
<header class="topbar">
<div class="brand">
<img src="assets/pasted-20260318-121747-861d2826.jpg" alt="logo" style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover;" />
<div>
<div class="app-frontend">
<header class="frontend-topbar">
<div class="brand">
<img src="assets/pasted-20260318-121747-861d2826.jpg" alt="logo" style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover;" />
SMS短信平台
</div>
</div>
<div class="meta">
<span class="status-pill"> 在线</span>
<span>客服Lina</span>
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#settingsModal">⚙️ 设置</button>
</div>
</header>
<main class="app-shell">
<section class="panel contacts-panel">
<div class="search d-flex gap-2">
<div class="form-control" role="button" id="searchTrigger" data-bs-toggle="modal" data-bs-target="#countryModal" style="cursor: pointer; background: #fff; border: 1px solid #ced4da;">🔍 点击选择国家开始聊天...</div>
<div class="meta">
<span class="status-pill"> 在线</span>
<span>客服Lina</span>
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#settingsModal">⚙️ 设置</button>
</div>
<div class="contacts-list" data-contacts></div>
</section>
</header>
<section class="panel chat-panel">
<div class="chat-header">
<div>
<div class="fw-semibold" data-chat-title>请选择联系人</div>
<div class="small muted" data-chat-meta>状态</div>
<main class="frontend-app-shell">
<section class="contacts-panel">
<div class="search d-flex gap-2">
<div class="form-control" role="button" id="searchTrigger" data-bs-toggle="modal" data-bs-target="#countryModal" style="cursor: pointer; background: #fff; border: 1px solid #ced4da;">🔍 点击选择国家开始聊天...</div>
</div>
</div>
<div class="chat-body" data-chat-body></div>
<div class="chat-input-wrapper">
<div class="shortcut-list mb-2" data-shortcuts></div>
<div class="chat-input">
<form class="d-flex gap-2 align-items-center" data-chat-form>
<div class="position-relative">
<button class="btn btn-light border" type="button" data-emoji-trigger>😊</button>
<emoji-picker class="position-absolute" style="bottom: 100%; left: 0; z-index: 1000;" data-emoji-picker></emoji-picker>
</div>
<input class="form-control" placeholder="输入短信内容..." data-chat-input />
<button class="btn btn-primary px-4" type="submit">发送</button>
</form>
<div class="contacts-list" data-contacts></div>
</section>
<section class="chat-panel">
<div class="frontend-chat-header">
<div>
<div class="fw-semibold" data-chat-title>请选择联系人</div>
<div class="small muted" data-chat-meta>状态</div>
</div>
</div>
</div>
</section>
</main>
<div class="frontend-chat-body" data-chat-body></div>
<div class="frontend-chat-input-wrapper">
<div class="shortcut-list mb-2" data-shortcuts></div>
<div class="chat-input">
<form class="d-flex gap-2 align-items-center" data-chat-form>
<div class="position-relative">
<button class="btn btn-light border" type="button" data-emoji-trigger>😊</button>
<emoji-picker class="position-absolute" style="bottom: 100%; left: 0; z-index: 1000;" data-emoji-picker></emoji-picker>
</div>
<input class="form-control" placeholder="输入短信内容..." data-chat-input />
<button class="btn btn-primary px-4" type="submit">发送</button>
</form>
</div>
</div>
</section>
</main>
</div>
<!-- Country Selection Modal -->
<div class="modal fade" id="countryModal" tabindex="-1">