project(8)

开发问题详情页面

首先来了解一下问题详情页面的整体结构

在这里插入图片描述

这个页面的整体开发流程如下

  1. 显示页面
  2. 异步查询本问题的详细信息显示在当前问题区域
  3. 异步完成讲师回答问题添加到数据的功能
  4. 异步查询当前问题的所有回答
  5. 异步添加指定回答的评论
  6. 异步查询所有回答的评论
  7. 异步实现评论的修改和删除

显示问题详情页

开发步骤

步骤1:

复制static/question/detail.html文件到templates/question/detail.html

步骤2:

在HomeController中编写显示这个页面的方法

代码如下

 //显示问题详情页面
    @GetMapping("/question/detail.html")
    public ModelAndView detail(){
        return new ModelAndView("question/detail");
    }

步骤3:

现在由于问题列表中任何一个问题的标题连接的都是/question/detail.html

所有都可以正确显示出问题详情页

但是我们现在没有办法知道用户请求的问题的具体id

我们可以采用将问题的id保存在url中的方法来实现页面显示之后使用异步查询问题详情

这个id的保存实现在index.html页面

因为index_teacher.html也是复用index.html页面内容的

index.html标题连接的a标签代码修改为

<a class="text-dark" href="question/detail.html"
           v-bind:href="'/question/detail.html?'+question.id"
           v-text="question.title">
       eclipse 如何导入项目?
</a>

显示问题详情

步骤1:

按id查询Question的方法是有MybatisPlus直接提供的,所以Mapper不用写

直接从业务逻辑层开始

IQuestionService接口添加方法

//按id查询问题详情的方法
    Question getQuestionById(Integer id);

步骤2:

QuestionServiceImpl类实现如下

@Override
    public Question getQuestionById(Integer id) {
        //先按id查询出Question
        Question question=questionMapper.selectById(id);
        //再按Question的tag_names列的标签转换为List<Tag>
        List<Tag> tags=tagNamesToTags(question.getTagNames());
        //将转换完成的List<Tag>保存到这个Question的tags属性中
        question.setTags(tags);
        return question;
    }

步骤3:

编写控制层方法调用

QuestionController代码如下

  //显示问题详细的Controller方法
    //为了遵守RESTful的风格这个位置的路径比较特殊
    //例如:/v1/questions/12
    //上面的路径SpringMvc会自动将12赋值给{id}
    //@PathVariable标记的同名属性的值也会是12
    @GetMapping("/{id}")
    public R<Question> question(
            @PathVariable Integer id){
        //判断必须要有id
        if(id==null){
            return R.invalidRequest("ID不能为空");
        }
        Question question=questionService.getQuestionById(id);
        return R.ok(question);

    }

步骤4:

编写detail.html代码

主要在当前问题显示详情的为位置

<!-- 素材187行  --->
<div id="questionApp" class="container-fluid bg-light">
<div id="questionApp" class="container-fluid bg-light">
        <div class="row">
          <div class="col-2 px-0  ">
            <div class="container-fluid ">
              <div class="row  mt-4 mx-0 px-0" >
                <a class="btn btn-outline-danger btn-md rounded-lg fa fa-close " style="font-size: x-small">删除</a>
              </div>

              <div class="row mt-4 mx-0 px-0" >
                <a class="btn btn-outline-primary btn-md rounded-lg fa fa-edit " style="font-size: x-small"
                   href="../question/edit.html">编辑</a>
              </div>
              <div class="row mt-4 mx-0 px-0">
                <a class="btn btn-outline-info btn-md rounded-lg fa fa-pencil " style="font-size: x-small"
                   href="#writeAnswer">回答</a>
              </div>
              <div class="row mt-4 mx-0 px-0" id="collectApp">
                <a class="btn btn-outline-secondary btn-md rounded-lg fa fa-star "
                    style="font-size: x-small">收藏</a>
                <a  class="btn btn-outline-secondary btn-md rounded-lg fa fa-star "
                    style="font-size: x-small;display: none" >已收藏</a>
              </div>

            </div>

          </div>
          <div class="col-10 px-0">
            <div class="container-fluid ">
              <div class="row px-0 mb-3">
                <div class="col-9 px-0">
                  <a class="badge badge-pill  badge-info mx-1"
                     href="../tag/tag_question.html"
                     v-for="tag in question.tags"
                     v-text="tag.name">Java基础</a>
                </div>
                <div class="col-3 px-0">
                  <div class="row px-0">
                    <div class="col border-right text-right">
                      <p class="font-weight-light mb-0">收藏</p>
                      <p class="font-weight-bold mt-1">1</p>
                    </div>
                    <div class="col">
                      <p class="font-weight-light mb-0">浏览</p>
                      <p class="font-weight-bold mt-1"
                        v-text="question.pageViews">100</p>
                    </div>
                  </div>
                </div>
              </div>
              <p class=" px-0 text-center font-weight-bold" style="font-size: x-large"
                v-text="question.title">
                Java中方法重载和重写的区别
              </p>
              <div class="px-0 container-fluid question-content"
                v-html="question.content">
                请问的方法中重写和重载的区别都是什么,如何使用
              </div>
              <p class="text-right px-0 mt-5">
                <span class="font-weight-light badge badge-primary"
                  v-text="question.userNickName">张三</span>
                <span class="font-weight-light badge badge-info"
                  v-text="question.duration">3天前</span>
              </p>
            </div>

          </div>
        </div>
      </div>

末尾引用

</body>
<script src="../js/utils.js"></script>
<script src="../js/question_detail.js"></script>
</html>

步骤5:

创建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;
                        questionApp.updateDuration();
                    }else{
                        alert(r.message);
                    }
                }
            })
        },
        updateDuration:function(){
            //获得问题中的创建时间属性(毫秒数)
            let createtime=new Date(this.question.createtime).getTime();
            //获得当前时间的毫秒数
            let now=new Date().getTime();
            //计算时间差(秒)
            let durtaion=(now-createtime)/1000;
            if(durtaion<60){
                // 显示刚刚
                //duration这个名字可以随便起,只要保证和页面上取的一样就行
                this.question.duration="刚刚";
            }else if(durtaion<60*60){
                // 显示XX分钟
                this.question.duration=
                    (durtaion/60).toFixed(0)+"分钟前";
            }else if (durtaion<60*60*24){
                //显示XX小时
                this.question.duration=
                    (durtaion/60/60).toFixed(0)+"小时前";
            }else{
                //显示XX天
                this.question.duration=
                    (durtaion/60/60/24).toFixed(0)+"天前";
            }
        }
    },
    created:function(){
        this.loadQuestion();
    }
});

完成讲师回复问题功能

开发页面和控制层

步骤1:

编写值对象类

在vo包中创建AnswerVo类

代码如下

@Data
@Accessors(chain = true)
public class AnswerVo implements Serializable {
    @NotNull(message = "问题编号不能为空")
    private Integer questionId;

    @NotBlank(message = "回复内容不能为空")
    private String content;

}

步骤2:

编写控制层代码

AnswerController中代码如下

@RestController
@RequestMapping("/v1/answers")
@Slf4j
public class AnswerController {

    @Resource
    private IAnswerService answerService;

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

        return R.created("回复问题成功!");
    }
}

步骤3:

编写detail.html页面的代码

主要针对富文本编辑器提交的表单范围

<div class="container-fluid mt-4" id="postAnswerApp">
              <h5 class="text-info mb-2"><i class="fa fa-edit"></i>写答案</h5>
              <form action="#" method="post"
                    v-on:submit.prevent="postAnswer"
                    class="needs-validation" novalidate>
                <div class="form-group">
                  <textarea id="summernote" name="content" required ></textarea>
                  <div class="invalid-feedback"
                    v-bind:class="{'d-block':hasError}">
                    <h5 v-text="message">回答内容不能为空!</h5>
                  </div>
                </div>
                <div class="form-group">
                  <p class="text-right">
                    <button type="submit" class="btn btn-primary">提交回答</button>
                  </p>
                </div>
              </form>
            </div>

步骤4:

为了完成讲师回复时选择的图片上载,和讲师回复的提交操作

在question_detail.js文件中添加如下代码

$(function(){
    $('#summernote').summernote({
        height: 300,
        lang: 'zh-CN',
        placeholder: '请输入问题的详细描述...',
        callbacks:{
            //在执行指定操作后自动调用下面的方法
            //onImageUpload方法就会在用户选中图片之后立即运行
            onImageUpload:function(files) {
                //参数是一个file数组取出第一个,因为我们只会选中一个
                let file =files[0];
                //构建表单
                let form=new FormData();
                form.append("imageFile",file);
                $.ajax({
                    url:"/upload/file",
                    method:"post",
                    data:form,//发送的是我们构建的表单中的数据
                    //下面有两个特殊参数,需要在文件上传时设置
                    cache:false,
                    contentType:false,
                    processData:false,
                    success:function(r){
                        if(r.code==OK){
                            console.log(r);
                            //将刚刚上传成功的图片显示在summernote富文本编辑器中
                            var img=new Image();//实例化了一个img标签
                            img.src=r.message;//将img标签的src属性赋值为刚上传的图片
                            //summernote方法中提供了插入标签的功能
                            //支持使用"insertNode"表示要向富文本编辑器中添加标签内容
                            $("#summernote").summernote(
                                "insertNode",img
                            )
                        }else{
                            alert(r.message);
                        }
                    }
                });
            }
        }
    });
})

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){
                        postAnswerApp.message=r.message;
                        postAnswerApp.hasError=true;
                    }else{
                        postAnswerApp.message=r.message;
                        postAnswerApp.hasError=true;
                    }
                }
            })
        }
    }
})

开发业务逻辑和数据访问层

步骤1:

IAnswerService接口中添加方法代码如下

public interface IAnswerService extends IService<Answer> {
    //提交讲师回复问题的答案信息
    Answer saveAnswer(AnswerVo answerVo,String username);
}

步骤2:

对这个接口方法进行实现

AnswerServiceImpl类中编写代码如下

@Service
@Slf4j
public class AnswerServiceImpl extends ServiceImpl<AnswerMapper, Answer> implements IAnswerService {

    @Resource
    private AnswerMapper answerMapper;

    @Resource
    private UserMapper userMapper;

    @Override
    @Transactional
    public Answer saveAnswer(AnswerVo answerVo, String username) {
        //收集信息,先获得当前回答问题的讲师的用户信息,结合answerVo
        User user=userMapper.findUserByUsername(username);
        Answer answer=new Answer()
                .setUserId(user.getId())
                .setUserNickName(user.getNickname())
                .setContent(answerVo.getContent())
                .setQuestId(answerVo.getQuestionId())
                .setLikeCount(0)
                .setAcceptStatus(0)
                .setCreatetime(LocalDateTime.now());
        int rows=answerMapper.insert(answer);
        if(rows!=1){
            throw new ServiceException("数据库忙!");
        }
        return answer;
    }
}

步骤3:

重构AnswerController类中处理新增回复方法

//新增回复的控制方法
    @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);
        }
        //这里调用业务逻辑层方法
        answerService.saveAnswer(answerVo,user.getUsername());
        return R.created("回复问题成功!");

    }

完成显示回答列表功能

如何实现显示回答问题列表功能

根据当前显示的问题的id,查询所有回答这个问题的答案

根据这个需求设计业务逻辑层开发

步骤1:

IAnswerService添加接口方法

 //根据问题id查询这个问题的所有回答的方法
    List<Answer> getAnswersByQuestionId(Integer questionId);

步骤2:

AnswerServiceImpl类中对这个方法进行实现

@Override
    public List<Answer> getAnswersByQuestionId(Integer questionId) {
        if(questionId==null){
            throw ServiceException.invalidRequest("问题id不能为空");
        }
        QueryWrapper<Answer> query=new QueryWrapper<>();
        query.eq("quest_id",questionId);
        query.orderByDesc("createtime");
        List<Answer> answers=answerMapper.selectList(query);
        return answers;
    }

步骤3:

完成控制器的调用

AnswerController中编写代码如下

//根据问题id获得这个问题的所有回答的方法
    // 例如:/v1/answers/question/12
    @GetMapping("/question/{id}")
    public R<List<Answer>> questionAnswers(
            @PathVariable Integer id){
        if(id==null){
            return R.invalidRequest("问题ID不能为空!");
        }
        List<Answer> answers=answerService
                .getAnswersByQuestionId(id);
        return R.ok(answers);
    }

步骤4:

改写detail.html页面,使用Vue绑定回答相关信息

<div class="row mt-5 ml-2" id="answersApp">
              <div class="col-12">
                <div class="well-sm"><h3>
                  <span v-text="answers.length">3</span>条回答
                </h3></div>
                <div class="card card-default my-5"
                  v-for="answer in answers">
                  <!-- Default panel contents -->
                  <div class="card-header">
                    <div class="row">
                      <div class="col-1">
                        <img style="width: 50px;height: 50px;border-radius: 50%;"
                             src="../img/user.jpg">
                      </div>
                      <div class="col-8 ">
                        <div class="row">
                          <span class="ml-3"
                           v-text="answer.userNickName">张三</span>
                        </div>
                        <div class="row">
                          <span class="ml-3"
                            v-text="answer.duration">2天前</span>
                        </div>
                      </div>
                      <div class="3">
                      </div>
                    </div>
                  </div>
                  <div class="card-body ">
                    <span class="question-content text-monospace"
                          v-html="answer.content">
                      方法的重载是overloading,方法名相同,参数的类型或个数不同,对权限没有要求
                      方法的重写是overrding 方法名称和参数列表,参数类型,返回值类型全部相同,但是所实现的内容可以不同,一般发生在继承中
                    </span>
           <!-- 以下代码略  -->
</div>

步骤5:

编写js文件

question_detail.js文件中再添加vue方法

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;
                    }else{
                        answersApp.message=r.message;
                        answersApp.hasError=true;
                    }
                }
            })
        }
    },
    created:function(){
        this.loadAnswers();
    }
})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值