简单实现 WordPress 评论点赞、排序和前台编辑

书接上回,上篇我记录了如何使用 Cloudflare R2 + Pages 搭建免费图床,并将其集成到 WordPress 评论系统 中。本文将继续分享我为 WordPress 评论功能增加 排序、点赞、前台删除与编辑 的实现过程和相关代码。

之所以折腾这些功能,主要是为了能在前台更高效地管理评论。比如点赞功能,其实核心目的也是为了更好地参与排序,目前我设置的是“按点赞量自动排序”,让互动更直观。

简单实现 WordPress 评论点赞、排序和前台编辑
简单实现 WordPress 评论点赞、排序和前台编辑

考虑到每个主题都不一样,所以我就只分享一下我的思路,具体的可以参考以下代码:

/**
 * =========================
 * WordPress 评论系统增强版
 * 支持点赞、排序、缓存、访客、删除
 * 版本:2025.10.13 优化性能版
 * =========================
 */

// 获取评论点赞数(带缓存)
function get_comment_likes($comment_id) {
    static $cache = [];
    if (isset($cache[$comment_id])) return $cache[$comment_id];
    $likes = get_comment_meta($comment_id, '_like_count', true);
    return $cache[$comment_id] = ($likes ? intval($likes) : 0);
}

/**
 * AJAX 点赞 / 取消赞
 */
function handle_comment_like() {
    if (empty($_POST['comment_id']) || !is_numeric($_POST['comment_id'])) {
        wp_send_json_error('Invalid request');
    }

    $comment_id = intval($_POST['comment_id']);
    $likes = get_comment_likes($comment_id);
    $cookie_name = 'comment_like_' . $comment_id;

    if (isset($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] === '1') {
        // 取消点赞
        $likes = max(0, $likes - 1);
        update_comment_meta($comment_id, '_like_count', $likes);
        setcookie($cookie_name, '', time() - 3600, '/');
        $liked = false;
    } else {
        // 点赞
        $likes++;
        update_comment_meta($comment_id, '_like_count', $likes);
        setcookie($cookie_name, '1', time() + (365 * DAY_IN_SECONDS), '/');
        $liked = true;
    }

    // 清理缓存(HTML + 排序缓存)
    if ($comment_obj = get_comment($comment_id)) {
        $post_id = $comment_obj->comment_post_ID;
        wp_cache_delete("comments_html_{$post_id}", 'comments');
        wp_cache_delete("comment_thread_order_{$post_id}", 'comments');
    }

    wp_send_json_success([
        'liked' => $liked,
        'likes' => $likes
    ]);
}
add_action('wp_ajax_handle_comment_like', 'handle_comment_like');
add_action('wp_ajax_nopriv_handle_comment_like', 'handle_comment_like');

/**
 * AJAX 删除评论(仅用户ID=1)
 */
function handle_comment_delete() {
    if (!isset($_POST['comment_id']) || !is_numeric($_POST['comment_id'])) {
        wp_send_json_error(['message' => '无效的评论 ID']);
    }

    $comment_id = intval($_POST['comment_id']);
    $comment = get_comment($comment_id);
    if ($comment) {
        wp_trash_comment($comment_id);
        wp_send_json_success();
    }
    wp_send_json_error(['message' => '评论不存在']);
}
add_action('wp_ajax_delete_comment', 'handle_comment_delete');

/**
 * 评论排序:按评论树中最大点赞数排序顶层评论
 * - 子评论内部顺序不变
 * - 性能优化:批量预热 commentmeta,结果缓存 1 小时
 */
add_filter('comments_array', function ($comments, $post_id) {
    if (!is_singular() || is_admin() || empty($comments)) return $comments;

    // 尝试读排序缓存
    $order_key = "comment_thread_order_{$post_id}";
    if ($cached_ids = wp_cache_get($order_key, 'comments')) {
        $map = [];
        foreach ($comments as $c) $map[$c->comment_ID] = $c;
        $sorted = [];
        foreach ($cached_ids as $cid) {
            if (isset($map[$cid])) $sorted[] = $map[$cid];
        }
        if (count($sorted) === count($comments)) return $sorted;
    }

    // 预热 meta 缓存
    $ids = wp_list_pluck($comments, 'comment_ID');
    update_meta_cache('comment', $ids);

    // 构建 parent=>children 映射
    $children_map = [];
    foreach ($comments as $c) {
        $pid = (int)$c->comment_parent;
        $children_map[$pid][] = $c;
    }

    // 计算树最大点赞数(缓存递归)
    $max_cache = [];
    $get_max_like = function ($id) use (&$children_map, &$get_max_like, &$max_cache) {
        if (isset($max_cache[$id])) return $max_cache[$id];
        $max = get_comment_likes($id);
        if (!empty($children_map[$id])) {
            foreach ($children_map[$id] as $child) {
                $m = $get_max_like($child->comment_ID);
                if ($m > $max) $max = $m;
            }
        }
        return $max_cache[$id] = $max;
    };

    // 排序顶层评论
    if (!empty($children_map[0])) {
        usort($children_map[0], function ($a, $b) use ($get_max_like) {
            $ma = $get_max_like($a->comment_ID);
            $mb = $get_max_like($b->comment_ID);
            if ($ma === $mb) {
                return strtotime($b->comment_date_gmt) <=> strtotime($a->comment_date_gmt);
            }
            return $mb <=> $ma; // 最大点赞数高的在前
        });
    }

    // 展平
    $sorted = [];
    $flatten = function ($node) use (&$flatten, &$children_map, &$sorted) {
        $sorted[] = $node;
        if (!empty($children_map[$node->comment_ID])) {
            foreach ($children_map[$node->comment_ID] as $child) $flatten($child);
        }
    };
    foreach ($children_map[0] ?? [] as $root) $flatten($root);

    // 缓存 ID 顺序
    wp_cache_set($order_key, wp_list_pluck($sorted, 'comment_ID'), 'comments', HOUR_IN_SECONDS);
    return $sorted;
}, 10, 2);

/**
 * 评论显示模板:增加点赞按钮、回复 title
 */
function impeka_grve_comments($comment, $args, $depth) {
    $current_user_id = $args['current_user_id'];
    $comment_id = get_comment_ID();
    $likes = get_comment_likes($comment_id);
    $liked = isset($_COOKIE['comment_like_' . $comment_id]) && $_COOKIE['comment_like_' . $comment_id] === '1';
    $title_text = $liked ? "点错了?取消赞...💔" : "当前 {$likes} 赞,点个赞吧...💕";
    ?>
    <li class="grve-comment-item">
        <div id="comment-<?php echo $comment_id; ?>" <?php comment_class(); ?>>
            <div class="grve-comment-inner">
                <div class="grve-author-image"><?php echo get_avatar($comment, 50); ?></div>
                <div class="grve-comment-content">
                    <div class="grve-title-wrapper">
                        <span class="grve-title grve-text-heading grve-h6">
                            <?php printf(__('%s'), get_comment_author_link()); ?>
                        </span>
                        <div class="grve-comment-meta grve-small-text">
                            <?php printf('%1$s at %2$s', get_comment_date(), get_comment_time()); ?>
                            <?php comment_reply_link(array_merge($args, [
                                'depth' => $depth,
                                'max_depth' => $args['max_depth'],
                                'reply_text' => '<span title="回复">↩</span>'
                            ])); ?>

                            <!-- 点赞 -->
                            <a href="#" class="comment-like-btn" data-comment-id="<?php echo $comment_id; ?>" title="<?php echo esc_attr($title_text); ?>">
                                <span class="grve-comment-edit like-icon"><?php echo $liked ? '❤' : '♡'; ?></span>
                            </a>

                            <?php edit_comment_link('🗎', ' ', ''); ?>
                            <?php if ($current_user_id == 1) : ?>
                                <a href="#" class="grve-comment-edit delete-comment" data-comment-id="<?php echo $comment_id; ?>">X</a>
                            <?php endif; ?>
                        </div>
                    </div>
                    <?php if ($comment->comment_approved == '0') : ?>
                        <p><?php esc_html_e('Your comment is awaiting moderation.', 'impeka'); ?></p>
                    <?php endif; ?>
                    <div class="grve-comment-text"><?php comment_text(); ?></div>
                </div>
            </div>
        </div>
    </li>
    <?php
}

这段代码为 WordPress 评论系统增加了点赞、排序、缓存、访客操作和前台删除等功能。get_comment_likes() 用于获取并缓存点赞数,handle_comment_like() 通过 AJAX 处理点赞与取消点赞逻辑,并根据 cookie 判断用户是否已点赞。每次点赞变化后会同步更新数据库,并清除缓存,确保页面数据实时更新。

在评论排序部分,代码通过 comments_array 钩子重新排序评论。它计算每个评论树中最高的点赞数,并按点赞量对顶层评论排序,让高互动评论排在前面。排序结果会缓存一小时以提升性能。最后,impeka_grve_comments() 定义了评论的前端显示模板,增加了点赞按钮、删除操作(仅限管理员)和交互提示,让评论区更直观、可操作性更强。


以下 JS 代码已被添加到我的核心脚本中,包括了上篇文章实现图片上传的部分:

/**
 * ==============================
 * WordPress 评论前端交互脚本
 * 功能:评论框工具栏、懒加载增强、点赞与删除
 * 版本:2025.10.13
 * ==============================
 */

document.addEventListener("DOMContentLoaded", function () {
    const commentBox = document.querySelector("#comment");
    const toolbar = document.querySelector("#comment-toolbar");
    if (!commentBox || !toolbar) return;

    // 当评论框获得焦点时显示工具栏
    commentBox.addEventListener("focus", () => toolbar.classList.add("active"));

    // 阻止鼠标在工具栏上引发失焦
    toolbar.addEventListener("mousedown", (e) => e.preventDefault());

    // 当评论框失去焦点且内容为空时隐藏工具栏
    commentBox.addEventListener("blur", () => {
        if (!commentBox.value.trim()) toolbar.classList.remove("active");
    });
});

// 动态加载评论增强脚本(仅在首次点击时加载)
let enhancerLoaded = false;
['#emoji-btn', '#img-btn'].forEach((sel) => {
    const btn = document.querySelector(sel);
    if (!btn) return;

    btn.addEventListener("click", async () => {
        if (!enhancerLoaded) {
            enhancerLoaded = true;
            // 懒加载 comment-enhancer.js 模块
            const { default: initCommentEnhancer } = await import(
                '/wp-content/themes/impeka/js/comment-enhancer.js'
            );
            initCommentEnhancer();
            btn.click(); // 再次触发原始点击事件
        }
    });
});

jQuery(document).ready(function ($) {

    // 删除评论(仅限管理员)
    $('.delete-comment').click(function (e) {
        e.preventDefault();
        const commentId = $(this).data('comment-id');

        $.post(ajax_obj.admin_url, {
            action: 'delete_comment',
            comment_id: commentId
        }, function (response) {
            if (response.success) {
                $('#comment-' + commentId).fadeOut();
            } else {
                alert('删除失败:' + response.data.message);
            }
        });
    });

    // 点赞 / 取消点赞
    $('.comment-like-btn').click(function (e) {
        e.preventDefault();
        const btn = $(this);
        const commentId = btn.data('comment-id');

        $.post(ajax_obj.admin_url, {
            action: 'handle_comment_like',
            comment_id: commentId
        }, function (response) {
            if (response.success) {
                const icon = btn.find('.like-icon');
                icon.text(response.data.liked ? '❤' : '♡');
                btn.attr(
                    'title',
                    response.data.liked
                        ? '点错了?取消赞...💔'
                        : '当前 ' + response.data.likes + ' 赞,点个赞吧...💕'
                );
            } else {
                alert('操作失败,请稍后重试。');
            }
        }, 'json');
    });
});
  • 交互逻辑
    当用户聚焦评论框时自动显示工具栏;点击 emoji 或图片按钮时,会懒加载额外增强模块;前台点击“赞”或“删除”按钮时,通过 AJAX 请求与后端交互,实现无刷新更新评论状态
  • 设计思路
    整体脚本追求轻量与响应性,核心交互均在前端完成,后台只负责状态变更与权限验证。通过懒加载、缓存和异步更新,实现了更快加载、更平滑操作体验
「简单实现 WordPress 评论点赞、排序和前台编辑」有 3 条评论
  • Lvtu
    10/19/2025 at 22:19

    越来越强大了,赞一个。。。

  • wu先生
    10/25/2025 at 23:55

    技术文,纯支持了。

  • seo
    10/25/2025 at 15:22

    很不错的知识备忘

发表评论

请输入关键词…