本文目录导读:

我来为您提供一个PHP项目中的公告与弹窗管理系统设计方案。
系统架构设计
数据库设计
-- 公告表
CREATE TABLE `announcements` (
`id` int(11) NOT NULL AUTO_INCREMENT, varchar(200) NOT NULL COMMENT '公告标题',
`content` text NOT NULL COMMENT '公告内容',
`type` tinyint(1) DEFAULT '1' COMMENT '类型:1普通公告 2重要公告 3紧急公告',
`status` tinyint(1) DEFAULT '1' COMMENT '状态:0草稿 1发布 2已下线',
`priority` int(11) DEFAULT '0' COMMENT '优先级',
`is_top` tinyint(1) DEFAULT '0' COMMENT '是否置顶',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_status` (`status`),
KEY `idx_type` (`type`),
KEY `idx_time` (`start_time`,`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 弹窗表
CREATE TABLE `popups` (
`id` int(11) NOT NULL AUTO_INCREMENT, varchar(200) NOT NULL COMMENT '弹窗标题',
`content` text COMMENT '弹窗内容',
`image_url` varchar(500) DEFAULT NULL COMMENT '弹窗图片',
`link_url` varchar(500) DEFAULT NULL COMMENT '点击链接',
`type` tinyint(1) DEFAULT '1' COMMENT '类型:1页面弹窗 2悬浮窗 3引导弹窗',
`position` varchar(50) DEFAULT 'center' COMMENT '位置:center/top/left/right/bottom',
`width` int(11) DEFAULT '400' COMMENT '宽度',
`height` int(11) DEFAULT '300' COMMENT '高度',
`show_type` tinyint(1) DEFAULT '1' COMMENT '显示方式:1一次性 2每次访问 3每天一次',
`status` tinyint(1) DEFAULT '1' COMMENT '状态:0禁用 1启用',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_status` (`status`),
KEY `idx_time` (`start_time`,`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户阅读记录表
CREATE TABLE `user_read_records` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`record_type` tinyint(1) NOT NULL COMMENT '类型:1公告 2弹窗',
`record_id` int(11) NOT NULL COMMENT '对应ID',
`read_time` datetime NOT NULL COMMENT '阅读时间',
`read_count` int(11) DEFAULT '1 COMMENT '阅读次数',
PRIMARY KEY (`id`),
KEY `idx_user_type` (`user_id`,`record_type`),
KEY `idx_record` (`record_type`,`record_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心PHP代码实现
<?php
// PopupManager.php
class PopupManager {
private $db;
private $userId;
public function __construct($db, $userId = null) {
$this->db = $db;
$this->userId = $userId;
}
/**
* 获取当前有效的公告列表
*/
public function getActiveAnnouncements($limit = 10) {
$now = date('Y-m-d H:i:s');
$sql = "SELECT * FROM announcements
WHERE status = 1
AND (start_time IS NULL OR start_time <= :now)
AND (end_time IS NULL OR end_time >= :now)
ORDER BY is_top DESC, priority DESC, created_at DESC
LIMIT :limit";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':now', $now);
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* 获取当前有效的弹窗
*/
public function getActivePopups($page = '') {
$now = date('Y-m-d H:i:s');
$sql = "SELECT * FROM popups
WHERE status = 1
AND (start_time IS NULL OR start_time <= :now)
AND (end_time IS NULL OR end_time >= :now)
AND (target_page IS NULL OR target_page = '' OR target_page = :page)
ORDER BY priority DESC, created_at DESC";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':now', $now);
$stmt->bindParam(':page', $page);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* 检查用户是否已读公告
*/
public function hasReadAnnouncement($announcementId) {
if (!$this->userId) return false;
$sql = "SELECT COUNT(*) FROM user_read_records
WHERE user_id = :user_id
AND record_type = 1
AND record_id = :record_id";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':user_id', $this->userId);
$stmt->bindParam(':record_id', $announcementId);
$stmt->execute();
return $stmt->fetchColumn() > 0;
}
/**
* 标记已读
*/
public function markAsRead($recordType, $recordId) {
if (!$this->userId) return false;
// 检查是否已存在记录
$existingSql = "SELECT id, read_count FROM user_read_records
WHERE user_id = :user_id
AND record_type = :record_type
AND record_id = :record_id";
$stmt = $this->db->prepare($existingSql);
$stmt->bindParam(':user_id', $this->userId);
$stmt->bindParam(':record_type', $recordType);
$stmt->bindParam(':record_id', $recordId);
$stmt->execute();
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing) {
// 更新阅读次数
$updateSql = "UPDATE user_read_records
SET read_count = read_count + 1,
read_time = :read_time
WHERE id = :id";
$stmt = $this->db->prepare($updateSql);
$stmt->bindParam(':read_time', date('Y-m-d H:i:s'));
$stmt->bindParam(':id', $existing['id']);
return $stmt->execute();
} else {
// 新增阅读记录
$insertSql = "INSERT INTO user_read_records
(user_id, record_type, record_id, read_time)
VALUES (:user_id, :record_type, :record_id, :read_time)";
$stmt = $this->db->prepare($insertSql);
$stmt->bindParam(':user_id', $this->userId);
$stmt->bindParam(':record_type', $recordType);
$stmt->bindParam(':record_id', $recordId);
$stmt->bindParam(':read_time', date('Y-m-d H:i:s'));
return $stmt->execute();
}
}
/**
* 检查弹窗是否应该显示给当前用户
*/
public function shouldShowPopup($popup) {
if (!$this->userId) return true;
switch ($popup['show_type']) {
case 1: // 一次性
$sql = "SELECT COUNT(*) FROM user_read_records
WHERE user_id = :user_id
AND record_type = 2
AND record_id = :record_id";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':user_id', $this->userId);
$stmt->bindParam(':record_id', $popup['id']);
$stmt->execute();
return $stmt->fetchColumn() == 0;
case 2: // 每次访问
return true;
case 3: // 每天一次
$today = date('Y-m-d');
$sql = "SELECT COUNT(*) FROM user_read_records
WHERE user_id = :user_id
AND record_type = 2
AND record_id = :record_id
AND DATE(read_time) = :today";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':user_id', $this->userId);
$stmt->bindParam(':record_id', $popup['id']);
$stmt->bindParam(':today', $today);
$stmt->execute();
return $stmt->fetchColumn() == 0;
default:
return true;
}
}
}
前端JavaScript实现
// popup-manager.js
class PopupManager {
constructor(options = {}) {
this.options = {
ajaxUrl: '/api/popup',
markReadUrl: '/api/mark-read',
...options
};
this.init();
}
init() {
this.loadAnnouncements();
this.loadPopups();
}
/**
* 加载公告
*/
async loadAnnouncements() {
try {
const response = await fetch(`${this.options.ajaxUrl}?type=announcement`);
const data = await response.json();
if (data.success && data.data.length > 0) {
this.renderAnnouncements(data.data);
}
} catch (error) {
console.error('Load announcements failed:', error);
}
}
/**
* 加载弹窗
*/
async loadPopups() {
try {
const currentPage = window.location.pathname;
const response = await fetch(`${this.options.ajaxUrl}?type=popup&page=${encodeURIComponent(currentPage)}`);
const data = await response.json();
if (data.success && data.data.length > 0) {
data.data.forEach(popup => {
if (this.shouldShowPopup(popup)) {
this.renderPopup(popup);
}
});
}
} catch (error) {
console.error('Load popups failed:', error);
}
}
/**
* 渲染公告列表
*/
renderAnnouncements(announcements) {
const container = document.getElementById('announcement-container');
if (!container) return;
let html = '';
announcements.forEach(ann => {
const isUrgent = ann.type === 3;
html += `
<div class="announcement-item ${isUrgent ? 'urgent' : ''}" data-id="${ann.id}">
<div class="announcement-header">
<span class="announcement-type">${this.getTypeLabel(ann.type)}</span>
<h4 class="announcement-title">${ann.title}</h4>
<span class="announcement-date">${ann.created_at}</span>
</div>
<div class="announcement-content">
${ann.content}
</div>
<button class="announcement-read-btn" onclick="popupManager.markAsRead('announcement', ${ann.id})">
我知道了
</button>
</div>
`;
});
container.innerHTML = html;
}
/**
* 渲染弹窗
*/
renderPopup(popup) {
const modal = document.createElement('div');
modal.className = 'popup-modal';
modal.dataset.id = popup.id;
modal.innerHTML = `
<div class="popup-overlay"></div>
<div class="popup-content" style="width: ${popup.width || 400}px; max-height: ${popup.height || 300}px;">
<button class="popup-close" onclick="popupManager.closePopup(this)">×</button>
${popup.image_url ? `<img src="${popup.image_url}" alt="${popup.title}" class="popup-image">` : ''}
<h3 class="popup-title">${popup.title}</h3>
<div class="popup-body">
${popup.content}
</div>
${popup.link_url ? `<a href="${popup.link_url}" class="popup-link" target="_blank">了解更多</a>` : ''}
</div>
`;
document.body.appendChild(modal);
// 弹窗动画
setTimeout(() => {
modal.classList.add('active');
}, 100);
}
/**
* 关闭弹窗
*/
closePopup(button) {
const modal = button.closest('.popup-modal');
const popupId = modal.dataset.id;
modal.classList.remove('active');
setTimeout(() => {
modal.remove();
}, 300);
// 记录关闭
this.markAsRead('popup', popupId);
}
/**
* 标记已读
*/
async markAsRead(type, id) {
try {
const response = await fetch(this.options.markReadUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: type,
id: id
})
});
const data = await response.json();
if (data.success) {
// 如果是公告,隐藏已读公告
if (type === 'announcement') {
const element = document.querySelector(`.announcement-item[data-id="${id}"]`);
if (element) {
element.style.display = 'none';
}
}
}
} catch (error) {
console.error('Mark as read failed:', error);
}
}
/**
* 获取类型标签
*/
getTypeLabel(type) {
const labels = {
1: '普通',
2: '重要',
3: '紧急'
};
return labels[type] || '普通';
}
/**
* 检查是否应该显示弹窗
*/
shouldShowPopup(popup) {
// 检查Cookie中的关闭记录
if (popup.show_type === 1) { // 一次性
const closedPopups = this.getCookie('closed_popups');
return !closedPopups || !closedPopups.includes(popup.id.toString());
}
if (popup.show_type === 3) { // 每天一次
const todayClosed = this.getCookie('today_closed_popups');
if (todayClosed) {
const today = new Date().toDateString();
const closedData = JSON.parse(todayClosed);
if (closedData.date === today && closedData.ids.includes(popup.id)) {
return false;
}
}
}
return true;
}
/**
* Cookie辅助方法
*/
getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? decodeURIComponent(match[2]) : null;
}
setCookie(name, value, days = 7) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = name + '=' + encodeURIComponent(value) + '; expires=' + expires + '; path=/';
}
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
window.popupManager = new PopupManager({
ajaxUrl: '/api/popup.php',
markReadUrl: '/api/mark-read.php'
});
});
CSS样式
/* popup-styles.css */
/* 公告样式 */
.announcement-container {
max-width: 800px;
margin: 20px auto;
padding: 0 15px;
}
.announcement-item {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.announcement-item.urgent {
border-left: 4px solid #ff4444;
background: #fff5f5;
}
.announcement-header {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.announcement-type {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.announcement-item .announcement-type {
background: #007bff;
color: white;
}
.announcement-item.urgent .announcement-type {
background: #ff4444;
}
.announcement-title {
flex: 1;
margin: 0 15px;
font-size: 16px;
}
.announcement-date {
font-size: 12px;
color: #888;
}
.announcement-content {
margin: 15px 0;
line-height: 1.6;
color: #333;
}
.announcement-read-btn {
background: #007bff;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.announcement-read-btn:hover {
background: #0056b3;
}
/* 弹窗样式 */
.popup-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
transition: opacity 0.3s ease;
}
.popup-modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.popup-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.popup-content {
position: relative;
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
max-width: 90%;
overflow-y: auto;
animation: popupFadeIn 0.3s ease;
}
@keyframes popupFadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.popup-close {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #999;
transition: color 0.3s;
}
.popup-close:hover {
color: #333;
}
.popup-image {
max-width: 100%;
height: auto;
margin-bottom: 15px;
border-radius: 8px;
}
.popup-title {
margin: 0 0 15px 0;
font-size: 20px;
color: #333;
}
.popup-body {
margin-bottom: 20px;
line-height: 1.6;
color: #666;
}
.popup-link {
display: inline-block;
background: #007bff;
color: white;
padding: 10px 25px;
border-radius: 6px;
text-decoration: none;
transition: background 0.3s;
}
.popup-link:hover {
background: #0056b3;
}
/* 响应式设计 */
@media (max-width: 768px) {
.popup-content {
width: 95% !important;
margin: 10px;
}
.announcement-header {
flex-direction: column;
align-items: flex-start;
}
.announcement-title {
margin: 10px 0;
}
}
API接口示例
<?php
// api/popup.php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: Content-Type');
require_once '../config/database.php';
require_once '../classes/PopupManager.php';
$db = new PDO("mysql:host=localhost;dbname=your_database", "username", "password");
$userId = $_SESSION['user_id'] ?? null;
$popupManager = new PopupManager($db, $userId);
$type = $_GET['type'] ?? '';
$page = $_GET['page'] ?? '';
switch ($type) {
case 'announcement':
$announcements = $popupManager->getActiveAnnouncements();
echo json_encode([
'success' => true,
'data' => $announcements
]);
break;
case 'popup':
$popups = $popupManager->getActivePopups($page);
$filteredPopups = [];
foreach ($popups as $popup) {
if ($popupManager->shouldShowPopup($popup)) {
$filteredPopups[] = $popup;
}
}
echo json_encode([
'success' => true,
'data' => $filteredPopups
]);
break;
default:
echo json_encode([
'success' => false,
'message' => 'Invalid type'
]);
}
?>
管理后台示例
<?php
// admin/announcements.php
// 创建公告
if ($_POST['action'] == 'create') {
$title = $_POST['title'];
$content = $_POST['content'];
$type = $_POST['type'];
$isTop = $_POST['is_top'] ?? 0;
$startTime = $_POST['start_time'] ?? null;
$endTime = $_POST['end_time'] ?? null;
$sql = "INSERT INTO announcements (title, content, type, is_top, start_time, end_time, status)
VALUES (:title, :content, :type, :is_top, :start_time, :end_time, 1)";
$stmt = $db->prepare($sql);
$stmt->execute([
':title' => $title,
':content' => $content,
':type' => $type,
':is_top' => $isTop,
':start_time' => $startTime,
':end_time' => $endTime
]);
}
// 更新公告
if ($_POST['action'] == 'update') {
$id = $_POST['id'];
$title = $_POST['title'];
$content = $_POST['content'];
$type = $_POST['type'];
$isTop = $_POST['is_top'] ?? 0;
$status = $_POST['status'] ?? 1;
$sql = "UPDATE announcements SET
title = :title,
content = :content,
type = :type,
is_top = :is_top,
status = :status
WHERE id = :id";
$stmt = $db->prepare($sql);
$stmt->execute([
':title' => $title,
':content' => $content,
':type' => $type,
':is_top' => $isTop,
':status' => $status,
':id' => $id
]);
}
// 删除公告
if ($_POST['action'] == 'delete') {
$id = $_POST['id'];
$sql = "DELETE FROM announcements WHERE id = :id";
$stmt = $db->prepare($sql);
$stmt->execute([':id' => $id]);
}
使用说明
- 安装:将数据库表结构和PHP文件部署到服务器
- 配置:修改数据库连接配置
- 前端集成:在页面中引入CSS和JS文件
- 管理:使用管理后台创建和管理公告与弹窗
这个系统支持:
- 公告的CRUD操作
- 弹窗的多种显示方式
- 用户阅读记录追踪
- 定时发布和下线
- 多种弹窗类型和位置
- 响应式设计
可根据实际需求进行扩展和定制。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播