project(09)

续 开发显示回答列表

重构updateDuration方法

步骤1:

在utils.js文件中添加通用的计算持续时间的方法

function addDuration(item){
    //判断参数状态
    if(item==null || item.createtime==null){
        return;
    }
    //获得问题中的创建时间属性(毫秒数)
    let createtime=new Date(item.createtime).getTime();
    //获得当前时间的毫秒数
    let now=new Date().getTime();
    //计算时间差(秒)
    let durtaion=(now-createtime)/1000;
    if(durtaion<60){
        // 显示刚刚
        //duration这个名字可以随便起,只要保证和页面上取的一样就行
        item.duration="刚刚";
    }else if(durtaion<60*60){
        // 显示XX分钟
        item.duration=
            (durtaion/60).toFixed(0)+"分钟前";
    }else if (durtaion<60*60*24){
        //显示XX小时
        item.duration=
            (durtaion/60/60).toFixed(0)+"小时前";
    }else{
        //显示XX天
        item.duration=
            (durtaion/60/60/24).toFixed(0)+"天前";
    }
}

步骤2:

在question_detail.js文件中

修改代码如下

let questionApp = new Vue({
    el:"#questionApp",
    data:{
        question:{}
    },
    methods:{
        loadQuestion:function(){
            //获取浏览器地址栏中当前url中?之后的内容
            let questionId=location.search;
            console.log("questionId:"+questionId);
            //判断是不是获得了?之后的内容
            if(!questionId){
                //如果没有?则终止
                alert("必须指定问题id");
                return;
            }
            //如果存在?之后的内容,则去掉?    ?354
            questionId=questionId.substring(1);
            //发送异步请求
            $.ajax({
                url:"/v1/questions/"+questionId,//v1/questions/15
                method:"get",
                success:function(r){
                    console.log(r);
                    if(r.code == OK){
                        questionApp.question=r.data;
                        addDuration(questionApp.question);//修改了这个里!!!!
                    }else{
                        alert(r.message);
                    }
                }
            })
        }
    },
    created:function(){
        this.loadQuestion();
    }
});

步骤3:

修改index.js和index_teacher.js中updateDuration方法的的内容

代码如下

 updateDuration:function () {
            let questions=this.questions;
            for(let i=0;i<questions.length;i++){
               addDuration(questions[i]);
            }
        }

步骤4:

编写回答列表中老师回答问题的持续时间

代码如下

let answersApp=new Vue({
    el:"#answersApp",
    data:{
        message:"",
        hasError:false,
        answers:[]
    },
    methods:{
        loadAnswers:function(){
            let questionId=location.search;
            if(!questionId){
                this.message="必须有问题ID";
                this.hasError=true;
                return;
            }
            questionId=questionId.substring(1);
            $.ajax({
                url:"/v1/answers/question/"+questionId,
                method:"get",
                success:function(r){
                    if(r.code==OK){
                        answersApp.answers=r.data;
                        answersApp.updateDuration();
                    }else{
                        answersApp.message=r.message;
                        answersApp.hasError=true;
                    }
                }
            })
        },
        updateDuration:function(){
            for(let i=0;i<this.answers.length;i++){
                addDuration(this.answers[i]);
            }
        }
    },
    created:function(){
        this.loadAnswers();
    }
})

将新回复的答案插入答案列表

步骤1:

在R类中补全一个方法

这个方法是在执行新增操作时,不但返回新增结果信息,还返回新增对象的方法

R类中添加方法如下

/**
     * 新增成功,并且需要获得新增成功对象时使用这个方法
     */
    public static R created(Object data){
        return new R().setCode(CREATED).setMessage("创建成功")
                .setData(data);
    }

步骤2:

重构AnswerController中的postAnswer方法

代码如下

//新增回复的控制方法
    @PostMapping("")
    @PreAuthorize("hasRole('ROLE_TEACHER')")
    public R postAnswer(
            @Validated AnswerVo answerVo,
            BindingResult result,
            @AuthenticationPrincipal User user){
        log.debug("收到回复信息{}",answerVo);
        if(result.hasErrors()){
            String message=result.getFieldError().getDefaultMessage();
            log.warn(message);
            return  R.unproecsableEntity(message);
        }
        //这里调用业务逻辑层方法
        Answer answer=
            answerService.saveAnswer(answerVo,user.getUsername());
        return R.created(answer);
    }

步骤3:

重构question_detail.js文件中的postAnswerApp区域的方法

代码如下

let postAnswerApp=new Vue({
    el:"#postAnswerApp",
    data:{
        message:"",
        hasError:false
    },
    methods:{
        postAnswer:function(){
            postAnswerApp.hasError=false;
            let questionId=location.search;
            if(!questionId){
                this.message="没有问题ID";
                this.hasError=true;
                return;
            }
            //去掉?
            questionId=questionId.substring(1);
            let content=$("#summernote").val();
            if(!content){
                this.message="请填写回复内容";
                this.hasError=true;
                return;
            }
            let data={
                questionId:questionId,
                content:content
            }
            $.ajax({
                url:"/v1/answers",
                method:"post",
                data:data,
                success:function(r){
                    if(r.code==CREATED){
                        let answer=r.data;//这个r.data就是新增的回答
                        //将这个问题的持续时间计算出来
                        addDuration(answer);
                        //将新增的方法插入到anwsers数组的后面
                        answersApp.answers.push(answer);
                        //回答已经显示,清空富文本编辑器中的内容
                        $("#summernote").summernote("reset");
                        postAnswerApp.message=r.message;
                        postAnswerApp.hasError=true;
                        //2秒中之后信息消失
                        setTimeout(function(){
                            postAnswerApp.hasError=false;
                        },2000);
                    }else{
                        postAnswerApp.message=r.message;
                        postAnswerApp.hasError=true;
                    }
                }
            })
        }
    }
})

开发评论答案的功能

再次强调<问题>、<回答>、<评论>的关系

在这里插入图片描述

question对answer是一对多

answer对comment又是一对多

要想查询一个问题的所有评论,是需要连表查询的

在这里插入图片描述

开发显示评论的功能

显示评论信息需要昵称和评论内容

但是数据表中没有昵称列,这样我们就需要连表查询才能得到

为了简化这样的操作,我们可以添加一个冗余列信息:user_nick_name

保存用户昵称,避免过多的连表操作

怎么样给当前的表添加一个列呢?

步骤1:

为comment表添加user_nick_name列,简化查询

代码如下

ALTER TABLE `comment` ADD COLUMN user_nick_name 
VARCHAR(255) AFTER user_id

UPDATE `comment` c SET user_nick_name =
(SELECT nickname FROM `user` u WHERE u.id=c.user_id )

步骤2:

我们在数据库中添加了昵称

那么也许要在实体类中添加对应的属性

找到model.Comment类,添加新增的列的属性

/**

  • 用户昵称
    */
    @TableField(“user_nick_name”)
    private String userNickName;

步骤3:

开始执行查询操作的准备工作

每个回答包含多个评论,所以我们先要在Answer实体类中添加一个Comment类型的集合

model.Answer实体类添加代码如下

/**

  • 当前回答的所有评论
    */
    @TableField(exist = false)
    private List comments=new ArrayList<>();

步骤4:

开始数据访问层的编写

这个查询是复杂查询,需要在xml文件中编写代码

所以我们要按照Mybatis复杂映射学习的内容完成这个查询

首先在AnswerMapper接口中编写下面的方法

@Repository
    public interface AnswerMapper extends BaseMapper<Answer> {
        //复杂映射查询按问题id获得所有回答以及每个回答包含的评论
        List<Answer> findAnswersByQuestionId(Integer questionId);
        
    }

步骤5:

编写执行复杂映射查询的xml文件

首先SpringBoot约定这些xml文件的位置必须在resources/mapper下

如果不在复制过去

步骤6:

编写AnswerMapper.xml文件,在文件中编写代码如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.straw.portal.mapper.AnswerMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="answerCommentMap" type="cn.tedu.straw.portal.model.Answer">
        <id column="id" property="id"/>
        <result column="content" property="content"/>
        <result column="like_count" property="likeCount"/>
        <result column="user_id" property="userId"/>
        <result column="user_nick_name" property="userNickName"/>
        <result column="quest_id" property="questId"/>
        <result column="createtime" property="createtime"/>
        <result column="accept_status" property="acceptStatus"/>
        <collection property="comments" ofType="cn.tedu.straw.portal.model.Comment">
            <id column="comment_id" property="id" />
            <result column="comment_user_id" property="userId" />
            <result column="comment_answer_id" property="answerId" />
            <result column="comment_user_nick_name" property="userNickName" />
            <result column="comment_content" property="content" />
            <result column="comment_createtime" property="createtime" />
        </collection>
    </resultMap>
    <select id="findAnswersByQuestionId" resultMap="answerCommentMap" >
        SELECT
            a.id,
            a.content,
            a.user_id,
            a.user_nick_name,
            a.quest_id,
            a.createtime,
            a.accept_status,
            a.like_count,
            c.id AS comment_id,
            c.user_id AS comment_user_id,
            c.user_nick_name AS comment_user_nick_name,
            c.content AS comment_content,
            c.createtime AS comment_createtime,
            c.answer_id AS comment_answer_id
        FROM answer a
        LEFT JOIN `comment` c
        ON c.answer_id=a.id
        WHERE a.quest_id=#{question_id}
        ORDER BY a.createtime,c.createtime
    </select>
</mapper>

步骤7:

编写业务逻辑层接口

IAnswerService接口中是有一个按问题id查询问题的所有会的的方法的

所以我们没有必要再添加新的方法

所以直接去重构这个方法的实现即可

AnswerServiceImpl类中getAnswersByQuestionId方法代码如下

@Override
    public List<Answer> getAnswersByQuestionId(Integer questionId) {
        if(questionId==null){
            throw ServiceException.invalidRequest("问题id不能为空");
        }
        List<Answer> answers=answerMapper.findAnswersByQuestionId(questionId);
        return answers;
    }

步骤8:

之前已经完成过控制器调用按id查询回答的方法

无需再次编写

现在直接到页面去修改vue的绑定即可

<p class="text-success">
                      <i class="fa fa-comment"></i>&nbsp;
                      <span v-text="answer.comments.length">1</span>条评论
                    </p>
                    <ul class="list-unstyled mt-3">
                      <li class="media my-2" v-for="comment in answer.comments">
                        <img style="width: 50px;height: 50px;border-radius: 50%;"
                             src="../img/user.jpg" class="mr-3"
                             alt="...">
                        <div class="media-body">
                          <h6 class="mt-0 mb-1">
                            <span v-text="comment.userNickName">李四</span>:
                          </h6>
                          <p class="text-dark">
                            <span class="text-monospace" v-text="comment.content">
                              明白了,谢谢老师!
                            </span>
                            <span class="font-weight-light text-info"
                                  style="font-size: small"></span>
                            <a class="text-primary ml-2"
                               style="font-size: small" data-toggle="collapse" href="#editCommemt1"
                               role="button"
                               v-bind:href="'#editComment'+comment.id"
                               aria-expanded="false" aria-controls="collapseExample">
                              <i class="fa fa-edit"></i>编辑
                            </a>
                            <!--老师角色或者属于本用户的评论可以删除该评论-->
                            <a class="ml-2  fa fa-close " style="font-size: small"
                               data-toggle="collapse"  role="button"
                               aria-expanded="false" aria-controls="collapseExample">
                              删除
                            </a>

                          </p>
                          <div class="collapse" id="editCommemt1"
                            v-bind:id="'editComment'+comment.id">
                            <div class="card card-body border-light">
                              <form action="" method="post" class="needs-validation" novalidate>
                                <div class="form-group">
                                  <textarea class="form-control"
                                            id="textareaComment1" name="content" rows="4"
                                            required></textarea>
                                  <div class="invalid-feedback">
                                    内容不能为空!
                                  </div>
                                </div>
                                <button type="submit" class="btn btn-primary my-1 float-right">提交修改</button>
                              </form>
                            </div>
                          </div>
                        </div>
                      </li>

                    </ul>

开发添加评论的功能

开发思路

1.创建CommentVo

2.编写控制器代码

3.页面表单的绑定

4.编写js文件代码

5.业务逻辑层和添加操作

步骤1:

编写CommentVo类来准备新增评论

@Data
@Accessors(chain=true)
public class CommentVo implements Serializable {

    @NotNull(message = "问题ID不能为空")
    private Integer answerId;

    @NotBlank(message = "评论内容不能为空")
    private String content;
}

步骤2:

编写控制器代码

CommentController类中添加方法

代码如下

@RestController
@RequestMapping("/v1/comments")
@Slf4j
public class CommentController {
    @Resource
    ICommentService commentService;
    @PostMapping
    public R<Comment> postComment(
            @Validated CommentVo commentVo,
            BindingResult result){
        if(result.hasErrors()){
            String message=result.getFieldError().getDefaultMessage();
            return R.unproecsableEntity(message);
        }
        log.debug("收到评论信息{}:",commentVo);
        //这里调用业务逻辑层方法执行评论的新增即可
        return R.created("OK");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值