续 显示学生问题列表
将问题包含的标签集合赋值到问题对象
上次课,我们完成了tagNames转换为List<Tag>方法的编写
转换代码编写完毕
要在QuestionServiceImpl类中查询学生问题列表的getMyQuestions方法中调用才行
具体代码如下
@Override
public List<Question> getMyQuestions(String username) {
// 先根据用户名查询用户信息(用户对象)
User user=userMapper.findUserByUsername(username);
// 再使用QueryWrapper完成该用户的问题列表的查询
QueryWrapper<Question> query=new QueryWrapper<>();
query.eq("user_id",user.getId());
query.eq("delete_status",0);
query.orderByDesc("createtime");
// 执行查询
List<Question> list=questionMapper.selectList(query);
// 遍历当前查询到的所有问题对象
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
for(Question question:list){
// 将当前问题对象的tagNames转换为List<Tag>
List<Tag> tags=tagNamesToTags(question.getTagNames());
// 将转换结果得到的list赋值给当前question对象的tags属性
question.setTags(tags);
}
// 别忘了返回list
return list;
}
因为业务逻辑层的修改
我们现在返回的Question对象就都包含tags集合属性了
为了测试,我们再次发送同步请求测试
localhost:8080/v1/questions/my
观察页面中json格式的tags属性是否有值
最后要想在页面上也显示标签信息
需要在index_student.html页面中
215行附近修改为
<a class="text-info badge badge-pill bg-light"
href="tag/tag_question.html"
v-for="tag in question.tags">
<small v-text="tag.name">Java基础 </small>
</a>
再重启服务,直接访问学生首页即可
显示当前问题的配图
question表中,没有对每个问题设置配图的信息
我们需要根据当前问题的标签来决定配图
我们设计使用当前问题所有标签中的第一个标签的id作为配图的依据
我们在静态资源文件中img文件夹下的tags文件夹内确实保存了1~20号图片
我们需要做的就是提取当前问题的第一个标签的id然后去确定显示对应的图片路径即可
实际上这个方法也在index.js文件中给大家写好了
我们只需要编写绑定即可
index_student.html的238行附近
<!-- / class="media-body"-->
<img src="img/tags/example0.jpg"
class="ml-3 border img-fluid rounded"
alt="" width="208" height="116"
:src="question.tagImage">
重启服务,观察图片变化
分页显示问题列表
分页的优势
所谓分页显示就是将查询结果按页显示在浏览器,而不是全部一次性显示
分页的好处:
- 相对于一次性全查所有信息,查询一页信息,服务器压力比较小
- 对客户端来说,一次性显示所有信息会消耗较多流量,加载页面的时间也更长
- 用户体验上,一般情况下最有价值的信息都会在前几页显示,后面的数据被浏览的几率很低
PageHelper简介
mysql数据库实现分页查询是通过limit关键字
SELECT * FROM question
WHERE user_id=11
ORDER BY createtime DESC
LIMIT 0,8
上面的分页可以看出,分页操作就是添加limt关键字
页码的变化就是变化limit关键字之后的数字
PageHelper框架就可以替我们完成limit关键字的编写和页码变化的设置
下面我们就是用pageHelper实现分页效果
添加PageHelper依赖
PageHelper和MybatisPlus一样,也要我们自己定义版本
父项目pom.xml文件添加版本设定
<properties>
<pagehelper.starter.version>1.3.0</pagehelper.starter.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.starter.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
portal子项目pom.xml文件添加具体依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
PageHelper基本使用
PageHelper框架的分页实现原理非常简单
就是在执行要分页的查询之前,设置分页的参数
PageHelper框架会在这次查询的sql语句后自动按照查询需求添加limit关键字和正确的页面参数(例如limit 8,limit 8,8 limit 16,8等)
QuestionServiceImpl类中的getMyQuestions方法中添加代码:
//PageHelper.startPage就是设置分页查询的方法
// 在查询执行之前设置,设置后,这次查询就会自动分页
// startPage([页码],[每页条数])
// 页码从1开始,第二页就写2即可
PageHelper.startPage(2,8);
// 执行查询
List<Question> list=questionMapper.selectList(query);
我们重启服务,访问学生首页,就会显示指定页码的问题了
重构学生问题列表代码
为了实现分页的效果
我们需要重构学生问题列表的部分代码
首先我们要知道,分页查询除了页码和每页条数,还需要很多其它信息
尤其是在需要进行上一页或下一页操作时,需要知道相关分页信息
它们包括
总页数,总条数,有没有上一页(是不是第一页),有没有下一页(是不是最后一页)
当前第几页等各种信息
也就是说,我们在进行分页查询的业务时,上面的这些信息,也要进行查询
PageHelper框架的另一个能够帮助我们的功能,就是计算这些数据
利用PageInfo的类型可以保存
- 原有的分页问题列表(list)
- 当前分页的信息(总页数总条数,当前页数等)
所以我们要将业务逻辑层和控制层的方法返回值都修改为PageInfo类型
以便于页面既能接收list又能接收分页信息
重构从业务逻辑层的接口开始
IQuestionService接口修改代码如下
public interface IQuestionService extends IService<Question> {
// 根据登录用户的用户名查询问题列表
// 返回值修改为PageInfo既能包含list,又能包含分页信息
// 添加分页参数pageNum页码,和PageSize每页条数,用于分页查询
PageInfo<Question> getMyQuestions(String username,
Integer pageNum,Integer pageSize);
}
QuestionServiceImpl实现方法也随之修改
@Override
// ↓↓↓↓↓↓↓↓
public PageInfo<Question> getMyQuestions(String username,
Integer pageNum,Integer pageSize) {
//↑↑↑↑↑↑↑↑↑↑
// 先根据用户名查询用户信息(用户对象)
User user=userMapper.findUserByUsername(username);
// 再使用QueryWrapper完成该用户的问题列表的查询
QueryWrapper<Question> query=new QueryWrapper<>();
query.eq("user_id",user.getId());
query.eq("delete_status",0);
query.orderByDesc("createtime");
//PageHelper.startPage就是设置分页查询的方法
// 在查询执行之前设置,设置后,这次查询就会自动分页
// startPage([页码],[每页条数])
// 页码从1开始,第二页就写2即可
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
PageHelper.startPage(pageNum,pageSize);
// 执行查询
List<Question> list=questionMapper.selectList(query);
// 遍历当前查询到的所有问题对象
for(Question question:list){
// 将当前问题对象的tagNames转换为List<Tag>
List<Tag> tags=tagNamesToTags(question.getTagNames());
// 将转换结果得到的list赋值给当前question对象的tags属性
question.setTags(tags);
}
// 别忘了返回PageInfo
// ↓↓↓↓↓↓↓↓
return new PageInfo<>(list);
}
最后修改控制层的调用
QuestionController的my方法修改为
@GetMapping("/my")
// ↓↓↓↓↓↓↓↓↓
public PageInfo<Question> my(
//@AuthenticationPrincipal注解效果
// 从Spring-Security框架获得当前登录用户的UserDetails对象
// 赋值给注解之后的参数
@AuthenticationPrincipal UserDetails user,
// ↓↓↓↓↓↓↓↓↓
Integer pageNum
){
// ↓↓↓↓↓↓↓↓↓
Integer pageSize=8;
if(pageNum==null){
pageNum=1;
}
// ↓↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓↓
PageInfo<Question> pageInfo=questionService
.getMyQuestions(user.getUsername(),pageNum,pageSize);
// ↓↓↓↓↓↓↓↓↓
return pageInfo;
}
修改完之后可以再发一个同步请求
localhost:8080/v1/questions/my
我们会发现,除了之前的list信息之外
现在还包含了分页信息,具体内容见笔记末尾文档
最后别忘了修改js文件
因为返回值类型的变化
Vuejs代码的赋值也修改
index.js的25行附近修改为
if(r.status == OK){
questionsApp.questions = r.data.list;
questionsApp.pageinfo = r.data;
//为question对象添加持续时间属性
questionsApp.updateDuration();
questionsApp.updateTagImage();
}
在重启服务,就恢复显示了!!!
实现页面分页导航条
后端(java代码)已经编写完成,要想实现上一页下一页效果
实际上就是调用QuestionController中的控制器方法,区别就是页码的不同而已
所以我们只需要修改页面上的代码,在上一页和下一页连接的位置
请求正确的页码,就可以实现翻页了
我们直接修改index_student.html页面244行附近的导航条就可以实现
<nav aria-label="Page navigation example">
<div class="pagination">
<a class="page-item page-link" href="#"
@click.prevent="loadQuestions(pageinfo.prePage)">上一页</a>
<a class="page-item page-link " href="#"
v-for="n in pageinfo.navigatepageNums"
v-text="n"
@click.prevent="loadQuestions(n)"
:class="{'bg-secondary text-white' : n==pageinfo.pageNum}"
>1</a>
<a class="page-item page-link" href="#"
@click.prevent="loadQuestions(pageinfo.nextPage)">下一页</a>
</div>
</nav>
重启服务测试翻页功能
开发学生发布问题的功能
达内知道问题流程
达内知道问答流程概述
- 学生登录系统后,可以在发布问题页面进行提问,问题状态为"未回复"
- 讲师登录系统后,可以对学生的提问进行回复,回答后问题状态为"已回复"
- 学生可以对讲师的回复进行追问或问题补充也就是讨论,讲师也可以对追问进行回复
- 直到学生问题解决,学生可以采纳讲师的回复,将问题标记为"已解决"状态
在完成实际的学生提问操作前
学生提问页面和表单中有一些信息需要先准备一下
复用全部标签列表
我们在学生首页的开发过程中已经完成了显示所有标签列表的功能
如果再次编写就是代码冗余,会造成维护困难
我们可以使用Vue模板,复用之前已经编写完成的代码
减少代码冗余,用于本次以及之后的显示所有标签列表的需求
Vue模板的使用分为3个步骤
1.定义模板
2.调用模板
3.引用模板
定义模板
所谓定义模板就是在js文件夹中定义一个js文件
这个文件中编写固定格式的代码,形成一个Vue模板的过程
在js文件夹中创建tags_nav_temp.js
Vue.component("tags-app",{
props:["tags"],
template:`
<div class="nav font-weight-light">
<a href="tag/tag_question.html" class="nav-item nav-link text-info"><small>全部</small></a>
<a href="tag/tag_question.html"
class="nav-item nav-link text-info"
v-for="tag in tags">
<small v-text="tag.name">Java基础</small>
</a>
</div>
`
})
一定要删除原有代码中id的属性!!!
调用模板
调用模板的原则是哪里需要,哪里调用
现在是question/create.html页面中需要这个模板的内容
就在这个页面的对应位置编写下面调用模板的代码
183行
<!--引入标签的导航栏-->
<div class="container-fluid" >
<!--
调用模板,直接使用定义模板时的模板名称,就是第一个参数
调用模板在模板标签中必须定义正确的id
别忘了为模板中props定义的tags属性赋值
-->
<tags-app id="tagsApp" :tags="tags"></tags-app>
</div>
引用模板
所谓引用模板就是需要将当前业务能够运行模板内容的所有支持的js都引用过来
1.由于是第一次在create.html页面中发送axios请求
所以先添加axios的引用
<!--引入CDN服务器的框架文件-->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
</head>
2.页面尾部添加所有可能需要的js文件和模板js文件的引用
顺序必须是先添加模板再添加模板依赖的js文件
<script src="../js/utils.js"></script>
<!-- 引用模板时必须先引用模板文件,再引用运行axios的js文件才能正常运行 -->
<script src="../js/tags_nav_temp.js"></script>
<script src="../js/tags_nav.js"></script>
</body>
重启服务,访问create.html页面,就可以显示所有标签列表了
我们编写后面页面时,还有机会使用模板复用的功能
富文本编辑器
富文本编辑器:丰富内容的文本编辑器
我们在开发网站功能时,经常需要用户输入比较复杂的内容,例如带样式的文字,甚至是图片表格等
富文本编辑器就可以实现这样的功能
富文本编辑器市面上流行的有很多种类,他们各有自己不同的特征
我们达内知道选用的是summernote
官网:https://summernote.org
我们页面中已经配置并启动了summernote
我们可以直接使用它,具体对它的操作,我们在使用到时再讲解
多选下拉框
在选择问题的标签和讲师时,
我们选用了用户体验更好的多选下拉框
这样,就支持学生提问时选中多个标签和讲师了
我们使用Vue框架下的一个组件v-select
实现多选下拉框的功能
官网https://vue-select.org/
多选下拉框的绑定的数据需要从数据库获得
应该在页面加载完毕之后立即使用axios向java请求获得所有标签和所有讲师并显示在多选下拉列表框中
下面我们就来完成在页面加载完毕之后将所有标签和所有讲师显示在下拉框中的功能
加载所有标签和所有讲师
编写页面的绑定
create.html页面200行附近
<div class="col-8" id="createQuestionApp">
create.html的211行附近
绑定所有标签和所有讲师的多选下拉框
<div class="form-group">
<label >请至少选择一个标签:</label>
<v-select multiple required
:options="tags"
v-model="selectedTags"
placeholder="请选择标签">
</v-select>
</div>
<div class="form-group">
<label >请选择老师:</label>
<v-select multiple required
:options="teachers"
v-model="selectedTeachers"
placeholder="请选择讲师">
</v-select>
</div>
还有最后一个提供给大家用的js文件,就是在这里使用的
页面尾部添加js文件引用
<script src="../js/utils.js"></script>
<!-- 引用模板时必须先引用模板文件,再引用运行axios的js文件才能正常运行 -->
<script src="../js/tags_nav_temp.js"></script>
<script src="../js/tags_nav.js"></script>
<!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
<script src="../js/createQuestion.js"></script>
</body>
</html>
编写查询所有讲师的功能
上面代码直接能够调用查询所有标签的方法并显示
我们只要在UserController中完成查询返回所有讲师的功能,也能实现页面下拉框中显示所有讲师
先开始编写数据访问层
UserMapper中添加查询所有讲师的方法
// 查询所有讲师的方法
@Select("select * from user where type=1")
List<User> findTeachers();
开发业务逻辑层接口
IUserService业务逻辑层添加获得所有讲师的方法
// 查询所有讲师的业务逻辑层方法
List<User> getTeachers();
UserServiceImpl实现方法
@Override
public List<User> getTeachers() {
return userMapper.findTeachers();
}
编写控制层方法UserController
@RestController
@RequestMapping("/v1/users")
public class UserController {
@Autowired
private IUserService userService;
// 返回所有讲师的控制器方法
@GetMapping("/master")
public List<User> master(){
// 调用业务逻辑层方法获得所有讲师
List<User> users=userService.getTeachers();
return users;
}
}
然后就可以重启服务
访问create.html测试是否能够显示所有讲师
如果失败,建议发送同步请求测试
localhost:8080/v1/users/master
学生提问流程图
创建发布问题的VO类
和注册功能类似
我们要创建一个能够封装发布问题页面表单数据的VO类
并且直接编写支持SpringValidation的验证的注解
vo包中创建QuestionVO类
代码如下
@Data
public class QuestionVO {
@NotBlank(message = "标签不能为空")
@Pattern(regexp = "^.{3,50}$",message = "标题需要3~50个字符")
private String title;
//@NotEmpty 专门用于判断集合或数组非空的注解
@NotEmpty(message = "请至少选择一个标签")
private String[] tagNames={};
@NotEmpty(message = "请至少选择一个讲师")
private String[] teacherNicknames={};
@NotBlank(message = "问题内容不能为空")
private String content;
}
控制器方法接收表单信息
本次学生发布问题是post请求
故我们先完成表单将数据提交的控制层的编码,并且测试
QuestionController类添加一个接收表单提交的控制器方法
先在类上添加@Slf4j注解
// 学生发布问题访问的控制层方法
// @PostMapping("")表明要访问此控制器方法
// 需要以post方式访问localhost:8080/v1/questions
@PostMapping("")
public String createQuestion(
@Validated QuestionVO questionVO,
BindingResult result
){
log.debug("接收表单信息:{}",questionVO);
if(result.hasErrors()){
String msg=result.getFieldError().getDefaultMessage();
return msg;
}
// 这里调用业务逻辑层方法运行新增问题
return "ok";
}
我们组最后还需要在create.html页面中编写一下表单提交时调用Vue中方法的绑定
203行附近
<form @submit.prevent="createQuestion" >
重启服务器,添写表单,提交之后
检查idea控制台的输出信息是否和填写的信息对应
如果正确表示一切正常
PageInfo类中的分页信息解释
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的行数量
private int size;
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总页数
private int pages;
//前一页页号
private int prePage;
//下一页页号
private int nextPage;
//是否为第一页
private boolean isFirstPage;
//是否为最后一页
private boolean isLastPage;
//是否有前一页
private boolean hasPreviousPage;
//是否有下一页
private boolean hasNextPage;
//导航条中页码个数
private int navigatePages;
//所有导航条中显示的页号
private int[] navigatepageNums;
//导航条上的第一页页号
private int navigateFirstPage;
//导航条上的最后一页号
private int navigateLastPage;