【Ruby on Rails实战】4.3 评论功能实现(二)--
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}", "/posts/show_posts/#{p.id}" %>
</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>