本文目录导读:

我将为您创建一个PHP项目邮件列表与分组管理的完整解决方案。
项目结构
mailing-list/
├── config/
│ └── database.php
├── includes/
│ ├── functions.php
│ └── auth.php
├── api/
│ ├── groups.php
│ ├── subscribers.php
│ └── send.php
├── templates/
│ └── email-template.php
├── admin/
│ ├── dashboard.php
│ ├── groups.php
│ └── subscribers.php
├── index.php
└── install.php
数据库配置
<?php
// config/database.php
class Database {
private $host = 'localhost';
private $db_name = 'mailing_list';
private $username = 'root';
private $password = '';
private $conn;
public function getConnection() {
$this->conn = null;
try {
$this->conn = new PDO(
"mysql:host={$this->host};dbname={$this->db_name}",
$this->username,
$this->password
);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch(PDOException $e) {
echo "Connection Error: " . $e->getMessage();
}
return $this->conn;
}
}
数据库安装脚本
-- install.php
CREATE DATABASE IF NOT EXISTS mailing_list;
USE mailing_list;
-- 用户表
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role ENUM('admin', 'manager', 'user') DEFAULT 'user',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 分组表
CREATE TABLE groups (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
created_by INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE
);
-- 订阅者表
CREATE TABLE subscribers (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(100),
status ENUM('active', 'unsubscribed', 'bounced') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 分组订阅者关联表
CREATE TABLE group_subscribers (
id INT AUTO_INCREMENT PRIMARY KEY,
group_id INT NOT NULL,
subscriber_id INT NOT NULL,
subscribed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE,
FOREIGN KEY (subscriber_id) REFERENCES subscribers(id) ON DELETE CASCADE,
UNIQUE KEY unique_group_subscriber (group_id, subscriber_id)
);
-- 邮件发送记录表
CREATE TABLE email_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
group_id INT,
subject VARCHAR(255) NOT NULL,
content TEXT,
sent_by INT,
recipient_count INT DEFAULT 0,
success_count INT DEFAULT 0,
failed_count INT DEFAULT 0,
sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE SET NULL,
FOREIGN KEY (sent_by) REFERENCES users(id) ON DELETE SET NULL
);
-- 邮件发送状态表
CREATE TABLE email_status (
id INT AUTO_INCREMENT PRIMARY KEY,
log_id INT,
subscriber_id INT,
status ENUM('pending', 'sent', 'failed') DEFAULT 'pending',
error_message TEXT,
sent_at TIMESTAMP NULL,
FOREIGN KEY (log_id) REFERENCES email_logs(id) ON DELETE CASCADE,
FOREIGN KEY (subscriber_id) REFERENCES subscribers(id) ON DELETE CASCADE
);
-- 插入管理员用户 (密码: admin123)
INSERT INTO users (username, email, password, role)
VALUES ('admin', 'admin@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin');
核心功能函数
<?php
// includes/functions.php
require_once __DIR__ . '/../config/database.php';
class MailListManager {
private $db;
private $conn;
public function __construct() {
$this->db = new Database();
$this->conn = $this->db->getConnection();
}
// 分组管理
public function createGroup($name, $description, $userId) {
$sql = "INSERT INTO groups (name, description, created_by) VALUES (?, ?, ?)";
$stmt = $this->conn->prepare($sql);
return $stmt->execute([$name, $description, $userId]);
}
public function getGroups() {
$sql = "SELECT g.*, u.username as creator_name,
(SELECT COUNT(*) FROM group_subscribers WHERE group_id = g.id) as subscriber_count
FROM groups g
LEFT JOIN users u ON g.created_by = u.id
ORDER BY g.created_at DESC";
$stmt = $this->conn->query($sql);
return $stmt->fetchAll();
}
public function getGroup($groupId) {
$sql = "SELECT g.*, u.username as creator_name
FROM groups g
LEFT JOIN users u ON g.created_by = u.id
WHERE g.id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->execute([$groupId]);
return $stmt->fetch();
}
public function updateGroup($groupId, $name, $description) {
$sql = "UPDATE groups SET name = ?, description = ? WHERE id = ?";
$stmt = $this->conn->prepare($sql);
return $stmt->execute([$name, $description, $groupId]);
}
public function deleteGroup($groupId) {
$sql = "DELETE FROM groups WHERE id = ?";
$stmt = $this->conn->prepare($sql);
return $stmt->execute([$groupId]);
}
// 订阅者管理
public function addSubscriber($email, $name = null) {
$sql = "INSERT INTO subscribers (email, name) VALUES (?, ?)
ON DUPLICATE KEY UPDATE name = ?, status = 'active'";
$stmt = $this->conn->prepare($sql);
return $stmt->execute([$email, $name, $name]);
}
public function getSubscribers($page = 1, $limit = 20) {
$offset = ($page - 1) * $limit;
$sql = "SELECT s.*,
(SELECT COUNT(*) FROM group_subscribers WHERE subscriber_id = s.id) as group_count
FROM subscribers s
ORDER BY s.created_at DESC
LIMIT ? OFFSET ?";
$stmt = $this->conn->prepare($sql);
$stmt->execute([$limit, $offset]);
return $stmt->fetchAll();
}
public function getSubscriber($subscriberId) {
$sql = "SELECT * FROM subscribers WHERE id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->execute([$subscriberId]);
return $stmt->fetch();
}
public function updateSubscriber($subscriberId, $email, $name, $status) {
$sql = "UPDATE subscribers SET email = ?, name = ?, status = ? WHERE id = ?";
$stmt = $this->conn->prepare($sql);
return $stmt->execute([$email, $name, $status, $subscriberId]);
}
public function deleteSubscriber($subscriberId) {
// 先删除关联
$sql1 = "DELETE FROM group_subscribers WHERE subscriber_id = ?";
$stmt1 = $this->conn->prepare($sql1);
$stmt1->execute([$subscriberId]);
$sql2 = "DELETE FROM subscribers WHERE id = ?";
$stmt2 = $this->conn->prepare($sql2);
return $stmt2->execute([$subscriberId]);
}
// 分组订阅者管理
public function addSubscriberToGroup($groupId, $subscriberId) {
$sql = "INSERT IGNORE INTO group_subscribers (group_id, subscriber_id) VALUES (?, ?)";
$stmt = $this->conn->prepare($sql);
return $stmt->execute([$groupId, $subscriberId]);
}
public function removeSubscriberFromGroup($groupId, $subscriberId) {
$sql = "DELETE FROM group_subscribers WHERE group_id = ? AND subscriber_id = ?";
$stmt = $this->conn->prepare($sql);
return $stmt->execute([$groupId, $subscriberId]);
}
public function getGroupSubscribers($groupId) {
$sql = "SELECT s.*, gs.subscribed_at
FROM subscribers s
INNER JOIN group_subscribers gs ON s.id = gs.subscriber_id
WHERE gs.group_id = ? AND s.status = 'active'
ORDER BY gs.subscribed_at DESC";
$stmt = $this->conn->prepare($sql);
$stmt->execute([$groupId]);
return $stmt->fetchAll();
}
public function getSubscriberGroups($subscriberId) {
$sql = "SELECT g.*, gs.subscribed_at
FROM groups g
INNER JOIN group_subscribers gs ON g.id = gs.group_id
WHERE gs.subscriber_id = ?
ORDER BY gs.subscribed_at DESC";
$stmt = $this->conn->prepare($sql);
$stmt->execute([$subscriberId]);
return $stmt->fetchAll();
}
// 邮件发送
public function sendEmail($groupId, $subject, $content, $userId) {
$subscribers = $this->getGroupSubscribers($groupId);
$recipientCount = count($subscribers);
if ($recipientCount == 0) {
return ['success' => false, 'message' => '该组没有订阅者'];
}
// 创建发送记录
$sql = "INSERT INTO email_logs (group_id, subject, content, sent_by, recipient_count)
VALUES (?, ?, ?, ?, ?)";
$stmt = $this->conn->prepare($sql);
$stmt->execute([$groupId, $subject, $content, $userId, $recipientCount]);
$logId = $this->conn->lastInsertId();
$successCount = 0;
$failedCount = 0;
// 创建发送状态记录
foreach ($subscribers as $subscriber) {
$sql = "INSERT INTO email_status (log_id, subscriber_id, status) VALUES (?, ?, 'pending')";
$stmt = $this->conn->prepare($sql);
$stmt->execute([$logId, $subscriber['id']]);
}
// 发送邮件
foreach ($subscribers as $subscriber) {
$emailSent = $this->sendActualEmail($subscriber['email'], $subscriber['name'], $subject, $content);
if ($emailSent) {
$sql = "UPDATE email_status SET status = 'sent', sent_at = NOW()
WHERE log_id = ? AND subscriber_id = ?";
$successCount++;
} else {
$sql = "UPDATE email_status SET status = 'failed', error_message = ?
WHERE log_id = ? AND subscriber_id = ?";
$failedCount++;
}
$stmt = $this->conn->prepare($sql);
$stmt->execute([$logId, $subscriber['id']]);
}
// 更新发送记录
$sql = "UPDATE email_logs SET success_count = ?, failed_count = ? WHERE id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->execute([$successCount, $failedCount, $logId]);
return [
'success' => true,
'message' => "发送完成:成功 {$successCount},失败 {$failedCount}",
'total' => $recipientCount,
'success_count' => $successCount,
'failed_count' => $failedCount
];
}
private function sendActualEmail($to, $name, $subject, $content) {
// 使用PHP mail()函数或第三方SMTP服务
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
$headers .= 'From: Your Company <noreply@yourdomain.com>' . "\r\n";
// 简单的邮件模板
$emailContent = "
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #007bff; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.footer { text-align: center; padding: 10px; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h2>{$subject}</h2>
</div>
<div class='content'>
<p>尊敬的 {$name},</p>
<div>{$content}</div>
</div>
<div class='footer'>
<p>如果您不想接收此类邮件,请<a href='{$this->getUnsubscribeLink($to)}'>点击此处退订</a></p>
</div>
</div>
</body>
</html>";
return mail($to, $subject, $emailContent, $headers);
}
private function getUnsubscribeLink($email) {
return "http://yourdomain.com/unsubscribe.php?email=" . urlencode($email);
}
// 统计功能
public function getStatistics() {
$stats = [];
// 总订阅者数
$sql = "SELECT COUNT(*) as total FROM subscribers WHERE status = 'active'";
$stats['total_subscribers'] = $this->conn->query($sql)->fetch()['total'];
// 总分组数
$sql = "SELECT COUNT(*) as total FROM groups";
$stats['total_groups'] = $this->conn->query($sql)->fetch()['total'];
// 总发送数
$sql = "SELECT COUNT(*) as total FROM email_logs";
$stats['total_emails_sent'] = $this->conn->query($sql)->fetch()['total'];
// 近期活动
$sql = "SELECT e.subject, e.sent_at, g.name as group_name, e.recipient_count, e.success_count, e.failed_count
FROM email_logs e
LEFT JOIN groups g ON e.group_id = g.id
ORDER BY e.sent_at DESC
LIMIT 10";
$stats['recent_activities'] = $this->conn->query($sql)->fetchAll();
return $stats;
}
// 导入导出功能
public function importSubscribersFromCSV($filePath, $groupId = null) {
$imported = 0;
$failed = 0;
if (($handle = fopen($filePath, "r")) !== FALSE) {
fgetcsv($handle); // 跳过表头
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$email = trim($data[0]);
$name = isset($data[1]) ? trim($data[1]) : null;
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
$subscriberId = $this->addSubscriber($email, $name);
if ($subscriberId && $groupId) {
$this->addSubscriberToGroup($groupId, $subscriberId);
}
$imported++;
} else {
$failed++;
}
}
fclose($handle);
}
return ['imported' => $imported, 'failed' => $failed];
}
public function exportSubscribersToCSV($groupId = null) {
if ($groupId) {
$subscribers = $this->getGroupSubscribers($groupId);
} else {
$subscribers = $this->getSubscribers(1, 100000);
}
$filename = 'subscribers_' . date('Y-m-d') . '.csv';
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$output = fopen('php://output', 'w');
fputcsv($output, ['Email', 'Name', 'Status', 'Created At']);
foreach ($subscribers as $subscriber) {
fputcsv($output, [
$subscriber['email'],
$subscriber['name'],
$subscriber['status'],
$subscriber['created_at']
]);
}
fclose($output);
exit;
}
}
分组管理页面
<?php
// admin/groups.php
session_start();
require_once '../includes/functions.php';
// 检查登录状态
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
$manager = new MailListManager();
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'create':
$manager->createGroup($_POST['name'], $_POST['description'], $_SESSION['user_id']);
break;
case 'update':
$manager->updateGroup($_POST['group_id'], $_POST['name'], $_POST['description']);
break;
case 'delete':
$manager->deleteGroup($_POST['group_id']);
break;
case 'add_subscriber':
$manager->addSubscriber($_POST['email'], $_POST['name']);
break;
case 'add_to_group':
$manager->addSubscriberToGroup($_POST['group_id'], $_POST['subscriber_id']);
break;
}
header('Location: groups.php');
exit;
}
}
// 搜索功能
$search = isset($_GET['search']) ? $_GET['search'] : '';
if ($search) {
$groups = $manager->searchGroups($search);
} else {
$groups = $manager->getGroups();
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">分组管理 - 邮件列表系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
</head>
<body>
<?php include '../includes/navbar.php'; ?>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>分组管理</h2>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createGroupModal">
<i class="bi bi-plus-circle"></i> 创建新分组
</button>
</div>
<!-- 搜索栏 -->
<div class="card mb-4">
<div class="card-body">
<form method="GET" class="row g-3">
<div class="col-md-8">
<input type="text" class="form-control" name="search" placeholder="搜索分组名称..." value="<?php echo htmlspecialchars($search); ?>">
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-search"></i> 搜索
</button>
</div>
</form>
</div>
</div>
<!-- 分组列表 -->
<div class="row">
<?php foreach ($groups as $group): ?>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<h5 class="card-title"><?php echo htmlspecialchars($group['name']); ?></h5>
<div class="dropdown">
<button class="btn btn-sm btn-link" type="button" data-bs-toggle="dropdown">
<i class="bi bi-three-dots"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#editGroupModal"
data-group-id="<?php echo $group['id']; ?>"
data-group-name="<?php echo htmlspecialchars($group['name']); ?>"
data-group-description="<?php echo htmlspecialchars($group['description']); ?>">
<i class="bi bi-pencil"></i> 编辑
</a></li>
<li><a class="dropdown-item" href="view_group.php?id=<?php echo $group['id']; ?>">
<i class="bi bi-people"></i> 查看订阅者
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" data-bs-toggle="modal" data-bs-target="#deleteGroupModal"
data-group-id="<?php echo $group['id']; ?>"
data-group-name="<?php echo htmlspecialchars($group['name']); ?>">
<i class="bi bi-trash"></i> 删除
</a></li>
</ul>
</div>
</div>
<p class="card-text text-muted">
<?php echo htmlspecialchars(substr($group['description'], 0, 100)) ?: '暂无描述'; ?>
</p>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-info">
<i class="bi bi-people"></i> <?php echo $group['subscriber_count']; ?> 个订阅者
</span>
<small class="text-muted">创建人: <?php echo htmlspecialchars($group['creator_name']); ?></small>
</div>
</div>
<div class="card-footer bg-transparent">
<div class="btn-group w-100">
<a href="send_email.php?group_id=<?php echo $group['id']; ?>" class="btn btn-outline-success btn-sm">
<i class="bi bi-send"></i> 发送邮件
</a>
<a href="manage_subscribers.php?group_id=<?php echo $group['id']; ?>" class="btn btn-outline-primary btn-sm">
<i class="bi bi-gear"></i> 管理订阅者
</a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if (empty($groups)): ?>
<div class="text-center py-5">
<i class="bi bi-inbox display-1 text-muted"></i>
<p class="mt-3 text-muted">暂无分组</p>
</div>
<?php endif; ?>
</div>
<!-- 创建分组模态框 -->
<div class="modal fade" id="createGroupModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST">
<input type="hidden" name="action" value="create">
<div class="modal-header">
<h5 class="modal-title">创建新分组</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="name" class="form-label">分组名称</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">描述</label>
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary">创建</button>
</div>
</form>
</div>
</div>
</div>
<!-- 编辑分组模态框 -->
<div class="modal fade" id="editGroupModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST">
<input type="hidden" name="action" value="update">
<input type="hidden" name="group_id" id="edit_group_id">
<div class="modal-header">
<h5 class="modal-title">编辑分组</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="edit_name" class="form-label">分组名称</label>
<input type="text" class="form-control" id="edit_name" name="name" required>
</div>
<div class="mb-3">
<label for="edit_description" class="form-label">描述</label>
<textarea class="form-control" id="edit_description" name="description" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
</div>
<!-- 删除分组模态框 -->
<div class="modal fade" id="deleteGroupModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="group_id" id="delete_group_id">
<div class="modal-header">
<h5 class="modal-title">删除分组</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>确定要删除分组 "<span id="delete_group_name"></span>" 吗?</p>
<p class="text-danger">此操作不可恢复!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-danger">删除</button>
</div>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 编辑分组模态框数据填充
document.getElementById('editGroupModal').addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var groupId = button.getAttribute('data-group-id');
var groupName = button.getAttribute('data-group-name');
var groupDescription = button.getAttribute('data-group-description');
document.getElementById('edit_group_id').value = groupId;
document.getElementById('edit_name').value = groupName;
document.getElementById('edit_description').value = groupDescription;
});
// 删除分组模态框数据填充
document.getElementById('deleteGroupModal').addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var groupId = button.getAttribute('data-group-id');
var groupName = button.getAttribute('data-group-name');
document.getElementById('delete_group_id').value = groupId;
document.getElementById('delete_group_name').textContent = groupName;
});
</script>
</body>
</html>
邮件发送管理页面
<?php
// admin/send_email.php
session_start();
require_once '../includes/functions.php';
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
$manager = new MailListManager();
$groupId = isset($_GET['group_id']) ? $_GET['group_id'] : null;
if ($groupId) {
$group = $manager->getGroup($groupId);
$subscribers = $manager->getGroupSubscribers($groupId);
}
// 处理邮件发送
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['send_email'])) {
$result = $manager->sendEmail(
$_POST['group_id'],
$_POST['subject'],
$_POST['content'],
$_SESSION['user_id']
);
if ($result['success']) {
$success_message = $result['message'];
} else {
$error_message = $result['message'];
}
}
$groups = $manager->getGroups();
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">发送邮件 - 邮件列表系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
</head>
<body>
<?php include '../includes/navbar.php'; ?>
<div class="container mt-4">
<h2>发送电子邮件</h2>
<?php if (isset($success_message)): ?>
<div class="alert alert-success alert-dismissible fade show">
<?php echo htmlspecialchars($success_message); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($error_message)): ?>
<div class="alert alert-danger alert-dismissible fade show">
<?php echo htmlspecialchars($error_message); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<form method="POST" id="emailForm">
<input type="hidden" name="group_id" value="<?php echo $groupId; ?>">
<div class="mb-3">
<label for="group_select" class="form-label">目标分组</label>
<select class="form-select" id="group_select" name="group_id" required
onchange="location.href='send_email.php?group_id='+this.value">
<option value="">选择分组...</option>
<?php foreach ($groups as $g): ?>
<option value="<?php echo $g['id']; ?>"
<?php echo ($g['id'] == $groupId) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($g['name']); ?>
(<?php echo $g['subscriber_count']; ?> 订阅者)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="subject" class="form-label">邮件主题</label>
<input type="text" class="form-control" id="subject" name="subject" required>
</div>
<div class="mb-3">
<label for="content" class="form-label">邮件内容</label>
<textarea class="form-control" id="content" name="content" rows="10"></textarea>
</div>
<div class="mb-3">
<button type="submit" name="send_email" class="btn btn-primary"
onclick="return confirm('确定要发送邮件给选中的订阅者吗?')">
<i class="bi bi-send"></i> 发送邮件
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<?php if ($groupId && isset($group)): ?>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">目标订阅者</h5>
</div>
<div class="card-body">
<p>分组: <strong><?php echo htmlspecialchars($group['name']); ?></strong></p>
<p>订阅者数量: <strong><?php echo count($subscribers); ?></strong></p>
<h6>订阅者列表:</h6>
<div class="list-group" style="max-height: 300px; overflow-y: auto;">
<?php foreach ($subscribers as $subscriber): ?>
<div class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1"><?php echo htmlspecialchars($subscriber['name'] ?: '无姓名'); ?></h6>
<small><?php echo date('Y-m-d', strtotime($subscriber['subscribed_at'])); ?></small>
</div>
<p class="mb-1 text-muted small"><?php echo htmlspecialchars($subscriber['email']); ?></p>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
<script>
$(document).ready(function() {
$('#content').summernote({
height: 300,
toolbar: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen', 'codeview', 'help']]
]
});
});
</script>
</body>
</html>
API接口文件
<?php
// api/subscribers.php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type');
require_once '../includes/functions.php';
$manager = new MailListManager();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
if (isset($_GET['id'])) {
$subscriber = $manager