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
相关产品推荐
相关产品推荐

