文章催更功能 完整使用教程(子比主题-饺子制作)
饺子

文章催更功能 完整使用教程(子比主题-饺子制作)

blogger头像
blogger 博主
2026.05.15 · 约 105 分钟 · 0 评论 · 0 喜爱

睡不着右闲着蛋疼,看有人需要就写了这么个玩意 📢 文章催更功能 完整使用教程 🔧 一、代码部署教程 ✅ 1. 部署前准备 ➤ 确保网站基于 WordPress + 子比主题(Zibll) 搭建,非子比主题可能无法正常显示…

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

📢 文章催更功能 完整使用教程

🔧 一、代码部署教程

✅ 1. 部署前准备

  • ➤ 确保网站基于 WordPress + 子比主题(Zibll) 搭建,非子比主题可能无法正常显示功能
  • ➤ 已配置网站邮箱(推荐QQ邮箱),并开启SMTP服务(避免邮件发送失败)
  • ➤ 拥有主题文件编辑权限(可通过后台直接编辑,或用FTP、宝塔面板操作)

📥 2. 代码安装步骤

  1. ① 登录WordPress后台,点击左侧菜单栏「外观」→「主题文件编辑器」
  2. ② 在右侧「主题文件」列表中,找到并点击「functions.php」(子比主题的核心函数文件)
  3. ③ 清空该文件原有内容(或直接粘贴到文件末尾,避免覆盖原有代码),粘贴完整的催更功能代码
  4. ④ 点击页面底部「更新文件」保存,若提示「文件不可写」,需先将文件权限设置为755
  5. ⑤ 刷新网站前台,打开任意文章详情页,验证侧边栏是否出现「催」字按钮(出现即部署成功)

⚠️ 安全提示:修改代码前,建议备份「functions.php」文件;若不熟悉后台操作,推荐用FTP工具编辑该文件,更安全不易出错。

🔄 3. 数据库自动升级

代码首次运行时,会自动完成数据库初始化,无需手动操作: ✅ 自动创建「wp_zib_cuigeng」催更记录表(表前缀随你的WordPress配置自动变化) ✅ 自动添加「is_notified」字段(用于标记催更用户是否已收到回复通知) ✅ 若已存在该表,自动检测并补充缺失字段,避免数据丢失

👥 二、前台用户操作教程

📌 1. 普通用户催更流程

  1. ① 打开网站任意文章详情页(首页、列表页不显示催更按钮)
  2. ② 在文章侧边栏找到「催」字按钮(带有自动扫光动画,样式醒目)
  3. ③ 点击按钮,弹出「确认催更」弹窗,弹窗内会显示当前已有催更人数
  4. ④ 点击弹窗内「确认催更」按钮,会立即弹出「催更成功」提示
  5. ⑤ 催更成功后,侧边栏「催」字按钮会变为「已催更」,不可重复点击(防重复催更)

🎁 2. 催更成功后用户权益

  • ➤ 当文章作者回复催更内容时,你会收到系统发送的邮件通知
  • ➤ 邮件包含:作者回复的「不更新原因/预计更新时间」+ 你自身的用户ID、用户名、绑定邮箱
  • ➤ 游客用户(未登录)仅能点击催更,但无法收到回复邮件(无绑定邮箱,系统自动跳过)

🔑 三、后台管理员/作者操作教程

📥 1. 进入催更管理页面

  1. ① 登录WordPress后台,左侧菜单栏找到「催更管理」(铃铛图标,辨识度高)
  2. ② 点击「催更管理」,进入核心管理页面,默认显示「催更文章列表」
  3. ③ 列表包含4列内容:文章ID、文章标题、催更次数、操作(编辑回复),排版清晰易操作

📋 2. 查看催更记录

  • ➤ 催更文章列表按「催更次数」降序排列,可快速找到催更人数最多的文章
  • ➤ 点击列表中的「文章标题」,可直接跳转到前台该文章页面,快速查看文章内容
  • ➤ 若暂无催更记录,页面会显示友好的「暂无催更记录」提示,不影响页面美观

✍️ 3. 回复催更用户(核心操作)

  1. ① 在「催更文章列表」中,找到需要回复的文章,点击右侧「编辑回复」按钮
  2. ② 进入「编辑催更回复」页面,包含两个可编辑项(均为选填,按需填写): ▫️ 不更新原因:填写文章不更新的具体理由,支持多行文本,可清晰告知用户 ▫️ 预计更新时间:选择具体的更新日期和时间,让用户了解更新进度
  3. ③ 填写完成后,点击页面底部「立即回复催更者」按钮,提交回复内容
  4. ④ 提交成功后,页面会弹出绿色提示「回复已发送!已通知所有催更用户」
  5. ⑤ 系统自动完成后续操作,无需手动干预: ▫️ 保存回复内容到数据库,前台文章下方会显示你的回复 ▫️ 向所有登录催更用户发送邮件通知(每人仅发送一次,避免重复打扰) ▫️ 标记用户为「已通知」状态,后续不再重复发送该文章的回复通知

💡 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」代码保存成功,无语法错误(语法错误会导致功能失效)
[hidecontent type="reply"]
// 安全拦截,防止直接访问
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]

弹幕表态
我的头像
0/300
文明发言,一起守护温柔的评论区

0 条回复

A文章作者 M管理员
这里需要一条查重率 0% 的评论

还没有评论,来说两句吧~

1会员总数
104文章总数
1079浏览总数
0今日发布
277天稳定运行
  • 波浪
  • 波浪
  • 波浪
  • 波浪

Hello World

页面耗时 0.127 秒 | 数据库查询 63 次 | 内存 1.32 MB | 在线人数:1人

104+

精品文章等您来关注

Copyright © 2025 - 2026 Hello World