From 25fd3891a3d72bc058784d7d5b0ebb79b8077c15 Mon Sep 17 00:00:00 2001 From: nanhaoluo <3075912108@qq.com> Date: Fri, 16 Jan 2026 22:24:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E8=AF=84=E8=AE=BA?= =?UTF-8?q?=E7=82=B9=E8=B5=9E=E7=B3=BB=E7=BB=9F=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E7=82=B9=E8=B5=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 改用 IP + User-Agent 哈希识别用户,登录用户使用 user_id - 服务端存储点赞用户列表,而非简单计数 - 同一用户只能点赞一次,再次点击可取消点赞 - 移除 Cookie 依赖,避免 IP 变化导致重复点赞 - 已点赞按钮 hover 时显示可点击状态 --- argontheme.js | 50 +++++++------ functions.php | 191 +++++++++++++++++++++++++++++++++++++++----------- style.css | 6 +- 3 files changed, 183 insertions(+), 64 deletions(-) diff --git a/argontheme.js b/argontheme.js index b944b2b..22290f7 100644 --- a/argontheme.js +++ b/argontheme.js @@ -1905,25 +1905,35 @@ if (argonConfig.waterflow_columns != "1") { }); }(); /*评论点赞*/ -$(document).on("click" , ".comment-upvote" , function(){ - $this = $(this); - ID = $this.attr("data-id"); - $this.addClass("comment-upvoting"); +$(document).on('click', '.comment-upvote', function(){ + let $this = $(this); + let ID = $this.attr('data-id'); + + // 防止重复点击 + if ($this.hasClass('comment-upvoting')) { + return; + } + + $this.addClass('comment-upvoting'); $.ajax({ - url : argonConfig.wp_path + "wp-admin/admin-ajax.php", - type : "POST", - dataType : "json", - data : { - action: "upvote_comment", - comment_id : ID, + url: argonConfig.wp_path + 'wp-admin/admin-ajax.php', + type: 'POST', + dataType: 'json', + data: { + action: 'upvote_comment', + comment_id: ID, }, - success : function(result){ - $this.removeClass("comment-upvoting"); - if (result.status == "success"){ - $(".comment-upvote-num" , $this).html(result.total_upvote); - $this.addClass("upvoted"); - }else{ - $(".comment-upvote-num" , $this).html(result.total_upvote); + success: function(result){ + $this.removeClass('comment-upvoting'); + if (result.status === 'success'){ + $('.comment-upvote-num', $this).html(result.total_upvote); + if (result.upvoted) { + $this.addClass('upvoted'); + } else { + $this.removeClass('upvoted'); + } + } else { + $('.comment-upvote-num', $this).html(result.total_upvote); iziToast.show({ title: result.msg, class: 'shadow-sm', @@ -1938,10 +1948,10 @@ $(document).on("click" , ".comment-upvote" , function(){ }); } }, - error : function(xhr){ - $this.removeClass("comment-upvoting"); + error: function(xhr){ + $this.removeClass('comment-upvoting'); iziToast.show({ - title: __("点赞失败"), + title: __('点赞失败'), class: 'shadow-sm', position: 'topRight', backgroundColor: '#f5365c', diff --git a/functions.php b/functions.php index 8592162..a8f9b50 100644 --- a/functions.php +++ b/functions.php @@ -867,6 +867,22 @@ function argon_debug_console_script() { } add_action('wp_footer', 'argon_debug_console_script', 999); +// 确保 jQuery easing 函数在所有脚本加载后仍可用 +function argon_ensure_jquery_easing() { + ?> + + comment_ID, "upvotes", true); + $upvotes = get_comment_meta($comment->comment_ID, 'upvotes', true); if ($upvotes == null) { $upvotes = 0; } return $upvotes; } -function set_comment_upvotes($id){ + +/** + * 获取用户唯一标识(基于 IP + User-Agent 的哈希) + * 用于识别同一用户,防止重复点赞 + */ +function get_user_upvote_identifier() { + $ip = ''; + // 获取真实 IP,考虑代理情况 + if (!empty($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + // 可能有多个 IP,取第一个 + $ip_list = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $ip = trim($ip_list[0]); + } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) { + $ip = $_SERVER['HTTP_X_REAL_IP']; + } else { + $ip = $_SERVER['REMOTE_ADDR']; + } + + // 结合 User-Agent 生成设备指纹 + $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; + + // 如果用户已登录,使用用户 ID 作为主要标识 + if (is_user_logged_in()) { + return 'user_' . get_current_user_id(); + } + + // 未登录用户使用 IP + UA 哈希 + // 使用 sha256 生成较短的哈希,取前 16 位 + return substr(hash('sha256', $ip . '|' . $user_agent), 0, 16); +} + +/** + * 获取评论的点赞用户列表 + */ +function get_comment_upvote_users($id) { $comment = get_comment($id); if ($comment == null){ - return 0; + return []; } - $upvotes = get_comment_meta($comment -> comment_ID, "upvotes", true); - if ($upvotes == null) { - $upvotes = 0; + $users = get_comment_meta($comment->comment_ID, 'upvote_users', true); + if (empty($users) || !is_array($users)) { + return []; } - $upvotes++; - update_comment_meta($comment -> comment_ID, "upvotes", $upvotes); - return $upvotes; + return $users; } -function is_comment_upvoted($id){ - $upvotedList = isset( $_COOKIE['argon_comment_upvoted'] ) ? $_COOKIE['argon_comment_upvoted'] : ''; - if (in_array($id, explode(',', $upvotedList))){ - return true; - } - return false; + +/** + * 检查当前用户是否已点赞该评论 + */ +function is_comment_upvoted($id) { + $identifier = get_user_upvote_identifier(); + $users = get_comment_upvote_users($id); + return in_array($identifier, $users); } + +/** + * 添加点赞 + */ +function add_comment_upvote($id) { + $comment = get_comment($id); + if ($comment == null){ + return false; + } + + $identifier = get_user_upvote_identifier(); + $users = get_comment_upvote_users($id); + + if (in_array($identifier, $users)) { + return false; // 已点赞 + } + + $users[] = $identifier; + update_comment_meta($comment->comment_ID, 'upvote_users', $users); + update_comment_meta($comment->comment_ID, 'upvotes', count($users)); + + return true; +} + +/** + * 取消点赞 + */ +function remove_comment_upvote($id) { + $comment = get_comment($id); + if ($comment == null){ + return false; + } + + $identifier = get_user_upvote_identifier(); + $users = get_comment_upvote_users($id); + + $key = array_search($identifier, $users); + if ($key === false) { + return false; // 未点赞 + } + + unset($users[$key]); + $users = array_values($users); // 重新索引 + update_comment_meta($comment->comment_ID, 'upvote_users', $users); + update_comment_meta($comment->comment_ID, 'upvotes', count($users)); + + return true; +} + +/** + * 处理评论点赞/取消点赞请求 + */ function upvote_comment(){ - if (get_option("argon_enable_comment_upvote", "false") != "true"){ + if (get_option('argon_enable_comment_upvote', 'false') != 'true'){ return; } header('Content-Type:application/json; charset=utf-8'); - $ID = $_POST["comment_id"]; + $ID = isset($_POST['comment_id']) ? intval($_POST['comment_id']) : 0; $comment = get_comment($ID); if ($comment == null){ - exit(json_encode(array( + exit(json_encode([ 'status' => 'failed', 'msg' => __('评论不存在', 'argon'), - 'total_upvote' => 0 - ))); + 'total_upvote' => 0, + 'upvoted' => false + ])); } - $upvotedList = isset( $_COOKIE['argon_comment_upvoted'] ) ? $_COOKIE['argon_comment_upvoted'] : ''; - if (in_array($ID, explode(',', $upvotedList))){ - exit(json_encode(array( - 'status' => 'failed', - 'msg' => __('该评论已被赞过', 'argon'), - 'total_upvote' => get_comment_upvotes($ID) - ))); + + $is_upvoted = is_comment_upvoted($ID); + + if ($is_upvoted) { + // 已点赞,执行取消点赞 + remove_comment_upvote($ID); + exit(json_encode([ + 'ID' => $ID, + 'status' => 'success', + 'action' => 'removed', + 'msg' => __('已取消点赞', 'argon'), + 'total_upvote' => format_number_in_kilos(get_comment_upvotes($ID)), + 'upvoted' => false + ])); + } else { + // 未点赞,执行点赞 + add_comment_upvote($ID); + exit(json_encode([ + 'ID' => $ID, + 'status' => 'success', + 'action' => 'added', + 'msg' => __('点赞成功', 'argon'), + 'total_upvote' => format_number_in_kilos(get_comment_upvotes($ID)), + 'upvoted' => true + ])); } - set_comment_upvotes($ID); - setcookie('argon_comment_upvoted', $upvotedList . $ID . "," , array( - 'expires' => time() + 3153600000, - 'path' => '/', - 'secure' => is_ssl(), - 'httponly' => true, - 'samesite' => 'Lax' - )); - exit(json_encode(array( - 'ID' => $ID, - 'status' => 'success', - 'msg' => __('点赞成功', 'argon'), - 'total_upvote' => format_number_in_kilos(get_comment_upvotes($ID)) - ))); } add_action('wp_ajax_upvote_comment' , 'upvote_comment'); add_action('wp_ajax_nopriv_upvote_comment' , 'upvote_comment'); diff --git a/style.css b/style.css index df55c8f..2bcd4ea 100644 --- a/style.css +++ b/style.css @@ -5455,10 +5455,10 @@ html.darkmode .comment-upvote:hover { } .comment-upvote.upvoted:hover { - background-color: var(--themecolor) !important; + background-color: var(--themecolor-dark) !important; color: #fff !important; - transform: translateX(-50%) !important; - cursor: default; + transform: translateX(-50%) scale(1.05) !important; + cursor: pointer; } .comment-upvote.comment-upvoting {