本文目录导读:

我将为您创建一个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
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播