一,课程的主页开发:
1,轮播的 bootstrap 样式
2,?过滤二级分类数据 cid
3,sql语句
二,课程展示页:
1,触发函数结合分页:
使用同一个触发函数,传递当前操作的分类数据的code,排序的字段,页码(页码的默认值是1),设置到 form表单对应的输入框,同步提交form表单。
//分类
<a href="javascript:void (0)" onclick="queryPage(1,'${key}')">${item.name!}</a>
<a href="javascript:void (0)" onclick="queryPage(1,'${classify.code!}')">全部</a>
//排序
<a onclick="queryPage(1,'${c!}','last')" href="javascript:void(0)" style="display: inline-block">
<span>最新</span>
</a>
<a onclick="queryPage(1,'${c!}','pop')" href="javascript:void(0)" style="display: inline-block">
<span>最热</span>
</a>
//分页
<a class="page-num active" href="javascript:void(0)" onclick="queryPage('${n}','${c!}','${sortField!}')">${n}</a>
<form id="queryForm" style="display: none">
<input type="text" id="c" name="c">
<input type="text" id="sortField" name="sortField">
<input type="text" id="pageNum" name="pageNum">
</form>
//触发函数点击时将对应的字段设置到form表单,同步提交form 表单
function queryPage(pageNum,c,sortField) {
$('#pageNum').val(pageNum);
$('#c').val(c)
if (sortField != null){
alert(sortField)
$('#sortField').val(sortField)
}
$('#queryForm').submit()
}
controller中根据前台提交的分类数据的code,getByCode 获取分类的数据添加到model,作美观设计。把分类数据的code设置到分页类的entity中。用StringUtils 判断如果 sortField 没有值就设置一个默认值传到 mapper 中,默认展示的是已上架的课程,一页展示12条数据,调用分页的函数实现分页。
@GetMapping("courses")
public ModelAndView courses(String c, String sortField, Pagination<Course> page){
//返回课程展示的页面
ModelAndView mv = new ModelAndView("zY/courses");
//从数据库中获取分类的数据展示
Map<String, ClassifyBean> classifyMap = classifyService.queryAllClassifyMap();
mv.addObject("classifyMap",classifyMap);
Course queryEntity = new Course();
if (StringUtils.isNotEmpty(c)){
//美观
Classify classify = classifyService.getByCode(c);
if ("0".equals(classify.getParentCode())){
mv.addObject("classify",classify);
queryEntity.setClassify(classify.getCode());
}else {
mv.addObject("classify",classifyService.getByCode(classify.getParentCode()));
mv.addObject("subClassify",classify);
queryEntity.setSubClassify(classify.getCode());
}
mv.addObject("c",c);
}
//默认展示出来的都是已上架的课程,设置一页展示几条数据
queryEntity.setOnsale(1);
page.setPageSize(12);
//分页结合排序
//根据提交的排序的字段,设置对应的 sortField
if ("last".equals(sortField)){
page.setSortField("id");
}
if ("pop".equals(sortField)){
page.setSortField("study_count");
}
//分类是根据动态的sql实现的,排序是根据orderBy实现的,所以要设置默认值
if (StringUtils.isEmpty(page.getSortField())){
page.setSortField("createAt");
}
if (StringUtils.isEmpty(page.getSortDirection())){
page.setSortDirection("DESC");
}
//将分类和排序的字段都添加到modal中,前端去判断有点击到,就添加对应的底色
mv.addObject("sortField",sortField);
//调用分页的函数,实现分页
page =courseService.queryPage(queryEntity,page);
mv.addObject("page",page);
return mv;
}
2,美观设计:
通过触发函数把分类的code传到后台,后台通过传入的code去获取一整条数据,并添加到model。
//分页的entity
Course queryEntity = new Course();
if (StringUtils.isNotEmpty(c)){
//美观
Classify classify = classifyService.getByCode(c);
if ("0".equals(classify.getParentCode())){
//一级分类的parentCode是0,当点击的是一级分类
//把一级分类的数据添加到modal
mv.addObject("classify",classify);
queryEntity.setClassify(classify.getCode());
}else {
//二级分类的parentCode是一级分类的code,当点击的是二级分类,把对应的一二级分类的数据都添加到modal
mv.addObject("classify",classifyService.getByCode(classify.getParentCode()));
mv.addObject("subClassify",classify);
queryEntity.setSubClassify(classify.getCode());
}
//把对应的分类的code也添加到modal,通过触发函数实现分页也展示分类下的课程
mv.addObject("c",c);
}
前台去判断model中有数据,就在对应的分类的值上做高亮展示,没有数据就在全部上做高亮展示。
a 当选择了一级分类,一级分类上有数据,就在对应的一级分类展示
<li class="course-nav-item <#if classify?? && classify.code==key>cur-course-nav</#if>">
b 当没有选择对应的分类,modal 中没有分类的值,就在全部上高亮
<li class="course-nav-item <#if !classify??>cur-course-nav</#if>" >
如果一级分类有值,就在对应的一级分类的值上做高亮展示。
如果对应的二级分类有值,就在对应的一二级分类上都做高亮展示。所以后台获取数据是二级分类时,二级分类对应的一级分类也应加载到model中。
3,过滤二级分类数据的效果:
当有一级分类,二级分类的数据才会过滤,(这句话怎么理解:当选择对应的一级分类, model中有一级分类的值,才会展示对应二级分类的数据。如果没有选择一级分类,就应该展示全部的一级分类,就不用过滤二级分类。)所以要先判断有一级分类,再根据当一级分类的code等于二级分类的parentCode,过滤二级分类的数据,如果没有选择一级分类就展示全部的分类的数据。
//展示数据的
<#if classifyMap??> //一级分类map存在
<#list classifyMap as key , item> //遍历一级分类map
<#list item.subClassifyList as subItem> //遍历二级分类map
//过滤数据的
<#if classify??> //当选择了一级分类
<#if classify.code == subItem.parentCode> //过滤二级分类
<li class="course-nav-item <#if subClassify?? && subClassify.code == subItem.code>cur-course-nav</#if>" >//在对应的二级分类上高亮
<a href="javascript:void (0)" onclick="queryPage(1,'${subItem.code!}')">${subItem.name!}</a>//通过触发函数把当前的分类传到后台
</li>
</#if>
<#else> //当没有选择一级分类,展示全部的分类数据
<li class="course-nav-item" >
<a href="javascript:void (0)" onclick="queryPage(1,'${subItem.code!}')">${subItem.name!}</a>
</li>
</#if>
</#list>
</#list>
</#if>
点击分类时,展示分类下对应的课程,用分页函数实现。
三,课程的详情页:
点击这里可以请求到课程的详情
1,章节和评论点击时底色切换的效果:
取包含点击对象的父节点的样式选择器,找到点击对象使用的标签,遍历这些标签去掉标签中底色样式,标签中使用的触发函数,把点击的对象传过来,给传过来的对象加上底色,一次只能传一个对象。
<div class="panel-menu">
<a href="javasript:void(0)">
<span onclick="show(this,'section')" class="menu-item cur">章 节</span>
</a>
<a href="javasript:void(0)">
<span onclick="show(this,'comment')" class="menu-item cur">评 论</span>
</a>
</div>
function show(el,id) {
$('.panel-menu').find('span').each(function (i,item) {
$(item).removeClass('cur')
})
$(el).addClass('cur')
if (id == 'section'){
$('#section').show()
$('#comment').hide()
}else {
$('#section').hide()
$('#comment').show()
}
}
章节和评论内容的切换效果:
触发函数传点击对象的同时,也把章节或评论的内容对应的id传过来,函数中判断id是section还是comment展示对应div的内容。
2,点击章名称节信息显示或隐藏效果:
点击对象,章名称的样式做点击事件,点击时定位到父节点,找到该显示或隐藏的内容使用的标签,通过css样式判断此标签是显示还是隐藏,如果返回的是none就是没显示,点击时就让对应的 ul 标签显示,else对应的 ul 标签就隐藏。
▼▲图标根据修改后节信息该显示或隐藏进行修改。
$(function () {
$('.js-open').click(function () {
let display = $(this).parent().find('ul').css('display')
if (display == 'none'){
$(this).parent().find('ul').css('display','block')
$(this).parent().find('.drop-down').html('▼')
}else {
$(this).parent().find('ul').css('display','none')
$(this).parent().find('.drop-down').html('▲')
}
})
3,课程评论的加载:使用 jquery load,load页面可以返回一个view,把页面的内容插入到div。
<div id="comment">
</div>
获取评论内容,查询数据库的url。
根据课程的 id 查询课程下的评论。
(后续还会传章节的id,查询课程具体章节下的评论。)
let url = '${base}/course/comment'
let courseId = '${course.id!}'
$('#comment').load(url,{'courseId':courseId})
mapper中做关联查询,返回到list前台去遍历。
//加载课程评论的数据,mapper要做关联查询
@RequestMapping("course/comment")
public ModelAndView comment(Long courseId,Long sectionId,String from){
ModelAndView mv = new ModelAndView("zY/course_comment");
if (courseId != null){
//传入一个对象
CourseComment courseComment = new CourseComment();
if (sectionId != null){
//如果有sectionId
courseComment.setSectionId(sectionId);
}
courseComment.setCourseId(courseId);
List<CourseComment> comments = courseCommentService.queryCourseComment(courseComment);
mv.addObject("comments",comments);
//把来自哪个页面的请求信息加载到modal中,详情和学习两个页面都需要加载课程的评论,在返回的view中判断如果是来自学习的页面请求就显示对应的a标签可以回复评论
mv.addObject("from",from);
}
return mv;
}
mapper中的关联查询:leftJoint查询左表的所有字段。查询课程评论表 c的所有字段和用户表的头像,根据 username 建立两个表的关联,添加result 和 domain 中的字段,否则图片数据取不出来。
<select id="queryCourseComment" parameterType="com.langshan.onlineschoollangshan.domain.CourseComment" resultMap="bean_map">
SELECT
c.id, c.username, c.to_username, c.courseId, c.sectionId, c.section_title, c.content, c.refId, c.ref_content, c.type, c.createAt, c.updateAt,
u.header
FROM course_comment c
LEFT JOIN auth_user u
ON c.username = u.username
<where>
<if test="courseId !=null">
AND c.courseId = #{courseId}
</if>
<if test="c.sectionId !=null">
AND c.sectionId = #{sectionId}
</if>
</where>
</select>
四,验证码
1,为什么发布评论要填写验证码?
避免有人频繁发布不好的评论。避免有人用工具发布评论。
2,如果以后想用把idCode拷贝到自己的项目中。
3,验证码的实现原理:
当请求到 idcode/comment,会随机自动生成验证码并放到图片里,用输出流输出出去。
也把验证码通过request,set到 session当中,方便之后校验,以key value的形式 set 到 session中,key对应验证码的不同功能,有登录,注册,评论功能的验证码。
controller中做验证码的校验,可以通过 request 从 session中获取,进行对比看是否一样,一样才能发布内容。
4,验证码的校验:request从session中把验证码 get key 获取出来返回一个对象,获取session中的验证码为空要返回失败信息。
将获取的对象转化成 String类型,忽略前端提交的验证码的大小写进行对比,不相同就返回错误的信息,相同就继续执行代码。
@RequestMapping("doLogin")
@ResponseBody
public String doLogin(String username,String password,String identityCode,
HttpServletRequest request){
//获取session中的验证码进行校验
Object sessionCode = request.getSession().getAttribute(IdentifyCodeKey.REGISTER);
if (sessionCode == null){
return JsonView.failureJson("验证码为空");
}
if (!sessionCode.toString().equalsIgnoreCase(identityCode)){
return JsonView.failureJson("验证码输入错误");
}
5,点击验证码图片变换效果:由于图片在 session 中有缓存,再次请求不会刷新,所以要获取当前时间的时间戳,修改src属性的值,再请求一次 idcode/comment 地址并加上时间戳,获取新的验证码再用输出流输出出来,展示到页面中。
<img src="${base}/idcode/comment" class="identity-code" id="identityCodeImg" onclick="reloadIdCode()">
function reloadIdCode() {
let t = new Date().getTime()
$('#identityCodeImg').attr('src','${base}/idcode/comment?t='+t)
}
五,课程的学习页面
1,课程的详情页面中要获取对应章节的 id 请求到课程的学习页面,播放对应小节的 videoUrl。(个人中心的开发会具体讲),load 加载评论。
2,章节数据的样式:首先要固定章节数据的panel 的高度,超出部分隐藏overflow:hidden,滚动效果 overflow-y:scroll。
3,添加评论的form表单页面结构,展示验证码,点击验证码的变换效果。
<form id="commentForm" action="${base}/saveComment" method="post">
<input type="hidden" id="sectionId" name="sectionId" value="${courseSection.id!}">
<input type="hidden" id="refId" name="refId" value="">
<div class="form-group clearfix">
<textarea id="content" name="content" rows="3px" cols="100px" class="form-control"></textarea>
</div>
<div class="form-group clearfix">
<input type="text" id="identityCode" name="identityCode" class="form-control" style="width: 200px;float: left">
<img src="${base}/idcode/comment" class="indeity-code" id="identityCodeImg" onclick="reloadIdCode()">
<div style="display: none" id="_alertInfo" class="alert-success"></div>
<input onclick="doSubmit()" type="button" value="发布" class="modal-btn modal-btn-bg" >
</div>
</form>
4,添加课程的评论:
需求是要求用户必须输入评论的内容,而且内容长度小于200,登录了,输入正确的验证码才能发布。
提交前先判断是否有评论的内容,且长度小于200,是否填写验证码,否:通过id 把错误信息传到页面,并将数据回滚。
ajaxSubmit提交form表单,发布评论成功,重新加载评论并清空输入框(把输入框的内容设成空串),发布失败返回java中定义的错误信息。
function doSubmit() {
let content = $('#content').val()
if (_os.isEmpty(content) || content.length > 200){
_os.alertMsg("评论的内容输入错误")
return;
}
let identityCode = $('#identityCode').val()
if (_os.isEmpty(identityCode)){
os.alertMsg("请输入验证码")
return;
}
$('#commentForm').ajaxSubmit({
dataType:'json',
success:function (resp) {
if (resp.errcode == 0){
loadComment();
$('#content').val("")
$('#identityCode').val("")
}else {
_os.alertMsg(resp.message)
}
}
})
}
controller中接收评论的form 表单和提交的验证码,对验证码进行校验。根据 form 表单隐藏的 input中,判断是否有refId,有是回复评论,没有是发表评论,创建课程评论数据到数据库。
@RequestMapping("saveComment")
@ResponseBody
public String saveComment(HttpServletRequest request,//获取session中的验证码
CourseComment comment,String identityCode) {
//提交的评论的数据,提交的验证码
//验证码校验
Object sessionCode = request.getSession().getAttribute(IdentifyCodeKey.COMMENT);
if (sessionCode != null && StringUtils.isNotEmpty(identityCode)) {
if (!sessionCode.toString().equalsIgnoreCase(identityCode)) {
return JsonView.failureJson("验证码错误");
}
} else {
return JsonView.failureJson("验证码错误");
}
//评论内容不应为空,和针对一个课程一个小节的评论
if (StringUtils.isEmpty(comment.getContent())) {
return JsonView.failureJson("评论为空,无法发布");
}
//获取课程小节的数据
Long sectionId = comment.getSectionId();
if (sectionId == null) {
return JsonView.failureJson("小节数据不存在,无法发布");
}
CourseSection courseSection = courseSectionService.getById(sectionId);
if (courseSection == null) {
return JsonView.failureJson("小节数据不存在,无法发布");
}
//创建评论,设置字段
comment.setSectionTitle(courseSection.getName());
comment.setCourseId(courseSection.getCourseId()); //每个小节的数据都有课程的id
comment.setUsername("admin"); //尚未实现登录功能直接写死
comment.setType(0);
boolean flag = false;
if (comment.getRefId() != null) {
CourseComment courseComment = courseCommentService.getById(comment.getRefId());//被援引的评论
comment.setRefContent(courseComment.getContent()); //被援引的评论的内容
comment.setToUsername(courseComment.getUsername());//被援引的评论是谁留下的
flag = true;
}
if (!flag) { //表示不执行回复的业务代码,直接执行添加的业务代码
comment.setToUsername("admin");
comment.setRefId(0L);
comment.setRefContent("");
}
courseCommentService.create(comment);
return JsonView.successJson();
}
以上是数据库的评论表对应的字段, sectoinId和 refId 都通过comment view中隐藏的input,form表单提交到后台,sectionId 是播放视频的videoUrl时把小节数据添加到modal可以遍历出来的,如果是回复的课程评论 被回复的 refId 在点击回复时把值设置进去,再提交到后台的,点击时获取用户名,显示被回复的用户名。
<#if from?? && from=='learn'>
<a class="user-name" href="#commentForm" onclick="reComment('${item.username!}','${item.id!}')" style="float: left">回复</a>
</#if>
function reComment(toUsername,id) {
if (toUsername != null){
$('#toUsername').html("回复"+ toUsername);
}
if (id != null){
$('#refId').val(id)
}
}
href =“#id”:锚点,点击时会滚动到这个id对应的div。例如 href="#commentForm" 点击回复时,会滚动到这个id对应的内容