PHP项目邮件列表与分组管理

wen PHP项目 1

本文目录导读:

PHP项目邮件列表与分组管理

  1. 项目结构
  2. 数据库配置
  3. 数据库安装脚本
  4. 核心功能函数
  5. 分组管理页面
  6. 邮件发送管理页面
  7. API接口文件

我将为您创建一个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

抱歉,评论功能暂时关闭!