【Ruby on Rails全栈课程】4.3 评论功能实现(二)--创建帖子详情页面

简介: 1、完善样式,用来显示帖子详情以及评论信息等,编辑app/assets/stylesheets/posts.scss文件,在原有代码下面添加代码:

1、完善样式,用来显示帖子详情以及评论信息等,编辑app/assets/stylesheets/posts.scss文件,在原有代码下面添加代码:


.head {
    font-size: 18px;
    font-weight: 700;
    padding: 10px 0;
}
.time_right {
    float: right;
    font-size: 12px;
    color: #959595;
    .fa {
    font-size: 20px;
    padding-right: 10px;
  }
}
.padding-thumb {
    padding-top: 8px;
}
.reply {
  position: relative;
  overflow: hidden;
  padding-top: 10px;
  /*margin-bottom: 10px;*/
  .body {
    padding: 8px;
    border-radius: 5px;
    position: relative;
    overflow: visible;
    float: left;
    width: 85%;
    border: 1px solid #ddd;
    line-height: 26px;
    &::before {
      content: "";
      display: block;
      position: absolute;
      top: 16px;
      left: -6px;
      width: 10px;
      height: 10px;
      background: #fff;
      border-left: 1px solid #cad5e0;
      border-top: 1px solid #cad5e0;
      -moz-transform: rotate(-45deg);
      -webkit-transform: rotate(-45deg);
    }
    .datetime {
        float: right;
            font-size: 12px;
            color: #959595;
    }
    .delete-content {
        color:#959595;
        font-style:italic;
    }
  }
  .avatar {
    float: left;
    margin-right: 18px;
    margin-top: 12px;
    position: relative;
    overflow: visible;
    text-align: center;
    .image-circle {
      width: 75px;
      border-radius: 50%;
    }
  }
  .reply-from {
      width: 93%;
      float: right;
      padding-right: 30px;
    }
}
.re-reply {
    border-top: 1px solid #e3e3e3;
    font-size: 13px;
  position: relative;
  overflow: hidden;
  padding-top: 6px;
  padding-bottom: 6px;
  clear:both;
}
.comment-form {
    padding-top: 15px;
    .comment-text {
        width: 88%;
        border-radius: 5px;
        border: 1px solid #959595;
    }
    .comment-submit {
      width: 10%;
      float:right;
    }
}
input::-webkit-input-placeholder {
  font-size: 13px;
}


2、编辑routes.rb文件,添加帖子详情页面的路由,并且传递post_id参数,post_id为被查看帖子的id


get 'posts/show_posts/:post_id' => 'posts#show_posts'


3、在posts_controller.rb中添加对应action方法show_posts


#进入帖子详情/评论页面
def show_posts
  post_id = params[:post_id]
  @post = Post.find(post_id)
  #as_type为0时代表帖子的评论,为1时代表评论的回复
  @comments = Comment.where(post_id:post_id,as_type:0)
end


4、创建一个partial文件views/home/_get_thumbs.html.erb


partial文件是什么?


partial文件是局部样板文件,一般用来保存不同页面的相同代码,避免代码冗余,partial文件名字的前面需要加下划线_区分。用render加载partial文件。


举例:<%= render :partial => 'account' %>

就是渲染当前目录下的 _account.html.erb文件


点赞部分的代码,不仅在将views/home/index.html.erb页面中显示,还需要在帖子详情页面(views/posts/show_posts.html.erb后面将创建)显示。如果不将这部分代码剪贴到partial文件中,这部分代码将会重复,造成代码冗余,增加后期维护难度。


所以我们在views/home/index.html.erb文件中,将点赞部分的代码剪切到这个partial文件中。后面我们会用<%= render :partial => "/home/get_thumbs"%>来调用partial文件。在partial文件views/home/_get_thumbs.html.erb中添加下列内容:


<!-- 获取用户是否为此帖子点过赞,分别显示不同的图标 -->
<% if @current_user && p.get_thumb_info(@current_user.id) %>
  <a data-remote="true" href="/posts/create_thumb/<%= p.id %>/0" id="reduce" class="fa fa-thumbs-up" onclick="praiseReplay(this)">
    <%= "#{p.get_thumbs}"%>
  </a>
<%elsif @current_user%>
  <a data-remote="true" href="/posts/create_thumb/<%= p.id %>/1" id="increase" class="fa fa-thumbs-o-up" onclick="praiseReplay(this)">
    <%= "#{p.get_thumbs}"%>
  </a>
<% else %>
<!-- 没有账户登录时的情况 -->
    <a data-remote="true" href="/home/login_in" class="fa fa-thumbs-o-up" onclick="alert('您还未登录,请先登录')">
    <%= "#{p.get_thumbs}"%>
  </a>
<%end%>
<script type="text/javascript">
  function praiseReplay(oldTotal){
    if(oldTotal.className == "fa fa-thumbs-up")
    {
      oldTotal.className = "fa fa-thumbs-o-up";
      var oldValue = oldTotal.innerHTML;
      oldTotal.innerHTML = " " + (parseInt(oldValue) - 1);
      href = oldTotal.href
      oldTotal.href = href.substring(0, href.length - 1) + "0"
    }
    else
    {
      oldTotal.className = "fa fa-thumbs-up";
      var oldValue = oldTotal.innerHTML;
      oldTotal.innerHTML = " " + (parseInt(oldValue) + 1);
      href = oldTotal.href
      oldTotal.href = href.substring(0, href.length - 1) + "1"
    }
  }
</script>


5、在views/home/index.html.erb文件中将上面在partial文件中粘贴的内容去掉,在去掉的地方加上引用partial文件的代码<%= render :partial => "/home/get_thumbs", :locals => { :p => p } %>。


目前index.html.erb文件中的代码如下:


<div class="home-banner" data-stretch="<%= image_url '/assets/timg.jpeg' %>">
    <div class="banner-inner container clearfix">
      <div class="home-banner-links">
        <%= link_to "发布新帖", "/posts/new", class: "banner-btn btn" %>
      </div>
      <div class="home-banner-links" style="left: 100px;top: 100px;">
        <%= link_to "个人中心", "#", class: "banner-btn btn" %>
      </div>
      <div class="home-banner-links" style="left: 350px;top: 150px;">
        <%= link_to "流浪猫救助活动", "#", class: "banner-btn btn" %>
      </div>
    </div>
</div>
<div class="issue-list-header">
  <div class="container clearfix">
    <h1 class="issue-list-heading"></h1>
  </div>
</div>
<div class="container clearfix">
    <div class="issue-list">
      <% @posts.each do |p| %>
        <article class="issue clearfix">
          <div class="avatar body">
            <!-- 获取发帖用户的名字,get_account_name是在post.rb文件中定义的实例方法 -->
            <a class="read-more" href="#"><%= p.get_account_name %></a>
            <!-- 获取发帖的时间 -->
            <p class="time"><%= p.get_updated_at %></p>
          </div>
          <div class="body">
            <% if p.as_type == 2 %>
              <div class="icon-top" title="置顶">置顶</div>
            <% elsif p.as_type == 1 %>
              <div class="icon-good" title="精">精</div>
            <% end %>
            <h5 class="title">
              <%= link_to "#{p.head}", "#" %>
            </h5>
            <a class="as_sb" href="#"><%= p.body %></a>
          </div>
          <div class="issue-comment-count">
              <a data-remote="true"><i class="fa fa-comment-o" padding-right: 15px;">
                <!-- 获取评论数,方法还没完善,先设为1 -->
                <%= "#{p.get_post_items}" %>
              </i></a>
            <!-- 添加的代码,加载partial文件_get_thumbs.html.erb -->
            <%= render :partial => "/home/get_thumbs", :locals => { :p  => p } %>
          </div>
        </article>
      <% end %>
    </div>
</div>


代码解析:


<%= render :partial => "/home/get_thumbs", :locals => { :p => p } %>

将与partial文件中重复的代码去掉后,加上这一行代码,意思是将partial文件中的代码加载到这个位置,:locals表示需要从index.html.erb文件传递到_get_thumbs.html.erb文件的参数。


6、在app/models/comment.rb文件中添加下列方法


用来获取评论用户的用户名以及评论的创建时间,我们会在帖子详情页面(后面会创建views/posts/show_posts.html.erb)直接用Comment实例对象调用这些方法。


#通过评论实例获取该评论客户的用户名
def get_account_name
  account = Account.find_by(id:self.account_id)
  if account
    name = account.name
  else
    name = "用户不存在"
  end
end
# 获取评论创建时间
# 当小于60秒的时候返回时间为xx秒前; 
# 当小于60分钟大于等于60秒时返回xx分钟前; 
# 当小于24小时大于等于60分钟时返回xx小时前;
# 当大于等于1天的时候,显示xxxx-xx-xx xx-xx时间;
def get_created_at
  created_at = self.created_at
  now = Time.now
  #时间间隔秒数
  time_distance = (now - created_at).to_i
  if time_distance == 0
    date = "刚刚"
  elsif time_distance < 60
    date = "#{time_distance}秒前"
  #判断时间间隔是否小于60分钟
  elsif time_distance/60 < 60
    date = "#{time_distance/60}分钟前"
  #判断时间间隔是否小于24小时
  elsif time_distance/(60*60) < 24
    date = "#{time_distance/(60*60)}小时前"
  #时间间隔大于1天,会进入else语句下面的代码
  else
    date = created_at.strftime("%Y-%m-%d %H:%M")
  end
  date
end


7、编辑app/models/post.rb文件中的get_post_items方法,来获取实际的评论数(评论数包括回复的数量)


 #获取评论数
def get_post_items
  num = Comment.where(post_id:self.id).count
end


8、创建views/posts/show_posts.html.erb文件,用来显示帖子详情页面,粘贴下列内容:


<div class="container" style="width: 60%">
<article class="clearfix">
  <div class="avatar body">
    <!-- 获取发帖用户的名字,get_account_name是在post.rb文件中定义的实例方法 -->
    <a class="read-more" href="#"><%= @post.get_account_name %></a>
    <!-- 获取用户的角色信息,ROLE是在account.rb文件中定义的常量数组,通过Account::ROLE调用该数组,Account::ROLE[0]取到的值为普通用户 -->
    <p class="time"><%= Account::ROLE[Account.find(@post.account_id).role] %></p>
  </div>
  <div class="head"><%= @post.head %></div>
  <div>
  <%= @post.body %>
  </div>
  <div class="time_right padding-thumb">
    <!-- 锚点定位,点击会定位到id为co-point的元素 -->
    <a href="#co-point"><i class="fa fa-comment-o">
    <!-- 获取评论数,get_post_items方法在app/models/post.rb中定义 -->
    <%= "#{@post.get_post_items}" %>
    </i></a>
    <!-- 加载partial文件,传递@post参数 -->
    <%= render :partial => "/home/get_thumbs", :locals => { :p  => @post } %>
    <!-- 帖子最后的修改时间 -->
    <%= @post.updated_at.strftime ("%Y-%m-%d %H:%M") %>
  </div>
</article>
<div id="data_content">
  <!-- 遍历as_type为0的评论对象集合@comments -->
  <% @comments.each do |comment| %>
  <div class="reply clearfix">
    <div class="avatar">
      <!-- get_account_name方法在comment.rb文件中已经定义,用来获取评论者的用户名 -->
      <a><%= comment.get_account_name %></a>
    </div>
    <div class="body">
      <!-- 评论status为0时代表正常显示,不为0是代表已经被删除,被删除的评论需要显示为「该评论已删除」 -->
      <span id="content_<%= comment.id %>">
      <% if comment.status == 0 %>
        <div><%= comment.content %></div>
      <% else %>
        <div class="delete-content">该评论已删除</div>
      <% end %>
      </span>
      <div class="time_right" id="time_<%= comment.id %>">
        <!-- 获取评论的创建时间 -->
        <%= comment.get_created_at %>
        <!-- 已被删除的帖子后面不显示回复按钮 -->
        <% if comment.status == 0 %>
          <a id="reply<%= comment.id %>" onclick="outIn(<%=comment.id%>,<%=comment.id%>)">回复</a>
        <% end %>
      </div>
      <div id="reply_page_<%= comment.id %>">
        <!-- 可以通过re_comment_id字段找到,对应本次遍历的评论对象的所有回复 -->
        <% @reply = Comment.where(re_comment_id:comment.id,as_type:1) %>
        <!-- 我们只默认展示两条回复,需要查看更多回复,需要点击查看更多回复
        @reply.limit(2)的意思是只选择查询结果的前两条数据 -->
        <% @reply.limit(2).each do |re| %>
          <div class="re-reply">  
            <a><%= re.get_account_name %></a>
            <!-- 如果回复的是评论的回复,该回复用户名后面需要跟被回复用户的用户名,re_reply_id字段保存被回复用户的id;如果直接回复评论,那么回复用户名后面直接跟回复内容,re_reply_id字段为空。-->
            <% if re.re_reply_id.blank? %>
              :
            <% else %>
              回复 <a><%= Comment.find(re.re_reply_id).get_account_name %></a> :
            <% end %>
            <span id="content_<%= re.id %>">
            <% if re.status == 0 %>
              <span><%= re.content %></span>
            <% else %>
              <span class="delete-content">该评论已删除</span>
            <% end %>
            </span>
            <div class="time_right">
              <%= re.get_created_at %>
              <span id="time_<%= re.id %>">
              <% if re.status == 0 %>
                <!-- outIn方法控制回复框,当客户点击回复按钮,出现回复框,
              回复变成取消回复,点击取消回复,回复框收起 -->
                <a id="reply<%= re.id %>" onclick="outIn(<%= comment.id %>,<%=re.id%>)"> 回复</a>
              <% end %>
              </span>
            </div>
          </div>
        <% end %>
      </div>
      <!-- 当该评论的回复大于两条时,下面会有「查看更多回复」的链接,点击会查看到更多回复
    主要通过js的控制点击查看更多回复,后面会讲到 -->
      <% if @reply.count > 2 %>
        <a id="spread-out" name="1" data-remote="true" href="#">更多<%= @reply.count - 2 %>条回复 ↓</a>
      <% end %>
    </div>
    <!-- 回复框的内容 -->
    <%= form_for Comment.new,url: "#" do |f| %>
      <!-- 给每个评论的回复框的id都加上comment.id,这样每个评论都有唯一的id,这样才能通过js控制回复框出现在相应的评论下 -->
      <div class="comment-form reply-from" id="co-reply<%= comment.id %>" style="display:none;">
        <input type="text" name="comment" placeholder="写下你的回复..." class="comment-text">
        <div class="comment-submit">
          <input type="submit" value="回复" class="submit-issue-button btn btn-primary">
        </div>
      </div>
    <% end %>
  </div>
  <%end%>
</div> 
 <!-- 评论框的内容 -->
<%= form_for Comment.new,url: "#" do |f| %>
  <!-- 评框的id为co-point,id后面不需要加上每个评论的id,因为评论框会出现在页面最下方,与每个评论的位置没有关系 -->
  <div class="comment-form" name="co-point" id="co-point">
    <input type="text" name="comment" placeholder="写下你的评论..." class="comment-text">
    <div class="comment-submit">
      <input type="submit" value="发布" class="submit-issue-button btn btn-primary">
    </div>
  </div>
<% end %>
</div>


9、编辑views/posts/show_posts.html.erb文件,实现点击回复按钮显示回复框的功能,用js实现。


(1)实现js方法。


点击回复<a>标签时,执行下面的js方法outIn(),先判断当前是否有用户登录,如果没有用户登录,需要提示「您还未登录,请先登录!」。


如果有用户登录,判断coReply回复框的显示状态,如果是未显示状态,将状态改为显示oReply.style.display = "block";,回复改为取消回复coA.innerHTML = "取消回复";。如果是显示状态,将状态改为未显示,取消回复改为回复。


<script type="text/javascript">
  function outIn(comment_id,reply_id){
    <% if @current_user %>
      //coReply为回复框对象
      var coReply = document.getElementById("co-reply" + comment_id);
      //coA为回复a标签对象
      var coA = document.getElementById("reply" + reply_id);
      if(coReply.style.display == "none"){
        coReply.style.display = "block";
        coA.innerHTML = "取消回复";
      }
      else{
        coReply.style.display = "none";
        coA.innerHTML = "回复";
      }
    <% else %>
      alert("您还未登录,请先登录!");
    <% end %>
  }
</script>


(2)编辑views/posts/show_posts.html.erb文件,将js方法添加到回复<a>标签的onclick元素中。


<!--原代码-->
<a id="reply<%= comment.id %>" onclick="">回复</a>
<!--改为-->
<a id="reply<%= comment.id %>" onclick="outIn(<%=comment.id%>,<%=comment.id%>)">回复</a>
<!--原代码-->
<a id="reply<%= re.id %>" onclick=""> 回复</a>
<!--改为-->
<a id="reply<%= re.id %>" onclick="outIn(<%= comment.id %>,<%=re.id%>)"> 回复</a>


代码解析:


<a id="reply<%= comment.id %>" onclick="outIn(<%=comment.id%>,

<%=comment.id%>)">回复</a>


其中id="reply<%= comment.id %>",嵌入了ruby代码,如果当前遍历的comment对象的id为20,则id=“reply20”


其中onclick="outIn(<%=comment.id%>,<%=comment.id%>)"给outIn()方法传递了两个参数,第一个参数是为了找到coReply回复框对象,第二个参数是为了找到coA回复a标签对象评论框对象


10、修改home/index.html.erb文件,将帖子标题、内容加上帖子详情页面的链接


<!--原代码-->
<%= link_to "#{p.head}", "#" %>
<!--改为-->
<%= link_to "#{p.head}", "/posts/show_posts/#{p.id}" %>
<!--原代码-->
<a class="as_sb" href="#"><%= p.body %></a>
<!--改为-->
<a class="as_sb" href="/posts/show_posts/<%= p.id %>"><%= p.body %></a>
目录
相关文章
|
3月前
|
前端开发 测试技术 数据库
使用Ruby on Rails进行快速Web开发的技术探索
【8月更文挑战第12天】Ruby on Rails以其高效、灵活和易于维护的特点,成为了快速Web开发领域的佼佼者。通过遵循Rails的约定和最佳实践,开发者可以更加专注于业务逻辑的实现,快速构建出高质量的Web应用。当然,正如任何技术框架一样,Rails也有其适用场景和局限性,开发者需要根据项目需求和个人偏好做出合适的选择。
|
3月前
|
前端开发 测试技术 API
揭秘Ruby on Rails的神秘力量:如何让你的Web应用飞起来?
【8月更文挑战第31天】Ruby on Rails(简称RoR)是一个基于Ruby语言的开源Web应用框架,自2005年发布以来,因简洁的语法、强大的功能和高效的开发效率而广受好评。RoR采用MVC架构,提高代码可读性和可维护性,拥有庞大的社区和丰富的库支持。本文通过示例代码展示其强大之处,并介绍RoR的核心概念与最佳实践,帮助开发者更高效地构建Web应用。
42 0
|
3月前
|
前端开发 API C++
在Ruby世界中寻找你的Web框架灵魂伴侣:Rails vs Sinatra
【8月更文挑战第31天】在Ruby的世界里,选择Web框架如同挑选衣物,需根据场合和需求。Rails与Sinatra是两大热门框架,前者以其“约定优于配置”理念和全面的功能成为企业级应用的首选;后者则以轻量级和灵活性著称,适用于快速原型开发和小规模应用。通过对比两者特性,如Rails的MVC架构与Sinatra的简洁API,我们可以看到它们各有所长。选择合适的框架,如同找到旅途中的最佳伙伴,让开发之路更加顺畅愉悦。这场探索之旅教会我们,没有绝对的好坏,只有最适合的选择。
34 0
|
3月前
|
安全 前端开发 数据安全/隐私保护
如何在Ruby on Rails中打造坚不可摧的OAuth认证机制
【8月更文挑战第31天】在构建现代Web应用时,认证与授权至关重要。本文介绍如何在Ruby on Rails中实现OAuth认证,通过使用`omniauth`和`devise` gems简化流程。首先安装并配置相关gem,接着在`User`模型中处理OAuth回调,最后设置路由及控制器完成登录流程。借助OAuth,用户可使用第三方服务安全地进行身份验证,提升应用安全性与用户体验。随着OAuth标准的演进,这一机制将在Rails项目中得到更广泛应用。
48 0
|
6月前
|
Ruby
|
6月前
|
Ruby
|
6月前
|
JSON 数据格式 Ruby
|
6月前
|
调度 Ruby
|
6月前
|
存储 JSON 数据格式
|
6月前
|
Ruby