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

Rails不使用act_as_votable实现无刷新点赞功能求助

实现自定义投票系统的无刷新更新(无需act_as_votable)

我来一步步帮你搞定无刷新投票功能,完全适配你自己写的投票逻辑,不用依赖第三方gem。咱们从视图、控制器到JS脚本一步步调整:

1. 修改视图代码:添加AJAX支持与唯一标识

首先要让投票请求变成异步的,还要给每个Tip的元素加唯一ID,这样JS才能精准定位到当前操作的Tip。修改后的ERB代码如下:

<% @tips.limit(5).order('votes DESC').each do |tip| %>
  <td id="votebox_<%= tip.id %>">
    <div>
      <%= link_to image_tag('', class: "uparrow"), 
                  tip_upvote_path(tip), 
                  method: :put, 
                  class: "tip_upvote", 
                  remote: true,
                  data: { tip_id: tip.id } %>
    </div>
    <div>
      <%= link_to image_tag('', class: "downarrow"), 
                  tip_downvote_path(tip), 
                  method: :put, 
                  class: "tip_downvote", 
                  remote: true,
                  data: { tip_id: tip.id } %>
    </div>
  </td>
  <td class="vote_count" id="vote_count_<%= tip.id %>"><%= tip.votes %></td>
<% end %>

这里的关键修改:

  • 添加remote: true,让链接发起AJAX请求而非页面跳转
  • 给votebox和vote_count添加带tip.id的唯一ID,方便JS定位
  • 给投票按钮加data-tip-id属性,辅助JS识别当前操作的Tip

2. 更新控制器:支持JS响应并传递数据

调整upvote和downvote方法,添加JS格式的响应,同时把最新的投票数据传给前端:

点赞控制器代码

def upvote
  @tip = Tip.find(params[:id])
  ip = request.ip
  @updated = false # 标记是否实际执行了投票操作

  unless Vrecord.exists?(:ip => ip, :tip_id => @tip.id, :pos => "u")
    Tip.increment_counter(:votes, @tip.id)
    @tip.reload # 重新加载最新的投票数
    # 删除已存在的点踩记录
    Vrecord.where(:ip => ip, :tip_id => @tip.id, :pos => "d").delete_all
    # 创建新的点赞记录
    Vrecord.create(:ip => ip, :tip_id => @tip.id, :pos => "u")
    @updated = true
  end

  respond_to do |format|
    format.html { redirect_to champion_path(ch, anchor:'countertip') }
    format.js # 自动渲染对应的upvote.js.erb
  end
end

点踩控制器代码(类似调整)

def downvote
  @tip = Tip.find(params[:id])
  ip = request.ip
  @updated = false

  unless Vrecord.exists?(:ip => ip, :tip_id => @tip.id, :pos => "d")
    Tip.decrement_counter(:votes, @tip.id)
    @tip.reload
    Vrecord.where(:ip => ip, :tip_id => @tip.id, :pos => "u").delete_all
    Vrecord.create(:ip => ip, :tip_id => @tip.id, :pos => "d")
    @updated = true
  end

  respond_to do |format|
    format.html { redirect_to champion_path(ch, anchor:'countertip') }
    format.js
  end
end

3. 编写JS脚本:实现无刷新更新

在app/views/tips/目录下创建两个JS.erb文件,分别处理点赞和点踩后的页面更新:

upvote.js.erb

// 只有当投票操作实际执行时才更新页面
if (<%= @updated.to_json %>) {
  // 更新投票计数
  const voteCountEl = document.getElementById('vote_count_<%= @tip.id %>');
  voteCountEl.textContent = <%= @tip.votes %>;

  // 可选:更新按钮状态(比如禁用点赞,恢复点踩)
  const upBtn = document.querySelector('.tip_upvote[data-tip-id="<%= @tip.id %>"]');
  const downBtn = document.querySelector('.tip_downvote[data-tip-id="<%= @tip.id %>"]');
  
  upBtn.classList.add('disabled');
  downBtn.classList.remove('disabled');
}

downvote.js.erb

if (<%= @updated.to_json %>) {
  // 更新投票计数
  const voteCountEl = document.getElementById('vote_count_<%= @tip.id %>');
  voteCountEl.textContent = <%= @tip.votes %>;

  // 可选:更新按钮状态
  const downBtn = document.querySelector('.tip_downvote[data-tip-id="<%= @tip.id %>"]');
  const upBtn = document.querySelector('.tip_upvote[data-tip-id="<%= @tip.id %>"]');
  
  downBtn.classList.add('disabled');
  upBtn.classList.remove('disabled');
}

额外优化:添加按钮状态样式

可以给.disabled类加CSS样式,让用户直观看到按钮状态:

.disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none; /* 阻止点击事件 */
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:57:02