睡不着右闲着蛋疼,看有人需要就写了这么个玩意








📢 文章催更功能 完整使用教程
🔧 一、代码部署教程
✅ 1. 部署前准备
- ➤ 确保网站基于 WordPress + 子比主题(Zibll) 搭建,非子比主题可能无法正常显示功能
- ➤ 已配置网站邮箱(推荐QQ邮箱),并开启SMTP服务(避免邮件发送失败)
- ➤ 拥有主题文件编辑权限(可通过后台直接编辑,或用FTP、宝塔面板操作)
📥 2. 代码安装步骤
- ① 登录WordPress后台,点击左侧菜单栏「外观」→「主题文件编辑器」
- ② 在右侧「主题文件」列表中,找到并点击「functions.php」(子比主题的核心函数文件)
- ③ 清空该文件原有内容(或直接粘贴到文件末尾,避免覆盖原有代码),粘贴完整的催更功能代码
- ④ 点击页面底部「更新文件」保存,若提示「文件不可写」,需先将文件权限设置为755
- ⑤ 刷新网站前台,打开任意文章详情页,验证侧边栏是否出现「催」字按钮(出现即部署成功)
⚠️ 安全提示:修改代码前,建议备份「functions.php」文件;若不熟悉后台操作,推荐用FTP工具编辑该文件,更安全不易出错。
🔄 3. 数据库自动升级
代码首次运行时,会自动完成数据库初始化,无需手动操作: ✅ 自动创建「wp_zib_cuigeng」催更记录表(表前缀随你的WordPress配置自动变化) ✅ 自动添加「is_notified」字段(用于标记催更用户是否已收到回复通知) ✅ 若已存在该表,自动检测并补充缺失字段,避免数据丢失
👥 二、前台用户操作教程
📌 1. 普通用户催更流程
- ① 打开网站任意文章详情页(首页、列表页不显示催更按钮)
- ② 在文章侧边栏找到「催」字按钮(带有自动扫光动画,样式醒目)
- ③ 点击按钮,弹出「确认催更」弹窗,弹窗内会显示当前已有催更人数
- ④ 点击弹窗内「确认催更」按钮,会立即弹出「催更成功」提示
- ⑤ 催更成功后,侧边栏「催」字按钮会变为「已催更」,不可重复点击(防重复催更)
🎁 2. 催更成功后用户权益
- ➤ 当文章作者回复催更内容时,你会收到系统发送的邮件通知
- ➤ 邮件包含:作者回复的「不更新原因/预计更新时间」+ 你自身的用户ID、用户名、绑定邮箱
- ➤ 游客用户(未登录)仅能点击催更,但无法收到回复邮件(无绑定邮箱,系统自动跳过)
🔑 三、后台管理员/作者操作教程
📥 1. 进入催更管理页面
- ① 登录WordPress后台,左侧菜单栏找到「催更管理」(铃铛图标,辨识度高)
- ② 点击「催更管理」,进入核心管理页面,默认显示「催更文章列表」
- ③ 列表包含4列内容:文章ID、文章标题、催更次数、操作(编辑回复),排版清晰易操作
📋 2. 查看催更记录
- ➤ 催更文章列表按「催更次数」降序排列,可快速找到催更人数最多的文章
- ➤ 点击列表中的「文章标题」,可直接跳转到前台该文章页面,快速查看文章内容
- ➤ 若暂无催更记录,页面会显示友好的「暂无催更记录」提示,不影响页面美观
✍️ 3. 回复催更用户(核心操作)
- ① 在「催更文章列表」中,找到需要回复的文章,点击右侧「编辑回复」按钮
- ② 进入「编辑催更回复」页面,包含两个可编辑项(均为选填,按需填写): ▫️ 不更新原因:填写文章不更新的具体理由,支持多行文本,可清晰告知用户 ▫️ 预计更新时间:选择具体的更新日期和时间,让用户了解更新进度
- ③ 填写完成后,点击页面底部「立即回复催更者」按钮,提交回复内容
- ④ 提交成功后,页面会弹出绿色提示「回复已发送!已通知所有催更用户」
- ⑤ 系统自动完成后续操作,无需手动干预: ▫️ 保存回复内容到数据库,前台文章下方会显示你的回复 ▫️ 向所有登录催更用户发送邮件通知(每人仅发送一次,避免重复打扰) ▫️ 标记用户为「已通知」状态,后续不再重复发送该文章的回复通知
💡 4. 关键操作说明
📌 回复内容支持部分HTML标签(如换行、加粗),邮件中会自动解析,排版更清晰
📌 仅登录用户能收到回复邮件,游客无绑定邮箱,系统会自动跳过,不报错
📌 邮件发送优先级:优先使用子比主题自带邮件函数,失败则自动切换到WordPress原生邮件函数,双重保障
📌 发件人信息自动读取网站设置的「管理员邮箱」和「网站名称」,无需手动配置
📧 四、邮件通知说明
📤 1. 作者收到的催更通知邮件
当有用户催更你的文章时,你会收到邮件通知,包含以下完整信息,清晰了解催更详情:
✅ 催更者ID(游客显示「游客」,登录用户显示具体数字ID)
✅ 催更者用户名(游客显示「游客」,登录用户显示其网站用户名)
✅ 催更者绑定邮箱(游客显示「无」,登录用户显示其绑定的邮箱地址)
✅ 被催更的文章标题+前台文章链接(可直接点击跳转查看)
✅ 催更管理后台链接(可直接点击进入,快速回复)
✅ 具体催更时间(精确到分,便于追溯)
📥 2. 用户收到的回复通知邮件
当作者回复催更后,你会收到邮件通知,包含以下信息,一目了然:
✅ 作者填写的「不更新原因」(若有填写)
✅ 作者填写的「预计更新时间」(若有填写)
✅ 被催更文章的标题+前台链接(可直接点击查看回复)
✅ 回复时间(精确到分)+ 回复作者名称(清晰知道是谁回复)
✅ 你自身的信息:用户ID、用户名、绑定邮箱(确认是自己的催更记录)
✅ 发件人信息(网站名称+管理员邮箱),可直接回复作者
❓ 五、常见问题排查
📩 1. 邮件发送失败
- ❌ 检查网站「设置→常规」中的「管理员邮箱」是否正确,需与发件邮箱一致
- ❌ QQ邮箱需开启SMTP服务,获取授权码,推荐使用「WP Mail SMTP」插件配置邮箱(更稳定)
- ❌ 联系主机商,确认服务器25/465端口未被封禁(端口封禁会导致邮件无法发送)
- ✅ 代码已做双重发送保障,子比主题邮件函数失败后,会自动切换到WordPress原生函数,提升成功率
🔍 2. 后台无「催更管理」菜单
- ❌ 检查催更功能代码是否完整粘贴到「functions.php」,缺失代码会导致菜单不显示
- ❌ 确认当前登录用户有「edit_posts」权限(作者及以上权限可见,订阅者、贡献者无权限)
- ✅ 刷新后台页面(Ctrl+F5),清除浏览器缓存,部分缓存会导致菜单显示异常
🚫 3. 催更按钮不显示
- ❌ 催更按钮仅在「文章详情页」显示,首页、分类列表页、页面(Page)不显示,属于正常现象
- ❌ 确认网站使用的是子比主题(Zibll),代码仅适配子比主题,其他主题可能无法正常显示
- ✅ 确认「functions.php」代码保存成功,无语法错误(语法错误会导致功能失效)
// 安全拦截,防止直接访问
if (!defined('ABSPATH')) exit;
// ==============================================
// 1. 创建催更数据库表(首次运行自动创建)
// ==============================================
function zib_cuigeng_create_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'zib_cuigeng';
$charset_collate = $wpdb->get_charset_collate();
// 检查表是否存在,不存在则创建
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
$sql = "CREATE TABLE $table_name (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT NOT NULL,
user_id BIGINT NOT NULL DEFAULT 0,
user_ip VARCHAR(50) NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
not_reason TEXT NULL,
expect_time DATETIME NULL,
is_notified TINYINT NOT NULL DEFAULT 0, -- 新增:是否已通知用户
PRIMARY KEY (id),
UNIQUE KEY unique_cuigeng (post_id, user_id, user_ip)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
} else {
// 检查是否有is_notified字段,没有则添加
$column_exists = $wpdb->get_var("SHOW COLUMNS FROM $table_name LIKE 'is_notified'");
if (!$column_exists) {
$wpdb->query("ALTER TABLE $table_name ADD COLUMN is_notified TINYINT NOT NULL DEFAULT 0");
}
}
}
add_action('init', 'zib_cuigeng_create_table');
// ==============================================
// 2. 全局配置(可自定义)
// ==============================================
define('CUI_GENG_EMAIL_SUBJECT', '你的文章收到新的催更请求'); // 邮件标题
define('CUI_GENG_REPLY_EMAIL_SUBJECT', '作者回复了你的催更请求'); // 回复通知邮件标题
define('CUI_GENG_FROM_EMAIL', get_option('admin_email')); // 发件人邮箱
define('CUI_GENG_FROM_NAME', get_option('blogname')); // 发件人名称
// ==============================================
// 3. 工具函数 - 获取用户真实IP
// ==============================================
function cui_geng_get_ip() {
$ip_keys = ['HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
foreach ($ip_keys as $key) {
if (!empty($_SERVER[$key]) && filter_var($_SERVER[$key], FILTER_VALIDATE_IP)) {
return $_SERVER[$key];
}
}
return '0.0.0.0';
}
// ==============================================
// 4. 工具函数 - 发送邮件通知作者(修复版+显示催更者信息)
// ==============================================
function cui_geng_notify_author($post_id) {
$post = get_post($post_id);
if (!$post) return;
$author_id = $post->post_author;
$author_info = get_userdata($author_id);
if (!$author_info || !is_email($author_info->user_email) || stristr($author_info->user_email, '@no')) {
return;
}
// 获取当前催更用户信息
$current_user_id = get_current_user_id();
$current_user_info = get_userdata($current_user_id);
$cuigeng_user_id = $current_user_id ?: '游客';
$cuigeng_user_name = $current_user_info ? $current_user_info->display_name : '游客';
$cuigeng_user_email = $current_user_info ? $current_user_info->user_email : '无';
$post_title = get_the_title($post_id);
$post_link = get_permalink($post_id);
// 仅作者/管理员能访问的后台链接(普通用户访问会403)
$admin_link = admin_url('admin.php?page=cuigeng-manage');
// 邮件内容(适配子比主题邮件样式+新增催更者信息)
$message = '您好!' . $author_info->display_name . '<br><br>';
$message .= '您的文章《' . $post_title . '》收到了新的催更请求:<br><br>';
$message .= '<strong>催更者ID:</strong>' . esc_html($cuigeng_user_id) . '<br>';
$message .= '<strong>催更者用户名:</strong>' . esc_html($cuigeng_user_name) . '<br>';
$message .= '<strong>催更者邮箱:</strong>' . esc_html($cuigeng_user_email) . '<br><br>';
$message .= '<strong>文章链接:</strong><a href="' . esc_url($post_link) . '">' . $post_link . '</a><br>';
$message .= '<strong>管理后台:</strong><a href="' . esc_url($admin_link) . '">' . $admin_link . '</a><br>';
$message .= '<strong>催更时间:</strong>' . date('Y-m-d H:i:s') . '<br>';
// 修复邮件发送(强制设置发件人)
$headers = [
'Content-Type: text/html; charset=UTF-8',
'From: ' . CUI_GENG_FROM_NAME . ' <' . CUI_GENG_FROM_EMAIL . '>',
'Reply-To: ' . CUI_GENG_FROM_EMAIL
];
// 优先调用子比邮件函数,兜底用wp_mail
$mail_sent = false;
if (function_exists('zib_send_email')) {
$mail_sent = zib_send_email($author_info->user_email, CUI_GENG_EMAIL_SUBJECT, $message);
}
// 如果子比函数失败,用原生wp_mail
if (!$mail_sent) {
$mail_sent = wp_mail($author_info->user_email, CUI_GENG_EMAIL_SUBJECT, $message, $headers);
}
}
// ==============================================
// 5. 工具函数 - 发送回复通知邮件给催更用户(修复版+详细信息)
// ==============================================
function cui_geng_notify_users($post_id, $not_reason = '', $expect_time = '') {
global $wpdb;
$table_name = $wpdb->prefix . 'zib_cuigeng';
// 获取文章信息
$post = get_post($post_id);
if (!$post) return;
$post_title = get_the_title($post_id);
$post_link = get_permalink($post_id);
$author_info = get_userdata($post->post_author);
$author_name = $author_info ? $author_info->display_name : '管理员';
$author_email = $author_info ? $author_info->user_email : CUI_GENG_FROM_EMAIL;
// 获取所有未通知的催更用户
$users = $wpdb->get_results($wpdb->prepare("
SELECT DISTINCT user_id
FROM $table_name
WHERE post_id = %d AND user_id > 0 AND is_notified = 0
", $post_id));
if (empty($users)) return;
// 构建邮件内容
$base_message = '您好!<br><br>';
$base_message .= '您之前催更的文章《' . $post_title . '》有了新的回复:<br><br>';
if ($not_reason) {
$base_message .= '<strong>不更新原因:</strong>' . nl2br(esc_html($not_reason)) . '<br><br>';
}
if ($expect_time) {
$base_message .= '<strong>预计更新时间:</strong>' . esc_html($expect_time) . '<br><br>';
}
if (!$not_reason && !$expect_time) {
$base_message .= '作者已收到你的催更请求,感谢你的关注!<br><br>';
}
$base_message .= '<strong>文章链接:</strong><a href="' . esc_url($post_link) . '">' . $post_link . '</a><br>';
$base_message .= '<strong>回复时间:</strong>' . date('Y-m-d H:i:s') . '<br>';
$base_message .= '<strong>回复作者:</strong>' . esc_html($author_name) . '<br>';
// 修复邮件发送头
$headers = [
'Content-Type: text/html; charset=UTF-8',
'From: ' . CUI_GENG_FROM_NAME . ' <' . CUI_GENG_FROM_EMAIL . '>',
'Reply-To: ' . $author_email
];
// 逐个发送邮件并标记已通知
foreach ($users as $user) {
$user_info = get_userdata($user->user_id);
if (!$user_info || !is_email($user_info->user_email) || stristr($user_info->user_email, '@no')) {
continue;
}
// 个性化邮件内容(包含用户自身信息)
$user_message = $base_message . '<br>';
$user_message .= '<strong>你的催更信息:</strong><br>';
$user_message .= '用户ID:' . esc_html($user->user_id) . '<br>';
$user_message .= '用户名:' . esc_html($user_info->display_name) . '<br>';
$user_message .= '绑定邮箱:' . esc_html($user_info->user_email) . '<br>';
// 发送邮件
$mail_sent = false;
if (function_exists('zib_send_email')) {
$mail_sent = zib_send_email($user_info->user_email, CUI_GENG_REPLY_EMAIL_SUBJECT, $user_message);
}
// 兜底发送
if (!$mail_sent) {
$mail_sent = wp_mail($user_info->user_email, CUI_GENG_REPLY_EMAIL_SUBJECT, $user_message, $headers);
}
// 只有发送成功才标记已通知
if ($mail_sent) {
$wpdb->update($table_name,
['is_notified' => 1],
['post_id' => $post_id, 'user_id' => $user->user_id]
);
}
}
}
// ==============================================
// 6. 工具函数 - 获取文章催更次数
// ==============================================
function cui_geng_count($post_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'zib_cuigeng';
return (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE post_id = %d", $post_id));
}
// ==============================================
// 7. 工具函数 - 检查用户是否已催更
// ==============================================
function cui_geng_has($post_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'zib_cuigeng';
$user_id = get_current_user_id() ?: 0;
$user_ip = cui_geng_get_ip();
// 登录用户按ID判断,游客按IP判断
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $table_name WHERE post_id = %d AND ((user_id > 0 AND user_id = %d) OR (user_id = 0 AND user_ip = %s))",
$post_id, $user_id, $user_ip
));
return $exists ? true : false;
}
// ==============================================
// 8. 工具函数 - 获取作者回复信息
// ==============================================
function cui_geng_get_reply($post_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'zib_cuigeng';
return $wpdb->get_row($wpdb->prepare("SELECT not_reason, expect_time FROM $table_name WHERE post_id = %d LIMIT 1", $post_id));
}
// ==============================================
// 9. Ajax提交催更(核心接口)
// ==============================================
add_action('wp_ajax_cui_geng_submit', 'cui_geng_submit');
add_action('wp_ajax_nopriv_cui_geng_submit', 'cui_geng_submit');
function cui_geng_submit() {
global $wpdb;
$table_name = $wpdb->prefix . 'zib_cuigeng';
// 验证文章ID
$post_id = isset($_POST['post_id']) ? (int)$_POST['post_id'] : 0;
if (!$post_id || !get_post($post_id)) {
wp_die('无效的文章ID');
}
// 获取用户信息
$user_id = get_current_user_id() ?: 0;
$user_ip = cui_geng_get_ip();
// 检查是否重复催更
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $table_name WHERE post_id = %d AND ((user_id > 0 AND user_id = %d) OR (user_id = 0 AND user_ip = %s))",
$post_id, $user_id, $user_ip
));
if ($exists) {
wp_die('already');
}
// 插入催更记录
$wpdb->insert($table_name, [
'post_id' => $post_id,
'user_id' => $user_id,
'user_ip' => $user_ip,
'is_notified' => 0 // 初始状态:未通知
]);
// 发送通知给作者
cui_geng_notify_author($post_id);
wp_die('ok');
}
// ==============================================
// 10. 侧边栏催更按钮 + 侧边栏切换(核心前端)
// ==============================================
add_action('wp_footer', function() {
if (!is_single()) return; // 只在文章详情页显示
global $post;
$post_id = $post->ID;
$count = cui_geng_count($post_id);
$has_cui = cui_geng_has($post_id);
$reply = cui_geng_get_reply($post_id);
// 样式定义(美化弹窗+居中+按钮自动扫光特效)
echo '
<style>
/* 催更按钮(侧边栏+自动扫光特效) */
.cui-geng-side-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 20px;
background: var(--focus-color);
color: #fff !important;
border-radius: 20px;
font-size: 12px;
cursor: pointer;
margin-right: 6px;
position: relative;
overflow: hidden;
transition: all 0.2s ease;
}
/* 自动循环扫光特效(更明显) */
.cui-geng-side-btn::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 40px;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent);
transition: left 1.2s ease-in-out;
animation: sweepLight 2s infinite;
}
/* 扫光动画 */
@keyframes sweepLight {
0% { left: -100%; }
100% { left: 100%; }
}
.cui-geng-side-btn:hover {
opacity: 0.9;
transform: scale(1.05);
}
.cui-geng-side-btn.disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
opacity: 1;
}
.cui-geng-side-btn.disabled::before {
display: none; /* 禁用状态关闭扫光 */
}
/* 侧边栏切换开关(原有样式) */
.stg {
width: 36px;
height: 20px;
background: #ccc;
border-radius: 20px;
cursor: pointer;
position: relative;
display: inline-block;
}
.stg::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
background: #fff;
border-radius: 50%;
transition: .2s;
}
.stg.on {
background: var(--focus-color);
}
.stg.on::after {
left: 18px;
}
/* 催更弹窗(美化+居中) */
#cuigeng-modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 99999;
display: none;
align-items: center;
justify-content: center;
backdrop-filter: blur(5px);
}
#cuigeng-modal .modal-box {
background: #fff;
border-radius: 16px;
padding: 30px;
max-width: 420px;
width: 90%;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
position: relative;
animation: cuiModalFade 0.3s ease;
}
@keyframes cuiModalFade {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
#cuigeng-modal .modal-close {
position: absolute;
top: 15px;
right: 15px;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
border-radius: 50%;
background: #f5f5f5;
color: #999;
cursor: pointer;
font-size: 16px;
transition: all 0.2s;
}
#cuigeng-modal .modal-close:hover {
background: #eee;
color: #666;
}
#cuigeng-modal .modal-box h3 {
margin: 0 0 15px 0;
font-size: 18px;
color: #333;
font-weight: 600;
}
#cuigeng-modal .modal-box .modal-desc {
margin: 0 0 20px 0;
color: #666;
font-size: 14px;
line-height: 1.6;
}
#cuigeng-modal .modal-box .modal-desc .count {
color: var(--focus-color);
font-weight: 600;
}
#cuigeng-confirm {
background: var(--focus-color);
color: #fff;
border: none;
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 15px;
font-weight: 500;
width: 100%;
transition: all 0.2s;
}
#cuigeng-confirm:hover {
opacity: 0.9;
transform: translateY(-2px);
}
#cuigeng-confirm:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
opacity: 1;
}
/* 催更成功提示弹窗 */
#cuigeng-success-modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
z-index: 999999;
display: none;
align-items: center;
justify-content: center;
backdrop-filter: blur(3px);
}
#cuigeng-success-modal .success-box {
background: #fff;
border-radius: 16px;
padding: 35px 25px;
max-width: 380px;
width: 90%;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
text-align: center;
animation: successFade 0.4s ease;
}
@keyframes successFade {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
#cuigeng-success-modal .success-icon {
width: 60px;
height: 60px;
line-height: 60px;
background: #f0f9ff;
color: var(--focus-color);
border-radius: 50%;
font-size: 28px;
margin: 0 auto 15px;
}
#cuigeng-success-modal .success-title {
font-size: 18px;
color: #333;
font-weight: 600;
margin: 0 0 8px 0;
}
#cuigeng-success-modal .success-desc {
font-size: 14px;
color: #666;
line-height: 1.5;
margin: 0 0 20px 0;
}
#cuigeng-success-modal .success-close {
background: var(--focus-color);
color: #fff;
border: none;
padding: 8px 20px;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
#cuigeng-success-modal .success-close:hover {
opacity: 0.9;
transform: translateY(-1px);
}
/* 重复催更提示弹窗 */
#cuigeng-duplicate-modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
z-index: 999999;
display: none;
align-items: center;
justify-content: center;
backdrop-filter: blur(3px);
}
#cuigeng-duplicate-modal .duplicate-box {
background: #fff;
border-radius: 16px;
padding: 30px 25px;
max-width: 360px;
width: 90%;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
text-align: center;
animation: duplicateFade 0.3s ease;
}
@keyframes duplicateFade {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
#cuigeng-duplicate-modal .duplicate-icon {
width: 50px;
height: 50px;
line-height: 50px;
background: #fef7fb;
color: #ff6b9e;
border-radius: 50%;
font-size: 22px;
margin: 0 auto 12px;
}
#cuigeng-duplicate-modal .duplicate-title {
font-size: 17px;
color: #333;
font-weight: 600;
margin: 0 0 6px 0;
}
#cuigeng-duplicate-modal .duplicate-desc {
font-size: 14px;
color: #666;
line-height: 1.5;
margin: 0 0 18px 0;
}
#cuigeng-duplicate-modal .duplicate-close {
background: var(--focus-color);
color: #fff;
border: none;
padding: 8px 18px;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
#cuigeng-duplicate-modal .duplicate-close:hover {
opacity: 0.9;
transform: translateY(-1px);
}
/* 作者回复提示 */
.cui-reply-box {
margin: 20px auto;
max-width: 1200px;
padding: 15px;
background: #f7f8fa;
border-radius: 8px;
border: 1px solid #eee;
}
</style>';
// 弹窗HTML(催更确认+成功提示+重复催更提示)
echo '
<!-- 催更确认弹窗 -->
<div id="cuigeng-modal">
<div class="modal-box">
<span class="modal-close">×</span>
<h3>确认催更本文?</h3>
<div class="modal-desc">
已有 <span class="count">'.$count.'</span> 位读者期待更新,点击确认后作者会收到邮件通知哦~
</div>
<button id="cuigeng-confirm">确认催更</button>
</div>
</div>
<!-- 催更成功提示弹窗 -->
<div id="cuigeng-success-modal">
<div class="success-box">
<div class="success-icon">✓</div>
<div class="success-title">催更成功!</div>
<div class="success-desc">作者已经收到你的期待啦~</div>
<button class="success-close">我知道了</button>
</div>
</div>
<!-- 重复催更提示弹窗 -->
<div id="cuigeng-duplicate-modal">
<div class="duplicate-box">
<div class="duplicate-icon">ℹ</div>
<div class="duplicate-title">温馨提示</div>
<div class="duplicate-desc">你已催更过这篇文章啦~</div>
<button class="duplicate-close">我知道了</button>
</div>
</div>';
// 作者回复区域(文章下方)
if ($reply && ($reply->not_reason || $reply->expect_time)) {
echo '
<div class="cui-reply-box">
<p style="margin:0 0 5px 0; font-weight:bold; color:#333;">作者回复:</p>
'.($reply->not_reason ? '<p style="margin:0 0 5px 0; color:#f5222d;">不更新原因:'.esc_html($reply->not_reason).'</p>' : '').'
'.($reply->expect_time ? '<p style="margin:0; color:#1677ff;">预计更新时间:'.esc_html($reply->expect_time).'</p>' : '').'
</div>';
}
// 核心JS(按钮逻辑 + 弹窗)
echo '
<script>
jQuery(document).ready(function($) {
// ========== 侧边栏切换逻辑(原有功能) ==========
var $body = $("body");
var $metabox = $(".single-metabox");
var layoutMatch = $body.attr("class").match(/site-layout-[23]/);
if (!$metabox.length || !layoutMatch) return;
var layoutClass = layoutMatch[0];
// 创建催更按钮和切换按钮(已催更显示"已催更")
var $cuiBtn = $(\'<span class="cui-geng-side-btn '.($has_cui ? 'disabled' : '').'">'.($has_cui ? '已催更' : '催').'</span>\');
var $stgBtn = $(\'<span class="stg"></span>\');
// 插入到侧边栏(催更按钮在前)
$metabox.append($cuiBtn).append($stgBtn);
// 侧边栏切换逻辑
var storageKey = "zib_hide_sidebar";
if (localStorage[storageKey] === "1") {
$body.removeClass(layoutClass).addClass("site-layout-1");
$stgBtn.addClass("on");
}
$stgBtn.on("click", function() {
$stgBtn.toggleClass("on");
$body.toggleClass("site-layout-1 "+layoutClass);
localStorage[storageKey] = $stgBtn.hasClass("on") ? "1" : "0";
});
// ========== 催更弹窗逻辑 ==========
var $modal = $("#cuigeng-modal");
var $successModal = $("#cuigeng-success-modal");
var $duplicateModal = $("#cuigeng-duplicate-modal");
var $modalClose = $(".modal-close");
var $confirmBtn = $("#cuigeng-confirm");
var $successClose = $(".success-close");
var $duplicateClose = $(".duplicate-close");
// 打开催更弹窗
$cuiBtn.on("click", function() {
if ($(this).hasClass("disabled")) {
// 显示重复催更提示弹窗
$duplicateModal.css("display", "flex");
$body.css("overflow", "hidden");
return;
}
$modal.css("display", "flex");
$body.css("overflow", "hidden");
});
// 关闭催更弹窗
function closeModal() {
$modal.css("display", "none");
$body.css("overflow", "auto");
}
// 关闭成功弹窗
function closeSuccessModal() {
$successModal.css("display", "none");
$body.css("overflow", "auto");
// 刷新页面更新按钮状态
location.reload();
}
// 关闭重复催更弹窗
function closeDuplicateModal() {
$duplicateModal.css("display", "none");
$body.css("overflow", "auto");
}
// 点击关闭按钮
$modalClose.on("click", closeModal);
$successClose.on("click", closeSuccessModal);
$duplicateClose.on("click", closeDuplicateModal);
// 点击弹窗背景关闭
$modal.on("click", function(e) {
if ($(e.target).is("#cuigeng-modal")) closeModal();
});
$successModal.on("click", function(e) {
if ($(e.target).is("#cuigeng-success-modal")) closeSuccessModal();
});
$duplicateModal.on("click", function(e) {
if ($(e.target).is("#cuigeng-duplicate-modal")) closeDuplicateModal();
});
// ESC键关闭弹窗
$(document).on("keydown", function(e) {
if (e.key === "Escape") {
if ($modal.css("display") === "flex") closeModal();
if ($successModal.css("display") === "flex") closeSuccessModal();
if ($duplicateModal.css("display") === "flex") closeDuplicateModal();
}
});
// 确认催更提交
$confirmBtn.on("click", function() {
var $btn = $(this);
$btn.prop("disabled", true).text("提交中...");
$.post("'.admin_url('admin-ajax.php').'", {
action: "cui_geng_submit",
post_id: '.$post_id.'
}, function(res) {
// 立即关闭原弹窗
closeModal();
if (res === "ok") {
// 显示成功弹窗
$successModal.css("display", "flex");
// 更新按钮显示"已催更"
$cuiBtn.addClass("disabled").text("已催更");
} else if (res === "already") {
// 显示重复催更弹窗
$duplicateModal.css("display", "flex");
$cuiBtn.addClass("disabled").text("已催更");
} else {
alert("催更失败:" + res + ",请稍后重试~");
$btn.prop("disabled", false).text("确认催更");
}
});
});
});
</script>';
});
// ==============================================
// 11. 后台催更管理菜单
// ==============================================
add_action('admin_menu', 'cui_geng_admin_menu');
function cui_geng_admin_menu() {
add_menu_page(
'文章催更管理',
'催更管理',
'edit_posts', // 只有编辑文章权限的用户能访问
'cuigeng-manage',
'cui_geng_admin_page',
'dashicons-bell',
50
);
}
// ==============================================
// 12. 后台管理页面(重新美化布局)
// ==============================================
function cui_geng_admin_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'zib_cuigeng';
$post_id = isset($_GET['pid']) ? (int)$_GET['pid'] : 0;
// 保存作者回复(移除阈值限制+发送通知)
if (isset($_POST['save_reply']) && $post_id) {
$not_reason = sanitize_text_field($_POST['not_reason']);
$expect_time = sanitize_text_field($_POST['expect_time']);
// 更新回复信息(无阈值限制)
$wpdb->update($table_name, [
'not_reason' => $not_reason,
'expect_time' => $expect_time
], ['post_id' => $post_id]);
// 发送邮件通知所有催更用户
cui_geng_notify_users($post_id, $not_reason, $expect_time);
$success_msg = '回复已发送!已通知所有催更用户。';
}
// 获取催更列表
$cuigeng_list = $wpdb->get_results("
SELECT post_id, COUNT(*) as cnt, MAX(not_reason) as not_reason, MAX(expect_time) as expect_time
FROM $table_name
GROUP BY post_id
ORDER BY cnt DESC
");
// 后台页面HTML(重新美化布局)
echo '
<style>
/* 全新后台管理页面样式 */
.cui-admin-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* 头部样式 */
.cui-admin-header {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
padding: 25px 30px;
border-radius: 16px;
margin-bottom: 30px;
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.2);
}
.cui-admin-header h1 {
margin: 0;
font-size: 28px;
font-weight: 700;
display: flex;
align-items: center;
gap: 12px;
}
.cui-admin-header h1 svg {
width: 28px;
height: 28px;
}
.cui-admin-header p {
margin: 8px 0 0 0;
font-size: 15px;
opacity: 0.9;
line-height: 1.5;
}
/* 卡片样式 */
.cui-admin-card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
margin-bottom: 25px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.cui-admin-card:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
}
/* 卡片头部 */
.cui-card-header {
padding: 20px 25px;
background: #f8fafc;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.cui-card-header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1e293b;
display: flex;
align-items: center;
gap: 8px;
}
.cui-card-header h2 svg {
width: 20px;
height: 20px;
color: #6366f1;
}
/* 卡片内容 */
.cui-card-body {
padding: 25px;
}
/* 表格样式 */
.cui-admin-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.cui-admin-table th {
background: #f8fafc;
padding: 15px 20px;
text-align: left;
color: #334155;
font-weight: 600;
border-bottom: 2px solid #e2e8f0;
position: relative;
}
.cui-admin-table th::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
width: 40px;
height: 2px;
background: #6366f1;
}
.cui-admin-table td {
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
color: #475569;
vertical-align: middle;
}
.cui-admin-table tr:last-child td {
border-bottom: none;
}
.cui-admin-table tr:hover td {
background: #fef7fb;
}
.cui-admin-table a {
color: #6366f1;
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
.cui-admin-table a:hover {
color: #4f46e5;
text-decoration: underline;
}
/* 表单样式 */
.cui-form-group {
margin-bottom: 20px;
}
.cui-form-label {
display: block;
margin-bottom: 8px;
color: #1e293b;
font-weight: 600;
font-size: 14px;
}
.cui-form-control {
width: 100%;
padding: 12px 16px;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
color: #1e293b;
transition: border-color 0.2s, box-shadow 0.2s;
}
.cui-form-control:focus {
outline: none;
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
textarea.cui-form-control {
min-height: 100px;
resize: vertical;
line-height: 1.5;
}
/* 按钮样式 */
.cui-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 24px;
background: #6366f1;
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 2px 5px rgba(99, 102, 241, 0.2);
}
.cui-btn:hover {
background: #4f46e5;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(99, 102, 241, 0.3);
}
.cui-btn svg {
width: 16px;
height: 16px;
}
/* 提示框样式 */
.cui-alert {
padding: 16px 20px;
border-radius: 8px;
margin-bottom: 25px;
font-size: 14px;
display: flex;
align-items: center;
gap: 12px;
}
.cui-alert-success {
background: #ecfdf5;
color: #065f46;
border: 1px solid #bbf7d0;
}
.cui-alert-success svg {
color: #10b981;
width: 20px;
height: 20px;
}
/* 空数据样式 */
.cui-empty-state {
padding: 60px 20px;
text-align: center;
color: #94a3b8;
}
.cui-empty-state svg {
width: 60px;
height: 60px;
margin-bottom: 15px;
color: #cbd5e1;
}
.cui-empty-state p {
margin: 0;
font-size: 15px;
}
/* 响应式适配 */
@media (max-width: 768px) {
.cui-admin-container {
padding: 15px;
}
.cui-admin-header {
padding: 20px 15px;
}
.cui-card-body {
padding: 15px;
}
.cui-admin-table th,
.cui-admin-table td {
padding: 12px 15px;
}
}
</style>';
// 页面HTML结构
echo '<div class="wrap cui-admin-container">';
// 头部区域
echo '
<div class="cui-admin-header">
<h1>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
</svg>
文章催更管理
</h1>
<p>作者可随时回复/不回复读者的催更请求,回复后自动邮件通知所有催更用户</p>
</div>';
// 成功提示
if (isset($success_msg)) {
echo '
<div class="cui-alert cui-alert-success">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
'.$success_msg.'
</div>';
}
// 催更列表卡片
echo '
<div class="cui-admin-card">
<div class="cui-card-header">
<h2>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="8" y1="6" x2="21" y2="6"></line>
<line x1="8" y1="12" x2="21" y2="12"></line>
<line x1="8" y1="18" x2="21" y2="18"></line>
<line x1="3" y1="6" x2="3.01" y2="6"></line>
<line x1="3" y1="12" x2="3.01" y2="12"></line>
<line x1="3" y1="18" x2="3.01" y2="18"></line>
</svg>
催更文章列表
</h2>
</div>
<div class="cui-card-body">
<table class="cui-admin-table">
<thead>
<tr>
<th>文章ID</th>
<th>文章标题</th>
<th>催更次数</th>
<th>操作</th>
</tr>
</thead>
<tbody>';
if (empty($cuigeng_list)) {
echo '
<tr>
<td colspan="4">
<div class="cui-empty-state">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
<p>暂无催更记录</p>
</div>
</td>
</tr>';
} else {
foreach ($cuigeng_list as $item) {
echo '
<tr>
<td>'.$item->post_id.'</td>
<td><a href="'.get_permalink($item->post_id).'" target="_blank">'.get_the_title($item->post_id).'</a></td>
<td>'.$item->cnt.'</td>
<td><a href="?page=cuigeng-manage&pid='.$item->post_id.'">编辑回复</a></td>
</tr>';
}
}
echo '
</tbody>
</table>
</div>
</div>';
// 回复编辑区域
if ($post_id) {
$count = cui_geng_count($post_id);
$reply = cui_geng_get_reply($post_id);
echo '
<div class="cui-admin-card">
<div class="cui-card-header">
<h2>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
</svg>
编辑催更回复
</h2>
</div>
<div class="cui-card-body">
<form method="post">
<input type="hidden" name="save_reply" value="1">
<div class="cui-form-group">
<label for="not_reason" class="cui-form-label">不更新原因(选填)</label>
<textarea name="not_reason" id="not_reason" class="cui-form-control" rows="4">'.esc_html($reply->not_reason ?? '').'</textarea>
</div>
<div class="cui-form-group">
<label for="expect_time" class="cui-form-label">预计更新时间(选填)</label>
<input type="datetime-local" name="expect_time" id="expect_time" class="cui-form-control" value="'.esc_html($reply->expect_time ?? '').'">
</div>
<button type="submit" class="cui-btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="16" height="16">
<path d="M4 12h16"></path>
<path d="M12 4v16"></path>
</svg>
立即回复催更者
</button>
</form>
</div>
</div>';
}
echo '</div>';
}
// ==============================================
// 13. 催更排行榜短代码 [cuigeng_rank limit=10]
// ==============================================
add_shortcode('cuigeng_rank', 'cui_geng_ranking');
function cui_geng_ranking($atts) {
global $wpdb;
$atts = shortcode_atts(['limit' => 10], $atts);
$table_name = $wpdb->prefix . 'zib_cuigeng';
$list = $wpdb->get_results($wpdb->prepare("
SELECT post_id, COUNT(*) as cnt
FROM $table_name
GROUP BY post_id
ORDER BY cnt DESC
LIMIT %d
", $atts['limit']));
if (empty($list)) {
return '<div style="padding:15px; background:#f7f8fa; border-radius:8px; text-align:center; color:#666;">暂无催更记录</div>';
}
$html = '<div style="padding:15px; background:#f7f8fa; border-radius:8px; border:1px solid #eee;">';
$html .= '<h4 style="margin:0 0 10px 0; font-size:16px;">🔥 催更排行榜</h4>';
$html .= '<ul style="margin:0; padding:0; list-style:none;">';
foreach ($list as $key => $item) {
$title = get_the_title($item->post_id);
$link = get_permalink($item->post_id);
$html .= '
<li style="padding:6px 0; border-bottom:1px dashed #eee;">
<span style="display:inline-block; width:20px; height:20px; line-height:20px; text-align:center; background:var(--focus-color); color:#fff; border-radius:50%; font-size:12px; margin-right:8px;">'.($key+1).'</span>
<a href="'.$link.'" style="color:#333; text-decoration:none;">'.$title.'</a>
<span style="float:right; color:#999;">'.$item->cnt.'次</span>
</li>';
}
$html .= '</ul></div>';
return $html;
}
[/hidecontent]

