Autosave: 20260318-133151

This commit is contained in:
Flatlogic Bot 2026-03-18 13:31:51 +00:00
parent adaa57d3e7
commit 678400ba66
10 changed files with 232 additions and 597 deletions

View File

@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
header('Location: login.php');
exit;
}
?>
<!doctype html>
<html lang="zh-CN">
@ -10,25 +11,6 @@ declare(strict_types=1);
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SMS Chat 管理后台</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<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>
@ -44,7 +26,8 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<div class="meta">
<span class="status-pill"> 系统正常</span>
<span>管理员Admin</span>
<a class="btn btn-sm btn-outline-secondary" href="index.php">返回工作台</a>
<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>
@ -210,4 +193,4 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" defer></script>
<script src="assets/js/main.js?v=<?= time() ?>" defer></script>
</body>
</html>
</html>

View File

@ -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; }

View File

@ -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 = `<span>${s}</span><button class="btn btn-sm btn-danger" onclick="removeShortcut(${i})">删除</button>`;
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} <small class="text-muted">(${c.code})</small>`;
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 = `
<div class="d-flex justify-content-between align-items-center w-100">
<div style="flex-grow: 1;" onclick="setActive('${contact.id}')">
<div class="fw-semibold">${contact.phone}</div>
<div class="meta text-truncate" style="font-size: 0.8em; color: #666;">${contact.last_message || '暂无消息'}</div>
</div>
<div class="dropdown">
<button class="btn btn-sm btn-link text-muted" data-bs-toggle="dropdown" aria-expanded="false"></button>
<ul class="dropdown-menu">
<li><button class="dropdown-item text-danger" onclick="deleteContact('${contact.id}')">删除</button></li>
<li><button class="dropdown-item text-dark" onclick="blockContact('${contact.id}')">拉黑</button></li>
</ul>
</div>
</div>
`;
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 => `
<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' });
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 => `
<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('');
};
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 => `
<div class="message ${msg.direction === 'out' ? 'out' : 'in'}">
<div>${msg.body}</div>
<div class="time">${formatTime(msg.created_at)}</div>
</div>
`).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();
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -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
);

6
db/migrations/run.php Normal file
View File

@ -0,0 +1,6 @@
<?php
require_once __DIR__ . '/../config.php';
$pdo = db();
$sql = file_get_contents(__DIR__ . '/001_create_tables.sql');
$pdo->exec($sql);
echo "Migration completed.\n";

47
login.php Normal file
View File

@ -0,0 +1,47 @@
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
// 默认账号密码设置admin / admin123
// 后续建议移动到环境变量或数据库加密存储
if ($username === 'admin' && $password === 'admin123') {
$_SESSION['loggedin'] = true;
$_SESSION['username'] = $username;
header('Location: admin.php');
exit;
} else {
$error = '账号或密码错误';
}
}
?>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>登录 - 管理后台</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #f8f9fa; }
.login-card { width: 100%; max-width: 400px; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
</style>
</head>
<body>
<div class="login-card">
<h4 class="mb-3">后台登录</h4>
<?php if (isset($error)): ?><div class="alert alert-danger"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<form method="POST">
<div class="mb-3">
<label class="form-label">账号</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">密码</label>
<input type="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">登入</button>
</form>
</div>
</body>
</html>

5
logout.php Normal file
View File

@ -0,0 +1,5 @@
<?php
session_start();
session_destroy();
header('Location: login.php');
exit;

View File

@ -105,5 +105,5 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" defer></script>
<script src="assets/js/countries.js?v=1773838803" defer></script>
<script src="assets/js/main.js?v=1773838803" defer></script>
<script>if(window.FL_SHOW_BUDGE!==false){document.body.style.position='relative';var w=document.createElement('a');w.href='https://flatlogic.com';w.target='_blank';w.innerHTML='<svg width="160" height="32" viewBox="0 0 320 65" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="320" height="65" rx="28" fill="#F5F6FA"/><g transform="translate(32 10) scale(1.5)"><rect x="15" y="10" width="7" height="5" rx="1" fill="#142C65"/><rect x="0" y="2" width="22" height="5" rx="1" fill="#5C7EF1"/><rect x="0" y="18" width="6" height="5" rx="1" fill="#8C9DFF"/><rect x="9" y="18" width="13" height="5" rx="1" fill="#142C65"/><rect x="0" y="10" width="12" height="5" rx="1" fill="#FFB229"/></g><text x="80" y="40" fill="#142C65" font-size="24" font-weight="600" font-family="Mulish,Arial,sans-serif">Built with Flatlogic</text></svg>';w.style.cssText='position:absolute!important;bottom:30px!important;right:30px!important;background:transparent!important;z-index:2147483647!important;user-select:none!important;text-decoration:none!important;cursor:pointer!important;border:none!important;padding:0!important;margin:0!important';document.body.appendChild(w);var o=new MutationObserver(function(m){m.forEach(function(n){if(n.type==='childList'&&n.removedNodes.length>0){for(var i=0;i<n.removedNodes.length;i++){if(n.removedNodes[i]===w||(n.removedNodes[i].innerHTML&&n.removedNodes[i].innerHTML.indexOf('Powered by')>-1)){setTimeout(function(){document.body.appendChild(w.cloneNode(true))},100);break}}}if(n.target===document.body&&n.type==='attributes'&&n.attributeName==='style'){if(document.body.style.position!=='relative'){document.body.style.position='relative'}}})});o.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['style']})}</script></body>
</html>
</body></html>