You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Web应用中点赞按钮刷新后状态异常及.includes() bug修复求助

问题描述

我正在开发一款Web应用,用户可对其他用户发布的帖子执行点赞(Like)或取消点赞(Unlike)操作。已实现点击按钮切换文本、样式,并通过fetch请求调用后端接口更新数据,但遇到以下问题:

  • 刷新页面后,所有按钮均显示"Like",无法保留用户已点赞状态。
  • 添加DOMContentLoaded事件处理代码,通过后端传递的liked_posts列表切换按钮状态时出现bug:仅点赞ID为12的帖子,ID为1、2的帖子按钮也显示"Unlike";点赞ID为11的帖子,ID为1的帖子按钮同样错误显示"Unlike"。推测问题与JavaScript的.includes()方法有关,求修复建议。

models.py

class Post(models.Model):
    """ 代表帖子的模型。 """
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    timestamp = models.DateTimeField(auto_now_add=True)
    no_of_likes = models.IntegerField(default=0)

    def __str__(self):
        return f"Post {self.id} by {self.user.username} on {self.timestamp}"
    
class Like(models.Model):
    """ 代表点赞的模型。 """
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.user} likes {self.post}"

urls.py

path("", views.index, name="index"),
path("like/<int:post_id>", views.like, name="like"),
path("unlike/<int:post_id>", views.unlike, name="unlike"),

views.py

def index(request):
    """ 首页。 """
    posts = Post.objects.all().order_by('-timestamp')
    paginator = Paginator(posts, 5)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    likes = Like.objects.all()
    
    # 已点赞帖子ID列表
    liked_posts = Like.objects.filter(user=request.user).values_list('post', flat=True)
    liked_posts = list(liked_posts)
    
    return render(request, "network/index.html", {
        "posts": posts,
        "page_obj": page_obj,
        "likes": likes,
        "liked_posts": liked_posts,              
    })

@login_required
def like(request, post_id):
    
    post = Post.objects.get(pk=post_id)
    user = User.objects.get(pk=request.user.id)
    like = Like.objects.create(user=user, post=post)
    like.save()
    
    post.no_of_likes = Like.objects.filter(post=post).count()
    post.save()
    return JsonResponse({"message": "successfully liked", "no_of_likes": post.no_of_likes})

@login_required   
def unlike(request, post_id):
    
    post = Post.objects.get(pk=post_id)
    user = User.objects.get(pk=request.user.id)
    like = Like.objects.filter(user=user, post=post)
    like.delete()

    post.no_of_likes = Like.objects.filter(post=post).count()
    post.save()
    return JsonResponse({"message": "successfully unliked", "no_of_likes": post.no_of_likes})

index.html 及 JavaScript

<!-- 创建按钮 -->
<button type="button" class="btn btn-success like" id="like{{ post.id }}" onclick="toggleLike('{{ post.id }}')">Like</button>
function toggleLike(postId) {
    var likeButton = document.getElementById('like' + postId);

    if (likeButton.innerText === 'Like') {
        // 切换为取消点赞按钮
        likeButton.innerText = 'Unlike';
        likeButton.style.background = '#dc3545'; // 按钮颜色改为红色
        // 更新点赞计数器
        fetch(`/like/${postId}`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-CSRFToken": "{{ csrf_token }}",
            },
        })
        .then(response => response.json())
        .then(data => {
            document.querySelector("#no-of-likes" + postId ).innerHTML = data.no_of_likes + " likes";
        })
    } else {
        // 切换为点赞按钮
        likeButton.innerText = 'Like';
        likeButton.style.background = ''; // 重置按钮颜色
        // 更新点赞计数器
        fetch(`/unlike/${postId}`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-CSRFToken": "{{ csrf_token }}",
            },
        })
        .then(response => response.json())
        .then(data => {
            document.querySelector("#no-of-likes" + postId).innerHTML = data.no_of_likes + " likes";
        })
    }
}

尝试修复的代码

document.addEventListener('DOMContentLoaded', function() {
    const buttons = document.querySelectorAll(".like");
     
    buttons.forEach(button => {
        const postId = button.getAttribute('id').replace('like', '');
        
        if ('{{ liked_posts }}'.includes(postId)) {
            button.innerText = "Unlike";
            button.style.background = "red";
        } else {
            button.innerText = "Like";
            button.style.background = "green";
        }            
    });
})

修复方案

问题根源在于你把Python列表liked_posts直接转成了字符串('{{ liked_posts }}'),比如列表[12]会变成字符串"[12]",此时用.includes('1')会返回true,因为字符串里包含"1"这个子串,导致误判。

有两种可靠的修复方式:

方式一:将Python列表转为JSON格式传递给JS

修改模板中的修复代码,把liked_posts转成JS数组:

document.addEventListener('DOMContentLoaded', function() {
    const buttons = document.querySelectorAll(".like");
    // 将Python列表转为JS数组
    const likedPosts = {{ liked_posts|safe }};
     
    buttons.forEach(button => {
        const postId = parseInt(button.getAttribute('id').replace('like', ''));
        
        if (likedPosts.includes(postId)) {
            button.innerText = "Unlike";
            button.style.background = "#dc3545";
        } else {
            button.innerText = "Like";
            button.style.background = "";
        }            
    });
})

同时确保views.py中liked_posts是整数列表(当前代码已满足),模板中用|safe过滤器让Django直接输出JSON格式,避免转义。

方式二:渲染按钮时直接设置初始状态

更高效的方式是在模板渲染按钮时,直接根据liked_posts设置按钮的文本和样式,无需JS额外处理:

<button type="button" class="btn {% if post.id in liked_posts %}btn-danger{% else %}btn-success{% endif %} like" 
        id="like{{ post.id }}" onclick="toggleLike('{{ post.id }}')">
    {% if post.id in liked_posts %}Unlike{% else %}Like{% endif %}
</button>

这种方式完全避免了JS处理字符串匹配的问题,页面加载时直接渲染正确状态,性能更好。

额外优化建议

后端点赞计数逻辑可以优化:每次点赞/取消点赞时,直接对no_of_likes做加减操作,无需重新查询计数,比如:

# like函数中
post.no_of_likes += 1
# unlike函数中
post.no_of_likes -= 1

这样比Like.objects.filter(post=post).count()更高效。

内容的提问来源于stack exchange,提问作者Alfalfa9055

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 04:09:53