onlineSchool 项目课 六:课程主页的开发

本文详细介绍了在线教育平台的课程主页、课程展示页、课程详情页、评论功能及验证码的开发过程。内容包括使用Bootstrap开发轮播,通过触发函数实现分页,课程详情页的交互效果,如章节评论的切换、评论加载,以及验证码的生成和验证机制。同时,讨论了课程学习页面的设计,如获取章节ID和评论,以及评论的添加规则和流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,课程的主页开发:
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对应的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值